/* This file is in the public domain. */ #include #include #include #include #include #include #include #include #include #include #include #include #include extern const char *__progname; #include "oq.h" #include "protocol.h" #include "pollloop.h" #include "stdio-util.h" typedef enum { CR_ACTIVE = 1, CR_PASSIVE } CONNROLE; typedef struct ipaddr IPADDR; typedef struct peer PEER; typedef struct uplink UPLINK; typedef struct listen LISTEN; typedef struct public PUBLIC; typedef struct isource ISOURCE; typedef struct apstate APSTATE; typedef struct cryptinit CRYPTINIT; /* * State for the initial crypto exchange. Ths is a separate struct so * it can be discarded easily when the exchange is over. */ struct cryptinit { CONNROLE myrole; PEER *p; unsigned char Rc[16]; unsigned char Rl[16]; int n; } ; /* * State for parsing ADDR/PORT stuff. This exists so that specific * uses of the parser can tweak it in a failure callback. */ struct apstate { char *addr; char *port; struct addrinfo hints; } ; /* * An item on the stack of includes for config files. */ struct isource { ISOURCE *link; char *file; FILE *f; int lno; } ; /* * A listen-at point. */ struct listen { LISTEN *link; struct sockaddr *sa; int salen; char *txt; int fd; } ; /* * An uplink point. Each of these is somewhere we want to maintain a * connection to. */ struct uplink { UPLINK *link; struct sockaddr *sa; int salen; char *txt; time_t stamp; PEER *peer; } ; /* * A connection to a peer. * * These include connections which we have initiated but which have not * yet completed at the socket layer and connections which have come * up at the socket layer but have not passed crypto verification; * they are just like other PEERs except that the callbacks for their * poll IDs are unusual. */ struct peer { struct sockaddr *lclsa; int lclsalen; struct sockaddr *remsa; int remsalen; char *txt; int fd; int id; OQ oq; } ; /* * An IP address, without a port number. */ struct ipaddr { int af; union { struct in_addr v4; struct in6_addr v6; } ; } ; /* * The shared-cloudwide secret key, pointer and length. */ static unsigned char *ck; static int cklen; /* * This host's cloud ID. */ static unsigned char cid; /* * This host's list of cloud IP addresses. */ static int ncipaddrs; static IPADDR **cipaddrs; /* * Boolean saying whether this host ignores PUBLIC packets. */ static int ignore_public; /* * Stack of input sources. Used when reading the config. */ static ISOURCE *isrc; /* * Listening points. The difference between types 1 and 2, and between * types 4 and 3, is whether this list is empty or not. */ static LISTEN *listens; /* * Uplink points. Initially, these come from uplink declarations in * the configuration; if ignore_public is false, the list is mutated * as PUBLIC packets come in and timers expire. */ static UPLINK *uplinks; /* * The list of PEERs. */ static int npeers; static int apeers; static PEER **peers; /* * The fd and poll ID for the timer socket driving attempts to * (re)connect to uplinks. */ static int pcfd; static int pcid; /* * The block ID for cleaning up dead PEERs, and a boolean indicating * whether it might have work to do. */ static int peercleanup_id; static int peercleanup_needed; #define Cisspace(c) isspace((unsigned char)(c)) static void config_err(const char *, ...) __attribute__((__format__(__printf__,1,2),__noreturn__)); static void config_err(const char *fmt, ...) { va_list ap; char *s; void dump_isrc_chain(ISOURCE *i) { if (i->link) dump_isrc_chain(i->link); fprintf(stderr," \"%s\", line %d:",i->file,i->lno); } va_start(ap,fmt); vasprintf(&s,fmt,ap); va_end(ap); fprintf(stderr,"%s:",__progname); dump_isrc_chain(isrc); fprintf(stderr," %s\n",s); free(s); exit(1); } static void config_err_cleanup(void (*)(void), const char *, ...) __attribute__((__format__(__printf__,2,3),__noreturn__)); static void config_err_cleanup(void (*cleanup)(void), const char *fmt, ...) { va_list ap; char *s; void dump_isrc_chain(ISOURCE *i) { if (i->link) dump_isrc_chain(i->link); fprintf(stderr," \"%s\", line %d:",i->file,i->lno); } va_start(ap,fmt); vasprintf(&s,fmt,ap); va_end(ap); fprintf(stderr,"%s:",__progname); dump_isrc_chain(isrc); fprintf(stderr," %s\n",s); free(s); (*cleanup)(); exit(1); } static void push_isrc(const char *s) { FILE *f; ISOURCE *i; f = fopen(s,"r"); if (f == 0) config_err("can't open config file %s: %s",s,strerror(errno)); i = malloc(sizeof(ISOURCE)); i->file = strdup(s); i->f = f; i->lno = 0; i->link = isrc; isrc = i; } static void pop_isrc(void) { ISOURCE *i; i = isrc; isrc = i->link; free(i->file); fclose(i->f); free(i); } static struct addrinfo *crack_addr_port(const char *s, int flags, void (*custom)(APSTATE *)) { APSTATE st; const char *slash; int i; struct addrinfo *rv; slash = index(s,'/'); if (! slash) config_err("no slash in ADDR/PORT"); i = slash - s; st.addr = malloc(i+1); bcopy(s,st.addr,i); st.addr[i] = '\0'; st.port = strdup(slash+1); st.hints.ai_flags = flags; st.hints.ai_family = PF_UNSPEC; st.hints.ai_socktype = SOCK_STREAM; st.hints.ai_protocol = 0; st.hints.ai_addrlen = 0; st.hints.ai_addr = 0; st.hints.ai_canonname = 0; st.hints.ai_next = 0; if (custom) (*custom)(&st); i = getaddrinfo(st.addr,st.port,&st.hints,&rv); if (i == 0) return(rv); config_err("lookup %s: %s",s,gai_strerror(i)); } static void confline__at(const char *s) { push_isrc(s); } static void confline_key(const char *s) { free(ck); cklen = strlen(s); ck = malloc(cklen); bcopy(s,ck,cklen); } static void confline_keyfile(const char *s) { int fd; struct stat stb; int r; int l; unsigned char *b; fd = open(s,O_RDONLY,0); if (fd < 0) config_err("open %s: %s",s,strerror(errno)); if (fstat(fd,&stb) < 0) config_err("fstat %s: %s",s,strerror(errno)); if (stb.st_size < 1) config_err("%s: empty",s); else if (stb.st_size > 65536) { fprintf(stderr,"%s: %s: too large, using only first 64K\n",__progname,s); l = 65536; } else { l = stb.st_size; } b = malloc(l); r = read(fd,b,l); if (r < 0) config_err("read from %s: %s",s,strerror(errno)); if (r != l) config_err("read from %s: wanted %d, got %d",s,l,r); close(fd); free(ck); ck = b; cklen = l; } static void confline_id(const char *s) { long int id; char *ep; id = strtol(s,&ep,0); if (ep != s) while (*ep && Cisspace(*ep)) ep ++; if (ep == s) config_err("bad/missing ID number"); if (* ep) config_err("junk after ID number"); if ((id < 0) || (id > 255)) config_err("ID %ld is out of range",id); cid = id; } static void confline_ip(const char *s) { IPADDR *a; a = malloc(sizeof(IPADDR)); if (inet_pton(AF_INET,s,&a->v4) > 0) { a->af = AF_INET; } else if (inet_pton(AF_INET6,s,&a->v6) > 0) { a->af = AF_INET6; } else { config_err("unparseable IP `%s'",s); } cipaddrs = realloc(cipaddrs,(ncipaddrs+1)*sizeof(IPADDR *)); cipaddrs[ncipaddrs++] = a; } static void confline_type(const char *s) { if (! strcmp(s,"public")) { ignore_public = 0; } else if (! strcmp(s,"private")) { ignore_public = 1; } else { config_err("bad `type' value `%s'",s); } } static void capcustom_listen(APSTATE *s) { if (! strcmp(s->addr,"*")) { } else if (! strcmp(s->addr,"*4")) { s->hints.ai_family = AF_INET; } else if (! strcmp(s->addr,"*6")) { s->hints.ai_family = AF_INET6; } else { return; } free(s->addr); s->addr = 0; } static void confline_listen(const char *s) { struct addrinfo *ai0; struct addrinfo *ai; LISTEN *l; char *txt; int fd; int e; void errcleanup(void) { if (fd >= 0) close(fd); if (txt) free(txt); freeaddrinfo(ai0); } ai0 = crack_addr_port(s,AI_PASSIVE,&capcustom_listen); for (ai=ai0;ai;ai=ai->ai_next) { char hstr[NI_MAXHOST]; char sstr[NI_MAXSERV]; fd = -1; txt = 0; e = getnameinfo(ai->ai_addr,ai->ai_addrlen,&hstr[0],NI_MAXHOST,&sstr[0],NI_MAXSERV,NI_NUMERICHOST|NI_NUMERICSERV); if (e) config_err_cleanup(&errcleanup,"getnameinfo [for %s]: %s",s,gai_strerror(e)); asprintf(&txt,"%s/%s",&hstr[0],&sstr[0]); fd = socket(ai->ai_family,ai->ai_socktype,ai->ai_protocol); if (fd < 0) continue; if (bind(fd,ai->ai_addr,ai->ai_addrlen) < 0) { config_err_cleanup(&errcleanup,"bind %s [for %s]: %s",txt,s,strerror(errno)); } if (listen(fd,10) < 0) { config_err_cleanup(&errcleanup,"bind %s [for %s]: %s",txt,s,strerror(errno)); } l = malloc(sizeof(LISTEN)); l->sa = malloc(ai->ai_addrlen); bcopy(ai->ai_addr,l->sa,ai->ai_addrlen); l->salen = ai->ai_addrlen; l->txt = txt; l->fd = fd; l->link = listens; listens = l; } } static void confline_uplink(const char *s) { struct addrinfo *ai0; struct addrinfo *ai; UPLINK *u; char *txt; int fd; int e; time_t now; void errcleanup(void) { if (fd >= 0) close(fd); if (txt) free(txt); freeaddrinfo(ai0); } time(&now); ai0 = crack_addr_port(s,0,0); for (ai=ai0;ai;ai=ai->ai_next) { char hstr[NI_MAXHOST]; char sstr[NI_MAXSERV]; fd = -1; txt = 0; e = getnameinfo(ai->ai_addr,ai->ai_addrlen,&hstr[0],NI_MAXHOST,&sstr[0],NI_MAXSERV,NI_NUMERICHOST|NI_NUMERICSERV); if (e) config_err_cleanup(&errcleanup,"getnameinfo [for %s]: %s",s,gai_strerror(e)); asprintf(&txt,"%s/%s",&hstr[0],&sstr[0]); u = malloc(sizeof(UPLINK)); u->sa = malloc(ai->ai_addrlen); bcopy(ai->ai_addr,u->sa,ai->ai_addrlen); u->salen = ai->ai_addrlen; u->txt = txt; u->stamp = now; u->peer = 0; u->link = uplinks; uplinks = u; } } static void config_line(const char *s) { const char *k0; int kl; static struct { const char *key; void (*handler)(const char *); int keylen; } keys[] = { { "@", &confline__at }, { "key", &confline_key }, { "keyfile", &confline_keyfile }, { "id", &confline_id }, { "ip", &confline_ip }, { "type", &confline_type }, { "listen", &confline_listen }, { "uplink", &confline_uplink }, { 0 } }; int i; if (keys[0].keylen == 0) for (i=0;keys[i].key;i++) keys[i].keylen = strlen(keys[i].key); while (*s && Cisspace(*s)) s ++; if (! *s) return; if (*s == '#') return; k0 = s; while (*s && !Cisspace(*s)) s ++; kl = s - k0; while (*s && Cisspace(*s)) s ++; for (i=0;keys[i].key;i++) { if ((kl == keys[i].keylen) && !bcmp(k0,keys[i].key,kl)) { (*keys[i].handler)(s); return; } } config_err("unrecognized config key `%.*s'",kl,k0); } static void config_read(void) { char *lb; int la; int ln; int c; void savec(char ch) { if (ln >= la) lb = realloc(lb,la=ln+16); lb[ln++] = ch; } void cl(void) { savec('\0'); config_line(lb); } lb = 0; la = 0; ln = 0; while <"read"> (1) { c = getc(isrc->f); switch (c) { case EOF: if (ln > 0) cl(); ln = 0; pop_isrc(); if (! isrc) break <"read">; break; case '\n': cl(); ln = 0; break; default: if (ln == 0) isrc->lno ++; savec(c); break; } } free(lb); } static void readconf(const char *fn) { isrc = 0; push_isrc(fn); config_read(); } 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,"-config")) { WANTARG(); readconf(av[skip]); continue; } #undef WANTARG fprintf(stderr,"%s: unrecognized option `%s'\n",__progname,*av); errs ++; } if (errs) exit(1); } static void init(void) { ck = 0; ncipaddrs = 0; cipaddrs = 0; ignore_public = -1; listens = 0; uplinks = 0; npeers = 0; apeers = 0; peers = 0; } static void set_defaults(void) { if (ignore_public < 0) { fprintf(stderr,"%s: no `type' line configured\n",__progname); exit(1); } if (uplinks == 0) { fprintf(stderr,"%s: no uplinks configured\n",__progname); exit(1); } } static PEER *new_peer(void) { PEER *p; if (npeers >= apeers) peers = realloc(peers,(apeers=npeers+1)*sizeof(PEER *)); p = malloc(sizeof(PEER)); p->lclsa = 0; p->remsa = 0; p->txt = 0; p->fd = -1; p->id = PL_NOID; peers[npeers++] = p; return(p); } static int peercleanup(void *arg __attribute__((__unused__))) { int i; PEER *p; if (! peercleanup_needed) return(BLOCK_NIL); peercleanup_needed = 0; for (i=npeers;i>=0;i--) { p = peers[i]; if (p->fd >= 0) continue; free(p->lclsa); free(p->remsa); free(p->txt); free(p); npeers --; if (i < npeers) peers[i] = peers[npeers]; } return(BLOCK_LOOP); } static void close_peer(PEER *p) { close(p->fd); p->fd = -1; peercleanup_needed = 1; } static void close_cryptinit(CRYPTINIT *ci) { remove_poll_id(ci->p->id); close_peer(ci->p); free(ci); } static void wr_cryptinit(void *civ) { CRYPTINIT *ci; int n; ci = civ; n = oq_writev(&ci->p->oq,ci->p->fd); if (n < 0) { switch (errno) { case EINTR: case EWOULDBLOCK: return; } fprintf(stderr,"%s: write to %s: %s\n",__progname,ci->p->txt,strerror(errno)); } } static int wtest_cryptinit(void *civ) { return(oq_nonempty(&((CRYPTINIT *)civ)->p->oq)); } static int rd_crypto_1(CRYPTINIT *ci, unsigned char *Rv) { int n; n = read(ci->p->fd,&Rv[ci->n],16-ci->n); if (n < 0) { switch (errno) { case EINTR: case EWOULDBLOCK: return(1); break; } close_cryptinit(ci); return(1); } if (n == 0) { close_cryptinit(ci); return(1); } ci->n += n; if (ci->n < 16) return(1); return(0); } static void rd_crypto_active_1(void *civ) { CRYPTINIT *ci; ci = civ; if (rd_crypto_1(ci,&ci->Rl[0])) return; random_data(&ci->Rc[0],16); XXX XXX XXX;;; } static void rd_crypto_passive_1(void *civ) { CRYPTINIT *ci; ci = civ; if (rd_crypto_1(ci,&ci->Rc[0])) return; XXX XXX XXX;;; } static void peer_start_crypto(CRYPTINIT *ci) { switch (ci->myrole) { case CR_ACTIVE: ci->p->id = add_poll_fd(ci->p->fd,&rwtest_always,&wtest_cryptinit,&rd_crypto_active_1,&wr_cryptinit,ci); break; case CR_PASSIVE: ci->p->id = add_poll_fd(ci->p->fd,&rwtest_always,&wtest_cryptinit,&rd_crypto_passive_1,&wr_cryptinit,ci); random_data(&ci->Rl[0],16); oq_queue_copy(&ci->p->oq,&ci->Rl[0],16); break; default: abort(); break; } ci->n = 0; } static int peer_get_sockaddrs(PEER *p) { struct sockaddr_storage ss; socklen_t l; FILE *f; void build_txt(struct sockaddr *sa, int salen, FILE *f) { char hstr[NI_MAXHOST]; char sstr[NI_MAXSERV]; int e; e = getnameinfo(sa,salen,&hstr[0],NI_MAXHOST,&sstr[0],NI_MAXSERV,NI_NUMERICHOST|NI_NUMERICSERV); if (e) { if (e == EAI_SYSTEM) { fprintf(f,"[getnameinfo: %s]",strerror(errno)); } else { fprintf(f,"[getnameinfo: %s]",gai_strerror(e)); } } else { fprintf(f,"%s/%s",&hstr[0],&sstr[0]); } } if (! p->lclsa) { l = sizeof(ss); if (getsockname(p->fd,(struct sockaddr *)&ss,&l) < 0) { fprintf(stderr,"%s: getsockname for %s: %s\n",__progname,p->txt,strerror(errno)); return(-1); } p->lclsa = malloc(l); bcopy(&ss,p->lclsa,l); p->lclsalen = l; } if (! p->remsa) { l = sizeof(ss); if (getpeername(p->fd,(struct sockaddr *)&ss,&l) < 0) { fprintf(stderr,"%s: getpeername for %s: %s\n",__progname,p->txt,strerror(errno)); return(-1); } p->remsa = malloc(l); bcopy(&ss,p->remsa,l); p->remsalen = l; } free(p->txt); f = open_accum(&p->txt,0); build_txt(p->lclsa,p->lclsalen,f); fprintf(f," <-> "); build_txt(p->remsa,p->remsalen,f); fclose(f); return(0); } static void peer_connect_ok(PEER *p) { CRYPTINIT *ci; if (peer_get_sockaddrs(p) < 0) { close_peer(p); return; } ci = malloc(sizeof(CRYPTINIT)); ci->myrole = CR_ACTIVE; ci->p = p; peer_start_crypto(ci); } static void peer_connect_done(void *pv) { PEER *p; int e; socklen_t el; p = pv; remove_poll_id(p->id); p->id = PL_NOID; el = sizeof(e); if ( (getsockopt(p->fd,SOL_SOCKET,SO_ERROR,&e,&el) < 0) || (el != sizeof(e)) || e ) { close_peer(p); return; } peer_connect_ok(p); } static void pcfd_rd(void *arg __attribute__((__unused__))) { struct timersock_event ev; UPLINK *ul; PEER *p; int s; while (read(pcfd,&ev,sizeof(ev)) > 0) ; for (ul=uplinks;ul;ul=ul->link) { if (ul->peer) continue; s = socket(ul->sa->sa_family,SOCK_STREAM,0); if (s < 0) continue; fcntl(s,F_SETFL,fcntl(s,F_GETFL,0)|O_NONBLOCK); p = new_peer(); p->fd = s; p->id = PL_NOID; p->remsa = malloc(ul->salen); bcopy(ul->sa,p->remsa,ul->salen); p->remsalen = ul->salen; asprintf(&p->txt,"(connect to %s)",ul->txt); if (connect(s,p->remsa,p->remsalen) < 0) { if (errno == EINPROGRESS) { p->id = add_poll_fd(s,&rwtest_always,&rwtest_never,0,&peer_connect_done,p); } else { close_peer(p); continue; } } else { peer_connect_ok(p); } } } static void startup(void) { struct itimerval itv; init_polling(); pcfd = socket(AF_TIMER,SOCK_STREAM,0); itv.it_value.tv_sec = 1; itv.it_value.tv_usec = 0; itv.it_interval.tv_sec = 60; itv.it_interval.tv_usec = 0; write(pcfd,&itv,sizeof(itv)); pcid = add_poll_fd(pcfd,&rwtest_always,&rwtest_never,&pcfd_rd,0,0); peercleanup_id = add_block_fn(&peercleanup,0); peercleanup_needed = 0; } int main(int, char **); int main(int ac, char **av) { init(); handleargs(ac,av); set_defaults(); startup(); return(0); }