/* * Uses tun interfaces to create a point-to-point tunnel. * * Flags: * * -e Encrypted mode. Encrypts all tunnel traffic, to defend * against passive snooping. This does not work with UDP. * * -v Verbose mode. Makes iptunnel substantially chattier. * Mostly for debugging. The details are not documented; * see the code. * * -unit N * Specifies which tun unit to use. * * -tun path * Specifies which tun device to use. * * -peer name-or-addr * Specifies the peer host (name or address). Port * numbers are specified elsewhere. The argument may also * be the word `dynamic', which specifies dynamic-peer * mode. * * -local name-or-addr * Specifies the local IP address to use (name or * address). Port numbers are specified elsewhere. If * this is not specified, wildcard/unspecified addresses * are used, which normally means that the underlying IP * layer controls what address gets used. * * -udp recv-port send-port * Specifies UDP mode. Packets are encapsulated in UDP; * they are sent to send-port (on the peer host) and * received on recv-port (on the local host). send-port * may be overridden by `dynamic' as a -peer argument. * * -tcp-accept listen-port * Specifies passive TCP mode. Packets are encapsulated * in a TCP data stream; this instance listens for * connections on listen-port (on the local host). * * -tcp-connect connect-port * Specifies active TCP mode. Packets are encapsulated in * a TCP data stream; this instance connects to port * connect-port (on the peer host). * * -secret secret * Specifies a secret which is used to sign packets (for * UDP mode) or generate a verifier (for TCP mode). If * the command-line argument begins with @, the rest is * taken as a file name, which is read up to EOF to obtain * the secret. * * -secret must be specified, as must exactly one of -udp, -tcp-accept, * and tcp-connect, and one of -unit and -tun. -e, -v, and -local are * optional. -peer must be specified for -udp and -tcp-connect and * must not be specified for -tcp-accept. * * TCP verifier computation: Each end sends one byte holding * CURRENT_VERSION (ENCRYPTED_VERSION, if -e). Then each end * generates and sends CHALL_LEN random bytes. Each end computes the * SHA-1 hash of * - the shared secret * - the random data it sent * - the shared secret * - "s" for the accepter, "c" for the connecter * - the shared secret * - the random data it recewived * - the shared secret * It then folds the result in half with * result[i] = (sha1[i] + sha1[19-(i^1)]) & 0xff * and sends the resulting 10 bytes. Each end also mimics the other * end's computation and verifies that the data it gets matches that. * Any mismatch produces an abrupt connection drop; if no mismatch, it * sends 0x00. If no -e, each end then drops into packet exchange. * If -e, then each end generates and sends CHALL_LEN more random * bytes, and computes the SHA-1 hash of * - the shared secret * - the first random data it sent * - the shared secret * - the second random data the accepter sent * - the shared secret * - "->" for the accepter, "<-" for the connecter * - the shared secret * - the first random data it received * - the shared secret * - the second random data the connecter sent * - the shared secret * This generates 20 bytes of data. This SHA-1 hash is then repeated * with each byte incremented by 1, generating 20 more bytes of data. * This is repeated, with another increment by 1 each time, until 240 * bytes have been generated. This 240-byte string is used as an * arcfour key; the first 65536 bytes of the resulting keystream are * thrown away and the rest is used to encrypt packet-exchange data * written by that end. (It also mimics the other end's computation * to generate a keystream for use when reading, of course.) * * Packet exchange represents each packet as some number N of bytes of * data. This data consists of * - 1 byte: HL, header length * - HL bytes: header * - 1 byte: packet address type * - HL-1 bytes: destination address * Packet types are: * PKTTYPE_IPV4: IPv4, HL must be 5 * PKTTYPE_IPV6: IPv6, HL must be 17 * - N-(1+HL+SIG_LEN) bytes of payload * - SIG_LEN bytes of signature * The signature is over the other N-SIG_LEN bytes. SIG_LEN is 10 and * signatures are computed by taking the SHA-1 hash of * - the shared secret * - the data to be signed over * - the shared secret * and folding it in half with * result[i] = sha1[i] ^ sha1[i+10] * * For UDP, ecah tunneled packet is one UDP packet; N is the UDP data * payload length. For TCP, packets are packed into the TCP byte * stream by preceding each N-byte packet data blob with N in two-byte * big-endian. * * This program is dedicated to Lugh Samildanach. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "oq.h" #include "pollloop.h" /* MAX_PKT_LEN plus the longest tun-provided address prefix must be <=65535. The code assumes this, but it is not checked for (cpp can't do sizeof). */ #define MAX_PKT_LEN 8192 #define SIG_LEN 10 #define CHALL_LEN 20 #define CURRENT_VERSION 3 #define ENCRYPTED_VERSION 4 #define PKTTYPE_IPV4 0x01 #define PKTTYPE_IPV6 0x02 #ifndef NI_WITHSCOPEID #define NI_WITHSCOPEID 0 #endif extern const char *__progname; static const char * const dev_random = "/dev/urandom"; typedef enum { OP_UNSET = 1, OP_UDP, OP_TCP_ACCEPT, OP_TCP_CONNECT } OPMODE; typedef enum { TE_ACCEPTER = 1, TE_CONNECTER } TCPEND; typedef struct udpfd UDPFD; typedef struct pending PENDING; typedef struct accfd ACCFD; typedef struct tcpconn TCPCONN; typedef struct tmo TMO; struct tmo { int fd; void (*cb)(void *); void *arg; int id; int killid; } ; struct accfd { ACCFD *link; int fd; struct addrinfo *ai; char *txt; int id; } ; struct udpfd { UDPFD *link; int fd; struct addrinfo *ai; char *txt; int id; } ; struct pending { int timeout; char *txt; struct addrinfo *ai; } ; struct tcpconn { int fd; OQ oq; TCPEND end; char *lcltxt; char *remtxt; unsigned char vers; unsigned char lcl_rnd[CHALL_LEN]; unsigned char rem_rnd[CHALL_LEN]; unsigned char lcl_resp[SIG_LEN]; unsigned char des_resp[SIG_LEN]; unsigned char rem_resp[SIG_LEN]; unsigned char conf; unsigned char lcl_rnd2[CHALL_LEN]; unsigned char rem_rnd2[CHALL_LEN]; int fill; int ioid; int blkid; TMO *tmo; int killid; void (*on_kill)(void); } ; static int eflag = 0; static int vflag = 0; static int tun_unit = -1; static const char *peer_str = 0; static const char *local_str = 0; static OPMODE opmode = OP_UNSET; static const char *lport_str = 0; static const char *rport_str = 0; static char *secret = 0; static int secretlen = 0; static char *tun_path = 0; static int rand_fd; static int tun_fd; static int tun_id; static TCPCONN *tcp_live; static unsigned char tcp_ibuf[2+MAX_PKT_LEN+sizeof(struct sockaddr_storage)+SIG_LEN]; static int tcp_ifill; static struct addrinfo *local_ai0; static int n_local_ai; static struct addrinfo *peer_ai0; static int n_peer_ai; static int dynamic_peer; static struct sockaddr_storage last_peer; static void (*tun_read)(const unsigned char *, int, const void *, int); static UDPFD *udpfds; static UDPFD *send_udp; static ARC4_STATE enc_r; static ARC4_STATE enc_w; static struct addrinfo *tcp_next_local; static struct addrinfo *tcp_next_peer; static TMO *tcp_next_tmo; static int tcp_next_connid; static ACCFD *accfds; static void *dequal(const volatile void *x) { return((((const volatile char *)x)-((const volatile char *)0))+(char *)0); } 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 != '-') { fprintf(stderr,"%s: stray argument `%s'\n",__progname,*av); errs ++; continue; } if (0) { needarg:; fprintf(stderr,"%s: %s needs a following argument\n",__progname,*av); errs ++; continue; } #define WANTARG() do { if (++skip >= ac) goto needarg; } while (0) if (!strcmp(*av,"-e")) { eflag = 1; continue; } if (!strcmp(*av,"-v")) { vflag ++; continue; } if (!strcmp(*av,"-unit")) { if (tun_path) fprintf(stderr,"%s: %s: overriding earlier -tun\n",__progname,*av); WANTARG(); tun_unit = atoi(av[skip]); tun_path = 0; continue; } if (!strcmp(*av,"-tun")) { if (tun_unit >= 0) fprintf(stderr,"%s: %s: overriding earlier -unit\n",__progname,*av); WANTARG(); tun_path = av[skip]; tun_unit = -1; continue; } if (!strcmp(*av,"-peer")) { WANTARG(); peer_str = av[skip]; continue; } if (!strcmp(*av,"-local")) { WANTARG(); local_str = av[skip]; continue; } if (!strcmp(*av,"-secret")) { WANTARG(); secret = strdup(av[skip]); secretlen = strlen(secret); bzero(av[skip],secretlen); continue; } if (!strcmp(*av,"-udp")) { if (opmode != OP_UNSET) fprintf(stderr,"%s: %s: overriding earlier mode flag\n",__progname,*av); WANTARG(); lport_str = av[skip]; WANTARG(); rport_str = av[skip]; opmode = OP_UDP; continue; } if (!strcmp(*av,"-tcp-accept")) { if (opmode != OP_UNSET) fprintf(stderr,"%s: %s: overriding earlier mode flag\n",__progname,*av); WANTARG(); lport_str = av[skip]; rport_str = 0; opmode = OP_TCP_ACCEPT; continue; } if (!strcmp(*av,"-tcp-connect")) { if (opmode != OP_UNSET) fprintf(stderr,"%s: %s: overriding earlier mode flag\n",__progname,*av); WANTARG(); lport_str = 0; rport_str = av[skip]; opmode = OP_TCP_CONNECT; continue; } #undef WANTARG fprintf(stderr,"%s: unrecognized option `%s'\n",__progname,*av); errs ++; } if (errs) exit(1); } static void checkargs(void) { int errs; errs = 0; if ((tun_unit < 0) && !tun_path) { fprintf(stderr,"%s: -unit or -tun must be specified\n",__progname); errs ++; } if (!secret) { fprintf(stderr,"%s: -secret must be specified\n",__progname); errs ++; } switch (opmode) { case OP_UNSET: fprintf(stderr,"%s: an operation mode must be specified\n",__progname); errs ++; break; case OP_UDP: if (eflag) { fprintf(stderr,"%s: -e does not work with -udp\n",__progname); errs ++; } /* fall through */ case OP_TCP_CONNECT: if (! peer_str) { fprintf(stderr,"%s: -peer must be specified for -udp and -tcp-connect\n",__progname); errs ++; } break; case OP_TCP_ACCEPT: if (peer_str) { fprintf(stderr,"%s: -peer must not be specified for -tcp-accept\n",__progname); errs ++; } break; default: abort(); break; } if (errs) exit(1); } static void nbio(int fd) { fcntl(fd,F_SETFL,fcntl(fd,F_GETFL,0)|O_NONBLOCK); } static void setup_tun(void) { int v; if (! tun_path) asprintf(&tun_path,"/dev/tun%d",tun_unit); tun_fd = open(tun_path,O_RDWR,0); if (tun_fd < 0) { fprintf(stderr,"%s: %s: %s\n",__progname,tun_path,strerror(errno)); exit(1); } if (vflag) printf("opened tun on %d\n",tun_fd); v = IFF_POINTOPOINT; if (ioctl(tun_fd,TUNSIFMODE,&v) < 0) { if (errno != EBUSY) { fprintf(stderr,"%s: %s TUNSIFMODE: %s\n",__progname,tun_path,strerror(errno)); exit(1); } } v = 1; if (ioctl(tun_fd,TUNSLMODE,&v) < 0) { fprintf(stderr,"%s: %s TUNSLMODE: %s\n",__progname,tun_path,strerror(errno)); exit(1); } nbio(tun_fd); fcntl(tun_fd,F_SETFL,fcntl(tun_fd,F_GETFL,0)|O_NONBLOCK); if (vflag) printf("set up tun\n"); } static void setup_misc(void) { rand_fd = open(dev_random,O_RDONLY,0); if (rand_fd < 0) { fprintf(stderr,"%s: %s: %s\n",__progname,dev_random,strerror(errno)); exit(1); } if (vflag) printf("set up %s\n",dev_random); signal(SIGPIPE,SIG_IGN); if (vflag) printf("set up SIGPIPE\n"); } static void random_data(void *data, int len) { char *dp; int n; dp = data; while (len > 0) { n = read(rand_fd,dp,len); if (n < 0) { fprintf(stderr,"%s: read from %s: %s\n",__progname,dev_random,strerror(errno)); exit(1); } if (n == 0) { fprintf(stderr,"%s: read from %s got EOF\n",__progname,dev_random); exit(1); } len -= n; dp += n; } } static void setup_secret(void) { char *buf; int len; if (secret[0] == '@') { int fd; struct stat stb; int r; fd = open(secret+1,O_RDONLY,0); if (fd < 0) { fprintf(stderr,"%s: open %s: %s\n",__progname,secret+1,strerror(errno)); exit(1); } if (fstat(fd,&stb) < 0) { fprintf(stderr,"%s: fstat %s: %s\n",__progname,secret+1,strerror(errno)); exit(1); } if (stb.st_size > 512) { fprintf(stderr,"%s: %s: unreasonably large\n",__progname,secret+1); exit(1); } len = stb.st_size; buf = malloc(len); r = read(fd,buf,len); if (r < 0) { fprintf(stderr,"%s: read %s: %s\n",__progname,secret+1,strerror(errno)); exit(1); } if (r != len) { fprintf(stderr,"%s: read %s: wanted %d, got %d\n",__progname,secret+1,len,r); exit(1); } close(fd); free(secret); secret = buf; secretlen = len; } if (vflag) printf("loaded secret, length = %d\n",secretlen); } static char *textfor(struct sockaddr *sa, int len) { char hn[NI_MAXHOST]; char sn[NI_MAXSERV]; char *txt; if (getnameinfo(sa,len,&hn[0],NI_MAXHOST,&sn[0],NI_MAXSERV,NI_NUMERICHOST|NI_NUMERICSERV|NI_DGRAM|NI_WITHSCOPEID)) { asprintf(&txt,"[can't get text address; %s]",strerror(errno)); } else { asprintf(&txt,"%s/%s",&hn[0],&sn[0]); } return(txt); } static void setup_local(int socktype) { struct addrinfo *ai; if (local_str || lport_str) { struct addrinfo hints; int e; void error_prefix(void) { fprintf(stderr,"%s: ",__progname); if (! local_str) { fprintf(stderr," port %s: ",lport_str); } else if (! lport_str) { fprintf(stderr," address %s: ",local_str); } else { fprintf(stderr," %s/%s: ",local_str,lport_str); } } hints.ai_flags = AI_PASSIVE; hints.ai_family = PF_UNSPEC; hints.ai_socktype = socktype; hints.ai_protocol = 0; hints.ai_addrlen = 0; hints.ai_canonname = 0; hints.ai_addr = 0; hints.ai_next = 0; e = getaddrinfo(local_str,lport_str,&hints,&local_ai0); if (e) { error_prefix(); fprintf(stderr,"%s\n",gai_strerror(e)); exit(1); } if (! local_ai0) { error_prefix(); fprintf(stderr,"lookup succeeded but no addresses\n"); exit(1); } } else { local_ai0 = 0; } n_local_ai = 0; for (ai=local_ai0;ai;ai=ai->ai_next) n_local_ai ++; if (vflag) { struct addrinfo *ai; char *t; printf("set up local, n_local_ai = %d\n",n_local_ai); for (ai=local_ai0;ai;ai=ai->ai_next) { t = textfor(ai->ai_addr,ai->ai_addrlen); printf(" %s\n",t); free(t); } } } static void lookup_peer(int socktype) { struct addrinfo hints; int e; struct addrinfo *ai; void error_prefix(void) { fprintf(stderr,"%s: ",__progname); if (! local_str) { fprintf(stderr,"port %s: ",lport_str); } else if (! lport_str) { fprintf(stderr,"address %s: ",local_str); } else { fprintf(stderr,"%s/%s: ",local_str,lport_str); } } if (! strcmp(peer_str,"dynamic")) { dynamic_peer = 1; last_peer.ss_len = 0; return; } dynamic_peer = 0; hints.ai_flags = 0; hints.ai_family = PF_UNSPEC; hints.ai_socktype = socktype; hints.ai_protocol = 0; hints.ai_addrlen = 0; hints.ai_canonname = 0; hints.ai_addr = 0; hints.ai_next = 0; e = getaddrinfo(peer_str,rport_str,&hints,&peer_ai0); if (e) { error_prefix(); fprintf(stderr,"%s\n",gai_strerror(e)); exit(1); } if (! peer_ai0) { error_prefix(); fprintf(stderr,"lookup succeeded but no addresses\n"); exit(1); } n_peer_ai = 0; for (ai=peer_ai0;ai;ai=ai->ai_next) n_peer_ai ++; if (vflag) { struct addrinfo *ai; char *t; printf("set up peer, n_peer_ai = %d\n",n_peer_ai); for (ai=peer_ai0;ai;ai=ai->ai_next) { t = textfor(ai->ai_addr,ai->ai_addrlen); printf(" %s\n",t); free(t); } } } #define SIGN_BLK(ptr,len) SIGN__BLK, ((const void *)(ptr)), ((int)(len)) #define SIGN__BLK 1 #define SIGN_INTO(loc) SIGN__INTO, ((void *)(loc)) #define SIGN__INTO 2 static void sign(int key, ...) { va_list ap; void *h; int i; unsigned char result[20]; const void *b; int l; void *into; h = sha1_init(); sha1_process_bytes(h,secret,secretlen); va_start(ap,key); while <"blks"> (1) { switch (key) { case SIGN__BLK: b = va_arg(ap,const void *); l = va_arg(ap,int); sha1_process_bytes(h,b,l); break; case SIGN__INTO: break <"blks">; break; default: abort(); } key = va_arg(ap,int); } into = va_arg(ap,void *); sha1_process_bytes(h,secret,secretlen); sha1_result(h,&result[0]); #if SIG_LEN != 10 #error "code wrong for SIG_LEN" #endif for (i=0;i<10;i++) ((unsigned char *)into)[i] = result[i] ^ result[i+10]; } static int goodsig(const void *pktdata, int pktlen, const void *sigloc) { void *h; int i; unsigned char result[20]; h = sha1_init(); sha1_process_bytes(h,secret,secretlen); sha1_process_bytes(h,pktdata,pktlen); sha1_process_bytes(h,secret,secretlen); sha1_result(h,&result[0]); #if SIG_LEN != 10 #error "code wrong for SIG_LEN" #endif for (i=0;i<10;i++) { if (((const unsigned char *)sigloc)[i] != (result[i]^result[i+10])) { return(0); } } return(1); } static BLOCKFN_RV tmo_free(void *tv) { TMO *t; t = tv; remove_block_id(t->killid); free(tv); return(BLOCK_LOOP); } static void tmo_fired(void *tv) { TMO *t; t = tv; if (t->id == PL_NOID) return; remove_poll_id(t->id); t->id = PL_NOID; (*t->cb)(t->arg); close(t->fd); t->killid = add_block_fn(&tmo_free,t); } static void timeout_cancel(TMO *t) { if (t->id == PL_NOID) return; remove_poll_id(t->id); t->id = PL_NOID; close(t->fd); t->killid = add_block_fn(&tmo_free,t); } static TMO *start_timeout(int seconds, void (*callback)(void *), void *arg) { TMO *t; int s; struct itimerval itv; s = socket(AF_TIMER,SOCK_STREAM,0); if (s < 0) { fprintf(stderr,"%s: timer socket: %s\n",__progname,strerror(errno)); exit(1); } nbio(s); itv.it_value.tv_sec = seconds; itv.it_value.tv_usec = 0; itv.it_interval.tv_sec = 0; itv.it_interval.tv_usec = 0; write(s,&itv,sizeof(itv)); t = malloc(sizeof(TMO)); t->fd = s; t->cb = callback; t->arg = arg; t->id = add_poll_fd(s,&rwtest_always,&rwtest_never,&tmo_fired,0,t); return(t); } static void dump_data(const void *data, int len) { const unsigned char *dp; int i; dp = data; for (i=0;i 1) { printf("writing to tun, %d+%d\n",(int)iov[0].iov_len,(int)iov[1].iov_len); dump_data(iov[0].iov_base,iov[0].iov_len); dump_data(iov[1].iov_base,iov[1].iov_len); } writev(tun_fd,&iov[0],2); if (dynamic_peer && from) bcopy(from,&last_peer,from->sa_len); return(1); } static void rd_udpfd(void *fv) { UDPFD *f; struct sockaddr_storage from; socklen_t fromlen; int len; char pkt[MAX_PKT_LEN+sizeof(struct sockaddr_storage)+SIG_LEN]; f = fv; fromlen = sizeof(from); len = recvfrom(f->fd,&pkt[0],sizeof(pkt),0,(void *)&from,&fromlen); if (len < 0) { switch (errno) { case EINTR: case EWOULDBLOCK: return; break; } fprintf(stderr,"%s: recvfrom: %s\n",__progname,strerror(errno)); exit(1); } have_packet(&pkt[0],len,(const void *)&from); } static void rd_tun(void *arg __attribute__((__unused__))) { static int hn = 0; int len; char pkt[MAX_PKT_LEN+sizeof(struct sockaddr_storage)+SIG_LEN]; unsigned char hdr[sizeof(union { unsigned char hdr4[1+4]; unsigned char hdr6[1+16]; })]; int hl; union { struct sockaddr sa; struct sockaddr_in sin; struct sockaddr_in6 sin6; } sau; if (hn == 0) { if (offsetof(struct sockaddr,sa_len) > offsetof(struct sockaddr,sa_family)) { hn = offsetof(struct sockaddr,sa_len) + sizeof(sau.sa.sa_len); } else { hn = offsetof(struct sockaddr,sa_family) + sizeof(sau.sa.sa_family); } } len = read(tun_fd,&pkt[0],MAX_PKT_LEN+sizeof(struct sockaddr_storage)); if (len < 0) { if (errno == EINTR) return; fprintf(stderr,"%s: tun read: %s\n",__progname,strerror(errno)); exit(1); } if (vflag) printf("got packet from tun, length %d\n",len); if (vflag > 1) dump_data(&pkt[0],len); if (len < hn) { if (vflag) printf("too short (len %d < hn %d)\n",len,hn); return; } bcopy(&pkt[0],&sau.sa,hn); if (sau.sa.sa_len >= len) { if (vflag) printf("nothing after tun header\n"); return; } if (vflag) printf("address family %d len %d\n",sau.sa.sa_family,sau.sa.sa_len); switch (sau.sa.sa_family) { union { char vbuf4[sizeof("255.255.255.255")]; char vbuf6[sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255")]; } vu; case AF_INET: bcopy(&pkt[0],&sau.sin,sizeof(struct sockaddr_in)); hdr[0] = PKTTYPE_IPV4; bcopy(&sau.sin.sin_addr,&hdr[1],4); hl = 5; if (vflag) printf("to %s\n",inet_ntop(AF_INET,&sau.sin.sin_addr,&vu.vbuf4[0],sau.sin.sin_len)); break; case AF_INET6: bcopy(&pkt[0],&sau.sin6,sizeof(struct sockaddr_in6)); hdr[0] = PKTTYPE_IPV6; bcopy(&sau.sin6.sin6_addr,&hdr[1],16); hl = 17; if (vflag) printf("to %s\n",inet_ntop(AF_INET6,&sau.sin6.sin6_addr,&vu.vbuf6[0],sau.sin6.sin6_len)); break; default: if (vflag) printf("af %d not supported\n",sau.sa.sa_family); return; break; } (*tun_read)(&hdr[0],hl,&pkt[sau.sa.sa_len],len-sau.sa.sa_len); } static void tun_read_udp(const unsigned char *hdr, int hdrlen, const void *data, int len) { struct msghdr mh; struct iovec iov[4]; unsigned char hl; unsigned char sig[10]; hl = hdrlen; sign( SIGN_BLK(&hl,1), SIGN_BLK(hdr,hdrlen), SIGN_BLK(data,len), SIGN_INTO(&sig[0]) ); iov[0].iov_base = &hl; iov[0].iov_len = 1; iov[1].iov_base = dequal(hdr); iov[1].iov_len = hdrlen; iov[2].iov_base = dequal(data); iov[2].iov_len = len; iov[3].iov_base = &sig[0]; iov[3].iov_len = 10; if (dynamic_peer) { if (last_peer.ss_len < 1) return; mh.msg_name = &last_peer; mh.msg_namelen = last_peer.ss_len; } else { mh.msg_name = peer_ai0->ai_addr; mh.msg_namelen = peer_ai0->ai_addrlen; } mh.msg_iov = &iov[0]; mh.msg_iovlen = sizeof(iov) / sizeof(iov[0]); mh.msg_control = 0; mh.msg_controllen = 0; mh.msg_flags = 0; if (send_udp == 0) send_udp = udpfds; if (sendmsg(send_udp->fd,&mh,0) < 0) send_udp = send_udp->link; } static void have_udp(int fd, char *txt, struct addrinfo *ai) { UDPFD *f; nbio(fd); f = malloc(sizeof(UDPFD)); f->fd = fd; f->ai = ai; f->txt = txt; f->id = add_poll_fd(fd,&rwtest_always,&rwtest_never,&rd_udpfd,0,f); f->link = udpfds; udpfds = f; } static void retry_udp(void *pv) { PENDING *p; int fd; p = pv; fd = socket(p->ai->ai_family,p->ai->ai_socktype,p->ai->ai_protocol); if (fd < 0) { fprintf(stderr,"%s: socket (af %d): %s\n",__progname,p->ai->ai_family,strerror(errno)); return; } if (bind(fd,p->ai->ai_addr,p->ai->ai_addrlen) < 0) { close(fd); p->timeout += p->timeout >> 1; if (p->timeout > 3600) p->timeout = 3600; start_timeout(p->timeout,&retry_udp,p); return; } fprintf(stderr,"%s: bind %s: succeeded\n",__progname,p->txt); have_udp(fd,p->txt,p->ai); free(p); } static void start_pending(char *txt, struct addrinfo *ai, void (*fn)(void *)) { PENDING *p; p = malloc(sizeof(PENDING)); p->timeout = 40; p->txt = txt; p->ai = ai; (*fn)(p); } static void start_udp(void) { struct addrinfo *ai; setup_local(SOCK_DGRAM); lookup_peer(SOCK_DGRAM); if (vflag) printf("starting UDP\n"); udpfds = 0; for (ai=local_ai0;ai;ai=ai->ai_next) { char *txt; txt = textfor(ai->ai_addr,ai->ai_addrlen); start_pending(txt,ai,&retry_udp); } send_udp = 0; tun_read = &tun_read_udp; } static void tcp_write(const void *data, int len) { const char *dp; unsigned char buf[4096]; int n; if (vflag > 1) { printf("tcp_write %d\n",len); dump_data(data,len); } if (eflag) { dp = data; while (len > 0) { n = (len > sizeof(buf)) ? sizeof(buf) : len; arc4_crypt(&enc_w,dp,n,&buf[0]); oq_queue_copy(&tcp_live->oq,&buf[0],n); len -= n; dp += n; } } else { oq_queue_copy(&tcp_live->oq,data,len); } } static void tun_read_tcp(const unsigned char *hdr, int hdrlen, const void *data, int len) { unsigned char lenpref[2]; unsigned char sig[SIG_LEN]; unsigned char hl; int totlen; if (! tcp_live) return; totlen = 1 + hdrlen + len + SIG_LEN; if (totlen > 65535) abort(); hl = hdrlen; sign( SIGN_BLK(&hl,1), SIGN_BLK(hdr,hdrlen), SIGN_BLK(data,len), SIGN_INTO(&sig[0]) ); lenpref[0] = totlen >> 8; lenpref[1] = totlen & 0xff; tcp_write(&lenpref[0],2); tcp_write(&hl,1); tcp_write(hdr,hdrlen); tcp_write(data,len); tcp_write(&sig[0],SIG_LEN); if (vflag) printf("tun_read_tcp queued\n"); } static BLOCKFN_RV destroy_tcpconn(void *vc) { TCPCONN *c; c = vc; close(c->fd); oq_flush(&c->oq); free(c->lcltxt); free(c->remtxt); remove_block_id(c->killid); free(c); return(BLOCK_LOOP); } static void kill_tcpconn(TCPCONN *c) { if (c->killid != PL_NOID) return; if (c->ioid != PL_NOID) remove_poll_id(c->ioid); if (c->blkid != PL_NOID) remove_block_id(c->blkid); c->ioid = PL_NOID; c->blkid = PL_NOID; c->killid = add_block_fn(&destroy_tcpconn,c); if (c->tmo) timeout_cancel(c->tmo); if (c->on_kill) (*c->on_kill)(); } static int tcpconn_fill(TCPCONN *c, void *buf, int len) { int want; int did; want = len - c->fill; did = read(c->fd,c->fill+(char *)buf,want); if (did < 0) { switch (errno) { case EINTR: case EWOULDBLOCK: return(1); break; } fprintf(stderr,"%s: read (%s<-%s): %s\n",__progname,c->lcltxt,c->remtxt,strerror(errno)); kill_tcpconn(c); return(1); } if (did == 0) { fprintf(stderr,"%s: read (%s<-%s) got EOF\n",__progname,c->lcltxt,c->remtxt); kill_tcpconn(c); return(1); } c->fill += did; return(c->fill < len); } static void wr_tcpconn(void *vc) { TCPCONN *c; int n; c = vc; n = oq_writev(&c->oq,c->fd); if (n < 0) { switch (errno) { case EINTR: case EWOULDBLOCK: return; break; } fprintf(stderr,"%s: write (%s->%s): %s\n",__progname,c->lcltxt,c->remtxt,strerror(errno)); kill_tcpconn(c); return; } oq_dropdata(&c->oq,n); } static void compute_verifier( const unsigned char *chall_1, const char *mid, const unsigned char *chall_2, unsigned char *into ) { void *h; unsigned char result[20]; int i; h = sha1_init(); sha1_process_bytes(h,secret,secretlen); sha1_process_bytes(h,chall_1,CHALL_LEN); sha1_process_bytes(h,secret,secretlen); sha1_process_bytes(h,mid,1); sha1_process_bytes(h,secret,secretlen); sha1_process_bytes(h,chall_2,CHALL_LEN); sha1_process_bytes(h,secret,secretlen); sha1_result(h,&result[0]); #if SIG_LEN != 10 #error "code wrong for SIG_LEN" #endif for (i=0;i<10;i++) into[i] = (result[i] + result[19-(i^1)]) & 0xff; } static void compute_verifiers(TCPCONN *c) { const char *mid1; const char *mid2; switch (c->end) { case TE_ACCEPTER: mid1 = "s"; mid2 = "c"; break; case TE_CONNECTER: mid1 = "c"; mid2 = "s"; break; default: abort(); break; } compute_verifier(&c->rem_rnd[0],mid1,&c->lcl_rnd[0],&c->lcl_resp[0]); compute_verifier(&c->lcl_rnd[0],mid2,&c->rem_rnd[0],&c->des_resp[0]); } static void tcpconn_down(void) { kill_tcpconn(tcp_live); tcp_live = 0; tcp_ifill = 0; } static void rd_tcpconn_live(void *cv) { TCPCONN *c; unsigned char buf[8192]; int n; int p; int i; c = cv; if (c != tcp_live) return; n = read(c->fd,&buf[0],sizeof(buf)); if (n < 0) { switch (errno) { case EINTR: case EWOULDBLOCK: return; break; } fprintf(stderr,"%s: read (%s<-%s): %s\n",__progname,c->lcltxt,c->remtxt,strerror(errno)); tcpconn_down(); return; } if (n == 0) { if (vflag) printf("live TCP read EOF\n"); tcpconn_down(); return; } if (vflag) printf("live TCP read %d, tcp_fill %d\n",n,tcp_ifill); if (eflag) arc4_crypt(&enc_r,&buf[0],n,&buf[0]); if (vflag > 1) dump_data(&buf[0],n); p = 0; if ((tcp_ifill < 0) || (tcp_ifill > sizeof(tcp_ibuf))) abort(); if (tcp_ifill > 0) { int pktlen; if (tcp_ifill == 1) { tcp_ibuf[1] = buf[p++]; tcp_ifill = 2; if (p >= n) return; } pktlen = (tcp_ibuf[0] * 256) + tcp_ibuf[1]; if ((pktlen < 0) || (pktlen > 65535)) abort(); if (2+pktlen > sizeof(tcp_ibuf)) { fprintf(stderr,"%s: %s<-%s: incoming packet too large (%d > %d)\n",__progname,c->lcltxt,c->remtxt,2+pktlen,(int)sizeof(tcp_ibuf)); tcpconn_down(); return; } i = n - p; if (tcp_ifill+i < 2+pktlen) { if ( (i < 0) || (tcp_ifill < 0) || (tcp_ifill > sizeof(tcp_ibuf)) || (i > sizeof(tcp_ibuf)) || (tcp_ifill+i > sizeof(tcp_ibuf)) ) abort(); bcopy(&buf[p],&tcp_ibuf[tcp_ifill],i); tcp_ifill += i; if ((tcp_ifill >= 2) && ((tcp_ibuf[0]*256)+tcp_ibuf[1]+2 <= tcp_ifill)) abort(); return; } i = 2 + pktlen - tcp_ifill; if ( (i < 0) || (tcp_ifill < 0) || (tcp_ifill > sizeof(tcp_ibuf)) || (i > sizeof(tcp_ibuf)) || (tcp_ifill+i > sizeof(tcp_ibuf)) ) abort(); bcopy(&buf[p],&tcp_ibuf[tcp_ifill],i); p += i; have_packet(&tcp_ibuf[2],pktlen,0); tcp_ifill = 0; } while (p < n) { int pktlen; if (p < 0) abort(); i = n - p; if (i < 2) { tcp_ibuf[0] = buf[p]; tcp_ifill = 1; if (vflag) printf("leftover %d\n",1); return; } pktlen = (buf[p] * 256) + buf[p+1]; if ((pktlen < 0) || (pktlen > 65535)) abort(); if (2+pktlen > sizeof(tcp_ibuf)) { fprintf(stderr,"%s: %s<-%s: incoming packet too large (%d > %d)\n",__progname,c->lcltxt,c->remtxt,2+pktlen,(int)sizeof(tcp_ibuf)); tcpconn_down(); return; } if (p+2+pktlen <= n) { have_packet(&buf[p+2],pktlen,0); p += 2 + pktlen; continue; } if ((i < 0) || (i > sizeof(tcp_ibuf))) abort(); bcopy(&buf[p],&tcp_ibuf[0],i); if ((i >= 2) && ((tcp_ibuf[0]*256)+tcp_ibuf[1]+2 <= i)) abort(); tcp_ifill = i; if (vflag) printf("leftover %d\n",i); return; } } static int wtest_tcpconn(void *cv) { return(oq_nonempty(&((TCPCONN *)cv)->oq)); } static void tcp_try_next(void); /* forward */ static void tcp_try_next_(void *arg __attribute__((__unused__))) { tcp_next_tmo = 0; tcp_try_next(); } static void tcp_try_next_delayed(void) { tcp_next_tmo = start_timeout(60,&tcp_try_next_,0); } static void tcpconn_up(TCPCONN *c) { if (tcp_live) kill_tcpconn(tcp_live); tcp_live = c; timeout_cancel(c->tmo); c->tmo = 0; if (c->ioid != PL_NOID) remove_poll_id(c->ioid); if (c->blkid != PL_NOID) remove_poll_id(c->blkid); c->ioid = add_poll_fd(c->fd,&rwtest_always,&wtest_tcpconn,&rd_tcpconn_live,&wr_tcpconn,c); tcp_ifill = 0; if (c->on_kill == &tcp_try_next_delayed) c->on_kill = &tcp_try_next; } static void setup_key(const void *r1, const void *r2, const char *arrow, const void *r3, const void *r4, ARC4_STATE *into) { unsigned char dbuf[(6*secretlen)+(4*CHALL_LEN)+2]; unsigned char key[240]; int dbo; int i; int j; void *h; static void append(const void *data, int len) { bcopy(data,&dbuf[dbo],len); dbo += len; } dbo = 0; append(secret,secretlen); append(r1,CHALL_LEN); append(secret,secretlen); append(r2,CHALL_LEN); append(secret,secretlen); append(arrow,2); append(secret,secretlen); append(r3,CHALL_LEN); append(secret,secretlen); append(r4,CHALL_LEN); append(secret,secretlen); for (i=0;i<240;i+=20) { if (i) for (j=sizeof(dbuf)-1;j>=0;j--) dbuf[j] ++; h = sha1_init(); sha1_process_bytes(h,&dbuf[0],sizeof(dbuf)); sha1_result(h,&key[i]); } arc4_setkey(into,&key[0],240,65536); } static void rd_tcpconn_step5(void *vc) { TCPCONN *c; c = vc; if (tcpconn_fill(c,&c->rem_rnd2[0],CHALL_LEN)) return; arc4_init(&enc_r); arc4_init(&enc_w); switch (c->end) { case TE_ACCEPTER: setup_key(&c->lcl_rnd[0],&c->lcl_rnd2[0],"->",&c->rem_rnd[0],&c->rem_rnd2[0],&enc_w); setup_key(&c->rem_rnd[0],&c->lcl_rnd2[0],"<-",&c->lcl_rnd[0],&c->rem_rnd2[0],&enc_r); break; case TE_CONNECTER: setup_key(&c->lcl_rnd[0],&c->rem_rnd2[0],"<-",&c->rem_rnd[0],&c->lcl_rnd2[0],&enc_w); setup_key(&c->rem_rnd[0],&c->rem_rnd2[0],"->",&c->lcl_rnd[0],&c->lcl_rnd2[0],&enc_r); break; default: abort(); break; } tcpconn_up(c); } static void rd_tcpconn_step4(void *vc) { TCPCONN *c; c = vc; if (tcpconn_fill(c,&c->conf,1)) return; remove_poll_id(c->ioid); if (eflag) { random_data(&c->lcl_rnd2[0],CHALL_LEN); oq_queue_point(&c->oq,&c->lcl_rnd2[0],CHALL_LEN); c->fill = 0; c->ioid = add_poll_fd(c->fd,&rwtest_always,&wtest_tcpconn,&rd_tcpconn_step5,&wr_tcpconn,c); } else { c->ioid = PL_NOID; tcpconn_up(c); } } static void rd_tcpconn_step3(void *vc) { TCPCONN *c; c = vc; if (tcpconn_fill(c,&c->rem_resp[0],SIG_LEN)) return; if (bcmp(&c->rem_resp[0],&c->des_resp[0],SIG_LEN)) { kill_tcpconn(c); return; } oq_queue_point(&c->oq,"",1); remove_poll_id(c->ioid); c->fill = 0; c->ioid = add_poll_fd(c->fd,&rwtest_always,&wtest_tcpconn,&rd_tcpconn_step4,&wr_tcpconn,c); } static void rd_tcpconn_step2(void *vc) { TCPCONN *c; c = vc; if (tcpconn_fill(c,&c->rem_rnd[0],CHALL_LEN)) return; compute_verifiers(c); oq_queue_point(&c->oq,&c->lcl_resp[0],SIG_LEN); remove_poll_id(c->ioid); c->fill = 0; c->ioid = add_poll_fd(c->fd,&rwtest_always,&wtest_tcpconn,&rd_tcpconn_step3,&wr_tcpconn,c); } static void rd_tcpconn_step1(void *vc) { TCPCONN *c; c = vc; if (tcpconn_fill(c,&c->vers,1)) return; if (c->vers != (eflag ? ENCRYPTED_VERSION : CURRENT_VERSION)) { kill_tcpconn(c); return; } remove_poll_id(c->ioid); c->fill = 0; c->ioid = add_poll_fd(c->fd,&rwtest_always,&wtest_tcpconn,&rd_tcpconn_step2,&wr_tcpconn,c); } static void tcpconn_verf_timeout(void *cv) { kill_tcpconn(cv); } static TCPCONN *setup_tcpconn(int fd, TCPEND end, char *lcl, char *rem) { TCPCONN *c; c = malloc(sizeof(TCPCONN)); c->fd = fd; oq_init(&c->oq); c->end = end; c->lcltxt = lcl; c->remtxt = rem; c->fill = 0; c->ioid = add_poll_fd(fd,&rwtest_always,&wtest_tcpconn,&rd_tcpconn_step1,&wr_tcpconn,c); c->blkid = PL_NOID; c->killid = PL_NOID; { unsigned char b; b = eflag ? ENCRYPTED_VERSION : CURRENT_VERSION; oq_queue_copy(&c->oq,&b,1); random_data(&c->lcl_rnd[0],CHALL_LEN); oq_queue_point(&c->oq,&c->lcl_rnd[0],CHALL_LEN); } c->tmo = start_timeout(10,&tcpconn_verf_timeout,c); c->on_kill = 0; return(c); } static void rd_accfd(void *vf) { ACCFD *f; int fd; struct sockaddr_storage from; socklen_t fromlen; TCPCONN *c; f = vf; fromlen = sizeof(from); fd = accept(f->fd,(void *)&from,&fromlen); if (fd < 0) { switch (errno) { case EINTR: case EWOULDBLOCK: return; break; } fprintf(stderr,"%s: accept (%s): %s\n",__progname,f->txt,strerror(errno)); exit(1); } nbio(fd); c = setup_tcpconn(fd,TE_ACCEPTER,strdup(f->txt),textfor((void *)&from,fromlen)); } static void have_tcp(int fd, char *txt, struct addrinfo *ai) { ACCFD *f; nbio(fd); if (vflag) printf("socket %d is %s\n",fd,txt); f = malloc(sizeof(ACCFD)); f->link = accfds; accfds = f; f->fd = fd; f->ai = ai; f->txt = txt; f->id = add_poll_fd(fd,&rwtest_always,&rwtest_never,&rd_accfd,0,f); } static void retry_tcp(void *pv) { PENDING *p; int fd; int on; p = pv; fd = socket(p->ai->ai_family,p->ai->ai_socktype,p->ai->ai_protocol); if (fd < 0) { fprintf(stderr,"%s: socket (af %d): %s\n",__progname,p->ai->ai_family,strerror(errno)); return; } on = 1; setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on)); if (bind(fd,p->ai->ai_addr,p->ai->ai_addrlen) < 0) { close(fd); p->timeout += p->timeout >> 1; if (p->timeout > 3600) p->timeout = 3600; start_timeout(p->timeout,&retry_tcp,p); return; } if (listen(fd,10) < 0) { fprintf(stderr,"%s: listen %s: %s\n",__progname,p->txt,strerror(errno)); close(fd); return; } fprintf(stderr,"%s: bind %s: succeeded\n",__progname,p->txt); have_tcp(fd,p->txt,p->ai); free(p); } static void start_tcp_accept(void) { struct addrinfo *ai; setup_local(SOCK_STREAM); peer_ai0 = 0; n_peer_ai = 0; accfds = 0; if (vflag) printf("starting TCP, accept mode\n"); for (ai=local_ai0;ai;ai=ai->ai_next) { char hn[NI_MAXHOST]; char sn[NI_MAXSERV]; char *txt; if (getnameinfo(ai->ai_addr,ai->ai_addrlen,&hn[0],NI_MAXHOST,&sn[0],NI_MAXSERV,NI_NUMERICHOST|NI_NUMERICSERV|NI_DGRAM|NI_WITHSCOPEID)) { asprintf(&txt,"[can't get text address; %s]",strerror(errno)); } else { asprintf(&txt,"%s/%s",&hn[0],&sn[0]); } start_pending(txt,ai,&retry_tcp); } tcp_live = 0; tun_read = &tun_read_tcp; if (vflag) printf("started TCP\n"); } static int tcp_advance_next(void) { tcp_next_peer = tcp_next_peer->ai_next; if (tcp_next_peer == 0) { tcp_next_peer = peer_ai0; if (local_ai0) { tcp_next_local = tcp_next_local->ai_next; if (tcp_next_local == 0) { tcp_next_local = local_ai0; tcp_next_tmo = start_timeout(60,&tcp_try_next_,0); if (vflag) printf("tcp_advance_next sleeping\n"); return(1); } } else { tcp_next_tmo = start_timeout(60,&tcp_try_next_,0); if (vflag) printf("tcp_advance_next sleeping\n"); return(1); } } if (vflag) printf("tcp_advance_next advanced\n"); return(0); } static void tcp_connect_succeeded(int fd) { TCPCONN *c; struct sockaddr_storage a; socklen_t al; al = sizeof(a); if (getsockname(fd,(void *)&a,&al) < 0) { fprintf(stderr,"%s: getsockname: %s\n",__progname,strerror(errno)); close(fd); if (! tcp_advance_next()) tcp_try_next(); return; } c = setup_tcpconn(fd,TE_CONNECTER,textfor((void *)&a,al),textfor((void *)tcp_next_peer->ai_addr,tcp_next_peer->ai_addrlen)); c->on_kill = &tcp_try_next_delayed; if (vflag) printf("TCP connect successful (%s->%s)\n",c->lcltxt,c->remtxt); } static void tcp_test_connect(void *fdpv) { int s; int e; socklen_t el; if (vflag) printf("connect complete\n"); remove_poll_id(tcp_next_connid); tcp_next_connid = PL_NOID; s = *(int *)fdpv; free(fdpv); el = sizeof(int); do <"fail"> { if (getsockopt(s,SOL_SOCKET,SO_ERROR,&e,&el) < 0) { fprintf(stderr,"%s: getsockopt(SO_ERROR): %s\n",__progname,strerror(errno)); break <"fail">; } if (el != sizeof(int)) { fprintf(stderr,"%s: getsockopt(SO_ERROR): %s\n",__progname,strerror(errno)); break <"fail">; } if (vflag) { if (e) { printf("connect error [%s]\n",strerror(e)); } else { printf("connect successful\n"); } } if (e) break <"fail">; tcp_connect_succeeded(s); return; } while (0); close(s); if (! tcp_advance_next()) tcp_try_next(); } static void tcp_try_next(void) { int s; while (1) { do <"nextaddr"> { if (vflag) { char *tl; char *tr; tl = local_ai0 ? textfor(tcp_next_local->ai_addr,tcp_next_local->ai_addrlen) : strdup("*"); tr = textfor(tcp_next_peer->ai_addr,tcp_next_peer->ai_addrlen); printf("tcp_try_next trying %s -> %s\n",tl,tr); free(tl); free(tr); } s = socket(tcp_next_peer->ai_family,tcp_next_peer->ai_socktype,tcp_next_peer->ai_protocol); if (s < 0) { if (vflag) printf("socket failed [%s]\n",strerror(errno)); break <"nextaddr">; } if (local_str || lport_str) { if (bind(s,tcp_next_local->ai_addr,tcp_next_local->ai_addrlen) < 0) { if (vflag) { char *t; t = textfor(tcp_next_local->ai_addr,tcp_next_local->ai_addrlen); if (vflag) printf("bind to %s failed [%s]\n",t,strerror(errno)); free(t); } close(s); break <"nextaddr">; } } nbio(s); if (connect(s,tcp_next_peer->ai_addr,tcp_next_peer->ai_addrlen) < 0) { if (errno == EINPROGRESS) { int *fdp; fdp = malloc(sizeof(int)); *fdp = s; if (vflag) printf("connect in progress\n"); tcp_next_connid = add_poll_fd(s,&rwtest_never,&rwtest_always,0,&tcp_test_connect,fdp); return; } else { if (vflag) printf("connect failed [%s]\n",strerror(errno)); close(s); break <"nextaddr">; } } if (vflag) printf("connect succeeded\n"); tcp_connect_succeeded(s); } while (0); if (tcp_advance_next()) return; } } static void restart_connection(void) { if (tcp_next_tmo) { timeout_cancel(tcp_next_tmo); tcp_next_tmo = 0; } tcp_next_peer = peer_ai0; tcp_next_local = local_ai0; tcp_try_next(); } static void start_tcp_connect(void) { setup_local(SOCK_STREAM); lookup_peer(SOCK_STREAM); if (vflag) printf("starting TCP, connect mode\n"); tcp_live = 0; tun_read = &tun_read_tcp; tcp_next_tmo = 0; tcp_next_connid = PL_NOID; restart_connection(); } int main(int, char **); int main(int ac, char **av) { handleargs(ac,av); checkargs(); setup_secret(); setup_tun(); setup_misc(); switch (opmode) { case OP_UDP: start_udp(); break; case OP_TCP_ACCEPT: start_tcp_accept(); break; case OP_TCP_CONNECT: start_tcp_connect(); break; default: abort(); break; } tun_id = add_poll_fd(tun_fd,&rwtest_always,&rwtest_never,&rd_tun,0,0); while (1) { pre_poll(); if (do_poll() < 0) { if (errno == EINTR) continue; fprintf(stderr,"%s: poll: %s\n",__progname,strerror(errno)); exit(1); } post_poll(); } }