server/admin.c: Remove spurious `ping' in usage message.
[tripe] / proxy / tripe-mitm.c
index 107362e..648296e 100644 (file)
@@ -1,29 +1,26 @@
 /* -*-c-*-
  *
- * $Id$
- *
  * An evil proxy for TrIPE
  *
  * (c) 2001 Straylight/Edgeware
  */
 
-/*----- Licensing notice --------------------------------------------------* 
+/*----- Licensing notice --------------------------------------------------*
  *
  * This file is part of Trivial IP Encryption (TrIPE).
  *
- * TrIPE is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- * 
- * TrIPE is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- * 
+ * TrIPE is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 3 of the License, or (at your
+ * option) any later version.
+ *
+ * TrIPE is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * for more details.
+ *
  * You should have received a copy of the GNU General Public License
- * along with TrIPE; if not, write to the Free Software Foundation,
- * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * along with TrIPE.  If not, see <https://www.gnu.org/licenses/>.
  */
 
 /*----- Header files ------------------------------------------------------*/
 #include <catacomb/mprand.h>
 #include <catacomb/dh.h>
 
+#include <catacomb/chacha20.h>
 #include <catacomb/noise.h>
 #include <catacomb/rand.h>
-#include <catacomb/rc4.h>
+
+#include "util.h"
 
 /*----- Data structures ---------------------------------------------------*/
 
 typedef struct peer {
   sel_file sf;
-  dh_pub kpub;
   const char *name;
   struct filter *f;
 } peer;
@@ -99,6 +97,7 @@ static peer peers[2];
 static unsigned npeer = 0;
 static key_file keys;
 static grand *rng;
+static const char *delim = ":";
 
 #define PASS(f, buf, sz) ((f) ? (f)->func((f), (buf), (sz)) : (void)0)
 #define RND(i) (rng->ops->range(rng, (i)))
@@ -116,52 +115,76 @@ static void dopacket(int fd, unsigned mode, void *vv)
   }
 }
 
-static void addpeer(unsigned ac, char **av)
+static void addpeer_common(const char *cmd, int af, unsigned ac, char **av)
 {
-  key_packstruct kps[DH_PUBFETCHSZ];
-  key_packdef *kp;
-  struct hostent *h;
-  struct sockaddr_in sin;
-  int len = PKBUFSZ;
+  struct addrinfo aihint = { 0 }, *ai0, *ai1;
+  int len = PKBUFSZ, yes = 1;
+  const char *outf, *serv;
+  FILE *fp;
+  union {
+    struct sockaddr sa;
+    struct sockaddr_in sin;
+    struct sockaddr_in6 sin6;
+  } addr;
+  socklen_t salen;
+  int err;
   peer *p;
-  int fd;
-  int e;
+  int fd, port;
 
-  if (ac != 4)
-    die(1, "syntax: peer:NAME:PORT:ADDR:PORT");
-  if (npeer >= 2)
-    die(1, "enough peers already");
+  if (ac != 4) die(1, "syntax: %s:NAME:PORT:ADDR:PORT", cmd);
+  if (!key_bytag(&keys, av[0])) die(1, "no key named `%s'", av[0]);
   p = &peers[npeer++];
   p->name = xstrdup(av[0]);
-  kp = key_fetchinit(dh_pubfetch, kps, &p->kpub);
-  e = key_fetchbyname(kp, &keys, av[0]);
-  key_fetchdone(kp);
-  if (e)
-    die(1, "key_fetch `%s': %s", av[0], key_strerror(e));
-  if ((fd = socket(PF_INET, SOCK_DGRAM, 0)) < 0)
+  aihint.ai_family = af;
+  aihint.ai_socktype = SOCK_DGRAM;
+  aihint.ai_flags = AI_ADDRCONFIG;
+  if ((err = getaddrinfo(av[2], av[3], &aihint, &ai1)) != 0)
+    die(1, "getaddrinfo(`%s', `%s'): %s", av[2], av[3], gai_strerror(err));
+  if (*av[1] == '?') { serv = "0"; outf = av[1] + 1; }
+  else { serv = av[1]; outf = 0; }
+  aihint.ai_family = ai1->ai_family;
+  aihint.ai_flags = AI_ADDRCONFIG | AI_PASSIVE;
+  if ((err = getaddrinfo(0, serv, &aihint, &ai0)) != 0)
+    die(1, "getaddrinfo(passive, `%s'): %s", av[1], gai_strerror(err));
+  if ((fd = socket(ai1->ai_family, SOCK_DGRAM, ai1->ai_protocol)) < 0)
     die(1, "socket: %s", strerror(errno));
-  fdflags(fd, O_NONBLOCK, O_NONBLOCK, FD_CLOEXEC, FD_CLOEXEC);
-  memset(&sin, 0, sizeof(sin));
-  sin.sin_family = AF_INET;
-  sin.sin_addr.s_addr = INADDR_ANY;
-  sin.sin_port = htons(atoi(av[1]));
-  if (bind(fd, (struct sockaddr *)&sin, sizeof(sin)))
+  if (ai1->ai_family == AF_INET6) {
+    if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &yes, sizeof(yes)))
+      die(1, "setsockopt: %s", strerror(errno));
+  }
+  if (bind(fd, ai0->ai_addr, ai0->ai_addrlen))
     die(1, "bind: %s", strerror(errno));
-  memset(&sin, 0, sizeof(sin));
-  sin.sin_family = AF_INET;
-  if ((h = gethostbyname(av[2])) == 0)
-    die(1, "gethostbyname `%s'", av[2]);
   if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &len, sizeof(len)) ||
       setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &len, sizeof(len)))
     die(1, "setsockopt: %s", strerror(errno));
-  memcpy(&sin.sin_addr, h->h_addr, sizeof(sin.sin_addr));
-  sin.sin_port = htons(atoi(av[3]));
-  if (connect(fd, (struct sockaddr *)&sin, sizeof(sin)))
+  if (connect(fd, ai1->ai_addr, ai1->ai_addrlen))
     die(1, "connect: %s", strerror(errno));
+  if (outf) {
+    salen = sizeof(addr);
+    if (getsockname(fd, &addr.sa, &salen))
+      die(1, "getsockname: %s", strerror(errno));
+    switch (addr.sa.sa_family) {
+      case AF_INET: port = ntohs(addr.sin.sin_port); break;
+      case AF_INET6: port = ntohs(addr.sin6.sin6_port); break;
+      default: assert(0);
+    }
+    fp = fopen(outf, "w");
+    if (!fp) die(1, "fopen(%s): %s", outf, strerror(errno));
+    fprintf(fp, "%d\n", port);
+    fclose(fp);
+  }
   sel_initfile(&sel, &p->sf, fd, SEL_READ, dopacket, p);
   sel_addfile(&p->sf);
+  freeaddrinfo(ai0); freeaddrinfo(ai1);
 }
 
+static void addpeer(unsigned ac, char **av)
+  { addpeer_common("peer", AF_UNSPEC, ac, av); }
+static void addpeer4(unsigned ac, char **av)
+  { addpeer_common("peer4", AF_INET, ac, av); }
+static void addpeer6(unsigned ac, char **av)
+  { addpeer_common("peer6", AF_INET6, ac, av); }
+
 /*----- Fork filter -------------------------------------------------------*/
 
 typedef struct forknode {
@@ -192,8 +215,7 @@ static void dofork(filter *f, const octet *buf, size_t sz)
 static void addfork(filter *f, unsigned ac, char **av)
 {
   forkfilt *ff;
-  if (ac != 1)
-    die(1, "syntax: filt:fork:NAME");
+  if (ac != 1) die(1, "syntax: filt:fork:NAME");
   ff = CREATE(forkfilt);
   ff->name = xstrdup(av[0]);
   ff->fn = 0;
@@ -209,23 +231,18 @@ static void nextfork(unsigned ac, char **av)
   forknode *fn, **ffn;
   peer *p;
 
-  if (ac < 1)
-    die(1, "syntax: next:NAME:...");
+  if (ac < 1) die(1, "syntax: next:NAME:...");
   for (i = 0; i < 2; i++) {
     p = &peers[i];
     for (f = p->f; f; f = f->next) {
-      if (f->func != dofork)
-       continue;
+      if (f->func != dofork) continue;
       ff = f->state;
-      for (j = 0; j < ac; j++) {
-       if (strcmp(av[j], ff->name) == 0)
-         goto match;
-      }
+      for (j = 0; j < ac; j++)
+       if (strcmp(av[j], ff->name) == 0) goto match;
       continue;
     match:
       fn = CREATE(forknode);
-      for (ffn = &ff->fn; *ffn; ffn = &(*ffn)->next)
-       ;
+      for (ffn = &ff->fn; *ffn; ffn = &(*ffn)->next);
       fn->f = f->next;
       f->next = 0;
       fn->next = 0;
@@ -256,17 +273,39 @@ static void docorrupt(filter *f, const octet *buf, size_t sz)
 static void addcorrupt(filter *f, unsigned ac, char **av)
 {
   corrupt *c;
-  if (ac > 1)
-    die(1, "syntax: filt:corrupt[:PCORRUPT]");
+  if (ac > 1) die(1, "syntax: filt:corrupt[:P-CORRUPT]");
   c = CREATE(corrupt);
-  if (ac > 0)
-    c->p_corrupt = atoi(av[0]);
-  else
-    c->p_corrupt = 5;
+  if (ac > 0) c->p_corrupt = atoi(av[0]);
+  else c->p_corrupt = 5;
   f->state = c;
   f->func = docorrupt;
 }
 
+/*----- Drop filter -------------------------------------------------------*/
+
+typedef struct drop {
+  unsigned p_drop;
+} drop;
+
+static void dodrop(filter *f, const octet *buf, size_t sz)
+{
+  drop *d = f->state;
+
+  if (!RND(d->p_drop)) puts("drop packet");
+  else PASS(f->next, buf, sz);
+}
+
+static void adddrop(filter *f, unsigned ac, char **av)
+{
+  drop *d;
+  if (ac > 1) die(1, "syntax: filt:drop[:P-DROP]");
+  d = CREATE(drop);
+  if (ac > 0) d->p_drop = atoi(av[0]);
+  else d->p_drop = 5;
+  f->state = d;
+  f->func = dodrop;
+}
+
 /*----- Delay filter ------------------------------------------------------*/
 
 typedef struct delaynode {
@@ -311,6 +350,8 @@ static void dsend(delaynode *dn, unsigned force)
 {
   delay *d = dn->d;
   delaynode *ddn;
+  unsigned i;
+
   fputs(" send...\n", stdout);
   assert(dn->buf);
   PASS(d->f->next, dn->buf, dn->sz);
@@ -333,7 +374,7 @@ static void dsend(delaynode *dn, unsigned force)
       ddn->flag = 0;
       printf(" move id %u from slot %u to slot %u", ddn->seq, ddn->i, dn->i);
     }
-    { unsigned i; for (i = 0; i < d->n; i++) assert(d->q[i].buf); }
+    for (i = 0; i < d->n; i++) assert(d->q[i].buf);
     fputs(" remove", stdout);
   }
 }
@@ -378,19 +419,14 @@ static void adddelay(filter *f, unsigned ac, char **av)
   delay *d;
   unsigned i;
 
-  if (ac < 1 || ac > 3)
-    die(1, "syntax: filt:delay:QLEN[:MILLIS:PREPLAY]");
+  if (ac < 1 || ac > 3) die(1, "syntax: filt:delay:QLEN[:MILLIS:P-REPLAY]");
   d = CREATE(delay);
   d->max = atoi(av[0]);
-  if (ac > 1)
-    d->t = strtoul(av[1], 0, 10);
-  else
-    d->t = 100;
+  if (ac > 1) d->t = strtoul(av[1], 0, 10);
+  else d->t = 100;
   d->t *= 1000;
-  if (ac > 2)
-    d->p_replay = atoi(av[2]);
-  else
-    d->p_replay = 20;
+  if (ac > 2) d->p_replay = atoi(av[2]);
+  else d->p_replay = 20;
   d->n = 0;
   d->q = xmalloc(d->max * sizeof(delaynode));
   d->f = f;
@@ -409,13 +445,12 @@ static void adddelay(filter *f, unsigned ac, char **av)
 static void dosend(filter *f, const octet *buf, size_t sz)
 {
   printf("send to `%s'\n", f->p_to->name);
-  write(f->p_to->sf.fd, buf, sz);
+  DISCARD(write(f->p_to->sf.fd, buf, sz));
 }
 
 static void addsend(filter *f, unsigned ac, char **av)
 {
-  if (ac)
-    die(1, "syntax: filt:send");
+  if (ac) die(1, "syntax: filt:send");
   f->func = dosend;
 }
 
@@ -426,6 +461,7 @@ const struct filtab {
   { "send",    addsend },
   { "fork",    addfork },
   { "delay",   adddelay },
+  { "drop",    adddrop },
   { "corrupt", addcorrupt },
   { 0,         0 }
 };
@@ -434,21 +470,16 @@ static void dofilter(peer *from, peer *to, unsigned ac, char **av)
 {
   filter **ff, *f = CREATE(filter);
   const struct filtab *ft;
-  if (ac < 1)
-    die(1, "syntax: {l,r,}filt:NAME:...");
+  if (ac < 1) die(1, "syntax: {l,r,}filt:NAME:...");
   f->next = 0;
   f->p_from = from;
   f->p_to = to;
   f->state = 0;
-  for (ff = &from->f; *ff; ff = &(*ff)->next)
-    ;
+  for (ff = &from->f; *ff; ff = &(*ff)->next);
   *ff = f;
-  for (ft = filtab; ft->name; ft++) {
-    if (strcmp(av[0], ft->name) == 0) {
-      ft->func(f, ac - 1, av + 1);
-      return;
-    }
-  }
+  for (ft = filtab; ft->name; ft++)
+    if (strcmp(av[0], ft->name) == 0)
+      { ft->func(f, ac - 1, av + 1); return; }
   die(1, "unknown filter `%s'", av[0]);
 }
 
@@ -477,13 +508,12 @@ static void floodtimer(struct timeval *tv, void *vv)
   sz /= 2;
 
   rng->ops->fill(rng, buf, sz);
-  if (f->type < 0x100)
-    buf[0] = f->type;
+  if (f->type < 0x100) buf[0] = f->type;
   puts("flood packet");
   PASS(f->p->f, buf, sz);
   setflood(f);
 }
-  
+
 static void setflood(flood *f)
 {
   struct timeval tv;
@@ -495,22 +525,15 @@ static void setflood(flood *f)
 static void doflood(peer *p, unsigned ac, char **av)
 {
   flood *f;
-  if (ac > 3)
-    die(1, "syntax: flood[:TYPE:MILLIS:SIZE]");
+  if (ac > 3) die(1, "syntax: flood[:TYPE:MILLIS:SIZE]");
   f = CREATE(flood);
   f->p = p;
-  if (ac > 0)
-    f->type = strtoul(av[0], 0, 16);
-  else
-    f->type = 0x100;
-  if (ac > 1)
-    f->t = atoi(av[1]);
-  else
-    f->t = 10;
-  if (ac > 2)
-    f->sz = atoi(av[2]);
-  else
-    f->sz = 128;
+  if (ac > 0) f->type = strtoul(av[0], 0, 16);
+  else f->type = 0x100;
+  if (ac > 1) f->t = atoi(av[1]);
+  else f->t = 10;
+  if (ac > 2) f->sz = atoi(av[2]);
+  else f->sz = 128;
   f->t *= 1000;
   setflood(f);
 }
@@ -545,15 +568,11 @@ static void include(unsigned ac, char **av)
 {
   FILE *fp;
   dstr d = DSTR_INIT;
-  if (!ac)
-    die(1, "syntax: include:FILE:...");
+  if (!ac) die(1, "syntax: include:FILE:...");
   while (*av) {
     if ((fp = fopen(*av, "r")) == 0)
       die(1, "fopen `%s': %s", *av, strerror(errno));
-    while (dstr_putline(&d, fp) != EOF) {
-      parse(d.buf);
-      DRESET(&d);
-    }
+    while (dstr_putline(&d, fp) != EOF) { parse(d.buf); DRESET(&d); }
     fclose(fp);
     av++;
   }
@@ -564,6 +583,8 @@ const struct cmdtab {
   void (*func)(unsigned /*ac*/, char **/*av*/);
 } cmdtab[] = {
   { "peer",    addpeer },
+  { "peer4",   addpeer4 },
+  { "peer6",   addpeer6 },
   { "include", include },
   { "filt",    addfilter },
   { "lfilt",   addlfilter },
@@ -583,12 +604,11 @@ static void parse(char *p)
   unsigned c = 0;
   const struct cmdtab *ct;
 
-  p = strtok(p, ":");
-  if (!p || *p == '#')
-    return;
+  p = strtok(p, delim);
+  if (!p || *p == '#') return;
   do {
     v[c++] = p;
-    p = strtok(0, ":");
+    p = strtok(0, delim);
   } while (p && c < AVMAX - 1);
   v[c] = 0;
   for (ct = cmdtab; ct->name; ct++) {
@@ -606,7 +626,7 @@ static void version(FILE *fp)
   { pquis(fp, "$, TrIPE version " VERSION "\n"); }
 
 static void usage(FILE *fp)
-  { pquis(fp, "Usage: $ [-k KEYRING] DIRECTIVE...\n"); }
+  { pquis(fp, "Usage: $ [-d CHAR] [-k KEYRING] DIRECTIVE...\n"); }
 
 static void help(FILE *fp)
 {
@@ -620,10 +640,11 @@ Options:\n\
 -v, --version          Show the version number.\n\
 -u, --usage            Show terse usage summary.\n\
 \n\
+-d, --delimiter=CHAR   Use CHAR rather than `:' as delimiter.\n\
 -k, --keyring=FILE     Fetch keys from FILE.\n\
 \n\
 Directives:\n\
-  peer:NAME:LOCAL-PORT:REMOTE-ADDR:REMOTE-PORT\n\
+  peer{,4,6}:NAME:LOCAL-PORT:REMOTE-ADDR:REMOTE-PORT\n\
   include:FILE\n\
   {,l,r}filt:FILTER:ARGS:...\n\
   next:TAG\n\
@@ -633,6 +654,7 @@ Filters:\n\
   send\n\
   fork:TAG\n\
   delay:QLEN[:MILLIS:P-REPLAY]\n\
+  drop[:P-DROP]\n\
   corrupt[:P-CORRUPT]\n",
        fp);
 }
@@ -642,7 +664,8 @@ int main(int argc, char *argv[])
   const char *kfname = "keyring.pub";
   int i;
   unsigned f = 0;
-  char buf[16];
+  char buf[32];
+  static octet zero[CHACHA_NONCESZ];
 
 #define f_bogus 1u
 
@@ -652,46 +675,39 @@ int main(int argc, char *argv[])
       { "help",                0,              0,      'h' },
       { "version",     0,              0,      'v' },
       { "usage",       0,              0,      'u' },
+      { "delimiter",   OPTF_ARGREQ,    0,      'd' },
       { "keyring",     OPTF_ARGREQ,    0,      'k' },
       { 0,             0,              0,      0 }
     };
-    if ((i = mdwopt(argc, argv, "hvuk:", opt, 0, 0, 0)) < 0)
-      break;
+    if ((i = mdwopt(argc, argv, "hvud:k:", opt, 0, 0, 0)) < 0) break;
     switch (i) {
-      case 'h':
-       help(stdout);
-       exit(0);
-      case 'v':
-       version(stdout);
-       exit(0);
-      case 'u':
-       usage(stdout);
-       exit(0);
-      case 'k':
-       kfname = optarg;
-       break;
-      default:
-       f |= f_bogus;
+      case 'h': help(stdout); exit(0);
+      case 'v': version(stdout); exit(0);
+      case 'u': usage(stdout); exit(0);
+      case 'd':
+       if (!optarg[0] || optarg[1])
+         die(1, "delimiter must be a single character");
+       delim = optarg;
        break;
+      case 'k': kfname = optarg; break;
+      default: f |= f_bogus; break;
     }
   }
-  if (f & f_bogus) {
-    usage(stderr);
-    exit(1);
-  }
+  if (f & f_bogus) { usage(stderr); exit(1); }
+
   rand_noisesrc(RAND_GLOBAL, &noise_source);
-  rand_seed(RAND_GLOBAL, 160);
+  rand_seed(RAND_GLOBAL, 256);
   rand_get(RAND_GLOBAL, buf, sizeof(buf));
-  rng = rc4_rand(buf, sizeof(buf));
+  rng = chacha20_rand(buf, sizeof(buf), zero);
   sel_init(&sel);
   if (key_open(&keys, kfname, KOPEN_READ, key_moan, 0))
     die(1, "couldn't open `%s': %s", kfname, strerror(errno));
-  for (i = optind; i < argc; i++)
-    parse(argv[i]);
-  if (npeer != 2)
-    die(1, "need two peers");
-  for (;;)
-    sel_select(&sel);
+  for (i = optind; i < argc; i++) parse(argv[i]);
+  if (npeer != 2) die(1, "need two peers");
+  for (;;) {
+    if (sel_select(&sel) && errno != EINTR)
+      die(1, "select failed: %s", strerror(errno));
+  }
 
 #undef f_bogus
 }