/* * IP-over-TCP tunnel designed to work through Web caches. This opens * two long-lived connections, one of which does a chunked PUT to * carry pacets from client to server, the other of which does a * chunked GET to carry packets the other way. (Neither connection is * persistent in the RFC2616 sense, because only one request/response * is carried out over each.) * * As one might expect given the intended use case, all traffic is * encrypted and, effectively, signed. A shared secret is leveraged * to generate encryption keys, which are then used to encrypt the * traffic. * * There is actually a third, short-lived connection, this one serving * as a rendezvous. Specifically, the protocol is: * * Let a `nonce' be a string of not less than 16 and not more than 256 * octets, randomly chosen (by whichever peer has to come up with it). * * Client does a GET on /rendezvous//, where is an * identifier for the client and is a nonce, as above, * expressed in hex (ie, 32..512 hex digits). If the is valid, * the server generates a nonce of its own and returns a text/plain * response containing its nonce, similarly represented in hex. (If * not, a 404 is returned.) * * Client then opens two parallel connections. On one of them, the * client->server connection, it does a PUT to /ctos// where * is the client's nonce in hex as above and is the * server's; on the other, the server->client connection, it does a * GET of /stoc//. (The client ID is not visible here; it is * implicit in the nonce pair.) * * The bodies (of the PUT and the response to the GET) are of type * application/octet-stream and are sent chunked in the sense of RFC * 2616 section 3.6.1; chunk boundaries are not significant and may be * introduced anywhere in the data stream the implementation finds * convenient. The data streams formed by concatenating the chunks * carries the protocol below (client-to-server data on the /ctos/ * PUT, server-to-client on the /stoc/ GET, rather than the more usual * network technique of using the two directions on the same socket). * * Let SS be the shared secret, CN be the client-generated nonce, and * SN the server-generated nonce. (All nonces are in raw binary, not * the hex form carried in the rendezvous transaction.) Also let B(i) * be a one-octet string whose octet contains the value i. Then each * end computes a master key thus: * * T = SS * K = '' * for i in 0 .. 31 * A = SHA1(SS||B(i)||'a'||T||CN||SS||SN||T||'z'||B(i)||SS) * T = A * v = T[0] XOR T[1] XOR T[2] XOR ... XOR T[18] XOR T[19] * K = K || B(v) * * The resulting 32-octet value is the master key for the connection. * * Whenever rekeying is desired, a data sender generates a random * 32-octet (256-bit) string R, computes * H = SHA256(R||SS|CN|SN||K) * TK = R XOR H * SK = RijndaelECB_8_8(R,K) * and sends SK. The peer decrypts to recover R and performs the same * computation to obtain TK. TK is then set as an arcfour key and the * first 64K of the keystream is thrown away; the following keystream * encrypts that sender's data. * * Rekeying is performed immediately after nonce exchange and every * 128MB thereafter (ie, exactly 128MB is sent between one SK value * and the next). Rekeying is performed independently in the two * directions. * * The next layer up, communication starts with a verifier exchange. * Each end generates 16 random bytes and sends them, encrypted as if * they were ordinary data. Each receives its peer's bytes, decrypts * them, and echoes them back encrypted appropriately for the other * direction. Each end verifies that it got its random bytes back * unchanged; if not, it drops the connection. After this, each end * streams length-and-data packets at the other. Each packet consists * of a length, represented as two octets in network byte order, * followed by that many octets of packet data, followed by a 5-byte * checksum computed thus: * * Let L be the 2-octet string holding the length * Let D be the packet data * Compute H = SHA1(L||D) * Compute S = H[0..4] XOR H[5..9] XOR H[10..14] XOR H[15..19] * * The checksum string is S. * * The details of the HTTP traffic are fairly simple. * * The rendezvous request looks like * * GET /rendezvous/fred/00112233445566778899aabbccddeeff HTTP/1.1 * Accept: text/plain * Accept-Language: en * Cache-Control: no-cache, no-store, no-transform * Pragma: no-cache * Connection: close * Content-Length: 0 * Date: Wed, 15 Nov 1995 06:25:24 GMT * Host: (whatever host) * User-Agent: webip * * and its response looks like * * HTTP/1.1 200 OK * Allow: GET * Cache-Control: no-cache, no-store, no-transform * Pragma: no-cache * Connection: close * Content-Type: text/plain * Content-Length: 33 * Date: Wed, 15 Nov 2005 06:25:24 GMT * Vary: * * * 0f1e2d3c4b5a69788796a5b4c3d2e1f0 * * The GET and PUT that establish the data-carrying connections look * like * * GET /stoc/00112233445566778899aabbccddeeff/0f1e2d3c4b5a69788796a5b4c3d2e1f0 HTTP/1.1 * Accept: application/octet-stream * Accept-Language: en * Cache-Control: no-cache, no-store, no-transform * Pragma: no-cache * Connection: close * Content-Length: 0 * Date: Wed, 15 Nov 1995 06:25:24 GMT * Host: (whatever host) * User-Agent: webip * * PUT /ctos/00112233445566778899aabbccddeeff/0f1e2d3c4b5a69788796a5b4c3d2e1f0 HTTP/1.1 * Accept-Language: en * Cache-Control: no-cache, no-store, no-transform * Pragma: no-cache * Connection: close * Transfer-Encoding: chunked * Date: Sun, 5 Jan 2000 06:25:24 GMT * Host: (whatever host) * User-Agent: webip * * The server's response to the GET looks like * * HTTP/1.1 200 OK * Allow: GET * Cache-Control: no-cache, no-store, no-transform * Pragma: no-cache * Connection: close * Content-Type: application/octet-stream * Transfer-Encoding: chunked * Date: Wed, 15 Nov 2005 06:25:24 GMT * Vary: * * * The body of the PUT and of the GET's response are the protocol * described above. * * This program's command line is just * * $0 [options] * * where the options are * * -client HOST[/PORT] * Run in client mode, connecting to HOST; if the /PORT is * not specified, the default is port 80. -client may be * given multiple times; the connect-to specs are tried in * order until one succeeds (or the list is exhausted, in * which case an error occurs). * * -server PATH * Run in server mode. PATH names a config file (see * below). * * In client mode (accepted but ignored in server mode): * * -secret STRING * Specify the shared secret directly. * * -secretfile PATH * Name a file holding the shared secret. * * -name STRING * Specify the client's name for the rendezvous request. * * -tun PATH * Name the tun control device to be used. * * The server config file consists of three kinds of lines: * * Comments * Any line containing nothing but whitespace (including * empty lines) h, and any line whose first nonblank * character is a #, is a comment. Such lines are ignored * (but see below about folding). * * Client specifiers * These are lines beginning with "CLIENT". They look * like * * CLIENT = [= ...] * * where the keys and their values are: * * name * Client name for /rendezvous/ GET requests. * dev * Path to this client's tun control device. * secret * Shared secret for this client. * secretfile * Path to file holding the shared secret for this * client. * * name, dev, and exactly one of secret and secretfile * must be given. A value cannot contain whitespace or * newlines. There is no provision for whitespace or * newlines in name or dev strings. A shred secret can * contain whitespace and newlines, but only if it's * stored in a file and loaded with secretfile. * * Listen specifiers * These are lines beginning with "LISTEN". They look * like * * LISTEN [!][/] [[!][/] ...] * * where is a port to listen on; is * specifies the address(es), with a default of "all local * addresses" (eg, INADDR_ANY for IPv4). The server * listens on all such addresses simultaneously; they are * all operationally equivalent. If a spec begins with a * !, SO_REUSEADDR is turned on for that socket. * * If a client which is already connected connects again, nothing * special happens until the crypto verifier passes, at which point * the older validated connection is summarily dropped in favour of * the new one. */ #include #include #include #include #include #include #include #include #include #include #include #include #include extern const char *__progname; #include "oq.h" #include "pollloop.h" #define MAXRQSIZE 2048 #define MINNONCE 16 #define OURNONCE 20 #define MAXNONCE 256 #define DEVRANDOM "/dev/urandom" typedef enum { SM_UNSPEC = 1, SM_FILE, SM_LITERAL, } SECRETMODE; typedef enum { OM_UNSPEC = 1, OM_CLIENT, OM_SERVER, } OPMODE; typedef struct strlist STRLIST; typedef struct acc ACC; typedef struct httpconn HTTPCONN; typedef struct delaycall DELAYCALL; typedef struct conn CONN; typedef struct rvrec RVREC; typedef struct nonce NONCE; typedef struct pending PENDING; typedef struct webhdr WEBHDR; typedef struct bl BL; typedef struct client CLIENT; typedef struct verf VERF; typedef struct live LIVE; struct bl { unsigned char *b; int l; } ; struct client { CLIENT *link; char *name; int namelen; char *tundev; int tunfd; BL secret; CONN *live; } ; struct webhdr { int nlines; BL *lines; } ; struct nonce { unsigned char *b; int l; char *h; int hl; } ; struct conn { CLIENT *c; BL secret; NONCE cn; NONCE sn; int r_fd; int w_fd; const char *tun_dev; int tun_fd; int tun_id; OQ oq; int tmofd; int tmoid; int r_id; int w_id; unsigned char master[32]; void *rjh; unsigned int w_rekey; unsigned int r_rekey; unsigned char rekey_buf[32]; int rekey_fill; OQ cq; int chunkid; OQ eq; int encodeid; int chunkleft; #define CHUNKLEFT_TRAILER (-1) #define CHUNKLEFT_HEADER (-2) int chunksize; ARC4_STATE w_arc4; ARC4_STATE r_arc4; void (*ufree)(CONN *); union { VERF *verf; LIVE *live; } ; } ; struct live { unsigned char *ib; int ia; int want; int have; } ; struct verf { unsigned char sent[32]; int necho; int ngot; unsigned char got[32]; } ; struct rvrec { int fd; int id; OQ oq; int nls; char *nhb; int nhl; int nha; } ; struct pending { PENDING *flink; PENDING *blink; CLIENT *c; NONCE cn; NONCE sn; int tmofd; int tmoid; int watchid; int ctos_fd; int stoc_fd; } ; struct delaycall { int id; void (*fn)(void *); void *arg; } ; struct httpconn { char *txt; int fd; int id; OQ oq; char *rqbuf; int rqfill; int chkstart; } ; struct strlist { STRLIST *link; char *s; } ; struct acc { char *txt; int fd; int id; } ; static OPMODE opmode = OM_UNSPEC; /* both modes */ static int rndfd = -1; /* client mode */ static STRLIST *client_net_specs = 0; static STRLIST **client_net_specs_t = &client_net_specs; static SECRETMODE secret_mode = SM_UNSPEC; static char *secret_spec; static const char *client_name = 0; static unsigned char *secret_data; static unsigned int secret_len; static struct sockaddr *server_addr; static char *server_text; static char *hosthdr; static NONCE clientnonce; static NONCE servernonce; static int ctos_fd; static OQ ctos_oq; static int ctos_id; static int stoc_fd; static OQ stoc_oq; static int stoc_id; static const char *tun_dev = 0; static int tun_fd; static int flush_id; /* server mode */ static char *server_config; static PENDING *pending; static CLIENT *clients; static int nacc; static void client_arg(char *arg) { STRLIST *sl; opmode = OM_CLIENT; sl = malloc(sizeof(STRLIST)); sl->link = 0; sl->s = arg; *client_net_specs_t = sl; client_net_specs_t = &sl->link; } 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 = 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,"-server")) { WANTARG(); opmode = OM_SERVER; server_config = av[skip]; continue; } if (!strcmp(*av,"-client")) { WANTARG(); client_arg(av[skip]); continue; } if (!strcmp(*av,"-name")) { WANTARG(); client_name = av[skip]; continue; } if (!strcmp(*av,"-tun")) { WANTARG(); tun_dev = av[skip]; continue; } if (!strcmp(*av,"-secret")) { WANTARG(); secret_mode = SM_LITERAL; secret_spec = strdup(av[skip]); bzero(*av,strlen(*av)); bzero(av[skip],strlen(av[skip])); continue; } if (!strcmp(*av,"-secretfile")) { WANTARG(); secret_mode = SM_FILE; secret_spec = strdup(av[skip]); bzero(*av,strlen(*av)); bzero(av[skip],strlen(av[skip])); continue; } #undef WANTARG fprintf(stderr,"%s: unrecognized option `%s'\n",__progname,*av); errs = 1; } if (errs) exit(1); if (opmode == OM_UNSPEC) { fprintf(stderr,"%s: no operation mode specified; use -client or -server\n",__progname); errs = 1; } if (opmode == OM_CLIENT) { if (secret_mode == SM_UNSPEC) { fprintf(stderr,"%s: no secret specified; use -secret or -secretfile\n",__progname); errs = 1; } if (! client_name) { fprintf(stderr,"%s: no name specified; use -name\n",__progname); errs = 1; } if (! tun_dev) { fprintf(stderr,"%s: no tun device specified; use -tun\n",__progname); errs = 1; } } if (errs) exit(1); } static void setup_secret(void) { int fd; struct stat stb; int i; switch (secret_mode) { default: abort(); break; case SM_FILE: fd = open(secret_spec,O_RDONLY,0); if (fd < 0) { fprintf(stderr,"%s: open %s: %s\n",__progname,secret_spec,strerror(errno)); exit(1); } if (fstat(fd,&stb) < 0) { fprintf(stderr,"%s: fstat %s: %s\n",__progname,secret_spec,strerror(errno)); exit(1); } if ((stb.st_size < 4) || (stb.st_size > 65536)) { fprintf(stderr,"%s: %s: unreasonable size %lld\n",__progname,secret_spec,(long long int)stb.st_size); exit(1); } secret_len = stb.st_size; secret_data = malloc(secret_len); i = read(fd,secret_data,secret_len); if (i < 0) { fprintf(stderr,"%s: read %s: %s\n",__progname,secret_spec,strerror(errno)); exit(1); } if (i != secret_len) { fprintf(stderr,"%s: read %s: wanted %d, got %d\n",__progname,secret_spec,secret_len,i); exit(1); } close(fd); break; case SM_LITERAL: secret_data = secret_spec; secret_len = strlen(secret_spec); break; } } static int call_delayed(void *dv) { DELAYCALL *d; void (*fn)(void *); void *arg; d = dv; fn = d->fn; arg = d->arg; remove_block_id(d->id); free(d); (*fn)(arg); return(BLOCK_LOOP); } static void delayed_call(void (*fn)(void *), void *arg) { DELAYCALL *d; d = malloc(sizeof(DELAYCALL)); d->fn = fn; d->arg = arg; d->id = add_block_fn(&call_delayed,d); } static void free_httpconn_common(void *cv) { HTTPCONN *c; c = cv; free(c->txt); free(c); } static void httpconn_shutdown_common(HTTPCONN *c) { if (c->fd >= 0) close(c->fd); remove_poll_id(c->id); oq_flush(&c->oq); delayed_call(&free_httpconn_common,c); } static void httpconn_shutdown_initial(HTTPCONN *c) { httpconn_shutdown_common(c); delayed_call(&free,c->rqbuf); } static void httpconn_shutdown_drain(HTTPCONN *c) { httpconn_shutdown_common(c); } static int wtest_httpconn(void *cv) { return(oq_nonempty(&((HTTPCONN *)cv)->oq)); } typedef enum { OQWS_NONE = 1, OQWS_ERR, OQWS_SOME, OQWS_ALL, } OQWSTATUS; static OQWSTATUS wr_oq_common(OQ *q, int fd) { int n; n = oq_writev(q,fd); if (n < 0) { switch (errno) { case EINTR: case EWOULDBLOCK: return(OQWS_NONE); break; } return(OQWS_ERR); } else { oq_dropdata(q,n); return(oq_empty(q)?OQWS_ALL:OQWS_SOME); } } static void wr_drain_httpconn(void *cv) { HTTPCONN *c; c = cv; switch (wr_oq_common(&c->oq,c->fd)) { default: break; case OQWS_ERR: fprintf(stderr,"%s: %s: write: %s\n",__progname,c->txt,strerror(errno)); /* fall through */ case OQWS_ALL: httpconn_shutdown_drain(c); break; } } static void httpconn_drain_and_die(HTTPCONN *c) { delayed_call(&free,c->rqbuf); remove_poll_id(c->id); if (oq_empty(&c->oq)) { httpconn_shutdown_drain(c); } else { c->id = add_poll_fd(c->fd,&rwtest_never,&wtest_httpconn,0,&wr_drain_httpconn,c); } } static int hex_digit(unsigned char c) { switch (c) { case '0': return(0); break; case '1': return(1); break; case '2': return(2); break; case '3': return(3); break; case '4': return(4); break; case '5': return(5); break; case '6': return(6); break; case '7': return(7); break; case '8': return(8); break; case '9': return(9); break; case 'a': case 'A': return(10); break; case 'b': case 'B': return(11); break; case 'c': case 'C': return(12); break; case 'd': case 'D': return(13); break; case 'e': case 'E': return(14); break; case 'f': case 'F': return(15); break; } return(-1); } static void init_nonce(NONCE *n, int len) { n->b = malloc((len*3)+1); n->l = len; n->h = n->b + len; n->hl = len * 2; n->h[n->hl] = '\0'; } static void free_nonce_data(NONCE *n) { delayed_call(&free,n->b); } static void clear_nonce_data(NONCE *n) { n->b = 0; } static void gen_hex(const unsigned char *bin, char *hex, int bytes) { for (;bytes>0;bytes--) { *hex++ = "0123456789abcdef"[bin[0]>>4]; *hex++ = "0123456789abcdef"[bin[0]&15]; bin ++; } *hex = '\0'; } static void random_data(unsigned char *buf, int len) { int n; if (rndfd < 0) { rndfd = open(DEVRANDOM,O_RDONLY,0); if (rndfd < 0) { fprintf(stderr,"%s: open %s: %s\n",__progname,DEVRANDOM,strerror(errno)); exit(1); } } while (len > 0) { n = read(rndfd,buf,len); if (n < 0) { fprintf(stderr,"%s: read %s: %s\n",__progname,DEVRANDOM,strerror(errno)); exit(1); } if (n == 0) { fprintf(stderr,"%s: read %s got EOF\n",__progname,DEVRANDOM); exit(1); } buf += n; len -= n; } } static void gen_nonce(NONCE *n) { init_nonce(n,OURNONCE); random_data(n->b,n->l); gen_hex(n->b,n->h,n->l); } static void gen_date(OQ *q) { time_t tt; struct tm *tm; tt = time(0); tm = gmtime(&tt); oq_queue_printf(q,"Date: %.3s, %2d %.3s %04d %02d:%02d:%02d GMT\r\n", "SunMonTueWedThuFriSat" + (3 * tm->tm_wday), tm->tm_mday, "JanFebMarAprMayJunJulAugSepOctNovDec" + (3 * tm->tm_mon), tm->tm_year + 1900, tm->tm_hour, tm->tm_min, tm->tm_sec ); } static const char *parse_nonce(NONCE *n, const unsigned char *nh, int nhl) { int i; int xdvh; int xdvl; if (nhl & 1) return("odd-length hex nonce"); if (nhl < 2*MINNONCE) return("nonce too short"); if (nhl > 2*MAXNONCE) return("nonce too long"); init_nonce(n,nhl>>1); for (i=n->l-1;i>=0;i--) { xdvl = hex_digit(nh[i+i+1]); xdvh = hex_digit(nh[i+i]); if ((xdvh < 0) || (xdvl < 0)) { free_nonce_data(n); return("invalid hex digit"); } n->b[i] = (xdvh << 4) | xdvl; } gen_hex(n->b,n->h,n->l); return(0); } static void abort_pending(PENDING *p) { if (p->blink) p->blink->flink = p->flink; else pending = p->flink; if (p->flink) p->flink->blink = p->blink; free_nonce_data(&p->cn); free_nonce_data(&p->sn); if (p->tmofd >= 0) close(p->tmofd); remove_poll_id(p->tmoid); if (p->ctos_fd >= 0) close(p->ctos_fd); if (p->stoc_fd >= 0) close(p->stoc_fd); delayed_call(&free,p); } static void rd_pending_tmo(void *pv) { printf("pending timeout sn=%s cn=%s\n", ((PENDING *)pv)->sn.h,((PENDING *)pv)->cn.h); abort_pending(pv); } static CLIENT *client_by_name(const unsigned char *name, int namelen) { CLIENT *c; for (c=clients;c;c=c->link) { if ((c->namelen == namelen) && !bcmp(c->name,name,namelen)) return(c); } return(0); } static void compute_master_key(CONN *c) { BL SS; BL T; int i; unsigned char A[20]; void *h; unsigned char B; int j; unsigned char v; SS = c->c ? c->c->secret : (BL) { .b = secret_data, .l = secret_len }; i = SS.l; if (i < 20) i = 20; T.l = SS.l; T.b = malloc(i); bcopy(SS.b,T.b,T.l); for (i=32-1;i>=0;i--) { h = sha1_init(); sha1_process_bytes(h,SS.b,SS.l); B = i; sha1_process_bytes(h,&B,1); sha1_process_bytes(h,"a",1); sha1_process_bytes(h,T.b,T.l); sha1_process_bytes(h,c->cn.b,c->cn.l); sha1_process_bytes(h,SS.b,SS.l); sha1_process_bytes(h,c->sn.b,c->sn.l); sha1_process_bytes(h,T.b,T.l); sha1_process_bytes(h,"z",1); sha1_process_bytes(h,&B,1); sha1_process_bytes(h,SS.b,SS.l); sha1_result(h,&A[0]); T.l = 20; bcopy(&A[0],T.b,20); v = 0; for (j=20-1;j>=0;j--) v ^= A[j]; c->master[i] = v; } c->rjh = rijndael_init(8,8); rijndael_set_key(c->rjh,&c->master[0]); } static void abort_conn(CONN *c) { if (! c->c) exit(1); if (c->c->live == c) c->c->live = 0; free_nonce_data(&c->cn); free_nonce_data(&c->sn); close(c->r_fd); close(c->w_fd); if (c->tun_id != PL_NOID) remove_poll_id(c->tun_id); oq_flush(&c->oq); if (c->tmofd >= 0) close(c->tmofd); if (c->tmoid != PL_NOID) remove_poll_id(c->tmoid); remove_poll_id(c->r_id); remove_poll_id(c->w_id); rijndael_done(c->rjh); oq_flush(&c->cq); remove_block_id(c->chunkid); oq_flush(&c->eq); remove_block_id(c->encodeid); (*c->ufree)(c); delayed_call(&free,c); } static void conn_free_verf(CONN *c) { free(c->verf); } static void conn_free_live(CONN *c) { free(c->live->ib); free(c->live); } static void rd_conn_tmo(void *cv) { printf("conn timeout sn=%s cn=%s\n", ((CONN *)cv)->sn.h,((CONN *)cv)->cn.h); abort_conn(cv); } static int wtest_conn_stoc(void *cv) { return(oq_nonempty(&((CONN *)cv)->oq)); } static void wr_conn(void *cv) { if (wr_oq_common(&((CONN *)cv)->oq,((CONN *)cv)->w_fd) == OQWS_ERR) { abort_conn(cv); } } static int timeout_fd(int sec) { struct itimerval itv; int fd; fd = socket(AF_TIMER,SOCK_STREAM,0); itv.it_value.tv_sec = 86400+sec; /* XXX */ itv.it_value.tv_usec = 0; itv.it_interval.tv_sec = 0; itv.it_interval.tv_usec = 0; write(fd,&itv,sizeof(itv)); return(fd); } static const char *conn_name(CONN *c) { return(c->c?c->c->name:"server"); } static void print_hex_data(const void *blk, int n) { int i; for (i=0;ichunkleft; if (n < 1) { unsigned char h; int v; if (n == 0) { c->chunkleft = CHUNKLEFT_TRAILER; c->chunksize = 0; } while <"size"> (1) { r = read(c->r_fd,&h,1); if (r < 0) { switch (errno) { case EINTR: case EWOULDBLOCK: return(-1); break; } fprintf(stderr,"%s: %s: read: %s\n",__progname,conn_name(c),strerror(errno)); abort_conn(c); return(-1); } if (r == 0) { fprintf(stderr,"%s: %s: read EOF\n",__progname,conn_name(c)); abort_conn(c); return(-1); } v = hex_digit(h); if (v < 0) { if (h == '\r') continue; if (h == '\n') { switch (c->chunkleft) { case CHUNKLEFT_TRAILER: c->chunkleft = CHUNKLEFT_HEADER; c->chunksize = 0; continue; break; case CHUNKLEFT_HEADER: break <"size">; } } fprintf(stderr,"%s: %s: bad non-hex-digit chunk size char %02x\n",__progname,conn_name(c),h); abort_conn(c); return(-1); } c->chunksize = (c->chunksize << 4) + v; } if (c->chunksize < 1) { fprintf(stderr,"%s: %s: end-of-data chunk\n",__progname,conn_name(c)); abort_conn(c); return(-1); } n = c->chunksize; c->chunkleft = n; } if (n > len) n = len; r = read(c->r_fd,buf,n); if (r < 0) { switch (errno) { case EINTR: case EWOULDBLOCK: return(-1); break; } fprintf(stderr,"%s: %s: read: %s\n",__progname,conn_name(c),strerror(errno)); abort_conn(c); return(-1); } if (r == 0) { fprintf(stderr,"%s: %s: read EOF\n",__progname,conn_name(c)); abort_conn(c); return(-1); } printf("dechunker read %d:",r); print_hex_data(buf,r); printf("\n"); c->chunkleft -= r; return(r); } static int conn_rekey_r(CONN *c) { int n; int r; unsigned char R[32]; unsigned char H[32]; unsigned char TK[32]; void *h; int i; if (c->r_rekey == 0) { c->r_rekey = -1; c->rekey_fill = 0; } while (1) { n = 32 - c->rekey_fill; r = dechunker_read(c,&c->rekey_buf[c->rekey_fill],n); if (r < 0) return(-1); printf("rekey_r read %d:",r); print_hex_data(&c->rekey_buf[c->rekey_fill],r); printf("\n"); c->rekey_fill += r; if (c->rekey_fill >= 32) break; } rijndael_decrypt(c->rjh,&c->rekey_buf[0],&R[0]); printf("rekey_r R = "); print_hex_data(&R[0],32); printf("\n"); h = sha256_init(); sha256_process_bytes(h,&R[0],32); sha256_process_bytes(h,c->secret.b,c->secret.l); sha256_process_bytes(h,c->cn.b,c->cn.l); sha256_process_bytes(h,c->sn.b,c->sn.l); sha256_process_bytes(h,&c->master[0],32); sha256_result(h,&H[0]); printf("rekey_r H = "); print_hex_data(&H[0],32); printf("\n"); for (i=32-1;i>=0;i--) TK[i] = R[i] ^ H[i]; printf("rekey_r TK = "); print_hex_data(&TK[0],32); printf("\n"); arc4_setkey(&c->r_arc4,&TK[0],32,65536); c->r_rekey = 128 << 20; return(0); } static int decrypted_read(CONN *c, void *buf, int len) { int n; int r; if ((c->r_rekey < 1) && (conn_rekey_r(c) < 0)) return(-1); n = c->r_rekey; if (n > len) n = len; r = dechunker_read(c,buf,n); if (r > 0) { printf("decrypter got %d:",r); print_hex_data(buf,r); printf("\n"); arc4_crypt(&c->r_arc4,buf,r,buf); printf("decrypter clr %d:",r); print_hex_data(buf,r); printf("\n"); } return(r); } static void rd_conn_packets(void *cv) { CONN *c; LIVE *l; int n; int r; void *h; unsigned char H[20]; c = cv; l = c->live; while (1) { n = l->want - l->have; r = decrypted_read(c,l->ib+l->have,n); if (r < 0) return; l->have += r; if (l->have < l->want) continue; if (l->want == 2) { n = (l->ib[0] * 256) + l->ib[1]; l->want = 2 + n + 5; if (l->want > l->ia) l->ib = realloc(l->ib,l->ia=l->want); } else { h = sha1_init(); sha1_process_bytes(h,&l->ib[0],l->want-5); sha1_result(h,&H[0]); H[0] ^= H[5] ^ H[10] ^ H[15]; H[1] ^= H[6] ^ H[11] ^ H[16]; H[2] ^= H[7] ^ H[12] ^ H[17]; H[3] ^= H[8] ^ H[13] ^ H[18]; H[4] ^= H[9] ^ H[14] ^ H[19]; if (bcmp(&l->ib[l->want-5],&H[0],5)) { fprintf(stderr,"%s: %s: packet checksum wrong\n",__progname,conn_name(c)); abort_conn(c); return; } write(c->tun_fd,&l->ib[2],l->want-7); l->have = 0; l->want = 2; } } } static void rd_tun(void *cv) { CONN *c; int r; unsigned char len[2]; unsigned char pkt[65535]; unsigned char H[20]; void *h; c = cv; while (1) { r = read(c->tun_fd,&pkt[0],sizeof(pkt)); if (r < 0) { switch (errno) { case EINTR: case EWOULDBLOCK: return; break; } fprintf(stderr,"%s: %s: tun read: %s\n",__progname,conn_name(c),strerror(errno)); abort_conn(c); return; } len[0] = r >> 8; len[1] = r & 0xff; h = sha1_init(); sha1_process_bytes(h,&len[0],2); sha1_process_bytes(h,&pkt[0],r); sha1_result(h,&H[0]); H[0] ^= H[5] ^ H[10] ^ H[15]; H[1] ^= H[6] ^ H[11] ^ H[16]; H[2] ^= H[7] ^ H[12] ^ H[17]; H[3] ^= H[8] ^ H[13] ^ H[18]; H[4] ^= H[9] ^ H[14] ^ H[19]; printf("queueing packet:"); print_hex_data(&len[0],2); printf(" "); print_hex_data(&pkt[0],r); printf(" "); print_hex_data(&H[0],5); printf("\n"); oq_queue_copy(&c->eq,&len[0],2); oq_queue_copy(&c->eq,&pkt[0],r); oq_queue_copy(&c->eq,&H[0],5); } } static void rd_conn_verf_part2(void *cv) { CONN *c; int n; int r; c = cv; n = 32 - c->verf->ngot; r = decrypted_read(c,&c->verf->got[c->verf->ngot],n); if (r < 0) return; printf("receiving echoed verifier %d:",r); print_hex_data(&c->verf->got[c->verf->ngot],r); printf("\n"); c->verf->ngot += r; if (c->verf->ngot < 32) return; if (bcmp(&c->verf->sent[0],&c->verf->got[0],32)) { fprintf(stderr,"%s: %s: verifier doesn't match\n",__progname,conn_name(c)); printf("sent:"); print_hex_data(&c->verf->sent[0],32); printf("\n"); printf("rcvd:"); print_hex_data(&c->verf->got[0],32); printf("\n"); abort_conn(c); return; } (*c->ufree)(c); c->ufree = &conn_free_live; c->live = malloc(sizeof(LIVE)); c->live->ib = malloc(2); c->live->ia = 2; c->live->want = 2; c->live->have = 0; remove_poll_id(c->r_id); if (c->c) { if (c->c->live) abort_conn(c->c->live); c->c->live = c; } c->r_id = add_poll_fd(c->r_fd,&rwtest_always,&rwtest_never,&rd_conn_packets,0,c); c->tun_id = add_poll_fd(c->tun_fd,&rwtest_always,&rwtest_never,&rd_tun,0,c); } static void rd_conn_verf_part1(void *cv) { CONN *c; unsigned char buf[32]; int n; int r; c = cv; n = 32 - c->verf->necho; r = decrypted_read(c,&buf[0],n); if (r < 0) return; printf("echoing verifier %d:",r); print_hex_data(&buf[0],r); printf("\n"); oq_queue_copy(&c->eq,&buf[0],r); c->verf->necho += r; if (c->verf->necho < 32) return; remove_poll_id(c->r_id); c->r_id = add_poll_fd(c->r_fd,&rwtest_always,&rwtest_never,&rd_conn_verf_part2,0,c); c->verf->ngot = 0; } static void conn_rekey_w(CONN *c) { unsigned char R[32]; unsigned char H[32]; unsigned char TK[32]; unsigned char SK[32]; void *h; int i; random_data(&R[0],32); printf("rekey_w: R ="); print_hex_data(&R[0],32); printf("\n"); h = sha256_init(); sha256_process_bytes(h,&R[0],32); sha256_process_bytes(h,c->secret.b,c->secret.l); sha256_process_bytes(h,c->cn.b,c->cn.l); sha256_process_bytes(h,c->sn.b,c->sn.l); sha256_process_bytes(h,&c->master[0],32); sha256_result(h,&H[0]); printf("rekey_w: H ="); print_hex_data(&H[0],32); printf("\n"); for (i=32-1;i>=0;i--) TK[i] = R[i] ^ H[i]; printf("rekey_w: TK ="); print_hex_data(&TK[0],32); printf("\n"); rijndael_encrypt(c->rjh,&R[0],&SK[0]); printf("rekey_w: SK ="); print_hex_data(&SK[0],32); printf("\n"); oq_queue_copy(&c->cq,&SK[0],32); arc4_setkey(&c->w_arc4,&TK[0],32,65536); c->w_rekey = 128 << 20; } static int conn_encode_block(void *cv) { CONN *c; int n; unsigned char blk[8192]; c = cv; if (oq_nonempty(&c->cq) || oq_empty(&c->eq)) return(BLOCK_NIL); if (c->w_rekey < 1) conn_rekey_w(c); n = oq_qlen(&c->eq); if (n > c->w_rekey) n = c->w_rekey; if (n > sizeof(blk)) n = sizeof(blk); if (n < 1) abort(); oq_dequeue(&c->eq,&blk[0],n); printf("crypter encrypting %d:",n); print_hex_data(&blk[0],n); printf("\n"); arc4_crypt(&c->w_arc4,&blk[0],n,&blk[0]); printf("crypter cq %d:",n); print_hex_data(&blk[0],n); printf("\n"); oq_queue_copy(&c->cq,&blk[0],n); c->w_rekey -= n; return(BLOCK_LOOP); } static int conn_chunk_block(void *cv) { CONN *c; int n; unsigned char blk[8192]; c = cv; if (oq_nonempty(&c->oq) || oq_empty(&c->cq)) return(BLOCK_NIL); n = oq_qlen(&c->cq); if (n > sizeof(blk)) n = sizeof(blk); if (n < 1) abort(); oq_dequeue(&c->cq,&blk[0],n); printf("chunker oq %x (%d):",n,n); print_hex_data(&blk[0],n); printf("\n"); oq_queue_printf(&c->oq,"%x\r\n",n); oq_queue_copy(&c->oq,&blk[0],n); oq_queue_point(&c->oq,"\r\n",OQ_STRLEN); return(BLOCK_LOOP); } static void conn_setup_common(CONN *c) { c->tun_id = PL_NOID; oq_init(&c->oq); c->tmofd = timeout_fd(60); c->tmoid = add_poll_fd(c->tmofd,&rwtest_always,&rwtest_never,&rd_conn_tmo,0,c); c->r_id = add_poll_fd(c->r_fd,&rwtest_always,&rwtest_never,&rd_conn_verf_part1,0,c); c->w_id = add_poll_fd(c->w_fd,&rwtest_never,&wtest_conn_stoc,0,&wr_conn,c); compute_master_key(c); c->w_rekey = 0; c->r_rekey = 0; oq_init(&c->cq); c->chunkid = add_block_fn(&conn_chunk_block,c); oq_init(&c->eq); c->encodeid = add_block_fn(&conn_encode_block,c); c->chunkleft = CHUNKLEFT_HEADER; c->chunksize = 0; arc4_init(&c->w_arc4); arc4_init(&c->r_arc4); c->ufree = &conn_free_verf; c->verf = malloc(sizeof(VERF)); random_data(&c->verf->sent[0],32); c->verf->necho = 0; oq_queue_point(&c->eq,&c->verf->sent[0],32); printf("eq verifier 32:"); print_hex_data(&c->verf->sent[0],32); printf("\n"); } static int pending_block(void *pv) { PENDING *p; CONN *c; p = pv; if ((p->ctos_fd < 0) || (p->stoc_fd < 0)) return(BLOCK_NIL); printf("pending cn=%s sn=%s has fds\n",p->cn.h,p->sn.h); remove_block_id(p->watchid); p->watchid = PL_NOID; c = malloc(sizeof(CONN)); c->c = p->c; c->secret = c->c->secret; c->cn = p->cn; clear_nonce_data(&p->cn); c->sn = p->sn; clear_nonce_data(&p->sn); c->r_fd = p->ctos_fd; p->ctos_fd = -1; c->w_fd = p->stoc_fd; p->stoc_fd = -1; c->tun_dev = c->c->tundev; c->tun_fd = c->c->tunfd; conn_setup_common(c); abort_pending(p); return(BLOCK_LOOP); } static void do_rendezvous(HTTPCONN *c, const unsigned char *nh, int nhl) { __label__ ret; NONCE n; PENDING *p; CLIENT *cl; const char *msg; int x; auto void fail_404(const char *, ...) __attribute__((__format__(__printf__,1,2))); auto void fail_404(const char *fmt, ...) { va_list ap; char *s; va_start(ap,fmt); vasprintf(&s,fmt,ap); va_end(ap); fprintf(stderr,"%s: %s: %.*s: %s\n",__progname,c->txt,nhl,nh,s); free(s); oq_queue_point(&c->oq,"404 Not Found\r\n\r\n",OQ_STRLEN); httpconn_drain_and_die(c); goto ret; } x = 0; while (1) { if ((x >= nhl) || isspace(nh[x])) { fail_404("bad/missing client name"); } if (nh[x] == '/') break; x ++; } cl = client_by_name(nh,x); if (! cl) fail_404("no client %.*s found",x,nh); nh += x + 1; nhl -= x + 1; msg = parse_nonce(&n,nh,nhl); if (msg) fail_404("%s",msg); p = malloc(sizeof(PENDING)); p->flink = pending; p->blink = 0; pending = p; if (p->flink) p->flink->blink = p; p->c = cl; p->cn = n; gen_nonce(&p->sn); p->tmofd = timeout_fd(60); p->tmoid = add_poll_fd(p->tmofd,&rwtest_always,&rwtest_never,&rd_pending_tmo,0,p); p->watchid = add_block_fn(&pending_block,p); p->ctos_fd = -1; p->stoc_fd = -1; printf("rendezvous request: cn=%s sn=%s\n",p->cn.h,p->sn.h); oq_queue_point(&c->oq, "HTTP/1.1 200 OK\r\n" "Allow: GET\r\n" "Cache-Control: no-cache, no-store, no-transform\r\n" "Pragma: no-cache\r\n" "Connection: close\r\n" "Content-Type: text/plain\r\n" "Content-Length: 33\r\n" ,OQ_STRLEN); gen_date(&c->oq); oq_queue_point(&c->oq,"Vary: *\r\n\r\n",OQ_STRLEN); oq_queue_point(&c->oq,p->sn.h,p->sn.hl); oq_queue_point(&c->oq,"\r\n",OQ_STRLEN); httpconn_drain_and_die(c); ret:; } static PENDING *find_pending(NONCE *cn, NONCE *sn) { PENDING *p; for (p=pending;p;p=p->flink) { if ( (cn->l == p->cn.l) && (sn->l == p->sn.l) && !bcmp(cn->b,p->cn.b,cn->l) && !bcmp(sn->b,p->sn.b,sn->l) ) return(p); } return(0); } static void do_stoc(HTTPCONN *c, const unsigned char *nh, int nhl) { __label__ ret; NONCE cn; NONCE sn; int x; const char *msg; PENDING *p; auto void fail_404(const char *, ...) __attribute__((__format__(__printf__,1,2))); auto void fail_404(const char *fmt, ...) { va_list ap; char *s; va_start(ap,fmt); vasprintf(&s,fmt,ap); va_end(ap); fprintf(stderr,"%s: %s: %.*s: %s\n",__progname,c->txt,nhl,nh,s); free(s); oq_queue_point(&c->oq,"404 Not Found\r\n\r\n",OQ_STRLEN); httpconn_drain_and_die(c); goto ret; } for (x=0;(x= nhl) fail_404("no separator slash"); msg = parse_nonce(&cn,nh,x); if (msg) fail_404(msg); msg = parse_nonce(&sn,nh+x+1,nhl-(x+1)); if (msg) { free_nonce_data(&cn); fail_404(msg); } printf("s->c request, cn=%s sn=%s\n",cn.h,sn.h); p = find_pending(&cn,&sn); free_nonce_data(&cn); free_nonce_data(&sn); if (! p) { free_nonce_data(&cn); free_nonce_data(&sn); fail_404("no pending"); } if (p->stoc_fd >= 0) fail_404("already have s->c fd"); p->stoc_fd = c->fd; c->fd = -1; httpconn_shutdown_initial(c); ret:; } static void do_ctos(HTTPCONN *c, const unsigned char *nh, int nhl) { __label__ ret; NONCE cn; NONCE sn; int x; const char *msg; PENDING *p; auto void fail_404(const char *, ...) __attribute__((__format__(__printf__,1,2))); auto void fail_404(const char *fmt, ...) { va_list ap; char *s; va_start(ap,fmt); vasprintf(&s,fmt,ap); va_end(ap); fprintf(stderr,"%s: %s: %.*s: %s\n",__progname,c->txt,nhl,nh,s); free(s); oq_queue_point(&c->oq,"404 Not Found\r\n\r\n",OQ_STRLEN); httpconn_drain_and_die(c); goto ret; } for (x=0;(x= nhl) fail_404("no separator slash"); msg = parse_nonce(&cn,nh,x); if (msg) fail_404(msg); msg = parse_nonce(&sn,nh+x+1,nhl-(x+1)); if (msg) fail_404(msg); if (msg) { free_nonce_data(&cn); fail_404(msg); } printf("c->s request, cn=%s sn=%s\n",cn.h,sn.h); p = find_pending(&cn,&sn); free_nonce_data(&cn); free_nonce_data(&sn); if (! p) { free_nonce_data(&cn); free_nonce_data(&sn); fail_404("no pending"); } if (p->ctos_fd >= 0) fail_404("already have c->s fd"); p->ctos_fd = c->fd; c->fd = -1; httpconn_shutdown_initial(c); ret:; } static void do_request(HTTPCONN *c, int hdrend) { int x; int l0; WEBHDR h; int alines; int methodlen; int resourcebase; int resourcelen; int versionbase; int versionlen; void gen_line(void) { if (l0 < 0) l0 = x; if (h.nlines >= alines) h.lines = realloc(h.lines,(alines=h.nlines+8)*sizeof(BL)); h.lines[h.nlines++] = (BL) { .b = c->rqbuf+l0, .l = x-l0 }; } h.lines = 0; alines = 0; h.nlines = 0; l0 = -1; for (x=0;xrqbuf[x] == '\n') { gen_line(); x ++; } else if ( (x+1 < hdrend) && (c->rqbuf[x] == '\r') && (c->rqbuf[x+1] == '\n') ) { gen_line(); x += 2; } else { if (l0 < 0) l0 = x; } } if (h.nlines < 1) { /* too malformed to even try */ free(h.lines); httpconn_shutdown_initial(c); return; } x = 0; while ((x < h.lines[0].l) && !isspace(h.lines[0].b[x])) x ++; methodlen = x; while ((x < h.lines[0].l) && isspace(h.lines[0].b[x])) x ++; resourcebase = x; while ((x < h.lines[0].l) && !isspace(h.lines[0].b[x])) x ++; resourcelen = x - resourcebase; while ((x < h.lines[0].l) && isspace(h.lines[0].b[x])) x ++; versionbase = x; versionlen = h.lines[0].l - versionbase; if (versionlen == 0) { fprintf(stderr,"%s: %.*s: severely malformed request\n",__progname,h.lines[0].l,h.lines[0].b); oq_queue_point(&c->oq,"400 Bad Request\r\n\r\n",OQ_STRLEN); } if ((versionlen != 8) || bcmp(h.lines[0].b+versionbase,"HTTP/1.1",8)) { fprintf(stderr,"%s: %.*s: not HTTP/1.1\n",__progname,h.lines[0].l,h.lines[0].b); oq_queue_point(&c->oq,"505 Version Not Supported\r\n\r\n",OQ_STRLEN); } if ( (methodlen == 3) && (resourcelen >= 44) && !bcmp(h.lines[0].b,"GET",3) && !bcmp(h.lines[0].b+resourcebase,"/rendezvous/",12) ) { do_rendezvous(c,h.lines[0].b+resourcebase+12,resourcelen-12); free(h.lines); return; } else if ( (methodlen == 3) && (resourcelen >= 38) && !bcmp(h.lines[0].b,"GET",3) && !bcmp(h.lines[0].b+resourcebase,"/stoc/",6) ) { do_stoc(c,h.lines[0].b+resourcebase+6,resourcelen-6); free(h.lines); return; } else if ( (methodlen == 3) && (resourcelen >= 38) && !bcmp(h.lines[0].b,"PUT",3) && !bcmp(h.lines[0].b+resourcebase,"/ctos/",6) ) { do_ctos(c,h.lines[0].b+resourcebase+6,resourcelen-6); free(h.lines); return; } else if ( ( (resourcelen >= 44) && !bcmp(h.lines[0].b+resourcebase,"/rendezvous/",12) ) || ( (resourcelen >= 38) && !bcmp(h.lines[0].b+resourcebase,"/stoc/",6) ) ) { fprintf(stderr,"%s: %.*s: method wrong\n",__progname,h.lines[0].l,h.lines[0].b); oq_queue_point(&c->oq,"405 Method Not Allowed\r\nAllow: GET\r\n\r\n",OQ_STRLEN); } else if ( ( (resourcelen >= 38) && !bcmp(h.lines[0].b+resourcebase,"/ctos/",6) ) ) { fprintf(stderr,"%s: %.*s: method wrong\n",__progname,h.lines[0].l,h.lines[0].b); oq_queue_point(&c->oq,"405 Method Not Allowed\r\nAllow: PUT\r\n\r\n",OQ_STRLEN); } else if ( (methodlen == 3) && ( !bcmp(h.lines[0].b,"GET",3) || !bcmp(h.lines[0].b,"PUT",3) ) ) { fprintf(stderr,"%s: %.*s: bogus resource\n",__progname,h.lines[0].l,h.lines[0].b); oq_queue_point(&c->oq,"404 Not Found\r\n\r\n",OQ_STRLEN); } else { fprintf(stderr,"%s: %.*s: unrecognized method\n",__progname,h.lines[0].l,h.lines[0].b); oq_queue_point(&c->oq,"501 Not Implemented\r\n\r\n",OQ_STRLEN); } free(h.lines); httpconn_drain_and_die(c); } static void maybe_do_request(HTTPCONN *c) { int i; int j; for (i=c->chkstart;irqfill;i++) { if (c->rqbuf[i] == '\n') { j = i + 1; } else if ( (c->rqbuf[i] == '\r') && (i < c->rqfill-1) && (c->rqbuf[i+1] == '\n') ) { j = i + 2; } else { continue; } if (j >= c->rqfill) continue; if (c->rqbuf[j] == '\n') { do_request(c,j+1); return; } else if ( (c->rqbuf[j] == '\r') && (j < c->rqfill-1) && (c->rqbuf[j+1] == '\n') ) { do_request(c,j+2); return; } } i = c->rqfill - 3; if (i < 0) i = 0; c->chkstart = i; } static void rd_initial_conn(void *cv) { HTTPCONN *c; int n; c = cv; do <"fail"> { if (c->rqfill >= MAXRQSIZE) { fprintf(stderr,"%s: %s: ridiculously large request\n",__progname,c->txt); break <"fail">; } n = read(c->fd,c->rqbuf+c->rqfill,MAXRQSIZE-c->rqfill); if (n < 0) { switch (errno) { case EINTR: case EWOULDBLOCK: return; break; } fprintf(stderr,"%s: %s: read: %s\n",__progname,c->txt,strerror(errno)); break <"fail">; } if (n == 0) { fprintf(stderr,"%s: %s: read unexpected EOF\n",__progname,c->txt); break <"fail">; } c->rqfill += n; maybe_do_request(c); return; } while (0); httpconn_shutdown_initial(c); } static void set_nonblock(int fd) { fcntl(fd,F_SETFL,fcntl(fd,F_GETFL,0)|O_NONBLOCK); } static void accept_acc(void *av) { ACC *a; struct sockaddr_storage from; socklen_t fromlen; int i; HTTPCONN *c; char hn[NI_MAXHOST]; char pn[NI_MAXSERV]; a = av; fromlen = sizeof(from); i = accept(a->fd,(struct sockaddr *)&from,&fromlen); if (i < 0) { fprintf(stderr,"%s: accept %s: %s\n",__progname,a->txt,strerror(errno)); exit(1); } if (getnameinfo((const void *)&from,fromlen,&hn[0],NI_MAXHOST,&pn[0],NI_MAXSERV,NI_NUMERICHOST|NI_NUMERICSERV)) { fprintf(stderr,"%s: can't get printable form of peer address: %s\n",__progname,strerror(errno)); close(i); return; } set_nonblock(i); c = malloc(sizeof(HTTPCONN)); asprintf(&c->txt,"%s/%s",&hn[0],&pn[0]); c->fd = i; c->id = add_poll_fd(i,&rwtest_always,&rwtest_never,&rd_initial_conn,0,c); oq_init(&c->oq); c->rqbuf = malloc(MAXRQSIZE); c->rqfill = 0; c->chkstart = 0; } static char *blk_to_cstr(const char *data, int len) { char *t; t = malloc(len+1); bcopy(data,t,len); t[len] = '\0'; return(t); } static int config_line_client(const unsigned char *b, int l, int x) { int namebase; int namelen; int devbase; int devlen; int secretbase; int secretlen; int indirect_secret; int keybase; int *basep; int *lenp; const char *what; CLIENT *c; char *t; int fd; struct stat stb; BL secret; namebase = -1; devbase = -1; secretbase = -1; while (1) { while ((x < l) && isspace(b[x])) x ++; if (x >= l) break; keybase = x; while ((x < l) && (b[x] != '=') && !isspace(b[x])) x ++; if ((x >= l) || (b[x] != '=')) { fprintf(stderr,"%s: %s: %.*s: missing =value on key `%.*s'\n", __progname,server_config,l,b,x-keybase,b+keybase); return(1); } x ++; if ((x-keybase == 4+1) && !bcmp(b+keybase,"name",4)) { basep = &namebase; lenp = &namelen; what = "names"; } else if ((x-keybase == 3+1) && !bcmp(b+keybase,"dev",3)) { basep = &devbase; lenp = &devlen; what = "devices"; } else if ((x-keybase == 6+1) && !bcmp(b+keybase,"secret",6)) { basep = &secretbase; lenp = &secretlen; what = "secrets"; indirect_secret = 0; } else if ((x-keybase == 10+1) && !bcmp(b+keybase,"secretfile",10)) { basep = &secretbase; lenp = &secretlen; what = "secrets"; indirect_secret = 1; } else { fprintf(stderr,"%s: %s: %.*s: unknown key `%.*s'\n", __progname,server_config,l,b,x-1-keybase,b+keybase); return(1); } if (*basep >= 0) { fprintf(stderr,"%s: %s: %.*s: multiple %s specified\n", __progname,server_config,l,b,what); return(1); } *basep = x; while ((x < l) && !isspace(b[x])) x ++; *lenp = x - *basep; } x = 0; if (namebase < 0) { fprintf(stderr,"%s: %s: %.*s: must specify a name\n", __progname,server_config,l,b); x = 1; } if (devbase < 0) { fprintf(stderr,"%s: %s: %.*s: must specify a device\n", __progname,server_config,l,b); x = 1; } if (secretbase < 0) { fprintf(stderr,"%s: %s: %.*s: must specify a secret\n", __progname,server_config,l,b); x = 1; } if (x) return(1); if (indirect_secret) { t = blk_to_cstr(b+secretbase,secretlen); do <"ok"> { do <"fail"> { fd = open(t,O_RDONLY,0); if (fd < 0) { fprintf(stderr,"%s: %s: %.*s: %s: %s\n", __progname,server_config,l,b,t,strerror(errno)); break <"fail">; } do <"fail"> { fstat(fd,&stb); if ((stb.st_size < 0) || (stb.st_size > 65536)) { fprintf(stderr,"%s: %s: %.*s: %s: unreaonable size\n", __progname,server_config,l,b,t); break <"fail">; } secret.b = malloc(stb.st_size); secret.l = stb.st_size; do <"fail"> { x = read(fd,secret.b,secret.l); if (x < 0) { fprintf(stderr,"%s: %s: %.*s: %s: read: %s\n", __progname,server_config,l,b,t,strerror(errno)); break <"fail">; } if (x != secret.l) { fprintf(stderr,"%s: %s: %.*s: %s: read: got %d, wanted %d\n", __progname,server_config,l,b,t,x,secret.l); break <"fail">; } break <"ok">; } while (0); free(secret.b); } while (0); close(fd); } while (0); free(t); return(1); } while (0); close(fd); free(t); } else { secret.b = malloc(secretlen); secret.l = secretlen; bcopy(b+secretbase,secret.b,secretlen); } t = blk_to_cstr(b+devbase,devlen); fd = open(t,O_RDWR,0); if (fd < 0) { fprintf(stderr,"%s: %s: %.*s: %s: %s\n", __progname,server_config,l,b,t,strerror(errno)); free(t); free(secret.b); return(1); } set_nonblock(fd); c = malloc(sizeof(CLIENT)); c->link = clients; clients = c; c->name = blk_to_cstr(b+namebase,namelen); c->namelen = namelen; c->tundev = t; c->tunfd = fd; c->secret = secret; c->live = 0; return(0); } static int config_line_listen(const unsigned char *b, int l, int x) { int hostbase; int hostlen; int portbase; int portlen; char *hoststr; char *portstr; struct addrinfo hints; struct addrinfo *ai0; struct addrinfo *ai; int errs; int set_reuseaddr; ACC *a; int s; int se; int i; char hn[NI_MAXHOST]; char pn[NI_MAXSERV]; errs = 0; while (1) { while ((x < l) && isspace(b[x])) x ++; if (x >= l) break; if ((x < l) && (b[x] == '!')) { x ++; set_reuseaddr = 1; } else { set_reuseaddr = 0; } hostbase = x; while ((x < l) && (b[x] != '/') && !isspace(b[x])) x ++; if ((x < l) && (b[x] == '/')) { hostlen = x - hostbase; x ++; portbase = x; while ((x < l) && !isspace(b[x])) x ++; portlen = x - portbase; } else { portbase = hostbase; hostlen = -1; portlen = x - portbase; } hoststr = (hostlen >= 0) ? blk_to_cstr(b+hostbase,hostlen) : 0; portstr = blk_to_cstr(b+portbase,portlen); printf("setting up listen, hoststr=%s portstr=%s\n",hoststr,portstr); hints.ai_flags = AI_PASSIVE; hints.ai_family = PF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = 0; hints.ai_addrlen = 0; hints.ai_canonname = 0; hints.ai_addr = 0; hints.ai_next = 0; do <"ok"> { do <"fail"> { if (getaddrinfo(hoststr,portstr,&hints,&ai0)) { fprintf(stderr,"%s: %s: %.*s: %s/%s: %s\n", __progname,server_config,l,b,hoststr?:"",portstr,gai_strerror(errno)); break <"fail">; } if (! ai0) { fprintf(stderr,"%s: %s: %.*s: %s/%s: successful lookup but no addresses?\n", __progname,server_config,l,b,hoststr?:"",portstr); break <"fail">; } se = 0; for (ai=ai0;ai;ai=ai->ai_next) { s = socket(ai->ai_family,ai->ai_socktype,ai->ai_protocol); if (s < 0) { if (se == 0) se = errno; continue; } se = -1; if (getnameinfo(ai->ai_addr,ai->ai_addrlen,&hn[0],NI_MAXHOST,&pn[0],NI_MAXSERV,NI_NUMERICHOST|NI_NUMERICSERV)) { fprintf(stderr,"%s: %s: %.*s: %s/%s: can't get printable form of actual address: %s\n", __progname,server_config,l,b,hoststr?:"",portstr,strerror(errno)); close(s); break <"fail">; } printf("setting up listen, hn=%s pn=%s\n",&hn[0],&pn[0]); if (set_reuseaddr) { i = 1; setsockopt(s,SOL_SOCKET,SO_REUSEADDR,&i,sizeof(i)); } if (bind(s,ai->ai_addr,ai->ai_addrlen) < 0) { fprintf(stderr,"%s: %s: %.*s: %s/%s: bind: %s\n", __progname,server_config,l,b,&hn[0],&pn[0],strerror(errno)); close(s); break <"fail">; } printf("setting up listen, fd=%d\n",s); listen(s,10); a = malloc(sizeof(ACC)); asprintf(&a->txt,"%s%s%s [%s/%s]",hoststr?:"",hoststr?"/":"",portstr,&hn[0],&pn[0]); a->fd = s; a->id = add_poll_fd(a->fd,&rwtest_always,&rwtest_never,&accept_acc,0,a); nacc ++; } break <"ok">; } while (0); errs = 1; } while (0); free(hoststr); free(portstr); } return(errs); } static int config_line(const unsigned char *b, int l) { int x; printf("config line: %.*s\n",l,b); for (x=0;(x= l) return(0); if (b[x] == '#') return(0); if ((l-x >= 6) && !bcmp(b+x,"CLIENT",6)) { return(config_line_client(b,l,x+6)); } else if ((l-x >= 6) && !bcmp(b+x,"LISTEN",6)) { return(config_line_listen(b,l,x+6)); } else { fprintf(stderr,"%s: %s: %.*s: unrecognized\n",__progname,server_config,l,b); return(1); } } static void load_config(void) { FILE *f; char *b; int l; int a; int c; int errs; f = fopen(server_config,"r"); if (! f) { fprintf(stderr,"%s: %s: %s\n",__progname,server_config,strerror(errno)); exit(1); } errs = 0; b = 0; a = 0; l = 0; while <"loading"> (1) { c = getc(f); switch (c) { case EOF: if (l > 0) { fprintf(stderr,"%s: %s: warning: partial line at EOF\n",__progname,server_config); if (config_line(b,l)) errs = 1; } break <"loading">; case '\n': if (config_line(b,l)) errs = 1; l = 0; break; default: if (l >= a) b = realloc(b,a=l+8); b[l++] = c; break; } } free(b); fclose(f); if (errs) exit(1); } static void setup_server(void) { clients = 0; nacc = 0; pending = 0; load_config(); } static int wtest_rvrec(void *rrv) { return(oq_nonempty(&((RVREC *)rrv)->oq)); } static void rvrec_abort(RVREC *rr) { close(rr->fd); remove_poll_id(rr->id); oq_flush(&rr->oq); delayed_call(&free,rr->nhb); delayed_call(&free,rr); } static int open_server_conn(void) { int s; s = socket(server_addr->sa_family,SOCK_STREAM,0); if (s < 0) { fprintf(stderr,"%s: %s: socket: %s\n",__progname,server_text,strerror(errno)); exit(1); } if (connect(s,server_addr,server_addr->sa_len) < 0) { fprintf(stderr,"%s: %s: connect: %s\n",__progname,server_text,strerror(errno)); exit(1); } set_nonblock(s); return(s); } static void gen_ctos_request(void) { oq_queue_point(&ctos_oq,"PUT /ctos/",OQ_STRLEN); oq_queue_point(&ctos_oq,clientnonce.h,clientnonce.hl); oq_queue_point(&ctos_oq,"/",OQ_STRLEN); oq_queue_point(&ctos_oq,servernonce.h,servernonce.hl); oq_queue_point(&ctos_oq," HTTP/1.1\r\n" "Accept-Language: en\r\n" "Cache-Control: no-cache, no-store, no-transform\r\n" "Pragma: no-cache\r\n" "Connection: close\r\n" "Transfer-Encoding: chunked\r\n", OQ_STRLEN); gen_date(&ctos_oq); oq_queue_point(&ctos_oq,"Host: ",OQ_STRLEN); oq_queue_point(&ctos_oq,hosthdr,OQ_STRLEN); oq_queue_point(&ctos_oq,"\r\nUser-Agent: webip\r\n\r\n",OQ_STRLEN); } static void gen_stoc_request(void) { oq_queue_point(&stoc_oq,"GET /stoc/",OQ_STRLEN); oq_queue_point(&stoc_oq,clientnonce.h,clientnonce.hl); oq_queue_point(&stoc_oq,"/",OQ_STRLEN); oq_queue_point(&stoc_oq,servernonce.h,servernonce.hl); oq_queue_point(&stoc_oq," HTTP/1.1\r\n" "Accept: application/octet-stream\r\n" "Accept-Language: en\r\n" "Cache-Control: no-cache, no-store, no-transform\r\n" "Pragma: no-cache\r\n" "Connection: close\r\n" "Content-Length: 0\r\n", OQ_STRLEN); gen_date(&stoc_oq); oq_queue_point(&stoc_oq,"Host: ",OQ_STRLEN); oq_queue_point(&stoc_oq,hosthdr,OQ_STRLEN); oq_queue_point(&stoc_oq,"\r\nUser-Agent: webip\r\n\r\n",OQ_STRLEN); } static int wtest_client_ctos(void *arg __attribute__((__unused__))) { return(oq_nonempty(&ctos_oq)); } static void wr_client_ctos(void *arg __attribute__((__unused__))) { if (wr_oq_common(&ctos_oq,ctos_fd) == OQWS_ERR) { fprintf(stderr,"%s: %s: write: %s\n",__progname,server_text,strerror(errno)); exit(1); } } static int wtest_client_stoc(void *arg __attribute__((__unused__))) { return(oq_nonempty(&stoc_oq)); } static void wr_client_stoc(void *arg __attribute__((__unused__))) { if (wr_oq_common(&stoc_oq,stoc_fd) == OQWS_ERR) { fprintf(stderr,"%s: %s: write: %s\n",__progname,server_text,strerror(errno)); exit(1); } } static int client_flush_check(void *arg __attribute__((__unused__))) { CONN *c; if (oq_nonempty(&ctos_oq) || oq_nonempty(&stoc_oq)) return(BLOCK_NIL); remove_poll_id(ctos_id); remove_poll_id(stoc_id); remove_block_id(flush_id); c = malloc(sizeof(CONN)); c->c = 0; c->secret = (BL) { .b = secret_data, .l = secret_len }; c->cn = clientnonce; c->sn = servernonce; c->w_fd = ctos_fd; c->r_fd = stoc_fd; c->tun_dev = tun_dev; c->tun_fd = tun_fd; conn_setup_common(c); return(BLOCK_LOOP); } static void start_data_conns(void) { ctos_fd = open_server_conn(); stoc_fd = open_server_conn(); oq_init(&ctos_oq); oq_init(&stoc_oq); gen_ctos_request(); gen_stoc_request(); ctos_id = add_poll_fd(ctos_fd,&rwtest_never,&wtest_client_ctos,0,&wr_client_ctos,0); stoc_id = add_poll_fd(stoc_fd,&rwtest_never,&wtest_client_stoc,0,&wr_client_stoc,0); flush_id = add_block_fn(&client_flush_check,0); } static void rd_rvrec(void *rrv) { RVREC *rr; unsigned char rb[256]; int n; int i; const char *failmsg; rr = rrv; n = read(rr->fd,&rb[0],sizeof(rb)); if (n < 0) { fprintf(stderr,"%s: %s: read: %s\n",__progname,server_text,strerror(errno)); rvrec_abort(rr); return; } if (n == 0) { if (rr->nls == 3) { failmsg = parse_nonce(&servernonce,rr->nhb,rr->nhl); if (failmsg) { fprintf(stderr,"%s: %s: bad server nonce (%s): %.*s\n",__progname,server_text,failmsg,rr->nhl,rr->nhb); exit(1); } printf("server nonce: %s\n",servernonce.h); start_data_conns(); } else { fprintf(stderr,"%s: %s: unexpected EOF\n",__progname,server_text); } rvrec_abort(rr); return; } for (i=0;inls ++; break; default: switch (rr->nls) { case 0: break; case 1: rr->nls = 0; break; case 2: if (rr->nhl >= MAXNONCE*2) { fprintf(stderr,"%s: %s: response nonce too long\n",__progname,server_text); rvrec_abort(rr); return; } if (rr->nhl >= rr->nha) rr->nhb = realloc(rr->nhb,rr->nha=rr->nhl+8); rr->nhb[rr->nhl++] = rb[i]; break; default: break; } break; } } } static void wr_rvrec(void *rrv) { RVREC *rr; rr = rrv; if (wr_oq_common(&rr->oq,rr->fd) == OQWS_ERR) { fprintf(stderr,"%s: %s: write: %s\n",__progname,server_text,strerror(errno)); rvrec_abort(rr); } } static void gen_initial_request(RVREC *rr) { gen_nonce(&clientnonce); printf("client nonce: %s\n",clientnonce.h); oq_queue_point(&rr->oq,"GET /rendezvous/",OQ_STRLEN); oq_queue_point(&rr->oq,client_name,OQ_STRLEN); oq_queue_point(&rr->oq,"/",OQ_STRLEN); oq_queue_point(&rr->oq,clientnonce.h,clientnonce.hl); oq_queue_point(&rr->oq," HTTP/1.1\r\n" "Accept: text/plain\r\n" "Accept-Language: en\r\n" "Cache-Control: no-cache, no-store, no-transform\r\n" "Pragma: no-cache\r\n" "Connection: close\r\n" "Content-Length: 0\r\n", OQ_STRLEN); gen_date(&rr->oq); oq_queue_point(&rr->oq,"Host: ",OQ_STRLEN); oq_queue_point(&rr->oq,hosthdr,OQ_STRLEN); oq_queue_point(&rr->oq,"\r\nUser-Agent: webip\r\n\r\n",OQ_STRLEN); } static void setup_client(void) { STRLIST *sl; char *slash; char *hoststr; const char *portstr; int errs; struct addrinfo hints; struct addrinfo *ai0; struct addrinfo *ai; int e; int se; int s; RVREC *rr; char hn[NI_MAXHOST]; char pn[NI_MAXSERV]; tun_fd = open(tun_dev,O_RDWR,0); if (tun_fd < 0) { fprintf(stderr,"%s: open %s: %s\n",__progname,tun_dev,strerror(errno)); exit(1); } set_nonblock(tun_fd); errs = 0; hoststr = 0; for (sl=client_net_specs;sl;sl=sl->link) { free(hoststr); slash = index(sl->s,'/'); if (slash) { *slash = '\0'; hoststr = strdup(sl->s); *slash = '/'; portstr = slash + 1; } else { hoststr = strdup(sl->s); portstr = "80"; } hints.ai_flags = 0; hints.ai_family = PF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = 0; hints.ai_addrlen = 0; hints.ai_canonname = 0; hints.ai_addr = 0; hints.ai_next = 0; e = getaddrinfo(hoststr,portstr,&hints,&ai0); if (e) { fprintf(stderr,"%s: %s/%s: %s\n",__progname,hoststr,portstr,gai_strerror(e)); exit(1); } if (! ai0) { fprintf(stderr,"%s: %s/%s: successful lookup but no addresses?\n",__progname,hoststr?:"",portstr); exit(1); } se = 0; for (ai=ai0;ai;ai=ai->ai_next) { s = socket(ai->ai_family,ai->ai_socktype,ai->ai_protocol); if (s < 0) { if (se == 0) se = errno; continue; } se = -1; if (getnameinfo(ai->ai_addr,ai->ai_addrlen,&hn[0],NI_MAXHOST,&pn[0],NI_MAXSERV,NI_NUMERICHOST|NI_NUMERICSERV)) { fprintf(stderr,"%s: %s/%s: can't get numeric host/port info [%s]\n",__progname,hoststr,portstr,strerror(errno)); exit(1); } if (connect(s,ai->ai_addr,ai->ai_addrlen) < 0) { if (ai0->ai_next) { fprintf(stderr,"%s: connect %s/%s [%s/%s]: %s\n",__progname,hoststr,portstr,&hn[0],&pn[0],strerror(errno)); } else { fprintf(stderr,"%s: connect %s/%s: %s\n",__progname,&hn[0],&pn[0],strerror(errno)); } close(s); continue; } if (ai->ai_family != ai->ai_addr->sa_family) { fprintf(stderr,"%s: AF mismatch (ai %d, sa_family %d)\n",__progname,ai->ai_family,ai->ai_addr->sa_family); exit(1); } if (ai->ai_addrlen != ai->ai_addr->sa_len) { fprintf(stderr,"%s: sockaddr length mismatch (ai %d, sa_family %d)\n",__progname,ai->ai_addrlen,ai->ai_addr->sa_len); exit(1); } hosthdr = hoststr; server_addr = malloc(ai->ai_addrlen); bcopy(ai->ai_addr,server_addr,ai->ai_addrlen); freeaddrinfo(ai0); asprintf(&server_text,"%s/%s",&hn[0],&pn[0]); rr = malloc(sizeof(RVREC)); rr->fd = s; rr->id = add_poll_fd(s,&rwtest_always,&wtest_rvrec,&rd_rvrec,&wr_rvrec,rr); oq_init(&rr->oq); rr->nls = 0; rr->nhb = 0; rr->nhl = 0; rr->nha = 0; gen_initial_request(rr); return; } if (se > 0) { fprintf(stderr,"%s: socket: %s\n",__progname,strerror(se)); } } exit(1); } int main(int, char **); int main(int ac, char **av) { setlinebuf(stdout); handleargs(ac,av); init_polling(); switch (opmode) { default: abort(); break; case OM_SERVER: setup_server(); break; case OM_CLIENT: setup_secret(); setup_client(); break; } poll_run(); exit(1); }