/* This file is in the public domain. */ static const char *RND_DEV = "/dev/urandom"; #define REKEY_N_BASE 0x3fff0000 #define QLIMIT 65536 #define UPLINK_EXPIRE (60*15) #define REACH_EXPIRE (60*10) #define REACH_KILL (60*15) #define TRY_CONNECT_MIN 55 #define TRY_CONNECT_MAX 60 #define UPLINK_EXPIRE_MIN 55 #define UPLINK_EXPIRE_MAX 60 #define REACH_EXPIRE_MIN 55 #define REACH_EXPIRE_MAX 60 #define REACH_GEN_MIN 55 #define REACH_GEN_MAX 60 #define PUBLIC_GEN_MIN 270 #define PUBLIC_GEN_MAX 300 #define IPMAP_GEN_MIN 3500 #define IPMAP_GEN_MAX 3600 #define LISTEN_RETRY_MIN 20 #define LISTEN_RETRY_MAX 30 #include #include #include #include #include #include #include #include #include #include #include #include #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 isource ISOURCE; typedef struct apstate APSTATE; typedef struct cryptinit CRYPTINIT; typedef struct nap NAP; typedef struct pq PQ; typedef struct ipmap IPMAP; typedef struct periodic PERIODIC; typedef struct pendlisten PENDLISTEN; typedef struct aia AIA; typedef struct admin ADMIN; typedef struct verbosity VERBOSITY; typedef struct vfp VFP; typedef struct vbit VBIT; typedef struct vops VOPS; typedef struct vpriv_file VPRIV_FILE; /* * Private data for file-output verbosity. */ struct vpriv_file { VPRIV_FILE *flink; VPRIV_FILE *blink; VERBOSITY *v; char *fn; FILE *f; } ; /* * Private data for a FILE * returned by verb_fopen(). */ struct vfp { unsigned int mask; char *stamp; int stamplen; char *s; int l; } ; /* * A consumer of verbosity. There is one of these for each * verbose-output output stream. */ struct verbosity { VERBOSITY *flink; VERBOSITY *blink; unsigned int mask; const VOPS *ops; void *priv; } ; /* * Method dispatch vector for VERBOSITYs. First arg to most of these * is the priv member of the VERBOSITY in question; the exception is * the find member, which is called to find or create a relevant * VERBOSITY, so of course it doesn't have a generic private-data * input. */ struct vops { VERBOSITY *(*find)(void (*)(const char *, ...), ...); void (*line)(VERBOSITY *, const char *, int); void (*maskchanged)(VERBOSITY *); } ; #define VOPS_INIT(name) { \ &vop_find_##name, \ &vop_line_##name, \ &vop_maskchanged_##name } #define VOP_FIND_ARGS void (*err)(const char *, ...) __attribute__((__unused__)), ... #define VOP_VA_START_ARG err /* * Verbosity bits. There's one of these per type of verbosity. One * also exists for each of certain cases where a single name in the * interface corresponds to multiple bits. */ struct vbit { int alias; unsigned int bit; const char *name; const char *help; int namelen; } ; /* * An administrative control point. This corresponds to a connection * accepted on an accept point resulting from a control line. f is * set only during command processing; it is the FILE * for output in * response to the command. */ struct admin { int fd; OQ oq; char *txt; int id; char *ilb; int ila; int iln; char ilprev; FILE *f; VERBOSITY *verb; } ; /* * This is an address returned by getaddrinfo(). We have our own * structure for these because we want to keep a list of them which we * gradually winnow, and struct addrinfo has no fields available for * us to scribble on. (With the current implementation, we could get * away with using ai_flags for this, but the API does not promise * this.) */ struct aia { AIA *link; struct addrinfo *ai; char *(*gettext)(AIA *); void (*postbind)(AIA *); union { struct { int mode; } local; } ; const char *errmsg; } ; /* * This represents a pending listen, one that was declared with `retry' * but which has not yet succeeded in bind()ing and thus turning into * an ordinary LISTEN. ai is the list of addresses returned by * getaddrinfo (normally just one); flags is the flags on the listen * line, in their LLF_* form. */ struct pendlisten { struct addrinfo *ai0; AIA *addrs; unsigned int flags; void (*onaccept)(LISTEN *, int); PERIODIC *pd; } ; /* * No, chemists, this has nothing to do with iodine. :) This * represents a recurring event. fd is the AF_TIMER socket, id is the * poll ID, and cb is the function to be called, with argument arg. * minsec and maxsec are the minmium and maximum times between events, * in seconds; intervals are randomly distributed between these * limits. ramp is used to start of with frequent calls, with the * interval ramping up to the ones specified; see periodic_rearm(). */ struct periodic { int fd; int id; void (*cb)(void *); void *arg; int minsec; int maxsec; int ramp; } ; /* * State for "delay briefly" operations. s is an AF_TIMER socket. id * is the poll ID. done is a function to be called when the delay is * over, with arg its argument. */ struct nap { int s; int id; void (*done)(void *); void *arg; } ; /* * State for the initial crypto exchange. This is a separate struct so * it can be discarded easily when the exchange is over. * * Rc is actually only 16 bytes long. The array here is dimensioned * [49] to simplify the code in rd_crypto_passive_1 - this way we can * read the whole reply from the connecter into Rc, rather than * creating another buffer for it. */ struct cryptinit { CONNROLE myrole; PEER *p; unsigned char Rc[49]; unsigned char Rl[16]; unsigned char b; unsigned char s[32]; 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. sa/salen describe the bind() arguments used. * LF_WILDCARD means the *, *4, or *6 syntax was used, and we thus * should never advertise it; LF_PUBLIC means that it was declared, or * defaulted, to be advertised. */ struct listen { LISTEN *link; struct sockaddr *sa; int salen; unsigned int flags; #define LF_WILDCARD 0x00000001 #define LF_PUBLIC 0x00000002 char *txt; int fd; int id; void (*onaccept)(LISTEN *, int); } ; /* * Flags used when parsing listen-on lines in the config file. */ #define LLF_RETRY 0x00000001 #define LLF_PUBLIC 0x00000002 #define LLF_PRIVATE 0x00000004 #define LLF_WILDCARD 0x00000008 /* * An uplink point. Each of these is somewhere we want to maintain a * connection to. * * LOCALCONF is true if this uplink was configured in the config file * and thus should never be thrown away. stamp is the timestamp of * last time we heard this uplink advertised. An uplink which we * haven't heard advertised, or which has timed out, but which we * don't want to throw away because it's configured in is represented * with stamp set to zero. Such uplinks are nonexistent for purposes * of generating PUBLIC messages, but still get connection attempts. * * MYSELF indicates that this uplink is an address we ourselves are * listening on and thus should be ignored. * * peer is the PEER we created to connect to this UPLINK. */ struct uplink { UPLINK *link; struct sockaddr *sa; int salen; char *txt; unsigned int flags; #define UF_LOCALCONF 0x00000001 #define UF_MYSELF 0x00000002 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. * * Kd is for the direction in which we write. Kd_ is what the protocol * document calls Kd', ie, Kd in the other direction. up is the * UPLINK for which we created this PEER, or nil if it wasn't created * to connect to an uplink. */ struct peer { char *txt; char *tag; int fd; int id; OQ oq; unsigned char Kd[32]; unsigned char Kd_[32]; int bytes_to_rekey_out; int bytes_to_rekey_in; unsigned int n_rekeys_out; unsigned int n_rekeys_in; ARC4_STATE a4_out; ARC4_STATE a4_in; unsigned char *ibuf; int ifill; int iwant; void (*readdone)(PEER *); unsigned int want; #define WANT_REACH 0x00000001 #define WANT_PUBLIC 0x00000002 #define WANT_IPMAP 0x00000004 int peerid; PQ *pq; PQ **pqt; PERIODIC *gen_reach; PERIODIC *gen_public; PERIODIC *gen_ipmap; } ; /* * An IP address, without a port number. */ struct ipaddr { int af; union { struct in_addr v4; struct in6_addr v6; } ; } ; /* * An IP packet, queued for output. dst is the address we want to send * it to; dstid is the same information as a cloud member ID. Once a * packet is moved from the shared output queue to a PEER-specific * output queue, dst no longer matters, but dstid still does. */ struct pq { PQ *link; IPADDR dst; unsigned char dstid; void *body; int bodylen; } ; /* * An IP<->ID mapping entry. */ struct ipmap { IPMAP *link; IPADDR a; unsigned char id; } ; /* * A boolean indicating whether we should auto-background on startup. */ static int autobg = 0; /* * 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; /* * Count of PEERs generated. Used for tag string creation. */ static unsigned int peerserial = 0; /* * PERIODICs for attempts to (re)connect to uplinks, for expiration of * learned uplinks, and for expiration of reachability data. */ static PERIODIC *pd_reconnect; static PERIODIC *pd_expire_uplink; static PERIODIC *pd_expire_reach; /* * 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; /* * Descriptor open onto RND_DEV. */ static int rndfd; /* * The queue of packets waiting to be sent. */ static PQ *pktq; static PQ **pktqt; /* * Pathname of the tun instance, descriptor open on it, and poll ID for * reading from it. tuntype is the interface type, IFF_POINTOPOINT or * IFF_BROADCAST, or 0 if no type is specified. */ static char *tunpath; static int tunfd; static int tunid; static int tuntype; /* * Block ID for generating output traffic, and boolean indicating it * might have something to do. */ static int sendid; static int want_send; /* * Table of PEERs, indexed by cloud member ID. This allows us to * quickly find the PEER, if any, for a given ID. */ static PEER *peertbl[256]; /* * Cloud member ID reachability data. This is indexed by the member ID * we want to reach. reachtbl is the member ID we can reach it via, * or -1 if we think it's unreachable. reachtime is the time_t at * which reachtbl was last updated. reachdist is the hop count to the * target. (reachtime and reachdist are meaningless for unreachable * targets.) This is updated by REACH packets. * * Note that reachabilities that have timed out are left marked * reachable but with infinite distance for a little while, to help * reduce the need to count to infinity. (Split horizon with poisoned * reverse deals with most but not all cases; broadcasting * unreachables helps with most of the rest.) */ static int reachtbl[256]; static int reachdist[256]; static time_t reachtime[256]; /* * IP <-> ID mapping. This is not 1-to-1; there may be any number of * IPs (zero, one, or more) for a given ID - but at most one ID for an * IP. This is updated by IPMAP packets. */ static IPMAP *ipmaps; /* * Variables for verbose output. * * verbose is bits indicating what, if any, verbosity we're doing. * This is the joint OR of the mask fields of all VERBOSITYs. * verbosities is the list of VERBOSITYs themselves. */ static unsigned int vmask; static VERBOSITY *verbosities; static VBIT vbits[] = { #define VERB_TIMER 0x00000001 { 0, VERB_TIMER, "timer", "AF_TIMER sockets", 0 }, #define VERB_LISTEN 0x00000002 { 0, VERB_LISTEN, "listen", "Listening point setup" }, #define VERB_IO 0x00000004 { 0, VERB_IO, "io", "reading/writing data" }, #define VERB_CRYPTO 0x00000008 { 0, VERB_CRYPTO, "crypto", "initial crypto exchange" }, #define VERB_REKEY 0x00000010 { 0, VERB_REKEY, "rekey", "crypto rekeying" }, #define VERB_PUBLIC 0x00000020 { 0, VERB_PUBLIC, "public", "PUBLIC packet processing" }, #define VERB_IP 0x00000040 { 0, VERB_IP, "ip", "IP packet processing" }, #define VERB_REACH 0x00000080 { 0, VERB_REACH, "reach", "REACH packet processing" }, #define VERB_REACHCHG 0x00000100 { 0, VERB_REACHCHG, "reachchg", "reachability changes" }, #define VERB_IPMAP 0x00000200 { 0, VERB_IPMAP, "ipmap", "IPMAP packet processing" }, #define VERB_IAM 0x00000400 { 0, VERB_IAM, "iam", "IAM packet processing" }, #define VERB_CONN 0x00000800 { 0, VERB_CONN, "conn", "connection setup/teardown" }, #define VERB_ROUTE 0x00001000 { 0, VERB_ROUTE, "route", "IP packet routing" }, #define VERB_CONFIG 0x00002000 { 0, VERB_CONFIG, "config", "configuration processing" }, #define VERB_DUP 0x00004000 { 0, VERB_DUP, "dup", "DUP packet processing" }, { 1, 0x00007fff, "all", "all verbosity" }, { 1, VERB_PUBLIC|VERB_REACH|VERB_IPMAP|VERB_IAM|VERB_DUP, "overhead", "overhead packet processing" }, { 0, 0, 0 } }; /* * The stdout VERBOSITY, or nil if none has been set up yet. */ static VERBOSITY *v_stdout = 0; /* * The list of VPRIV_FILEs. */ static VPRIV_FILE *v_files = 0; /* * Data for a simple PRNG, used for data that does not need to be * cryptographically strong (such as randomizing intervals between * events for which we want to avoid traffic spikes). The fd and id * are used to set wrnudge, to give it a small nudge every few hours * in an attempt to break any possible fluke synchronization. */ #define WRPOOLSIZE 64 static unsigned int wrpool[WRPOOLSIZE]; static unsigned int wrhand; static unsigned int wrfd; static unsigned int wrid; static unsigned int wrnudge; #define Cisspace(c) isspace((unsigned char)(c)) #define Cisalpha(c) isalpha((unsigned char)(c)) #define V(kind,fmt,args...) do { if (vmask & VERB_##kind) verb(VERB_##kind, fmt , ## args); } while (0) #define VS(kinds,fmt,args...) do { if (vmask & (kinds)) verb((kinds), fmt , ## args); } while (0) static void *dequal(const volatile void *arg) { return( ((char *)0) + (((const volatile char *)arg) - ((const volatile char *)0)) ); } static void init_verbose(void) { vmask = 0; verbosities = 0; } static void verb_line(unsigned int mask, const char *s, int l) { VERBOSITY *v; for (v=verbosities;v;v=v->flink) { if (v->mask & mask) (*v->ops->line)(v,s,l); } } static void verb_stamp_prefix(FILE *f, const struct tm *tm, unsigned int sf) { fprintf( f, "%04d-%02d-%02d %02d:%02d:%02d.%02u ", tm->tm_year+1900, tm->tm_mon+1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, sf ); } static void verb(unsigned int, const char *, ...) __attribute__((__format__(__printf__,2,3))); static void verb(unsigned int mask, const char *fmt, ...) { va_list ap; struct timeval nowtv; time_t nowtt; struct tm nowtm_; struct tm *nowtm; char *s; int l; FILE *f; if (! (vmask & mask)) return; gettimeofday(&nowtv,0); nowtt = nowtv.tv_sec; nowtm = localtime_r(&nowtt,&nowtm_); f = open_accum(&s,&l); verb_stamp_prefix(f,nowtm,nowtv.tv_usec/10000); va_start(ap,fmt); vfprintf(f,fmt,ap); va_end(ap); fclose(f); verb_line(mask,s,l); free(s); } static int vfp_w(void *pv, const char *buf, int len) { VFP *p; int i; int n; char *sp; int nl; p = pv; nl = (p->l < 1) || (p->s[p->l-1] == '\n'); n = nl ? 1 : 0; for (i=len-2;i>=0;i--) if (buf[i] == '\n') n ++; n = len + (n * p->stamplen); p->s = realloc(p->s,p->l+n+1); sp = p->s + p->l; for (i=0;istamp,sp,p->stamplen); sp += p->stamplen; } nl = ((*sp++ = buf[i]) == '\n'); } if (sp != p->s+p->l+n) abort(); p->l += n; return(len); } static int vfp_c(void *pv) { VFP *p; int i; int i0; p = pv; i0 = -1; for (i=0;il;i++) { if (i0 < 0) i0 = i; if (p->s[i] == '\n') { verb_line(p->mask,p->s+i0,i-i0); i0 = -1; } } if (i0 >= 0) verb_line(p->mask,p->s+i0,i-i0); free(p->s); free(p->stamp); free(p); return(0); } static FILE *verb_fopen(unsigned int mask) { VFP *p; FILE *f; struct timeval nowtv; time_t nowtt; struct tm nowtm_; struct tm *nowtm; if (! (vmask & mask)) return(0); gettimeofday(&nowtv,0); nowtt = nowtv.tv_sec; nowtm = localtime_r(&nowtt,&nowtm_); p = malloc(sizeof(VFP)); p->mask = mask; f = open_accum(&p->stamp,&p->stamplen); verb_stamp_prefix(f,nowtm,nowtv.tv_usec/10000); fclose(f); p->s = malloc(1); p->l = 0; return(funopen(p,0,&vfp_w,0,&vfp_c)); } static void crack_verbosity( const char *arg, unsigned int *setp, unsigned int *clrp, const char **trailp, void (*err)(const char *, ...) ) { unsigned int set; unsigned int clr; int neg; int i; const char *ap; int l; if (! vbits[0].namelen) { for (i=0;vbits[i].bit;i++) vbits[i].namelen = strlen(vbits[i].name); } set = 0; clr = 0; ap = arg; while (1) { neg = 0; switch (*ap) { case '-': neg = 1; ap ++; break; case '+': ap ++; break; } for (l=0;ap[l]&&Cisalpha(ap[l]);l++) ; if (l == 0) break; do <"found"> { for (i=0;vbits[i].bit;i++) { if ( (vbits[i].namelen == l) && !strncasecmp(vbits[i].name,ap,l) ) { if (neg) { clr |= vbits[i].bit; set &= ~vbits[i].bit; } else { set |= vbits[i].bit; clr &= ~vbits[i].bit; } break <"found">; } } (*err)("unrecognized verbosity keyword `%.*s'",l,ap); abort(); } while (0); ap += l; switch (*ap) { case ',': case ' ': ap ++; break; } } *setp = set; *clrp =clr; *trailp = ap; } static VERBOSITY *new_verbosity(const VOPS *ops) { VERBOSITY *v; v = malloc(sizeof(VERBOSITY)); v->flink = verbosities; v->blink = 0; verbosities = v; if (v->flink) v->flink->blink = v; v->mask = 0; v->ops = ops; v->priv = 0; return(v); } static void drop_verbosity(VERBOSITY *v) { if (v->flink) v->flink->blink = v->blink; if (v->blink) v->blink->flink = v->flink; else verbosities = v->flink; free(v); } static void recompute_vmask(void) { VERBOSITY *v; vmask = 0; for (v=verbosities;v;v=v->flink) vmask |= v->mask; } static VOPS vops_stdout; /* forward */ static VERBOSITY *vop_find_stdout(VOP_FIND_ARGS) { if (! v_stdout) v_stdout = new_verbosity(&vops_stdout); return(v_stdout); } static void vop_line_stdout(VERBOSITY *v __attribute__((__unused__)), const char *buf, int len) { fwrite(buf,1,len,stdout); putchar('\n'); fflush(stdout); } static void vop_maskchanged_stdout(VERBOSITY *v) { if (v->mask == 0) drop_verbosity(v); } static VOPS vops_stdout = VOPS_INIT(stdout); static VOPS vops_file; /* forward */ static VERBOSITY *vop_find_file(VOP_FIND_ARGS) { va_list ap; const char *fn; VPRIV_FILE *p; VERBOSITY *v; FILE *f; va_start(ap,VOP_VA_START_ARG); fn = va_arg(ap,const char *); va_end(ap); for (p=v_files;p;p=p->flink) { if (! strcmp(p->fn,fn)) { return(p->v); } } f = fopen(fn,"w"); if (f == 0) (*err)("%s: open: %s",fn,strerror(errno)); p = malloc(sizeof(VPRIV_FILE)); p->flink = v_files; p->blink = 0; v_files = p; if (p->flink) p->flink->blink = p; v = new_verbosity(&vops_file); p->v = v; p->fn = strdup(fn); p->f = f; v->priv = p; return(v); } static void vop_line_file(VERBOSITY *v, const char *buf, int len) { VPRIV_FILE *p; p = v->priv; fwrite(buf,1,len,p->f); putc('\n',p->f); fflush(p->f); } static void vop_maskchanged_file(VERBOSITY *v) { VPRIV_FILE *p; if (v->mask) return; p = v->priv; fclose(p->f); free(p->fn); drop_verbosity(v); if (p->flink) p->flink->blink = p->blink; if (p->blink) p->blink->flink = p->flink; else v_files = p->flink; } static VOPS vops_file = VOPS_INIT(file); static VOPS vops_admin; /* forward */ static VERBOSITY *vop_find_admin(VOP_FIND_ARGS) { va_list ap; VERBOSITY *v; ADMIN *a; va_start(ap,VOP_VA_START_ARG); a = va_arg(ap,ADMIN *); va_end(ap); if (a->verb) return(a->verb); v = new_verbosity(&vops_admin); a->verb = v; v->priv = a; return(v); } static void vop_line_admin(VERBOSITY *v, const char *buf, int len) { ADMIN *a; a = v->priv; oq_queue_copy(&a->oq,buf,len); oq_queue_point(&a->oq,"\r\n",2); } static void vop_maskchanged_admin(VERBOSITY *v) { ADMIN *a; if (v->mask) return; a = v->priv; a->verb = 0; drop_verbosity(v); } static VOPS vops_admin = VOPS_INIT(admin); static void set_verbose(const char *arg) { unsigned int set; unsigned int clr; const char *trail; VERBOSITY *v; auto void err(const char *, ...) __attribute__((__format__(__printf__,1,2))); auto void err(const char *fmt, ...) { va_list ap; fprintf(stderr,"%s: ",__progname); va_start(ap,fmt); vfprintf(stderr,fmt,ap); va_end(ap); fprintf(stderr,"\n"); exit(1); } if (! strcmp(arg,"?")) { int i; for (i=0;vbits[i].bit;i++) { fprintf(stderr,"%-12s%s\n",vbits[i].name,vbits[i].help); } exit(0); } crack_verbosity(arg,&set,&clr,&trail,&err); if (! *trail) { v = (*vops_stdout.find)(&err); } else if (*trail == '=') { v = (*vops_file.find)(&err,trail+1); } else { err("trailing junk `%s' on verbosity spec",trail); } v->mask = (v->mask & ~clr) | set; (*v->ops->maskchanged)(v); recompute_vmask(); } 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) return; 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 push_isrc(FILE *f, const char *name) { ISOURCE *i; i = malloc(sizeof(ISOURCE)); i->file = strdup(name); 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 nonblocking_fd(int fd) { fcntl(fd,F_SETFL,fcntl(fd,F_GETFL,0)|O_NONBLOCK); } static void random_data(void *buf, int len) { unsigned char *bp; int n; bp = buf; while (len > 0) { n = read(rndfd,bp,len); if (n < 0) { fprintf(stderr,"%s: read from %s: %s\n",__progname,RND_DEV,strerror(errno)); exit(1); } if (n == 0) { fprintf(stderr,"%s: read EOF from %s\n",__progname,RND_DEV); exit(1); } bp += n; len -= n; } } static unsigned int weak_random(void) { if (wrnudge) { unsigned char c; wrnudge = 0; random_data(&c,1); wrpool[wrhand] += c; } if (wrhand == 0) { wrpool[WRPOOLSIZE-1] += wrpool[0]; wrhand = WRPOOLSIZE - 1; } else { wrpool[wrhand-1] += wrpool[wrhand]; wrhand --; } return(wrpool[wrhand]>>1); } static void periodic_rearm(PERIODIC *pd) { struct itimerval itv; unsigned int us; if (pd->ramp) { itv.it_value.tv_sec = pd->ramp; itv.it_value.tv_usec = weak_random() % 1000000; pd->ramp <<= 1; if (pd->ramp >= pd->maxsec) pd->ramp = 0; } else { us = weak_random() % ((pd->maxsec - pd->minsec) * 1000000); itv.it_value.tv_sec = pd->minsec + (us / 1000000); itv.it_value.tv_usec = us % 1000000; } itv.it_interval.tv_sec = 0; itv.it_interval.tv_usec = 0; write(pd->fd,&itv,sizeof(itv)); } static void periodic_tick(void *pdv) { PERIODIC *pd; struct timersock_event ev; pd = pdv; while (read(pd->fd,&ev,sizeof(ev)) > 0) ; periodic_rearm(pd); (*pd->cb)(pd->arg); } static void kill_periodic(PERIODIC *pd) { remove_poll_id(pd->id); close(pd->fd); free(pd); } static PERIODIC *start_periodic( unsigned int sec_min, unsigned int sec_max, void (*cb)(void *), void *cbarg ) { PERIODIC *pd; pd = malloc(sizeof(PERIODIC)); pd->fd = socket(AF_TIMER,SOCK_STREAM,0); nonblocking_fd(pd->fd); pd->id = add_poll_fd(pd->fd,&rwtest_always,&rwtest_never,&periodic_tick,0,pd); pd->minsec = sec_min; pd->maxsec = sec_max; pd->cb = cb; pd->arg = cbarg; pd->ramp = 1; V(TIMER,"AF_TIMER %d start_periodic %d..%d %p(%p)",pd->fd,sec_min,sec_max,(void *)cb,cbarg); periodic_rearm(pd); return(pd); } static int do_bind(int s, const struct sockaddr *sa, socklen_t salen) { int e; const struct sockaddr_un *sun; struct stat stb; char *t; int l; if (bind(s,sa,salen) >= 0) return(0); e = errno; if ((errno != EADDRINUSE) || (sa->sa_family != AF_LOCAL)) return(-1); sun = (const void *) sa; l = salen - (sizeof(*sun) - sizeof(sun->sun_path)); t = malloc(l+1); bcopy(&sun->sun_path[0],t,l); t[l] = '\0'; if ( (stat(t,&stb) < 0) || ((stb.st_mode & S_IFMT) != S_IFSOCK) || (unlink(t) < 0) ) { free(t); errno = e; return(-1); } free(t); return(bind(s,sa,salen)); } static LISTEN *try_setup_listen( AIA **addrp, unsigned int flags, const char *textarg, void (*onaccept)(LISTEN *, int) ) { AIA *a; AIA **ap; int fd; char *txt; LISTEN *l; #define SNIPOUT() do { *ap = a->link; free(a); } while (0) #define NEXTADDR() do { ap = &a->link; } while (0) V(LISTEN,"try_setup_listen starting"); ap = addrp; while ((a = *ap)) { fd = -1; txt = (*a->gettext)(a); if (! txt) { if (textarg) { V(LISTEN,"try_setup_listen failing: can't get text"); config_err("can't get text [for %s]: %s",textarg,a->errmsg); } V(LISTEN,"try_setup_listen: can't get text form (af %d - %s), dropping address",a->ai->ai_addr->sa_family,a->errmsg); SNIPOUT(); continue; } fd = socket(a->ai->ai_family,a->ai->ai_socktype,a->ai->ai_protocol); if (fd < 0) continue; if (do_bind(fd,a->ai->ai_addr,a->ai->ai_addrlen) < 0) { if (textarg) { V(LISTEN,"try_setup_listen failing: bind failed"); config_err("bind %s [for %s]: %s",txt,textarg,strerror(errno)); } free(txt); close(fd); NEXTADDR(); continue; } (*a->postbind)(a); if (listen(fd,10) < 0) { if (textarg) { V(LISTEN,"try_setup_listen failing: listen failed"); config_err("listen %s [for %s]: %s",txt,textarg,strerror(errno)); } free(txt); close(fd); NEXTADDR(); continue; } nonblocking_fd(fd); l = malloc(sizeof(LISTEN)); l->sa = malloc(a->ai->ai_addrlen); bcopy(a->ai->ai_addr,l->sa,a->ai->ai_addrlen); l->salen = a->ai->ai_addrlen; if (flags & LLF_WILDCARD) flags = (flags & ~LLF_PUBLIC) | LLF_PRIVATE; if ((flags & (LLF_PRIVATE|LLF_PUBLIC)) == 0) { switch (l->sa->sa_family) { case AF_INET: { unsigned int a; a = ntohl(((const struct sockaddr_in *)l->sa)->sin_addr.s_addr); flags |= ( ((a & 0xff000000) == 0x0a000000) || ((a & 0xfff00000) == 0xac100000) || ((a & 0xffff0000) == 0xc0a80000) ) ? LLF_PRIVATE : LLF_PUBLIC; } break; case AF_INET6: { const struct in6_addr *a; a = &((const struct sockaddr_in6 *)l->sa)->sin6_addr; flags |= ( IN6_IS_ADDR_LINKLOCAL(a) || IN6_IS_ADDR_SITELOCAL(a) ) ? LLF_PRIVATE : LLF_PUBLIC; } break; default: flags |= LLF_PRIVATE; break; } } l->flags = ((flags & LLF_WILDCARD) ? LF_WILDCARD : 0) | ((flags & LLF_PUBLIC) ? LF_PUBLIC : 0); l->txt = txt; l->fd = fd; l->onaccept = onaccept; l->link = listens; listens = l; V(LISTEN,"try_setup_listen worked (%s)",txt); SNIPOUT(); return(l); } V(LISTEN,"try_setup_listen failing: out of addresses"); return(0); #undef SNIPOUT #undef NEXTADDR } static void print_sa_txt(const struct sockaddr *sa, int salen, FILE *f) { switch (sa->sa_family) { default: abort(); break; case AF_LOCAL: fprintf( f, "%.*s", (int) (sa->sa_len - (sizeof(struct sockaddr_un) - sizeof(((struct sockaddr_un *)0)->sun_path))), &((const struct sockaddr_un *)sa)->sun_path[0] ); break; case AF_INET: case AF_INET6: { 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) { fprintf(f,"[getnameinfo (af %d): ",sa->sa_family); if (e == EAI_SYSTEM) { fprintf(f," %s]",strerror(errno)); } else { fprintf(f," %s]",gai_strerror(e)); } } else { fprintf(f,"%s/%s",&hstr[0],&sstr[0]); } } break; } } static UPLINK *new_uplink(const struct sockaddr *sa, int salen) { FILE *f; UPLINK *u; u = malloc(sizeof(UPLINK)); u->sa = malloc(salen); bcopy(sa,u->sa,salen); u->salen = salen; f = open_accum(&u->txt,0); print_sa_txt(sa,salen,f); fclose(f); u->flags = 0; u->stamp = 0; u->peer = 0; u->link = uplinks; uplinks = u; return(u); } static void raise_fdlimit(void) { struct rlimit rl; getrlimit(RLIMIT_NOFILE,&rl); rl.rlim_cur = rl.rlim_max; setrlimit(RLIMIT_NOFILE,&rl); } static void init(void) { init_polling(); init_verbose(); ck = 0; ncipaddrs = 0; cipaddrs = 0; ignore_public = -1; listens = 0; uplinks = 0; npeers = 0; apeers = 0; peers = 0; tunpath = 0; raise_fdlimit(); } static void set_defaults(void) { if (ignore_public < 0) { fprintf(stderr,"%s: no `type' line configured\n",__progname); exit(1); } if (tunpath == 0) { fprintf(stderr,"%s: no tun device configured\n",__progname); exit(1); } } static void gen_tag(PEER *p) { int i; peerserial ++; asprintf(&p->tag,"%d",peerserial); for (i=0;p->tag[i];i++) { switch (p->tag[i]) { case '0': p->tag[i] = 'f'; break; case '1': p->tag[i] = 's'; break; case '2': p->tag[i] = 'm'; break; case '3': p->tag[i] = 'k'; break; case '4': p->tag[i] = 'g'; break; case '5': p->tag[i] = 't'; break; case '6': p->tag[i] = 'v'; break; case '7': p->tag[i] = 'j'; break; case '8': p->tag[i] = 'b'; break; case '9': p->tag[i] = 'r'; break; } } } static PEER *new_peer(void) { PEER *p; if (npeers >= apeers) peers = realloc(peers,(apeers=npeers+1)*sizeof(PEER *)); p = malloc(sizeof(PEER)); gen_tag(p); p->txt = 0; p->fd = -1; p->id = PL_NOID; oq_init(&p->oq); arc4_init(&p->a4_out); arc4_init(&p->a4_in); p->ibuf = malloc(1+2+65535); p->ifill = 0; p->peerid = -1; p->pq = 0; p->pqt = &p->pq; p->gen_reach = 0; p->gen_public = 0; p->gen_ipmap = 0; 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-1;i>=0;i--) { p = peers[i]; if (p->fd >= 0) continue; free(p->tag); free(p->txt); oq_flush(&p->oq); free(p->ibuf); while (p->pq) { PQ *q; q = p->pq; p->pq = q->link; free(q); } if (p->gen_reach) kill_periodic(p->gen_reach); if (p->gen_public) kill_periodic(p->gen_public); if (p->gen_ipmap) kill_periodic(p->gen_ipmap); free(p); npeers --; if (i < npeers) peers[i] = peers[npeers]; } return(BLOCK_LOOP); } static void set_flood(unsigned int bit) { int i; for (i=npeers-1;i>=0;i--) peers[i]->want |= bit; want_send = 1; } static void close_peer(PEER *p) { UPLINK *u; int i; V(CONN,"closing peer %s",p->tag); if ((p->peerid >= 0) && (peertbl[p->peerid] == p)) { peertbl[p->peerid] = 0; for (i=256-1;i>=0;i--) { if (reachtbl[i] == p->peerid) reachdist[i] = REACH_INFINITY; } set_flood(WANT_REACH); } if (p->id != PL_NOID) remove_poll_id(p->id); close(p->fd); p->fd = -1; for (u=uplinks;u;u=u->link) if (u->peer == p) u->peer = 0; peercleanup_needed = 1; } static void close_cryptinit(CRYPTINIT *ci) { remove_poll_id(ci->p->id); close_peer(ci->p); free(ci); } static int wr_oq(OQ *q, int fd, const char *txt) { int n; n = oq_writev(q,fd); if (n < 0) { switch (errno) { case EINTR: case EWOULDBLOCK: return(0); } fprintf(stderr,"%s: write to %s: %s\n",__progname,txt,strerror(errno)); return(-1); } V(IO,"wrote %d from oq",n); oq_dropdata(q,n); return(0); } static void wr_cryptinit(void *civ) { CRYPTINIT *ci; ci = civ; if (wr_oq(&ci->p->oq,ci->p->fd,ci->p->txt) < 0) close_cryptinit(ci); } static int wtest_cryptinit(void *civ) { return(oq_nonempty(&((CRYPTINIT *)civ)->p->oq)); } static int rd_crypto_1(CRYPTINIT *ci, unsigned char *Rv, int len) { int n; n = read(ci->p->fd,&Rv[ci->n],len-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 < len) return(1); return(0); } static void wr_rd(void *arg __attribute__((__unused__))) { struct timersock_event ev; while (read(wrfd,&ev,sizeof(ev)) > 0) ; wrnudge = 1; } static void init_wrandom(void) { unsigned int r; int i; struct itimerval itv; random_data(&r,sizeof(r)); for (i=64-1;i>=0;i--) { wrpool[i] = r; r = (r * 1103515245U) + 12345U; } wrhand = 0; wrnudge = 0; for (i=1000;i>0;i--) weak_random(); wrfd = socket(AF_TIMER,SOCK_STREAM,0); nonblocking_fd(wrfd); itv.it_interval.tv_sec = 3600 * 6; itv.it_interval.tv_usec = 0; itv.it_value = itv.it_interval; write(wrfd,&itv,sizeof(itv)); wrid = add_poll_fd(wrfd,&rwtest_always,&rwtest_never,&wr_rd,0,0); V(TIMER,"AF_TIMER %d for weak random nudging",wrfd); } static unsigned int crc32(const void *data, ...) #define CRC32TERM ((const void *)0) { unsigned int h; static const unsigned int crctbl[256] = { #include "crctable" }; va_list ap; int len; const unsigned char *dp; h = 0x31bb29b0; va_start(ap,data); while (data) { len = va_arg(ap,int); for (dp=data;len>0;len--) { h = (h >> 8) ^ crctbl[(h^*dp++)&0xff]; } data = va_arg(ap,const void *); } va_end(ap); return(h); } static void hash(unsigned char *rv, const void *data, ...) #define HASHTERM ((const void *)0) { void *hh; va_list ap; int len; hh = sha256_init(); va_start(ap,data); while (data) { len = va_arg(ap,int); sha256_process_bytes(hh,data,len); data = va_arg(ap,const void *); } va_end(ap); sha256_result(hh,rv); } static void nap_done(void *nv) { NAP *n; n = nv; remove_poll_id(n->id); (*n->done)(n->arg); close(n->s); free(n); } static void start_nap(unsigned int sec, unsigned int usec, void (*done)(void *), void *arg) { int s; NAP *n; struct itimerval itv; s = socket(AF_TIMER,SOCK_STREAM,0); n = malloc(sizeof(NAP)); n->s = s; n->done = done; n->arg = arg; n->id = add_poll_fd(s,&rwtest_always,&rwtest_never,&nap_done,0,n); itv.it_value.tv_sec = sec; itv.it_value.tv_usec = usec; itv.it_interval.tv_sec = 0; itv.it_interval.tv_usec = 0; write(s,&itv,sizeof(itv)); V(TIMER,"AF_TIMER %d for nap %u.%06u %p(%p)",s,sec,usec,(void *)done,arg); } static void cryptinit_gen_b_s(CRYPTINIT *ci) { unsigned int t; unsigned char b; unsigned char xs[32]; unsigned char s[36]; unsigned char hk[32]; int sl; int i; int j; void *rj1; void *rj2; unsigned char key1[32]; unsigned char B; static const unsigned char zero[32] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }; unsigned char data2[32]; if (vmask & VERB_CRYPTO) { int i; FILE *f; f = verb_fopen(VERB_CRYPTO); fprintf(f,"Rl = %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x, " "Rc = %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x\n", ci->Rl[ 0], ci->Rl[ 1], ci->Rl[ 2], ci->Rl[ 3], ci->Rl[ 4], ci->Rl[ 5], ci->Rl[ 6], ci->Rl[ 7], ci->Rl[ 8], ci->Rl[ 9], ci->Rl[10], ci->Rl[11], ci->Rl[12], ci->Rl[13], ci->Rl[14], ci->Rl[15], ci->Rc[ 0], ci->Rc[ 1], ci->Rc[ 2], ci->Rc[ 3], ci->Rc[ 4], ci->Rc[ 5], ci->Rc[ 6], ci->Rc[ 7], ci->Rc[ 8], ci->Rc[ 9], ci->Rc[10], ci->Rc[11], ci->Rc[12], ci->Rc[13], ci->Rc[14], ci->Rc[15]); fprintf(f,"K = "); for (i=0;iRl[0],16,ck,cklen,&ci->Rc[0],16,ck,cklen,CRC32TERM); b = ((t >> 24) ^ (t >> 16) ^ (t >> 8) ^ t) & 0xff; for (i=0;i<32;i++) xs[i] = (b + i) & 0xff; sl = 36; bcopy(&ci->Rl[0],&s[0],16); s[16] = (t >> 24) & 0xff; s[17] = (t >> 16) & 0xff; s[18] = (t >> 8) & 0xff; s[19] = t & 0xff; bcopy(&ci->Rc[0],&s[20],16); hash(&hk[0],ck,cklen,HASHTERM); rj1 = rijndael_init(8,8); rj2 = rijndael_init(8,8); rijndael_set_key(rj2,&hk[0]); for (i=0;i<64;i++) { B = i; hash(&key1[0],ck,cklen,&B,1,&s[0],sl,&B,1,ck,cklen,HASHTERM); rijndael_set_key(rj1,&key1[0]); rijndael_encrypt(rj1,&zero[0],&data2[0]); for (j=32-1;j>=0;j--) data2[j] ^= xs[j]; rijndael_encrypt(rj2,&data2[0],&s[0]); sl = 32; } ci->b = b; bcopy(&s[0],&ci->s[0],32); rijndael_done(rj1); rijndael_done(rj2); V(CRYPTO,"b = %02x, s = %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", ci->b, ci->s[ 0], ci->s[ 1], ci->s[ 2], ci->s[ 3], ci->s[ 4], ci->s[ 5], ci->s[ 6], ci->s[ 7], ci->s[ 8], ci->s[ 9], ci->s[10], ci->s[11], ci->s[12], ci->s[13], ci->s[14], ci->s[15], ci->s[16], ci->s[17], ci->s[18], ci->s[19], ci->s[20], ci->s[21], ci->s[22], ci->s[23], ci->s[24], ci->s[25], ci->s[26], ci->s[27], ci->s[28], ci->s[29], ci->s[30], ci->s[31]); } static void cryptinit_gen_Kd(CRYPTINIT *ci, unsigned char Kl2c[32], unsigned char Kc2l[32]) { hash(&Kl2c[0],"->",2,ck,cklen,&ci->Rl[0],16,"->",2,&ci->Rc[0],16,ck,cklen,"->",2,HASHTERM); hash(&Kc2l[0],"<-",2,ck,cklen,&ci->Rl[0],16,"<-",2,&ci->Rc[0],16,ck,cklen,"<-",2,HASHTERM); } static unsigned int min_bits(unsigned int v) { int n; if (v == 0) return(0); n = 1; if (v & 0xffff0000) { v >>= 16; n += 16; } if (v & 0xff00) { v >>= 8; n += 8; } if (v & 0xf0) { v >>= 4; n += 4; } if (v & 0xc) { v >>= 2; n += 2; } if (v & 0x2) { /* v >>= 1; unnecessary */ n += 1; } return(n); } static void gen_a4_key( ARC4_STATE *a4, const unsigned char *Kd, const unsigned char *Kd_, const unsigned char *Rk, unsigned int n_rekeys ) { unsigned char t0[96]; unsigned char *t; int tlen; unsigned char k[256]; int klen; void *rj; int i; int B; unsigned char SERbuf[8]; unsigned char *SER; int SERlen; unsigned char h[32]; SER = &SERbuf[8]; SERlen = 0; while (n_rekeys) { *--SER = n_rekeys & 0xff; n_rekeys >>= 8; SERlen ++; } rj = rijndael_init(8,8); rijndael_set_key(rj,Kd); bcopy(Kd,&t0[0],32); bcopy(Rk,&t0[32],32); bcopy(Kd_,&t0[64],32); t = &t0[0]; tlen = 96; klen = 0; for (i=0;i<8;i++) { B = i; hash(&h[0],t,tlen,Rk,32,&B,1,SER,SERlen,t,tlen,HASHTERM); rijndael_encrypt(rj,&h[0],&k[klen]); t = &k[klen]; tlen = 32; klen += 32; } rijndael_done(rj); arc4_setkey(a4,&k[0],klen,65536); } static void send_rekey(PEER *p) { unsigned char Rk[32]; int L; unsigned char *P1; unsigned char P2[32]; int N; unsigned char enc[32]; void *rj; unsigned int Lmask; V(REKEY,"sending rekey for %s",p->txt); random_data(&Rk[0],32); L = 8; // XXX select intelligently P1 = malloc(L); random_data(P1,L); random_data(&P2[0],32); N = min_bits(L); if ((N < 0) || (N > 15)) abort(); P2[0] = (P2[0] & 0x0f) | (N << 4); Lmask = (~0U) << N; P2[31] = ((P2[31] & Lmask) | (L & ~Lmask)) & 0xff; P2[30] = ((P2[30] & (Lmask >> 8)) | ((L & ~Lmask) >> 8)) & 0xff; rj = rijndael_init(8,8); rijndael_set_key(rj,&p->Kd[0]); rijndael_encrypt(rj,&Rk[0],&enc[0]); oq_queue_copy(&p->oq,&enc[0],32); rijndael_encrypt(rj,&P2[0],&enc[0]); oq_queue_copy(&p->oq,&enc[0],32); rijndael_done(rj); oq_queue_free(&p->oq,P1,L); gen_a4_key(&p->a4_out,&p->Kd[0],&p->Kd_[0],&Rk[0],p->n_rekeys_out); p->bytes_to_rekey_out = REKEY_N_BASE + (P2[1] << 8) + P2[2]; p->n_rekeys_out ++; } static int wtest_peer(void *pv) { return(oq_nonempty(&((PEER *)pv)->oq)); } static void rd_peer_rekey_2(PEER *p) { p->ifill = 0; } static void rd_peer_rekey_1(PEER *p) { void *rj; unsigned char Rk[32]; unsigned char P2[32]; int L; int N; rj = rijndael_init(8,8); rijndael_set_key(rj,&p->Kd_[0]); rijndael_decrypt(rj,p->ibuf,&Rk[0]); rijndael_decrypt(rj,p->ibuf+32,&P2[0]); N = P2[0] >> 4; L = ((P2[30] * 256U) | P2[31]) & ~((~0U) << N); gen_a4_key(&p->a4_in,&p->Kd_[0],&p->Kd[0],&Rk[0],p->n_rekeys_in); p->bytes_to_rekey_in = REKEY_N_BASE + (P2[1] << 8) + P2[2]; p->n_rekeys_in ++; if (L == 0) { rd_peer_rekey_2(p); } else { p->ifill = 1; p->iwant = L + 1; p->readdone = &rd_peer_rekey_2; } } static void set_quietdrop(int fd) { int on; on = 1; setsockopt(fd,IPPROTO_TCP,TCP_QUIETDROP,&on,sizeof(on)); } static void send_encrypted(PEER *p, const void *data, int len) { unsigned char *obuf; if (len < 1) return; obuf = malloc(len); arc4_crypt(&p->a4_out,data,len,obuf); oq_queue_free(&p->oq,obuf,len); p->bytes_to_rekey_out -= len; } static void send_postpacket(PEER *p) { if (p->bytes_to_rekey_out < 1) send_rekey(p); } static int addr_port_match(const struct sockaddr *a, const struct sockaddr *b) { if (a->sa_family != b->sa_family) return(0); switch (a->sa_family) { default: abort(); break; case AF_INET: return( ( ((const struct sockaddr_in *)a)->sin_addr.s_addr == ((const struct sockaddr_in *)b)->sin_addr.s_addr ) && ( ((const struct sockaddr_in *)a)->sin_port == ((const struct sockaddr_in *)b)->sin_port ) ); break; case AF_INET6: return( ( ((const struct sockaddr_in6 *)a)->sin6_port == ((const struct sockaddr_in6 *)b)->sin6_port ) && IN6_ARE_ADDR_EQUAL( &((const struct sockaddr_in6 *)a)->sin6_addr, &((const struct sockaddr_in6 *)b)->sin6_addr ) ); break; } } static int ipaddr_match(const IPADDR *a, const IPADDR *b) { if (a->af != b->af) return(0); switch (a->af) { default: abort(); break; case AF_INET: return(a->v4.s_addr==b->v4.s_addr); break; case AF_INET6: return(IN6_ARE_ADDR_EQUAL(&a->v6,&b->v6)); break; } } static void learn_public(const struct sockaddr *sa, int salen, time_t stamp) { UPLINK *u; if (vmask & VERB_PUBLIC) { char obuf[256]; FILE *f; f = verb_fopen(VERB_PUBLIC); fprintf(f,"learn_public: sa="); switch (sa->sa_family) { default: fprintf(f,"AF %d",sa->sa_family); break; case AF_INET: #define sin ((const struct sockaddr_in *)sa) fprintf(f,"%s/%d",inet_ntop(AF_INET,&sin->sin_addr,&obuf[0],sizeof(obuf)),ntohs(sin->sin_port)); #undef sin break; case AF_INET6: #define sin6 ((const struct sockaddr_in6 *)sa) fprintf(f,"%s/%d",inet_ntop(AF_INET6,&sin6->sin6_addr,&obuf[0],sizeof(obuf)),ntohs(sin6->sin6_port)); #undef sin6 break; } fprintf(f," stamp=%lu\n",(unsigned long int)stamp); fclose(f); } for (u=uplinks;u;u=u->link) { if (addr_port_match(u->sa,sa)) { u->stamp = stamp; V(PUBLIC,"updating existing"); return; } } V(PUBLIC,"new"); new_uplink(sa,salen)->stamp = stamp; } static void my_ip_pkt(PEER *p, int addrtype, const unsigned char *data, int len) { struct sockaddr sa; struct iovec iov[2]; if (len <= 3) abort(); V(IP,"my IP packet: addrtype %d, data %02x%02x%02x%02x%02x%02x...%02x%02x", addrtype, data[0],data[1],data[2],data[3],data[4],data[5], data[len-2],data[len-1]); switch (addrtype) { default: fprintf(stderr,"%s: ignoring AF-%d packet from %s\n",__progname,addrtype,p?p->txt:"myself"); return; break; case IPTYPE_v4: sa.sa_family = AF_INET; break; case IPTYPE_v6: sa.sa_family = AF_INET6; break; } #define L (offsetof(struct sockaddr,sa_len) + sizeof(sa.sa_len)) #define F (offsetof(struct sockaddr,sa_family) + sizeof(sa.sa_family)) sa.sa_len = (L > F) ? L : F; #undef L #undef F iov[0].iov_base = (void *) &sa; iov[0].iov_len = sa.sa_len; iov[1].iov_base = dequal(data); iov[1].iov_len = len; writev(tunfd,&iov[0],2); } static void peer_pkt_ip(PEER *p, const unsigned char *data, int len) { PEER *nexthop; unsigned char hdr[6]; if (p->peerid < 0) return; if (len <= 3) { fprintf(stderr,"%s: ignoring runt IP from peer %s (len %d)\n",__progname,p->txt,len); return; } V(IP,"peer_pkt_ip: hopcount=%d id=%d dsttype=%d len=%d",data[0],data[1],data[2],len-3); if (data[1] == cid) { my_ip_pkt(p,data[2],data+3,len-3); return; } else { if (data[0] == 0) return; if ( (reachtbl[data[1]] < 0) || (reachdist[data[1]] >= REACH_INFINITY) ) return; nexthop = peertbl[reachtbl[data[1]]]; if (! nexthop) return; if (oq_qlen(&nexthop->oq) > QLIMIT) return; if (len > 65535) abort(); V(IP,"forwarding IP from %s to %s",p->txt,nexthop->txt); hdr[0] = PKT_IP; hdr[1] = len >> 8; hdr[2] = len & 0xff; hdr[3] = data[0] - 1; hdr[4] = data[1]; hdr[5] = data[2]; send_encrypted(nexthop,&hdr[0],6); send_encrypted(nexthop,data+3,len-3); send_postpacket(nexthop); } } static int canreach( unsigned char tgt, unsigned char dist, unsigned char via, time_t stamp ) { int effdist; V(REACH,"canreach tgt=%d dist=%d via=%d stamp=%lu " "reachtbl[]=%d reachdist[]=%d reachtime[]=%lu", tgt, dist, via, (unsigned long int)stamp, reachtbl[tgt], reachdist[tgt], (unsigned long int)reachtime[tgt]); if (tgt == cid) { V(REACH,"ignoring (our ID)"); return(0); } do <"done"> { do <"replace"> { if (reachtbl[tgt] < 0) break <"replace">; effdist = reachdist[tgt] + ((stamp-reachtime[tgt]) >> 6); if ( (dist < effdist) || (via == reachtbl[tgt]) ) break <"replace">; break <"done">; } while (0); if (dist >= REACH_INFINITY) { if (reachtbl[tgt] != -1) { reachtbl[tgt] = -1; V(REACH,"returning 1 (unreachable)"); V(REACHCHG,"%d unreachable",tgt); return(1); } } else { reachtime[tgt] = stamp; if ( (reachtbl[tgt] != via) || (reachdist[tgt] != dist) ) { reachtbl[tgt] = via; reachdist[tgt] = dist; V(REACH,"returning 1 (reachable)"); V(REACHCHG,"%d reachable, dist %d via %d",tgt,dist,via); return(1); } } } while (0); V(REACH,"returning 0"); return(0); } static void peer_pkt_reach(PEER *p, const unsigned char *data, int len) { int o; time_t now; int wantflood; if (p->peerid < 0) { V(REACH,"peer_pkt_reach: from %s, ignored (peerid < 0)",p->txt); return; } V(REACH,"peer_pkt_reach: from %s",p->txt); time(&now); o = 0; wantflood = 0; while (1) { if (o > len) abort(); if (o == len) break; if (o+2 > len) { fprintf(stderr,"%s: peer %s sent REACH that overruns packet (%d > %d)\n",__progname,p->txt,o+2,len); close_peer(p); return; } if (canreach(data[o],data[o+1]+1,p->peerid,now)) wantflood = 1; o += 2; } if (wantflood) { V(REACH,"flooding REACH"); set_flood(WANT_REACH); } } static void peer_pkt_public(PEER *p, const unsigned char *data, int len) { __label__ throwto; int o; void overrun(int o) { if (o <= len) return; fprintf(stderr,"%s: peer %s sent PUBLIC that overruns packet (%d > %d)\n",__progname,p->txt,o,len); goto throwto; } if (0) { throwto:; close_peer(p); return; } if (ignore_public) { V(PUBLIC,"peer_pkt_public: from %s, ignored (ignore_public)",p->txt); return; } if (p->peerid < 0) { V(PUBLIC,"peer_pkt_public: from %s, ignored (peerid < 0)",p->txt); return; } V(PUBLIC,"peer_pkt_public: from %s",p->txt); o = 0; while (1) { if (o > len) abort(); if (o == len) break; switch (data[o]) { default: fprintf(stderr,"%s: unknown IP type %d in PUBLIC from %s\n",__progname,data[o],p->txt); close_peer(p); return; break; case IPTYPE_v4: { struct sockaddr_in sin; overrun(o+11); sin.sin_len = sizeof(sin); sin.sin_family = AF_INET; bcopy(data+o+1,&sin.sin_addr,4); sin.sin_port = htons((data[o+5]*256)+data[o+6]); learn_public( (void *)&sin, sizeof(sin), (data[o+7] * 0x01000000) + (data[o+8] * 0x00010000) + (data[o+9] * 0x00000100) + (data[o+10] * 0x00000001) ); o += 11; } break; case IPTYPE_v6: { struct sockaddr_in6 sin6; overrun(o+23); sin6.sin6_len = sizeof(sin6); sin6.sin6_family = AF_INET6; bcopy(data+o+1,&sin6.sin6_addr,16); sin6.sin6_port = htons((data[o+17]*256)+data[o+18]); learn_public( (void *)&sin6, sizeof(sin6), (data[o+19] * 0x01000000) + (data[o+20] * 0x00010000) + (data[o+21] * 0x00000100) + (data[o+22] * 0x00000001) ); o += 23; } break; } } } static IPMAP *new_ipmap(unsigned char id, const IPADDR *a) { IPMAP *m; m = malloc(sizeof(IPMAP)); m->a = *a; m->id = id; return(m); } static IPMAP *replace_ipmap(unsigned char id, const IPADDR *a, IPMAP *list) { IPMAP *m; for (m=list;m;m=m->link) { if (ipaddr_match(&m->a,a)) { m->id = id; return(list); } } m = new_ipmap(id,a); m->link = list; return(m); } static IPMAP *save_ipmaps(unsigned char id, IPMAP *list) { IPMAP *m; IPMAP **mp; mp = &ipmaps; while ((m = *mp)) { if (m->id == id) { *mp = m->link; m->link = list; list = m; } else { mp = &m->link; } } return(list); } /* * It is important that the ID be the most significant comparison key * here, because there is code that depends on sort_ipmaps to group * all mappings with the same ID together. */ static int ipmap_lt(IPMAP *a, IPMAP *b) { int i; if (a->id != b->id) return(a->id < b->id); if (a->a.af != b->a.af) return(a->a.af < b->a.af); switch (a->a.af) { default: abort(); break; case AF_INET: return(a->a.v4.s_addr < b->a.v4.s_addr); break; case AF_INET6: for (i=16-1;i>=0;i--) { if (a->a.v6.s6_addr[i] != b->a.v6.s6_addr[i]) { return(a->a.v6.s6_addr[i] < b->a.v6.s6_addr[i]); } } return(0); break; } } static IPMAP *sort_ipmaps(IPMAP *list) { IPMAP *a; IPMAP *b; IPMAP *t; IPMAP **lt; if (!list || !list->link) return(list); a = 0; b = 0; while (list) { t = list; list = t->link; t->link = a; a = b; b = t; } a = sort_ipmaps(a); b = sort_ipmaps(b); lt = &list; while (a || b) { if (!b || (a && ipmap_lt(a,b))) { t = a; a = a->link; } else { t = b; b = b ->link; } *lt = t; lt = &t->link; } *lt = 0; return(list); } static IPMAP *prepend_ipmaps(IPMAP *m, IPMAP *list) { IPMAP *t; while (m) { t = m; m = m->link; t->link = list; list = t; } return(list); } static void peer_pkt_ipmap(PEER *p, const unsigned char *data, int len) { __label__ throwto; int o; IPADDR a; unsigned char sawid[256/8]; unsigned char id; IPMAP *old; IPMAP *new; IPMAP *om; IPMAP *nm; char obuf[256]; void fail(void) { goto throwto; } void overrun(int o) { if (o > len) { fprintf(stderr,"%s: packet overrun in IPMAP from %s (%d > %d)\n",__progname,p->txt,o,len); fail(); } } if (p->peerid < 0) { V(IPMAP,"peer_pkt_ipmap: from %s, ignored (peerid < 0)",p->txt); return; } V(IPMAP,"peer_pkt_ipmap: from %s",p->txt); old = 0; new = 0; if (0) { throwto:; close_peer(p); ipmaps = prepend_ipmaps(old,ipmaps); return; } bzero(&sawid[0],sizeof(sawid)); o = 0; while (1) { if (o > len) abort(); if (o == len) break; id = data[o]; overrun(o+2); switch (data[o+1]) { default: fprintf(stderr,"%s: unknown IP type %d in IPMAP from %s\n",__progname,data[o+1],p->txt); fail(); break; case IPTYPE_v4: overrun(o+6); a.af = AF_INET; bcopy(data+o+2,&a.v4,4); o += 6; break; case IPTYPE_v6: overrun(o+18); a.af = AF_INET6; bcopy(data+o+2,&a.v6,16); o += 18; break; } if (id == cid) continue; if (! (sawid[id>>3] & (1 << (id & 7)))) { sawid[id>>3] |= 1 << (id & 7); V(IPMAP,"saving maps for %d",id); old = save_ipmaps(id,old); } V(IPMAP,"mapping %s to %d",inet_ntop(a.af,&a.v4,&obuf[0],sizeof(obuf)),id); new = replace_ipmap(id,&a,new); } old = sort_ipmaps(old); new = sort_ipmaps(new); if (vmask & VERB_IPMAP) { IPMAP *tm; FILE *f; f = verb_fopen(VERB_IPMAP); fprintf(f,"old:"); for (tm=old;tm;tm=tm->link) { fprintf(f," %s->%d",inet_ntop(tm->a.af,&tm->a.v4,&obuf[0],sizeof(obuf)),tm->id); } fprintf(f,"\nnew:"); for (tm=new;tm;tm=tm->link) { fprintf(f," %s->%d",inet_ntop(tm->a.af,&tm->a.v4,&obuf[0],sizeof(obuf)),tm->id); } fclose(f); } do <"match"> { do <"mismatch"> { for (nm=new,om=old;nm;nm=nm->link,om=om->link) { if (! om) break <"mismatch">; if (om->id != nm->id) break <"mismatch">; if (om->a.af != nm->a.af) break <"mismatch">; switch (om->a.af) { default: abort(); break; case AF_INET: if (om->a.v4.s_addr != nm->a.v4.s_addr) break <"mismatch">; break; case AF_INET6: if (! IN6_ARE_ADDR_EQUAL(&om->a.v6,&nm->a.v6)) break <"mismatch">; break; } } if (om) break <"mismatch">; break <"match">; } while (0); V(IPMAP,"mismatch, flooding IPMAP"); set_flood(WANT_IPMAP); } while (0); V(IPMAP,"done, saving new"); ipmaps = prepend_ipmaps(new,ipmaps); } static void gen_reach(void *pv) { ((PEER *)pv)->want |= WANT_REACH; want_send = 1; } static void gen_public(void *pv) { ((PEER *)pv)->want |= WANT_PUBLIC; want_send = 1; } static void gen_ipmap(void *pv) { ((PEER *)pv)->want |= WANT_IPMAP; want_send = 1; } static void peer_pkt_iam(PEER *p, const unsigned char *data, int len) { if (len != 1) { V(IAM,"peer %s sent IAM with length %d",p->txt,len); fprintf(stderr,"%s: peer %s sent IAM with length %d\n",__progname,p->txt,len); close_peer(p); } if (p->peerid >= 0) { V(IAM,"peer %s sent another IAM",p->txt); fprintf(stderr,"%s: peer %s sent another IAM\n",__progname,p->txt); close_peer(p); } if (data[0] == cid) { V(IAM,"peer %s claims to be us",p->txt); fprintf(stderr,"%s: peer %s claims to be us\n",__progname,p->txt); set_quietdrop(p->fd); close_peer(p); } V(IAM,"peer_pkt_iam: from %s, id %d",p->txt,data[0]); if (peertbl[data[0]] && (cid < data[0])) { unsigned char opkt[4]; UPLINK *u; PEER *p2; opkt[0] = PKT_DUP; opkt[1] = 0; opkt[2] = 1; opkt[3] = DUP_DUPLICATE; p2 = peertbl[data[0]]; send_encrypted(p2,&opkt[0],4); p2->peerid = -1; for (u=uplinks;u;u=u->link) { if (u->peer == p2) u->peer = p; } } p->peerid = data[0]; peertbl[data[0]] = p; p->gen_reach = start_periodic(REACH_GEN_MIN,REACH_GEN_MAX,&gen_reach,p); p->gen_public = start_periodic(PUBLIC_GEN_MIN,PUBLIC_GEN_MAX,&gen_public,p); p->gen_ipmap = start_periodic(IPMAP_GEN_MIN,IPMAP_GEN_MAX,&gen_ipmap,p); p->want |= WANT_REACH | WANT_PUBLIC | WANT_IPMAP; want_send = 1; } static void peer_pkt_dup(PEER *p, const unsigned char *data, int len) { int i; PEER *p2; UPLINK *u; if (len != 1) { V(DUP,"peer %s sent DUP with length %d",p->txt,len); fprintf(stderr,"%s: peer %s sent DUP with length %d\n",__progname,p->txt,len); close_peer(p); } if (p->peerid < 0) { V(DUP,"peer %s sent DUP before IAM",p->txt); fprintf(stderr,"%s: peer %s sent DUP before IAM\n",__progname,p->txt); set_quietdrop(p->fd); close_peer(p); } switch (data[0]) { default: V(IAM,"peer %s sent bad DUP sub-opcode %02x",p->txt,data[0]); fprintf(stderr,"%s: peer %s sent bad DUP sub-opcode %02x",__progname,p->txt,data[0]); set_quietdrop(p->fd); close_peer(p); break; case DUP_DUPLICATE: for (i=npeers-1;i>=0;i--) { p2 = peers[i]; if (p2 == p) continue; if (p2->peerid != p->peerid) continue; V(DUP,"peer_pkt_dup: closing %s, switching to %s",p->txt,p2->txt); for (u=uplinks;u;u=u->link) { if (u->peer == p) u->peer = p2; } if (peertbl[p->peerid] == p) peertbl[p->peerid] = p2; close_peer(p); return; } V(DUP,"peer_pkt_dup: couldn't find a replacement"); close_peer(p); break; } want_send = 1; } static void peer_packet(PEER *p, unsigned char type, const unsigned char *data, int len) { switch (type) { case PKT_IGNORE: break; case PKT_IP: peer_pkt_ip(p,data,len); break; case PKT_REACH: peer_pkt_reach(p,data,len); break; case PKT_PUBLIC: peer_pkt_public(p,data,len); break; case PKT_IPMAP: peer_pkt_ipmap(p,data,len); break; case PKT_IAM: peer_pkt_iam(p,data,len); break; case PKT_DUP: peer_pkt_dup(p,data,len); break; default: fprintf(stderr,"%s: received bad packet type 0x%02x from %s\n",__progname,type,p->txt); close_peer(p); break; } } static void rd_peer_packet_2(PEER *p) { arc4_crypt(&p->a4_in,&p->ibuf[3],p->iwant-3,&p->ibuf[3]); p->bytes_to_rekey_in -= p->iwant; peer_packet(p,p->ibuf[0],&p->ibuf[3],p->iwant-3); p->ifill = 0; } static void rd_peer_packet_1(PEER *p) { int len; arc4_crypt(&p->a4_in,&p->ibuf[0],3,&p->ibuf[0]); len = (p->ibuf[1] * 256) + p->ibuf[2]; if (len == 0) { peer_packet(p,p->ibuf[0],0,0); } else { p->iwant = len + 3; p->readdone = &rd_peer_packet_2; } } static void rd_peer(void *pv) { PEER *p; int n; p = pv; while (1) { if (p->fd < 0) return; if (p->ifill == 0) { if (p->bytes_to_rekey_in < 1) { p->iwant = 64; p->readdone = &rd_peer_rekey_1; } else { p->iwant = 3; p->readdone = &rd_peer_packet_1; } } if (p->ifill >= p->iwant) abort(); n = read(p->fd,p->ibuf+p->ifill,p->iwant-p->ifill); if (n < 0) { switch (errno) { case EINTR: case EWOULDBLOCK: return; } V(IO,"read from %s: %s",p->txt,strerror(errno)); close_peer(p); return; } if (n == 0) { V(IO,"read EOF from %s",p->txt); close_peer(p); return; } V(IO,"read %d from %s",n,p->txt); p->ifill += n; if (p->ifill >= p->iwant) (*p->readdone)(p); } } static void wr_peer(void *pv) { PEER *p; p = pv; if (p->fd < 0) return; if (wr_oq(&p->oq,p->fd,p->txt) < 0) close_peer(p); } static void send_iam(PEER *p) { unsigned char opkt[4]; V(IAM,"sending IAM to %s",p->txt); opkt[0] = PKT_IAM; opkt[1] = 0; opkt[2] = 1; opkt[3] = cid; send_encrypted(p,&opkt[0],4); send_postpacket(p); } static void start_peer(PEER *p) { V(CRYPTO,"crypto done for %s",p->txt); p->n_rekeys_out = 0; p->n_rekeys_in = 0; p->bytes_to_rekey_in = 0; p->want = 0; want_send = 1; send_rekey(p); send_iam(p); p->id = add_poll_fd(p->fd,&rwtest_always,&wtest_peer,&rd_peer,&wr_peer,p); } static void start_peer_v(void *pv) { start_peer(pv); } static void rd_crypto_active_1(void *civ) { CRYPTINIT *ci; PEER *p; ci = civ; if (rd_crypto_1(ci,&ci->Rl[0],16)) return; p = ci->p; random_data(&ci->Rc[0],16); cryptinit_gen_b_s(ci); oq_queue_copy(&p->oq,&ci->Rc[0],16); oq_queue_copy(&p->oq,&ci->b,1); oq_queue_copy(&p->oq,&ci->s[0],32); cryptinit_gen_Kd(ci,&p->Kd_[0],&p->Kd[0]); remove_poll_id(p->id); free(ci); start_peer(p); } static void rd_crypto_passive_1(void *civ) { CRYPTINIT *ci; PEER *p; ci = civ; if (rd_crypto_1(ci,&ci->Rc[0],49)) return; p = ci->p; cryptinit_gen_b_s(ci); if ( (ci->b != ci->Rc[16]) || bcmp(&ci->s[0],&ci->Rc[17],32) ) { set_quietdrop(p->fd); close_cryptinit(ci); return; } cryptinit_gen_Kd(ci,&p->Kd[0],&p->Kd_[0]); remove_poll_id(p->id); free(ci); start_nap(5,0,&start_peer_v,p); } static void peer_start_crypto(CRYPTINIT *ci) { V(CRYPTO,"starting crypto on %s",ci->p->txt); 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 char *get_sockaddrs_txt(int fd, const char *what) { struct sockaddr_storage lss; socklen_t ll; struct sockaddr_storage rss; socklen_t rl; FILE *f; char *rv; ll = sizeof(lss); if (getsockname(fd,(struct sockaddr *)&lss,&ll) < 0) { fprintf(stderr,"%s: getsockname for %s: %s\n",__progname,what,strerror(errno)); return(0); } rl = sizeof(rss); if (getpeername(fd,(struct sockaddr *)&rss,&rl) < 0) { fprintf(stderr,"%s: getpeername for %s: %s\n",__progname,what,strerror(errno)); return(0); } f = open_accum(&rv,0); print_sa_txt((const struct sockaddr *)&lss,ll,f); fprintf(f," <-> "); print_sa_txt((const struct sockaddr *)&rss,rl,f); fclose(f); return(rv); } static int peer_get_sockaddrs(PEER *p) { char *t; t = get_sockaddrs_txt(p->fd,p->txt); if (t) { free(p->txt); p->txt = t; return(0); } else { return(-1); } } static void peer_connect_ok(PEER *p) { CRYPTINIT *ci; if (peer_get_sockaddrs(p) < 0) { VS(VERB_CONN|VERB_IO,"peer_get_sockaddrs failed"); close_peer(p); return; } VS(VERB_CONN|VERB_IO,"peer_get_sockaddrs worked: %s",p->txt); 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 ) { V(CONN,"%s failed",p->txt); close_peer(p); return; } V(CONN,"%s worked",p->txt); peer_connect_ok(p); } static void try_reconnect(void *arg __attribute__((__unused__))) { UPLINK *ul; PEER *p; int s; FILE *vf; for (ul=uplinks;ul;ul=ul->link) { if (ul->peer) continue; if (ul->flags & UF_MYSELF) continue; s = socket(ul->sa->sa_family,SOCK_STREAM,0); if (s < 0) continue; nonblocking_fd(s); p = new_peer(); p->fd = s; p->id = PL_NOID; asprintf(&p->txt,"connect to %s",ul->txt); vf = verb_fopen(VERB_CONN); if (vf) fprintf(vf,"trying to connect to %s...",ul->txt); if (connect(s,ul->sa,ul->salen) < 0) { if (errno == EINPROGRESS) { p->id = add_poll_fd(s,&rwtest_never,&rwtest_always,0,&peer_connect_done,p); if (vf) fprintf(vf,"pending"); } else { if (vf) fprintf(vf,"failed (%s)",strerror(errno)); fclose(vf); close_peer(p); continue; } } else { if (vf) fprintf(vf,"worked"); peer_connect_ok(p); } ul->peer = p; if (vf) fclose(vf); } } static void open_random(void) { rndfd = open(RND_DEV,O_RDONLY,0); if (rndfd < 0) { fprintf(stderr,"%s: %s: %s\n",__progname,RND_DEV,strerror(errno)); exit(1); } } static void open_tun(void) { int v; tunfd = open(tunpath,O_RDWR,0); if (tunfd < 0) { fprintf(stderr,"%s: %s: %s\n",__progname,tunpath,strerror(errno)); exit(1); } if (tuntype) { v = tuntype; if (ioctl(tunfd,TUNSIFMODE,&v) < 0) { fprintf(stderr,"%s: TUNSIFMODE on %s: %s\n",__progname,tunpath,strerror(errno)); exit(1); } } v = 1; if (ioctl(tunfd,TUNSLMODE,&v) < 0) { fprintf(stderr,"%s: TUNSLMODE on %s: %s\n",__progname,tunpath,strerror(errno)); exit(1); } nonblocking_fd(tunfd); } static void tun_rd(void *arg __attribute__((__unused__))) { unsigned char pkt[2048]; int len; int n; struct sockaddr_storage ss; PQ *pq; while (1) { len = read(tunfd,&pkt[0],sizeof(pkt)); if (len < 0) { switch (errno) { case EINTR: case EWOULDBLOCK: return; } fprintf(stderr,"%s: %s read: %s\n",__progname,tunpath,strerror(errno)); exit(1); } if (len == 0) { fprintf(stderr,"%s: %s read: %s\n",__progname,tunpath,strerror(errno)); exit(1); } V(IO,"tun_rd got len %d",len); n = offsetof(struct sockaddr_storage,ss_len) + sizeof(ss.ss_len); bcopy(&pkt[0],&ss,n); if ((ss.ss_len < n) || (ss.ss_len > sizeof(ss))) { fprintf(stderr,"%s: %s packet has impossible dst len %d\n",__progname,tunpath,ss.ss_len); continue; } if (ss.ss_len > len) { fprintf(stderr,"%s: %s: sockaddr overruns packet (%d > %d)\n",__progname,tunpath,ss.ss_len,len); continue; } bcopy(&pkt[0],&ss,ss.ss_len); pq = malloc(sizeof(PQ)+len-ss.ss_len); switch (ss.ss_family) { default: fprintf(stderr,"%s: %s packet has unknown AF %d\n",__progname,tunpath,ss.ss_family); continue; break; case AF_INET: pq->dst.af = AF_INET; pq->dst.v4 = ((const struct sockaddr_in *)&ss)->sin_addr; break; case AF_INET6: pq->dst.af = AF_INET6; pq->dst.v6 = ((const struct sockaddr_in6 *)&ss)->sin6_addr; break; } pq->body = (void *)(pq+1); pq->bodylen = len - ss.ss_len; bcopy(&pkt[ss.ss_len],pq->body,pq->bodylen); pq->link = 0; *pktqt = pq; pktqt = &pq->link; want_send = 1; } } static void set_pkt_length(unsigned char *pktbase, int pktoff) { int l; l = pktoff - 3; if (l < 1) abort(); pktbase[1] = l >> 8; pktbase[2] = l & 0xff; } static void send_reach(PEER *p) { unsigned char pkt[512]; int pp; int maxpp; int i; void flush(void) { set_pkt_length(&pkt[0],pp); send_encrypted(p,&pkt[0],pp); send_postpacket(p); pp = 0; } void add_target(unsigned char target, unsigned char distance) { if (maxpp-pp < 2) flush(); if (pp == 0) { pkt[pp++] = PKT_REACH; pp += 2; } pkt[pp++] = target; pkt[pp++] = distance; } if (p->peerid < 0) { V(REACH,"not sending REACH to %s (peerid < 0)",p->txt); return; } V(REACH,"sending REACH to %s",p->txt); pp = 0; maxpp = sizeof(pkt); for (i=256-1;i>=0;i--) { if (i == cid) { add_target(i,0); } else if (reachtbl[i] == p->peerid) { add_target(i,REACH_INFINITY); } else if (reachtbl[i] >= 0) { add_target(i,reachdist[i]); } } if (pp > 0) flush(); } static void send_public(PEER *p) { unsigned char pkt[512]; int pp; int maxpp; time_t now; LISTEN *l; UPLINK *u; void flush(void) { set_pkt_length(&pkt[0],pp); send_encrypted(p,&pkt[0],pp); send_postpacket(p); pp = 0; } void add_target(const struct sockaddr *a, time_t stamp) { int port; if (maxpp-pp < 1+16+4+4) flush(); if (pp == 0) { pkt[pp++] = PKT_PUBLIC; pp += 2; } switch (a->sa_family) { default: abort(); break; case AF_INET: pkt[pp++] = IPTYPE_v4; bcopy(&((const struct sockaddr_in *)a)->sin_addr,pkt+pp,4); pp += 4; port = ntohs(((const struct sockaddr_in *)a)->sin_port); pkt[pp++] = port >> 8; pkt[pp++] = port & 0xff; break; case AF_INET6: pkt[pp++] = IPTYPE_v6; bcopy(&((const struct sockaddr_in6 *)a)->sin6_addr,pkt+pp,16); pp += 16; port = ntohs(((const struct sockaddr_in6 *)a)->sin6_port); pkt[pp++] = port >> 8; pkt[pp++] = port & 0xff; break; } pkt[pp++] = (stamp >> 24) & 0xff; pkt[pp++] = (stamp >> 16) & 0xff; pkt[pp++] = (stamp >> 8) & 0xff; pkt[pp++] = stamp & 0xff; } if (p->peerid < 0) { V(PUBLIC,"not sending PUBLIC to %s (peerid < 0)",p->txt); return; } V(PUBLIC,"sending PUBLIC to %s",p->txt); pp = 0; maxpp = sizeof(pkt); now = 0; for (l=listens;l;l=l->link) { if ((l->flags & (LF_PUBLIC|LF_WILDCARD)) != LF_PUBLIC) continue; if (now == 0) time(&now); add_target(l->sa,now); } for (u=uplinks;u;u=u->link) { if (u->stamp && !(u->flags & UF_MYSELF)) { add_target(u->sa,u->stamp); } } if (pp > 0) flush(); } static void send_ipmap(PEER *p) { unsigned char pkt[512]; int pp; int maxpp; int lastid; int lastidswitch; IPMAP *m; void add_mapping(unsigned char id, const IPADDR *a) { if (id != lastid) { lastid = id; lastidswitch = pp; } if (pp == 0) { pkt[pp++] = PKT_IPMAP; pp += 2; } if (pp+18 > maxpp) { if (lastidswitch == 0) { fprintf(stderr,"%s: too many IPs for ID %d\n",__progname,lastid); exit(1); } set_pkt_length(&pkt[0],lastidswitch); send_encrypted(p,&pkt[0],lastidswitch); send_postpacket(p); if (lastidswitch < pp) bcopy(&pkt[lastidswitch],&pkt[1],pp-lastidswitch); pp -= lastidswitch - 1; lastidswitch = 0; } pkt[pp++] = id; switch (a->af) { default: abort(); break; case AF_INET: pkt[pp++] = IPTYPE_v4; bcopy(&a->v4,pkt+pp,4); pp += 4; break; case AF_INET6: pkt[pp++] = IPTYPE_v6; bcopy(&a->v6,pkt+pp,16); pp += 16; break; } } if (p->peerid < 0) { V(IPMAP,"not sending IPMAP to %s (peerid < 0)",p->txt); return; } V(IPMAP,"sending IPMAP to %s",p->txt); ipmaps = sort_ipmaps(ipmaps); lastid = -1; pp = 0; maxpp = sizeof(pkt); for (m=ipmaps;m;m=m->link) { add_mapping(m->id,&m->a); } if (pp > 0) { set_pkt_length(&pkt[0],pp); send_encrypted(p,&pkt[0],pp); send_postpacket(p); } } static int send_packet(PEER *p) { PQ *q; unsigned char hdr[6]; int l; if (p->peerid < 0) { V(IP,"not sending IP to %s (peerid < 0)",p->txt); return(-1); } V(IP,"sending IP to %s",p->txt); q = p->pq; p->pq = q->link; if (! p->pq) p->pqt = &p->pq; l = q->bodylen + 3; if (l > 65535) { fprintf(stderr,"%s: dropping huge packet (length %d)\n",__progname,q->bodylen); free(q); return(0); } hdr[0] = PKT_IP; hdr[1] = l >> 8; hdr[2] = l & 0xff; hdr[3] = REACH_INFINITY; hdr[4] = q->dstid; switch (q->dst.af) { default: abort(); break; case AF_INET: hdr[5] = IPTYPE_v4; break; case AF_INET6: hdr[5] = IPTYPE_v6; break; } V(IP,"IP packet: hopcount=%d id=%d dsttype=%d len=%d",hdr[3],hdr[4],hdr[5],q->bodylen); V(IP,"IP packet: addrtype %d, data %02x%02x%02x%02x%02x%02x...%02x%02x",hdr[5], ((unsigned char *)q->body)[0], ((unsigned char *)q->body)[1], ((unsigned char *)q->body)[2], ((unsigned char *)q->body)[3], ((unsigned char *)q->body)[4], ((unsigned char *)q->body)[5], ((unsigned char *)q->body)[q->bodylen-2], ((unsigned char *)q->body)[q->bodylen-1]); send_encrypted(p,&hdr[0],6); send_encrypted(p,q->body,q->bodylen); send_postpacket(p); free(q); return(0); } static void route_pq(PQ *q) { IPMAP *m; PEER *p; if (vmask & (VERB_IP|VERB_ROUTE)) { FILE *f; char astr[256]; f = verb_fopen(VERB_IP|VERB_ROUTE); fprintf(f,"route_pq, dst "); switch (q->dst.af) { default: fprintf(f,"af %d",q->dst.af); break; case AF_INET: fprintf(f,"%s",inet_ntop(AF_INET,&q->dst.v4,&astr[0],sizeof(astr))); break; case AF_INET6: fprintf(f,"%s",inet_ntop(AF_INET6,&q->dst.v6,&astr[0],sizeof(astr))); break; } fclose(f); } do <"fail"> { for (m=ipmaps;m;m=m->link) { if (ipaddr_match(&q->dst,&m->a)) { VS(VERB_IP|VERB_ROUTE,"match, id %d",m->id); if (m->id == cid) { int atype; switch (q->dst.af) { default: abort(); break; case AF_INET: atype = IPTYPE_v4; break; case AF_INET6: atype = IPTYPE_v6; break; } my_ip_pkt(0,atype,q->body,q->bodylen); free(q); return; } if ( (reachtbl[m->id] < 0) || (reachdist[m->id] >= REACH_INFINITY) ) break <"fail">; p = peertbl[reachtbl[m->id]]; if (! p) break <"fail">; q->dstid = m->id; q->link = 0; *p->pqt = q; p->pqt = &q->link; } } return; } while (0); free(q); } static int send_output(void *arg __attribute__((__unused__))) { PEER *p; int i; if (! want_send) return(BLOCK_NIL); want_send = 0; for (i=npeers-1;i>=0;i--) { p = peers[i]; if (oq_qlen(&p->oq) > QLIMIT) continue; if (p->want & WANT_REACH) send_reach(p); if (p->want & WANT_PUBLIC) send_public(p); if (p->want & WANT_IPMAP) send_ipmap(p); p->want = 0; } while (pktq) { PQ *q; q = pktq; pktq = q->link; route_pq(q); } pktqt = &pktq; for (i=npeers-1;i>=0;i--) { p = peers[i]; while ((oq_qlen(&p->oq) < QLIMIT) && p->pq && (send_packet(p) >= 0)) ; } return(BLOCK_LOOP); } static void listen_accept(void *lv) { LISTEN *l; int new; struct sockaddr_storage ss; socklen_t sslen; l = lv; sslen = sizeof(ss); new = accept(l->fd,(void *)&ss,&sslen); if (new < 0) { fprintf(stderr,"%s: accept on %s: %s\n",__progname,l->txt,strerror(errno)); return; } nonblocking_fd(new); V(CONN,"accepted on %s",l->txt); (*l->onaccept)(l,new); } static void start_listener(LISTEN *l) { l->id = add_poll_fd(l->fd,&rwtest_always,&rwtest_never,&listen_accept,0,l); new_uplink(l->sa,l->salen)->flags |= UF_MYSELF; } static void start_accepts(void) { LISTEN *l; for (l=listens;l;l=l->link) start_listener(l); } static void expire_uplinks(void *arg __attribute__((__unused__))) { time_t now; UPLINK *u; UPLINK **up; int changed; time(&now); changed = 0; up = &uplinks; while ((u = *up)) { if (u->stamp && (u->stamp+UPLINK_EXPIRE < now)) { if (u->flags & (UF_LOCALCONF|UF_MYSELF)) { u->stamp = 0; } else { *up = u->link; free(u->sa); free(u->txt); free(u); changed = 1; continue; } } up = &u->link; } if (changed) set_flood(WANT_PUBLIC); } static void expire_reaches(void *arg __attribute__((__unused__))) { time_t now; int changed; int i; time(&now); changed = 0; for (i=256-1;i>=0;i--) { if (reachtbl[i] < 0) continue; if (reachdist[i] >= REACH_INFINITY) { if (reachtime[i] + REACH_KILL < now) { reachtbl[i] = -1; changed = 1; } continue; } if (reachtime[i] + REACH_EXPIRE < now) { reachdist[i] = REACH_INFINITY; changed = 1; } } if (changed) set_flood(WANT_REACH); } static void init_ipmap(void) { int i; IPMAP *m; ipmaps = 0; for (i=ncipaddrs-1;i>=0;i--) { m = new_ipmap(cid,cipaddrs[i]); m->link = ipmaps; ipmaps = m; } } static void confline__at(const char *s) { FILE *f; f = fopen(s,"r"); if (f == 0) config_err("can't open %s: %s",s,strerror(errno)); push_isrc(f,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 retry_listen(void *plv) { PENDLISTEN *pl; LISTEN *l; pl = plv; l = try_setup_listen(&pl->addrs,pl->flags,0,pl->onaccept); if (l) { start_listener(l); if (pl->addrs == 0) { kill_periodic(pl->pd); freeaddrinfo(pl->ai0); free(pl); } } } static int capcustom_stars(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(0); } free(s->addr); s->addr = 0; return(1); } static void onaccept_listen(LISTEN *l, int fd) { PEER *p; CRYPTINIT *ci; p = new_peer(); p->fd = fd; p->id = PL_NOID; asprintf(&p->txt,"(accept from %s)",l->txt); if (peer_get_sockaddrs(p) < 0) { V(CONN,"peer_get_sockaddrs failed"); close_peer(p); return; } V(CONN,"peer_get_sockaddrs worked: %s",p->txt); ci = malloc(sizeof(CRYPTINIT)); ci->myrole = CR_PASSIVE; ci->p = p; peer_start_crypto(ci); } static char *gettext_inet(AIA *a) { int e; char *txt; char hstr[NI_MAXHOST]; char sstr[NI_MAXSERV]; e = getnameinfo(a->ai->ai_addr,a->ai->ai_addrlen,&hstr[0],NI_MAXHOST,&sstr[0],NI_MAXSERV,NI_NUMERICHOST|NI_NUMERICSERV); if (e) { a->errmsg = gai_strerror(e); return(0); } asprintf(&txt,"%s/%s",&hstr[0],&sstr[0]); return(txt); } static void postbind_nil(AIA *a __attribute__((__unused__))) { } static AIA *aia_for_addrinfo(struct addrinfo *ai) { AIA *list; AIA *a; list = 0; for (;ai;ai=ai->ai_next) { a = malloc(sizeof(AIA)); a->ai = ai; a->gettext = &gettext_inet; a->postbind = &postbind_nil; a->link = list; list = a; } return(list); } static void confline_listen(const char *s) { int i; int i0; char *aps; unsigned int flags; struct addrinfo *ai0; AIA *ailist; void capcustom_listen(APSTATE *s) { if (capcustom_stars(s)) flags |= LLF_WILDCARD; } for (i=0;s[i]&&Cisspace(s[i]);i++) ; if (! s[i]) config_err("no argument given on listen line"); for (i0=i;s[i]&&!Cisspace(s[i]);i++) ; aps = malloc((i-i0)+1); bcopy(s+i0,aps,i-i0); aps[i-i0] = '\0'; V(CONFIG,"confline_listen: aps %s",aps); flags = 0; while (1) { for (;s[i]&&Cisspace(s[i]);i++) ; if (! s[i]) break; for (i0=i;s[i]&&!Cisspace(s[i]);i++) ; V(CONFIG,"confline_listen: option %.*s",i-i0,s+i0); if (((i-i0) == 5) && !strncasecmp(s+i0,"retry",5)) { flags |= LLF_RETRY; } else if (((i-i0) == 6) && !strncasecmp(s+i0,"public",6)) { flags = (flags & ~LLF_PRIVATE) | LLF_PUBLIC; } else if (((i-i0) == 7) && !strncasecmp(s+i0,"private",7)) { flags = (flags & ~LLF_PUBLIC) | LLF_PRIVATE; } else { free(aps); config_err("unrecognized listen option `%.*s'",i-i0,s+i0); } } if (vmask & VERB_CONFIG) { FILE *f; f = verb_fopen(VERB_CONFIG); fprintf(f,"confline_listen: cracking %s, flags",aps); if (flags == 0) fprintf(f," none"); if (flags & LLF_RETRY) fprintf(f," RETRY"); if (flags & LLF_PUBLIC) fprintf(f," PUBLIC"); if (flags & LLF_PRIVATE) fprintf(f," PRIVATE"); if (flags & LLF_WILDCARD) fprintf(f," WILDCARD"); fclose(f); } ai0 = crack_addr_port(aps,AI_PASSIVE,&capcustom_listen); ailist = aia_for_addrinfo(ai0); if (flags & LLF_RETRY) { PENDLISTEN *pl; V(CONFIG,"confline_listen starting retry"); pl = malloc(sizeof(PENDLISTEN)); pl->ai0 = ai0; pl->addrs = ailist; pl->flags = flags; pl->onaccept = &onaccept_listen; pl->pd = start_periodic(LISTEN_RETRY_MIN,LISTEN_RETRY_MAX,&retry_listen,pl); } else { V(CONFIG,"confline_listen doing setup now"); while (ailist) try_setup_listen(&ailist,flags,s,&onaccept_listen); freeaddrinfo(ai0); } } static void confline_peer(const char *s) { struct addrinfo *ai0; struct addrinfo *ai; ai0 = crack_addr_port(s,0,0); for (ai=ai0;ai;ai=ai->ai_next) { new_uplink(ai->ai_addr,ai->ai_addrlen)->flags |= UF_LOCALCONF; } freeaddrinfo(ai0); } static void confline_tun(const char *s) { long int n; int i; int k0; int kl; int t0; char *t; char *ep; for (i=0;s[i]&&Cisspace(s[i]);i++) ; k0 = i; for (;s[i]&&!Cisspace(s[i]);i++) ; kl = i - k0; for (;s[i]&&Cisspace(s[i]);i++) ; t0 = i; t = malloc(kl+1); bcopy(s+k0,t,kl); t[kl] = '\0'; free(tunpath); n = strtol(t,&ep,0); if (ep == t) { tunpath = strdup(t); } else { if (* ep) { tunpath = strdup(t); } else { asprintf(&tunpath,"/dev/tun%ld",n); } } free(t); if (! s[t0]) { tuntype = 0; } else if (! strcasecmp(s+t0,"broadcast")) { tuntype = IFF_BROADCAST; } else if (! strcasecmp(s+t0,"pointopoint")) { tuntype = IFF_POINTOPOINT; } else { config_err("bad tun type `%s'",s+t0); } } static void dump_status(FILE *f) { int i; time_t now; time(&now); fprintf(f,"ID %d, %s PUBLIC packets\n",cid,ignore_public?"ignores":"listens to"); if (listens == 0) { fprintf(f,"No listening points\n"); } else { LISTEN *l; for (l=listens;l;l=l->link) { fprintf(f,"Listening: fd %d, %s",l->fd,l->txt); switch (l->flags & (LF_WILDCARD|LF_PUBLIC)) { case 0: break; case LF_WILDCARD: fprintf(f," (wildcard)"); break; case LF_PUBLIC: fprintf(f," (advertised)"); break; case LF_WILDCARD|LF_PUBLIC: fprintf(f," (IMPOSSIBLE advertised wildcard)"); break; } fprintf(f,"\n"); } } if (uplinks == 0) { fprintf(f,"No uplinks\n"); } else { UPLINK *u; for (u=uplinks;u;u=u->link) { fprintf(f,"Uplink: %s",u->txt); switch (u->flags & (UF_LOCALCONF|UF_MYSELF)) { case 0: fprintf(f," (learned)"); break; case UF_LOCALCONF: fprintf(f," (locally configured)"); break; case UF_MYSELF: fprintf(f," (myself)"); break; case UF_LOCALCONF|UF_MYSELF: fprintf(f," (configured to talk to myself?)"); break; } fprintf(f,"\n"); } } fprintf(f,"Active peer count: %d\n",npeers); for (i=0;itag,p->txt,p->fd,p->peerid); } fprintf(f,"tun path %s, fd %d\n",tunpath,tunfd); for (i=0;i<256;i++) if (peertbl[i]) fprintf(f,"ID %d -> peer %s\n",i,peertbl[i]->tag); for (i=0;i<256;i++) { if (reachtbl[i] >= 0) { fprintf(f,"reach: %d via %d distance %d age %lu\n", i,reachtbl[i],reachdist[i],(unsigned long int)(now-reachtime[i])); } } if (ipmaps == 0) { fprintf(f,"No IP mapping information\n"); } else { IPMAP *m; char astr[256]; ipmaps = sort_ipmaps(ipmaps); for (m=ipmaps;m;m=m->link) { fprintf(f,"IP map: %d : ",m->id); switch (m->a.af) { default: fprintf(f,"af %d",m->a.af); break; case AF_INET: fprintf(f,"%s",inet_ntop(AF_INET,&m->a.v4,&astr[0],sizeof(astr))); break; case AF_INET6: fprintf(f,"%s",inet_ntop(AF_INET6,&m->a.v6,&astr[0],sizeof(astr))); break; } fprintf(f,"\n"); } } } static int cleanup_admin(void *av) { ADMIN *a; a = av; remove_block_id(a->id); oq_flush(&a->oq); free(a->txt); free(a); return(BLOCK_NIL); } static void close_admin(ADMIN *a) { if (a->fd < 0) return; close(a->fd); if (a->f) { fclose(a->f); a->f = 0; } if (a->verb) drop_verbosity(a->verb); a->fd = -1; remove_poll_id(a->id); a->id = add_block_fn(&cleanup_admin,a); } static int wtest_admin(void *av) { return(oq_nonempty(&((ADMIN *)av)->oq)); } static void wr_admin(void *av) { ADMIN *a; a = av; if (wr_oq(&a->oq,a->fd,a->txt) < 0) close_admin(a); } static void admin_prompt(ADMIN *a) { if (a->verb) return; oq_queue_point(&a->oq,"cloud> ",7); } static int admin_out_w(void *av, const char *buf, int len) { ADMIN *a; int i0; int i; void flush(void) { if (i0 < 0) return; oq_queue_copy(&a->oq,buf+i0,i-i0); i0 = -1; } a = av; if (a->fd >= 0) { i0 = -1; for (i=0;ioq,"\r\n",2); } else { if (i0 < 0) i0 = i; } } flush(); } return(len); } static void admin_verbosity(ADMIN *a, const char *arg) { __label__ err_throw; unsigned int set; unsigned int clr; const char *trail; unsigned int m; int i; const char *sep; char *errmsg; VERBOSITY *v; void err(const char *fmt, ...) { va_list ap; char *s; va_start(ap,fmt); vasprintf(&s,fmt,ap); va_end(ap); errmsg = s; goto err_throw; } if (0) { err_throw:; fprintf(a->f,"%s\n",errmsg); free(errmsg); return; } while (*arg && Cisspace(*arg)) arg ++; if (! *arg) { if (! a->verb) { fprintf(a->f,"No verbosity.\n"); return; } m = a->verb->mask; sep = ""; for (i=0;vbits[i].bit;i++) { if (vbits[i].alias) continue; if (m & vbits[i].bit) { m &= ~vbits[i].bit; fprintf(a->f,"%s%s",sep,vbits[i].name); sep = " "; } } if (m) fprintf(a->f,"%s0x%x",sep,m); fprintf(a->f,"\n"); return; } if ((arg[0] == '?') && !arg[1]) { for (i=0;vbits[i].bit;i++) { fprintf(a->f,"%-12s%s\n",vbits[i].name,vbits[i].help); } return; } crack_verbosity(arg,&set,&clr,&trail,&err); v = (*vops_admin.find)(&err,a); v->mask = (v->mask & ~clr) | set; (*v->ops->maskchanged)(v); recompute_vmask(); } static void admin_drop(ADMIN *a, const char *arg) { int i; PEER *p; while (*arg && Cisspace(*arg)) arg ++; for (i=0;itag)) { close_peer(p); return; } } fprintf(a->f,"Not found.\n"); } static void admin_line(ADMIN *a) { char *p; char *p0; int l; for (p=a->ilb;*p&&Cisspace(*p);p++) ; p0 = p; for (p=a->ilb;*p&&!Cisspace(*p);p++) ; l = p - p0; a->f = funopen(a,0,&admin_out_w,0,0); if ((l == 1) && (*p0 == '?')) { fprintf(a->f, "? This help\n" "V [-]arg[,[-]arg...]\n" " Turn verbosity on/off for this connection.\n" "drop name\n" " Forcibly drop a PEER, identified by the name printed by `stat'.\n" "quit Disconnect\n" "exit Disconnect\n" "stat Show status\n"); } else if ((l == 1) && (*p0 == 'V')) { admin_verbosity(a,p0+1); } else if ((l == 4) && !bcmp(p0,"drop",4)) { admin_drop(a,p0+4); } else if ((l == 4) && (!bcmp(p0,"quit",4) || !bcmp(p0,"exit",4))) { close_admin(a); } else if ((l == 4) && !bcmp(p0,"stat",4)) { dump_status(a->f); } else { fprintf(a->f,"Unrecognized command `%.*s'\n",l,p0); } fclose(a->f); a->f = 0; } static void admin_input(ADMIN *a, char ch) { void savec(char c) { if (a->iln >= a->ila) a->ilb = realloc(a->ilb,a->ila=a->iln+8); a->ilb[a->iln++] = c; } switch (ch) { case '\r': if (a->ilprev == '\n') { a->ilprev = '\0'; return; } if (0) { case '\n': if (a->ilprev == '\r') { a->ilprev = '\0'; return; } } savec('\0'); a->iln --; admin_line(a); if (a->fd < 0) return; a->iln = 0; admin_prompt(a); break; default: savec(ch); break; } a->ilprev = ch; } static void rd_admin(void *av) { ADMIN *a; char rbuf[512]; int n; int i; a = av; n = read(a->fd,&rbuf[0],sizeof(rbuf)); if (n <= 0) { close_admin(a); return; } for (i=0;(ifd>=0);i++) admin_input(a,rbuf[i]); } static void onaccept_control(LISTEN *l, int fd) { ADMIN *a; a = malloc(sizeof(ADMIN)); a->fd = fd; oq_init(&a->oq); if (l->sa->sa_family == AF_LOCAL) { asprintf( &a->txt, "local %.*s", (int)(l->sa->sa_len-offsetof(struct sockaddr_un,sun_path)), &((struct sockaddr_un *)l->sa)->sun_path[0] ); } else { a->txt = get_sockaddrs_txt(fd,l->txt); } a->id = add_poll_fd(fd,&rwtest_always,&wtest_admin,&rd_admin,&wr_admin,a); a->ilb = 0; a->ila = 0; a->iln = 0; a->ilprev = '\0'; a->f = 0; a->verb = 0; admin_prompt(a); } static char *gettext_local(AIA *a) { const struct sockaddr_un *sun; int l; char *t; sun = (const struct sockaddr_un *)a->ai->ai_addr; l = sun->sun_len - (sizeof(*sun) - sizeof(sun->sun_path)); t = malloc(l+1); bcopy(&sun->sun_path[0],t,l); t[l] = '\0'; return(t); } static void postbind_local(AIA *a) { if (a->local.mode >= 0) chmod(&((const struct sockaddr_un *)a->ai->ai_addr)->sun_path[0],a->local.mode); } static void confline_control(const char *s) { int i; unsigned int flags; struct addrinfo *ai0; AIA *ailist; void capcustom_control(APSTATE *s) { if (capcustom_stars(s)) flags |= LLF_WILDCARD; } for (i=0;s[i]&&Cisspace(s[i]);i++) ; if (! s[i]) config_err("no argument given on control line"); if (s[i] == '/') { struct addrinfo ai; struct sockaddr_un *sun; int l; int j; for (j=i;s[j]&&!Cisspace(s[j]);j++) ; l = sizeof(struct sockaddr_un) - sizeof(sun->sun_path) + (j-i); sun = malloc(l+1); sun->sun_family = AF_LOCAL; sun->sun_len = l; bcopy(s+i,&sun->sun_path[0],j-i); sun->sun_path[j-i] = '\0'; for (;s[j]&&Cisspace(s[j]);j++) ; /* just the fields try_setup_listen uses */ ai.ai_family = AF_LOCAL; ai.ai_socktype = SOCK_STREAM; ai.ai_protocol = 0; ai.ai_addr = (struct sockaddr *)sun; ai.ai_addrlen = l; ailist = malloc(sizeof(AIA)); ailist->link = 0; ailist->ai = &ai; ailist->gettext = &gettext_local; ailist->postbind = &postbind_local; ailist->local.mode = -1; if (s[j]) { unsigned long int mode; char *ep; mode = strtoul(s+j,&ep,8); if (*ep) { config_err("junk after mode on local control line"); } else if (mode & ~0777UL) { config_err("invalid mode on local control line"); } else { ailist->local.mode = mode; } } try_setup_listen(&ailist,LLF_PRIVATE,s,&onaccept_control); free(sun); } else { flags = LLF_PRIVATE; ai0 = crack_addr_port(s+i,AI_PASSIVE,&capcustom_control); ailist = aia_for_addrinfo(ai0); while (ailist) try_setup_listen(&ailist,flags,s,&onaccept_control); } } 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 }, { "peer", &confline_peer }, { "tun", &confline_tun }, { "control", &confline_control }, { 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) && !strncasecmp(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) { FILE *f; isrc = 0; f = fopen(fn,"r"); if (f == 0) config_err("can't open config file %s: %s",fn,strerror(errno)); push_isrc(f,fn); config_read(); } static void readconfline(const char *line) { FILE *f; isrc = 0; f = open_strings_r(line,OSR_STRLEN,"\n",1,OSR_END); push_isrc(f,""); 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; } if (!strcmp(*av,"-C")) { WANTARG(); readconfline(av[skip]); continue; } if (!strcmp(*av,"-v")) { WANTARG(); set_verbose(av[skip]); continue; } if (!strcmp(*av,"-bg")) { autobg = 1; continue; } #undef WANTARG fprintf(stderr,"%s: unrecognized option `%s'\n",__progname,*av); errs ++; } if (errs) exit(1); } static void startup(void) { int i; open_random(); init_wrandom(); pd_reconnect = start_periodic(TRY_CONNECT_MIN,TRY_CONNECT_MAX,&try_reconnect,0); peercleanup_id = add_block_fn(&peercleanup,0); peercleanup_needed = 0; open_tun(); start_accepts(); pktq = 0; pktqt = &pktq; tunid = add_poll_fd(tunfd,&rwtest_always,&rwtest_never,&tun_rd,0,0); want_send = 0; sendid = add_block_fn(&send_output,0); for (i=256-1;i>=0;i--) { peertbl[i] = 0; reachtbl[i] = -1; } pd_expire_reach = start_periodic(REACH_EXPIRE_MIN,REACH_EXPIRE_MAX,&expire_reaches,0); pd_expire_uplink = ignore_public ? 0 : start_periodic(UPLINK_EXPIRE_MIN,UPLINK_EXPIRE_MAX,&expire_uplinks,0); init_ipmap(); } static void background(void) { pid_t p; p = fork(); if (p < 0) { fprintf(stderr,"%s: fork: %s\n",__progname,strerror(errno)); exit(1); } if (p > 0) exit(0); } int main(int, char **); int main(int ac, char **av) { init(); handleargs(ac,av); set_defaults(); startup(); if (autobg) background(); while (1) { pre_poll(); if (do_poll() < 0) { if (errno == EINTR) continue; fprintf(stderr,"%s: poll: %s",__progname,strerror(errno)); exit(1); } post_poll(); } return(0); }