/* * 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 a random 16-byte nonce and sends it, encrypted * as if it were ordinary data. Each receives its peer's nonce and * echos it back encrypted appropriately for the other direction. * Each end verifies that it got its nonce 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 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; struct bl { unsigned char *b; int l; } ; struct client { CLIENT *link; char *name; int namelen; int tun; BL secret; } ; struct webhdr { int nlines; BL *lines; } ; struct nonce { unsigned char *b; int l; char *h; int hl; } ; struct conn { CONN *link; NONCE cn; NONCE sn; int ctos_fd; int stoc_fd; OQ oq; } ; struct rvrec { char *txt; int fd; int id; OQ oq; int nls; char *nhb; int nhl; int nha; } ; struct pending { PENDING *flink; PENDING *blink; NONCE cn; NONCE sn; int tmofd; int tmoid; 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 SECRETMODE secret_mode = SM_UNSPEC; static int rndfd = -1; /* client mode */ static STRLIST *client_net_specs = 0; static STRLIST **client_net_specs_t = &client_net_specs; static char *secret_spec; static unsigned char *secret_data; static unsigned int secret_len; static struct sockaddr *server_addr; static char *hosthdr; static NONCE clientnonce; static NONCE servernonce; /* 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 ++; 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,"-client")) { WANTARG(); client_arg(av[skip]); continue; } if (!strcmp(*av,"-server")) { WANTARG(); opmode = OM_SERVER; server_config = 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 ++; } if (errs) exit(1); if (opmode == OM_UNSPEC) { fprintf(stderr,"%s: no operation mode specified; use -client or -server\n",__progname); errs ++; } if ((opmode == OM_CLIENT) && (secret_mode == SM_UNSPEC)) { fprintf(stderr,"%s: no secret specified; use -secret or -secretfile\n",__progname); errs ++; } 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) { 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)); } static void wr_drain_httpconn(void *cv) { HTTPCONN *c; int n; c = cv; if (oq_empty(&c->oq)) return; n = oq_writev(&c->oq,c->fd); if (n < 0) { switch (errno) { case EINTR: case EWOULDBLOCK: return; break; } fprintf(stderr,"%s: %s: write: %s\n",__progname,c->txt,strerror(errno)); httpconn_shutdown_drain(c); } else { oq_dropdata(&c->oq,n); if (oq_empty(&c->oq)) { httpconn_shutdown_drain(c); } } } static void httpconn_drain_and_die(HTTPCONN *c) { delayed_call(&free,c->rqbuf); remove_poll_id(c->id); c->id = add_poll_fd(c->fd,&rwtest_never,&wtest_httpconn,0,&wr_drain_httpconn,c); 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 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); 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 void do_rendezvous(HTTPCONN *c, const unsigned char *nh, int nhl) { NONCE n; PENDING *p; const char *msg; struct itimerval itv; msg = parse_nonce(&n,nh,nhl); if (msg) { fprintf(stderr,"%s: %s: %.*s: %s\n",__progname,c->txt,nhl,nh,msg); oq_queue_point(&c->oq,"404 Not Found\r\n\r\n",OQ_STRLEN); httpconn_drain_and_die(c); return; } p = malloc(sizeof(PENDING)); p->flink = pending; p->blink = 0; pending = p; if (p->flink) p->flink->blink = p; p->cn = n; gen_nonce(&p->sn); p->tmofd = socket(AF_TIMER,SOCK_STREAM,0); itv.it_value.tv_sec = 60; itv.it_value.tv_usec = 0; itv.it_interval.tv_sec = 0; itv.it_interval.tv_usec = 0; write(p->tmofd,&itv,sizeof(itv)); p->tmoid = add_poll_fd(p->tmofd,&rwtest_always,&rwtest_never,&rd_pending_tmo,0,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); } static void do_stoc(HTTPCONN *c, const unsigned char *nh, int nhl) { /* XXX */ c=c; nh=nh; nhl=nhl; printf("s->c request\n"); } static void do_ctos(HTTPCONN *c, const unsigned char *nh, int nhl) { /* XXX */ c=c; nh=nh; nhl=nhl; printf("c->s request\n"); } 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); } free(t); c = malloc(sizeof(CLIENT)); c->link = clients; clients = c; c->name = blk_to_cstr(b+namebase,namelen); c->namelen = namelen; c->tun = fd; c->secret = secret; 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) { delayed_call(&free,rr->txt); close(rr->fd); remove_poll_id(rr->id); oq_flush(&rr->oq); delayed_call(&free,rr->nhb); delayed_call(&free,rr); } static void start_data_conns(void) { /* XXX */ } 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,rr->txt,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,rr->txt,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,rr->txt); } 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,rr->txt); 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; int n; rr = rrv; n = oq_writev(&rr->oq,rr->fd); if (n < 0) { switch (errno) { case EINTR: case EWOULDBLOCK: return; break; } fprintf(stderr,"%s: %s: write: %s\n",__progname,rr->txt,strerror(errno)); rvrec_abort(rr); } else { oq_dropdata(&rr->oq,n); } } static void gen_initial_request(RVREC *rr) { gen_nonce(&clientnonce); oq_queue_point(&rr->oq,"GET /rendezvous/",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]; 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); rr = malloc(sizeof(RVREC)); asprintf(&rr->txt,"%s/%s",&hn[0],&pn[0]); 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) { 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); }