#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include extern const char *__progname; #define REKEY_INTERVAL 8192 //#define REKEY_INTERVAL 0x10000000 static const char *tap_path = "/dev/net/tun"; static const char *ifname = "vpn"; static const char *host = 0; static const char *port = 0; static const char *ipstr = 0; static const char *keyfile = 0; static int verbose = 0; typedef unsigned long long int TIME; #define TIME_SECOND ((TIME)1000000) typedef struct cip CIP; typedef struct cflow CFLOW; struct cip { // Connection In Progress CIP *link; int fd; int id; char *txt; struct addrinfo *ai; } ; struct cflow { unsigned int until_rekey; unsigned char kc[32]; unsigned char ke[33]; unsigned char r; void *rj; ARC4_STATE enc; } ; static int tap_fd; static int tap_id; static struct addrinfo *net_ai; static char *setaddr; static unsigned char *key; static int keylen; static int net_fd; static int net_id; static char *net_txt; static int net_phase; static AIO_OQ net_oq; static time_t next_connect; static int connect_id; static CIP *cips; static int cip_cleanup_id; static int netfill; static unsigned char cn[16]; static unsigned char sn[16]; static unsigned char cmk[32]; static unsigned char smk[32]; static CFLOW r; static CFLOW w; static unsigned char cv[16]; static unsigned char vsend[16]; static unsigned char vrecv[16]; static unsigned int randpool[64]; static unsigned char ipacket[65538]; static int randtweak; static TIME nexttweak; static int randptr; static int randid; static void handleargs(int ac, char **av) { int skip; int errs; skip = 0; errs = 0; for (ac--,av++;ac;ac--,av++) { if (skip > 0) { skip --; continue; } if (**av != '-') { if (! host) { host = *av; } else if (! port) { port = *av; } else if (! ipstr) { ipstr = *av; } else if (! keyfile) { keyfile = *av; } else { fprintf(stderr,"%s: extra argument `%s'\n",__progname,*av); errs = 1; } continue; } if (0) { needarg:; fprintf(stderr,"%s: %s needs a following argument\n",__progname,*av); errs = 1; continue; } #define WANTARG() do { if (++skip >= ac) goto needarg; } while (0) if (! strcmp(*av,"-v")) { verbose ++; continue; } if (! strcmp(*av,"-V")) { WANTARG(); verbose += atoi(av[skip]); continue; } #undef WANTARG fprintf(stderr,"%s: unrecognized option `%s'\n",__progname,*av); errs = 1; } if (!host || !port || !ipstr || !keyfile) { fprintf(stderr,"%s: need arguments: host port ip keyfile\n",__progname); errs = 1; } if (errs) exit(1); } static TIME now_time(void) { struct timeval tv; gettimeofday(&tv,0); return((tv.tv_sec*TIME_SECOND)+tv.tv_usec); } static void dump_data(FILE *to, const void *data, int len) { const unsigned char *dp; int o; dp = data; for (o=0;o= sizeof(randpool)) break; h = sha512_init(); sha512_process_bytes(h,&t,1); sha512_process_bytes(h,&randpool,sizeof(randpool)); sha512_process_bytes(h,&t,1); sha512_result(h,&hr[0]); i = sizeof(randpool) - o; if (i > 64) i = 64; for (j=i-1;j>=0;j--) ((unsigned char *)&randpool)[o+j] ^= hr[j]; o += 64; } randptr = sizeof(randpool) / 2; } static void random_bytes(void *data, int len) { unsigned char *dp; dp = data; for (;len>0;len--) { if (randptr < 1) random_stir(); *dp++ = ((unsigned char *)&randpool)[--randptr]; } } static void load_keyfile(void) { int fd; struct stat stb; int r; fd = open(keyfile,O_RDONLY,0); if (fd < 0) { fprintf(stderr,"%s: can't open keyfile %s: %s\n",__progname,keyfile,strerror(errno)); exit(1); } if (fstat(fd,&stb) < 0) { fprintf(stderr,"%s: can't fstat keyfile %s: %s\n",__progname,keyfile,strerror(errno)); exit(1); } if (stb.st_size > 65536) { fprintf(stderr,"%s: keyfile %s is too large\n",__progname,keyfile); exit(1); } keylen = stb.st_size; key = malloc(keylen); r = read(fd,key,keylen); if (r < 0) { fprintf(stderr,"%s: can't read keyfile %s: %s\n",__progname,keyfile,strerror(errno)); exit(1); } if (r != keylen) { fprintf(stderr,"%s: can't read keyfile %s: wanted %d, got %d\n",__progname,keyfile,keylen,r); exit(1); } close(fd); } static void run_command(const char *argv0, ...) { va_list ap; const char *arg; const char **argv; int nargs; int i; pid_t kid; pid_t dead; int status; int xp[2]; int e; nargs = 1; va_start(ap,argv0); while (1) { arg = va_arg(ap,const char *); if (! arg) break; nargs ++; } va_end(ap); argv = malloc((nargs+1)*sizeof(const char *)); argv[0] = argv0; i = 1; va_start(ap,argv0); while (1) { arg = va_arg(ap,const char *); if (! arg) break; if (i >= nargs) abort(); argv[i++] = arg; } va_end(ap); if (i != nargs) abort(); argv[i] = 0; if (socketpair(AF_LOCAL,SOCK_STREAM,0,&xp[0]) < 0) { fprintf(stderr,"%s: exec socketpair: %s\n",__progname,strerror(errno)); exit(1); } fflush(0); kid = fork(); if (kid < 0) { fprintf(stderr,"%s: fork: %s\n",__progname,strerror(errno)); exit(1); } if (kid == 0) { close(xp[0]); fcntl(xp[1],F_SETFD,1); execvp(argv0,(void *)argv); e = errno; write(xp[1],&e,sizeof(int)); _exit(0); } close(xp[1]); i = recv(xp[0],&e,sizeof(int),MSG_WAITALL); if (i < 0) { fprintf(stderr,"%s: exec recv: %s\n",__progname,strerror(errno)); exit(1); } if (i == sizeof(int)) { fprintf(stderr,"%s: exec %s: %s\n",__progname,argv0,strerror(e)); exit(1); } if (i != 0) { fprintf(stderr,"%s: exec protocol error: expected 0 or %d, got %d\n",__progname,(int)sizeof(int),i); exit(1); } close(xp[0]); while (1) { dead = wait4(kid,&status,0,0); if (dead < 0) { if (errno == EINTR) continue; fprintf(stderr,"%s: wait for %s: %s\n",__progname,argv0,strerror(errno)); exit(1); } if (dead != kid) { fprintf(stderr,"%s: unexpected child died (expected %d, got %d)\n",__progname,(int)kid,(int)dead); continue; } if (WIFEXITED(status)) { if (WEXITSTATUS(status) != 0) { fprintf(stderr,"%s: %s: exit %d\n",__progname,argv0,WEXITSTATUS(status)); exit(1); } } else if (WIFSIGNALED(status)) { i = WTERMSIG(status); // XXX Nobody seems to have thought to export the number of // entries in sys_siglist[](!!) We have to just hope it's big // enough for any signal that we can get, here. fprintf(stderr,"%s: %s: signal %d (%s)%s\n",__progname,argv0,i,sys_siglist[i],WCOREDUMP(status)?" (core dumped)":""); exit(1); } else { // Shouldn't get anything else, given our wait4() call. fprintf(stderr,"%s: %s: incomprehensible exit status %d\n",__progname,argv0,status); exit(1); } break; } } static void queue_encrypted(AIO_OQ *oq, ARC4_STATE *enc, const void *data, int len) { const unsigned char *dp; unsigned char buf[32]; dp = data; if (verbose) { printf("queue_encrypted %d:\n",len); dump_data(stdout,data,len); } while (len >= sizeof(buf)) { arc4_crypt(enc,dp,sizeof(buf),&buf[0]); aio_oq_queue_copy(oq,&buf[0],sizeof(buf)); dp += sizeof(buf); len -= sizeof(buf); } if (len) { arc4_crypt(enc,dp,len,&buf[0]); aio_oq_queue_copy(oq,&buf[0],len); } } static void crypto_nonce(void *data, int len) { random_bytes(data,len); } static void crypto_gen_mk(unsigned char *cn, unsigned char *sn, const void *ss, int sslen, const char *x2y, int ibase, unsigned char *mk) { void *hh; unsigned char ib; int i; const unsigned char *t; int tl; unsigned char hbuf[64]; t = ss; tl = sslen; for (i=0;i<64;i++) { ib = ibase + i; hh = sha512_init(); sha512_process_bytes(hh,ss,sslen); sha512_process_bytes(hh,t,tl); sha512_process_bytes(hh,&ib,1); sha512_process_bytes(hh,x2y,4); sha512_process_bytes(hh,sn,16); sha512_process_bytes(hh,cn,16); sha512_process_bytes(hh,t,tl); sha512_process_bytes(hh,ss,sslen); sha512_result(hh,&hbuf[0]); t = &hbuf[0]; tl = 64; } for (i=0;i<32;i++) mk[i] = t[i] ^ t[63-i]; } static void crypto_gen_mks(unsigned char *cn, unsigned char *sn, const void *ss, int sslen, unsigned char *smk, unsigned char *cmk) { if (verbose) { int i; printf("sn = %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x\n", sn[0],sn[1],sn[2],sn[3],sn[4],sn[5],sn[6],sn[7], sn[8],sn[9],sn[10],sn[11],sn[12],sn[13],sn[14],sn[15]); printf("cn = %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x\n", cn[0],cn[1],cn[2],cn[3],cn[4],cn[5],cn[6],cn[7], cn[8],cn[9],cn[10],cn[11],cn[12],cn[13],cn[14],cn[15]); printf("ss ="); for (i=0;i= w.until_rekey) abort(); if (len > 0) { queue_encrypted(&net_oq,&w.enc,data,w.until_rekey); w.until_rekey -= len; } } static void tap_rd(void *arg __attribute__((__unused__))) { unsigned char pkt[2000]; int pl; int i; pl = read(tap_fd,&pkt[2],sizeof(pkt)-2); if (verbose) { printf("Got packet from kernel, len = %d:",pl); for (i=0;(i<16)&&(i> 8; pkt[1] = pl & 0xff; queue_data(&pkt[0],2+pl); } static void setup_tap(void) { struct ifreq ifr; tap_fd = open(tap_path,O_RDWR,0); if (tap_fd < 0) { fprintf(stderr,"%s: open %s: %s\n",__progname,tap_path,strerror(errno)); exit(1); } bzero(&ifr,sizeof(ifr)); // XXX API botch ifr.ifr_flags = IFF_TAP | IFF_NO_PI; strncpy(&ifr.ifr_name[0],ifname,sizeof(ifr.ifr_name)); if (ioctl(tap_fd,TUNSETIFF,&ifr) < 0) { fprintf(stderr,"%s: TUNSETIFF failed: %s\n",__progname,strerror(errno)); exit(1); } tap_id = aio_add_poll(tap_fd,&aio_rwtest_always,&aio_rwtest_never,&tap_rd,0,0); } static void setup_gai_hints(struct addrinfo *h) { h->ai_flags = 0; h->ai_family = PF_UNSPEC; h->ai_socktype = SOCK_STREAM; h->ai_protocol = 0; h->ai_addrlen = 0; // XXX API botch h->ai_canonname = 0; // XXX API botch h->ai_addr = 0; // XXX API botch h->ai_next = 0; // XXX API botch } static void set_nonblock(int fd) { fcntl(fd,F_SETFL,fcntl(fd,F_GETFL,0)|O_NONBLOCK); } static void close_net(void) { close(net_fd); net_fd = -1; net_phase = -1; aio_remove_poll(net_id); net_id = AIO_NOID; aio_oq_flush(&net_oq); next_connect = time(0) + 60; } static int net_wtest(void *arg __attribute__((__unused__))) { return(aio_oq_nonempty(&net_oq)); } static void net_wr(void *arg __attribute__((__unused__))) { int w; w = aio_oq_writev(&net_oq,net_fd,-1); switch (w) { case AIO_WRITEV_ERROR: fprintf(stderr,"%s: write to %s: %s\n",__progname,net_txt,strerror(errno)); close_net(); return; } if (w < 0) { fprintf(stderr,"%s: aio_oq_writev on %s returned impossible value %d\n",__progname,net_txt,w); exit(1); } aio_oq_dropdata(&net_oq,w); } static void got_packet(const unsigned char *data, int len) { int i; if (verbose) { printf("got packet, len %d, begins"); for (i=0;(i<16)&&(i ilen) l = ilen; bcopy(idata,&sn[netfill],l); netfill += l; if (netfill >= 16) { do_step1(); netfill = 0; } return(l); } if (r.until_rekey < 1) { l = 33 - netfill; if (l > ilen) l = ilen; bcopy(idata,&r.ke[netfill],l); netfill += l; if (netfill >= 33) { rekey_r(); netfill = 0; } return(l); } l = r.until_rekey; if (l > ilen) l = ilen; switch (net_phase) { case 2: bp = &cv[0]; want = 16; break; case 3: bp = &vrecv[0]; want = 16; break; case 0: bp = &ipacket[0]; want = (netfill < 2) ? 2 : (2 + (ipacket[0] * 256) + ipacket[1]); break; default: abort(); break; } if (l > want - netfill) l = want - netfill; if (l < 0) abort(); arc4_crypt(&r.enc,idata,l,bp+netfill); if (verbose) { printf("decrypted %d:\n",l); dump_data(stdout,bp+netfill,l); } netfill += l; if (netfill >= want) { netfill = 0; switch (net_phase) { case 2: queue_data(&cv[0],16); net_phase = 3; break; case 3: if (bcmp(&vsend[0],&vrecv[0],16)) { fprintf(stderr,"%s: verifier failure\n",__progname); exit(1); } net_phase = 0; break; case 0: pl = (ipacket[0] * 256) + ipacket[1]; if (want == 2+pl) { got_packet(&ipacket[2],pl); } else if (want != 2) { abort(); } else { netfill = 2; } break; default: abort(); break; } } return(l); } static void net_rd(void *arg __attribute__((__unused__))) { unsigned char rbuf[8192]; int l; int o; int n; unsigned char *bp; int want; l = read(net_fd,&rbuf[0],sizeof(rbuf)); if (l < 0) { switch (errno) { case EINTR: case EWOULDBLOCK: return; break; } fprintf(stderr,"%s: network read (%s): %s\n",__progname,net_txt,strerror(errno)); close_net(); return; } if (l == 0) { if (verbose) printf("network read (%s) EOF\n",net_txt); close_net(); return; } if (verbose) { printf("net read got %d\n",l); dump_data(stdout,&rbuf[0],l); } o = 0; while (o < l) { n = net_input(&rbuf[o],l-o); if ((n < 0) || (o+n > l)) abort(); o += n; } } static char *printable_for_ai(struct addrinfo *ai) { char h[NI_MAXHOST]; char s[NI_MAXSERV]; int e; char *txt; e = getnameinfo(ai->ai_addr,ai->ai_addrlen,&h[0],NI_MAXHOST,&s[0],NI_MAXSERV,NI_NUMERICHOST|NI_NUMERICSERV); if (e) { asprintf(&txt,"[getnameinfo (AF %d): %s]",ai->ai_addr->sa_family,gai_strerror(e)); } else { asprintf(&txt,"%s/%s",&h[0],&s[0]); } return(txt); } static void have_net(int fd, char *txt) { if ((net_fd >= 0) || (net_id != AIO_NOID)) abort(); set_nonblock(fd); if (verbose) printf("Have connection, to %s\n",txt); net_fd = fd; net_id = aio_add_poll(fd,&aio_rwtest_always,&net_wtest,&net_rd,&net_wr,0); free(net_txt); net_txt = txt; net_phase = 1; crypto_nonce(&cn[0],16); if (verbose) printf("client nonce: %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x\n", cn[0],cn[1],cn[2],cn[3],cn[4],cn[5],cn[6],cn[7], cn[8],cn[9],cn[10],cn[11],cn[12],cn[13],cn[14],cn[15]); aio_oq_queue_point(&net_oq,&cn[0],16); } static void cip_close(CIP *c) { close(c->fd); c->fd = -1; if (c->id != AIO_NOID) { aio_remove_poll(c->id); c->id = AIO_NOID; } } static void shutdown_cips(void) { CIP *c; for (c=cips;c;c=c->link) cip_close(c); } static char *cip_text(CIP *c) { if (! c->txt) { c->txt = printable_for_ai(c->ai); } return(c->txt); } static void cip_check_connect(void *cv) { CIP *c; int err; socklen_t errlen; c = cv; errlen = sizeof(err); if (getsockopt(c->fd,SOL_SOCKET,SO_ERROR,&err,&errlen) < 0) { err = errno; fprintf(stderr,"%s: getsockopt SO_ERROR for %s: %s\n",__progname,cip_text(c),strerror(err)); cip_close(c); return; } if (errlen != sizeof(int)) { fprintf(stderr,"%s: getsockopt SO_ERROR for %s returned size %d (expected %d)\n",__progname,cip_text(c),(int)sizeof(int),(int)errlen); cip_close(c); return; } if (err) { fprintf(stderr,"%s: connect to %s: %s\n",__progname,cip_text(c),strerror(err)); cip_close(c); return; } have_net(c->fd,cip_text(c)); c->txt = 0; c->fd = -1; shutdown_cips(); } static int cip_cleanup(void *arg __attribute__((__unused__))) { CIP *c; if (! cips) abort(); if (cips->fd >= 0) return(AIO_BLOCK_NIL); while ((c=cips) && (c->fd < 0)) { cips = c->link; if (c->id != AIO_NOID) aio_remove_poll(c->id); free(c->txt); free(c); } if (! cips) { if (net_fd < 0) next_connect = time(0) + 60; aio_remove_block(cip_cleanup_id); cip_cleanup_id = AIO_NOID; } return(AIO_BLOCK_LOOP); } static void cip_start_cleanup(void) { if (cip_cleanup_id != AIO_NOID) abort(); cip_cleanup_id = aio_add_block(&cip_cleanup,0); } static int check_connect(void *arg __attribute__((__unused__))) { time_t now; struct addrinfo *ai; int fd; CIP *c; char *txt; if (net_fd >= 0) return(AIO_BLOCK_NIL); if (cips) return(AIO_BLOCK_NIL); now = time(0); if (now < next_connect) return(AIO_BLOCK_NIL); if (cips) abort(); if (net_id != AIO_NOID) abort(); for (ai=net_ai;ai;ai=ai->ai_next) { fd = socket(ai->ai_family,ai->ai_socktype,ai->ai_protocol); if (fd < 0) { fprintf(stderr,"%s: socket (AF %d): %s\n",__progname,ai->ai_family,strerror(errno)); continue; } set_nonblock(fd); if (connect(fd,ai->ai_addr,ai->ai_addrlen) < 0) { if (errno == EINPROGRESS) { c = malloc(sizeof(CIP)); c->fd = fd; c->id = aio_add_poll(fd,&aio_rwtest_never,&aio_rwtest_always,0,&cip_check_connect,c); c->ai = ai; c->txt = 0; c->link = cips; cips = c; } else { txt = printable_for_ai(ai); fprintf(stderr,"%s: connect to %s: %s\n",__progname,txt,strerror(errno)); free(txt); close(fd); continue; } } else { shutdown_cips(); have_net(fd,printable_for_ai(ai)); return(AIO_BLOCK_LOOP); } } cip_start_cleanup(); return(AIO_BLOCK_LOOP); } static void cflow_init(CFLOW *cf) { cf->rj = rijndael_init(8,8); arc4_init(&cf->enc); } static void startup(void) { struct addrinfo hints; int e; struct addrinfo *ai; char *slash; char *t; int l; int width; char hn[NI_MAXHOST]; setup_gai_hints(&hints); e = getaddrinfo(host,port,&hints,&net_ai); if (e) { fprintf(stderr,"%s: can't resolve %s/%s: %s\n",__progname,host,port,gai_strerror(e)); exit(1); } if (! net_ai) { fprintf(stderr,"%s: resolved %s/%s to zero addresses?\n",__progname,host,port); exit(1); } slash = index(ipstr,'/'); if (! slash) { fprintf(stderr,"%s: no slash in IP configuration: %s\n",__progname,ipstr); exit(1); } l = slash - ipstr; t = malloc(l+1); bcopy(ipstr,t,l); t[l] = '\0'; width = atoi(slash+1); setup_gai_hints(&hints); e = getaddrinfo(t,0,&hints,&ai); if (e) { fprintf(stderr,"%s: can't resolve %s: %s\n",__progname,host,port,gai_strerror(e)); exit(1); } if (! ai) { fprintf(stderr,"%s: resolved %s/%s to zero addresses?\n",__progname,host,port); exit(1); } if (ai->ai_next) { fprintf(stderr,"%s: %s resolved to multiple addresses\n",__progname,t); exit(1); } e = getnameinfo(ai->ai_addr,ai->ai_addrlen,&hn[0],NI_MAXHOST,0,0,NI_NUMERICHOST); if (e) { fprintf(stderr,"%s: can't format address for %s (AF %d): %s\n",__progname,host,port,ai->ai_family,gai_strerror(e)); exit(1); } asprintf(&setaddr,"%s/%d",&hn[0],width); freeaddrinfo(ai); run_command("ip","address","add",setaddr,"dev",ifname,(const char *)0); run_command("ip","link","set","dev",ifname,"up",(const char *)0); cips = 0; cip_cleanup_id = AIO_NOID; net_fd = -1; net_phase = -1; net_id = AIO_NOID; net_txt = 0; aio_oq_init(&net_oq); next_connect = 0; connect_id = aio_add_block(&check_connect,0); netfill = 0; cflow_init(&r); cflow_init(&w); } static int random_set_tweak(void *arg __attribute__((__unused__))) { TIME t; t = now_time(); if (t >= nexttweak) { if (! randtweak) printf("Setting randtweak\n"); randtweak = 1; nexttweak += 60123456; if (t >= nexttweak) { nexttweak = t + 60123456; } } return(((nexttweak-t)/(TIME_SECOND/1000))+1); } static void setup_random(void) { TIME now; int i; unsigned char rdat[4]; randtweak = 0; now = now_time(); nexttweak = now + (60 * TIME_SECOND); randid = aio_add_block(&random_set_tweak,0); i = sizeof(now); if (i > sizeof(randpool)) i = sizeof(randpool); for (i--;i>=0;i--) ((unsigned char *)&randpool)[i] ^= ((unsigned char *)&now)[i]; getrandom(&rdat[0],4,0); ((unsigned char *)&randpool)[sizeof(randpool)-1] ^= rdat[0]; ((unsigned char *)&randpool)[sizeof(randpool)-2] ^= rdat[1]; ((unsigned char *)&randpool)[sizeof(randpool)-3] ^= rdat[2]; ((unsigned char *)&randpool)[sizeof(randpool)-4] ^= rdat[3]; random_stir(); randptr = 0; } int main(int, char **); int main(int ac, char **av) { aio_poll_init(); handleargs(ac,av); setup_random(); load_keyfile(); setup_tap(); startup(); aio_event_loop(); exit(1); }