/* 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 "protocol.h" #include "pollloop.h" 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; /* * 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; } ; /* * 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 peer_check_connect(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(p->fd); p->fd = -1; peercleanup_needed = 1; 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; if (connect(s,p->remsa,p->remsalen) < 0) { if (errno == EINPROGRESS) { p->id = add_poll_fd(s,&rwtest_always,&rwtest_never,0,&peer_check_connect,p); } else { close(s); p->fd = -1; peercleanup_needed = 1; 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); }