/* This file is in the public domain. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define FIXisspace(x) isspace((unsigned char)(x)) #define FIXisdigit(x) isdigit((unsigned char)(x)) #include "lb.h" #include "getput.h" #include "pollloop.h" #include "timer-socket.h" #ifndef NI_WITHSCOPEID #define NI_WITHSCOPEID 0 #endif extern const char *__progname; static const char *rnddev = "/dev/urandom"; static const unsigned char pre_s2c[2] = { 0x73, 0x2d }; static const unsigned char pre_c2s[2] = { 0x63, 0x2d }; static const unsigned char post_s2c[2] = { 0x3e, 0x63 }; static const unsigned char post_c2s[2] = { 0x3e, 0x73 }; static const unsigned char pingmsg[1] = { LB_PING }; static const unsigned char pongmsg[1] = { LB_PONG }; static const unsigned char abortedmsg[1] = { LB_ABORTED }; static int verbose = 0; static const char *configfile = 0; typedef struct acc ACC; typedef struct accops ACCOPS; typedef struct accsock ACCSOCK; typedef struct conn CONN; typedef struct oq OQ; typedef struct ob OB; typedef struct client CLIENT; typedef struct conflict CONFLICT; typedef struct cclist CCLIST; typedef struct config CONFIG; typedef struct admin ADMIN; struct config { CLIENT *clients; ACC *admin; } ; /* the _cf elements form a DLL rooted in the conflict; the _cl elements form a DLL rooted in the client. */ struct cclist { CCLIST *f_cf; CCLIST *b_cf; CCLIST *f_cl; CCLIST *b_cl; CLIENT *client; CONFLICT *conflict; } ; struct conflict { CONFLICT *link; char *tag; int taglen; CONN *busy; CCLIST *clients; } ; struct client { CLIENT *link; char *backingfile; char *keyfile; CCLIST *conflicts; ACC *accs; int bffd; char *keydata; int keylen; CONN *conns; void *tmp; } ; struct acc { ACC *link; char *hoststr; char *portstr; ACCSOCK *socks; ACCOPS *ops; void *private; } ; struct accops { void (*dbgprint)(FILE *, ACC *, CLIENT *); void (*accept)(ACCSOCK *, int, struct sockaddr_storage *, int); } ; struct accsock { ACCSOCK *link; ACC *a; struct sockaddr *addr; int addrlen; int fd; int id; char *txt; } ; struct oq { OB *q; OB **qt; int qlen; } ; struct ob { OB *link; const char *data; int left; char *free; } ; struct admin { ADMIN *flink; ADMIN *blink; struct sockaddr_storage from; int fromlen; int fd; char *b; int a; int l; OQ oq; int ioid; int bid; unsigned int flags; #define AF_DEAD 0x00000001 #define AF_DRAIN 0x00000002 char *prompt; } ; struct conn { CONN *link; ACCSOCK *s; CLIENT *c; int state; #define CS_RR_A 1 #define CS_RR_B 2 #define CS_RR_C 3 #define CS_UP 4 #define CS_DEAD 5 struct sockaddr_storage from; int fromlen; int fd; int id; int crid; OQ oq; int cfill; int clen; int sumid; unsigned int start; unsigned int hand; unsigned int end; unsigned int bf; unsigned int ckt; unsigned int cklen; int tmofd; int tmoid; int rbfill; int rbptr; unsigned int countdown; ARC4_STATE s2cenc; ARC4_STATE c2senc; unsigned char srnd[16]; unsigned char crbuf[517]; unsigned char rbuf[8192]; char *status; } ; static int rndfd; static CONFIG config; static int sighup_pipe_r; static volatile int sighup_pipe_w; static int sighup_pipe_id; static int sighup_block_id; static CONFLICT *conflicts; static ADMIN *admins; static void *dequal(volatile const void *v) { void *tmp; tmp = &v; return(*(void **)tmp); } static void vprf(int, const char *, ...) __attribute__((__format__(__printf__,2,3))); static void vprf(int level, const char *fmt, ...) { va_list ap; if (verbose < level) return; va_start(ap,fmt); vprintf(fmt,ap); va_end(ap); } #define ISV(n) (verbose >= (n)) static void panic(const char *, ...) __attribute__((__format__(__printf__,1,2),__noreturn__)); static void panic(const char *fmt, ...) { va_list ap; static int panicking = 0; fprintf(stderr,"%s: panic: ",__progname); va_start(ap,fmt); vfprintf(stderr,fmt,ap); va_end(ap); fprintf(stderr,"\n"); if (ISV(1) && !panicking) { CLIENT *c; ACC *a; CONN *n; CCLIST *l; CCLIST *lb; CONFLICT *f; fflush(0); panicking = 1; for (c=config.clients;c;c=c->link) { printf("client %p: %s\n",(void *)c,c->backingfile); for (a=c->accs;a;a=a->link) { printf(" acc %p: %s/%s\n",(void *)a,a->hoststr,a->portstr); (*a->ops->dbgprint)(stdout,a,c); } for (n=c->conns;n;n=n->link) { printf(" conn %p: sock %p state ",(void *)n->s,(void *)n); switch (n->state) { case CS_RR_A: printf("RR_A"); break; case CS_RR_B: printf("RR_B"); break; case CS_RR_C: printf("RR_C"); break; case CS_UP: printf("UP"); break; case CS_DEAD: printf("DEAD"); break; default: printf("?%d",n->state); break; } printf(" fd %d oq %d cfill %d/%d\n",n->fd,n->oq.qlen,n->cfill,n->clen); } printf(" conflicts\n"); lb = 0; for (l=c->conflicts;l;l=l->f_cl) { printf(" %p %p\n",(void *)l,(void *)l->conflict); if (l->client != c) printf("*** client backlink %p\n",(void *)l->client); if (l->b_cl != lb) printf("*** b_cl %p\n",(void *)l->b_cl); lb = l; } } for (f=conflicts;f;f=f->link) { printf("conflict %p: %.*s busy=%p\n",(void *)f,f->taglen,f->tag,(void *)f->busy); lb = 0; for (l=f->clients;l;l=l->f_cf) { printf(" %p %p\n",(void *)l,(void *)l->client); if (l->conflict != f) printf("*** conflict backlink %p\n",(void *)l->conflict); if (l->b_cf != lb) printf("*** b_cf %p\n",(void *)l->b_cf); lb = l; } } } abort(); } static void handleargs(int ac, char **av) { int skip; int errs; static void setconfig(const char *s) { if (configfile) { fprintf(stderr,"%s: config file already specified, using later value\n",__progname); } configfile = s; } skip = 0; errs = 0; for (ac--,av++;ac;ac--,av++) { if (skip > 0) { skip --; continue; } if (**av != '-') { if (! configfile) { setconfig(*av); } else { 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,"-v")) { verbose ++; continue; } if (!strcmp(*av,"-config")) { WANTARG(); setconfig(av[skip]); continue; } #undef WANTARG fprintf(stderr,"%s: unrecognized option `%s'\n",__progname,*av); errs ++; } if (errs) exit(1); } static void oq_init(OQ *q) { q->q = 0; q->qt = &q->q; q->qlen = 0; } static void oq_flusho(OQ *q) { OB *b; while (q->q) { b = q->q; q->q = b->link; q->qlen -= b->left; free(b->free); free(b); } q->qt = &q->q; if (q->qlen) panic("qlen != 0 after flush"); } static void oq_queue_copy(OQ *q, const void *data, int len) { OB *b; if (len < 0) len = strlen(data); if (len < 1) return; b = malloc(sizeof(OB)+len); b->link = 0; bcopy(data,b+1,len); *q->qt = b; q->qt = &b->link; b->data = (void *)(b+1); b->left = len; b->free = 0; q->qlen += len; } static void oq_queue_free(OQ *q, void *data, int len) { OB *b; if (len < 0) len = strlen(data); if (len < 1) return; b = malloc(sizeof(OB)); b->link = 0; *q->qt = b; q->qt = &b->link; b->data = data; b->left = len; b->free = data; q->qlen += len; } static void oq_queue_point(OQ *q, const void *data, int len) { OB *b; if (len < 0) len = strlen(data); if (len < 1) return; b = malloc(sizeof(OB)); b->link = 0; *q->qt = b; q->qt = &b->link; b->data = data; b->left = len; b->free = 0; q->qlen += len; } static void oq_printf(OQ *, const char *, ...) __attribute__((__format__(__printf__,2,3))); static void oq_printf(OQ *q, const char *fmt, ...) { va_list ap; char *s; int l; va_start(ap,fmt); l = vasprintf(&s,fmt,ap); va_end(ap); oq_queue_free(q,s,l); } static int oq_write(OQ *q, int fd) { static int maxiov = -1; static struct iovec *iov; static int iovn; int iovl; OB *b; int w; if (! q->q) return(0); if (maxiov < 0) { int mib[2]; size_t oldlen; mib[0] = CTL_KERN; mib[1] = KERN_IOV_MAX; oldlen = sizeof(maxiov); if (sysctl(&mib[0],2,&maxiov,&oldlen,0,0) < 0) { fprintf(stderr,"%s: can't get kern.iov_max: %s\n",__progname,strerror(errno)); exit(1); } if (maxiov > 64) maxiov = 64; if (maxiov < 1) maxiov = 1; iov = 0; iovn = 0; } iovl = 0; for (b=q->q;b;b=b->link) { if (iovl >= maxiov) break; if (iovl >= iovn) iov = realloc(iov,(iovn=iovl+4)*sizeof(*iov)); iov[iovl++] = (struct iovec){ .iov_base=dequal(b->data), .iov_len=b->left }; } w = writev(fd,iov,iovl); if (w < 0) { switch (errno) { case EWOULDBLOCK: case EINTR: return(0); break; } return(1); } while ((b=q->q) && w && (w >= b->left)) { q->q = b->link; q->qlen -= b->left; w -= b->left; free(b->free); free(b); } if (b) { if (w) { b->data += w; b->left -= w; q->qlen -= w; } } else { q->qt = &q->q; } if (q->qlen < 0) panic("queue underflow"); if (q->qlen && !q->q) panic("queue lost"); return(0); } static void wcrypted(CONN *c, const void *data, int len) { unsigned char *buf; buf = malloc(len); arc4_crypt(&c->s2cenc,data,len,buf); oq_queue_free(&c->oq,buf,len); } static void crypto_step1(CONN *c) { void *h; int i; int j; int k; unsigned char m[20]; unsigned char key[257]; bzero(&key[0],237); for (i=0;i<32;i++) { h = sha1_init(); sha1_process_bytes(h,c->c->keydata,c->c->keylen); sha1_process_bytes(h,&c->srnd[0],16); sha1_process_bytes(h,&pre_s2c[0],2); if (i) sha1_process_bytes(h,&m[0],20); sha1_process_bytes(h,&post_s2c[0],2); sha1_process_bytes(h,&c->crbuf[0],16); sha1_process_bytes(h,c->c->keydata,c->c->keylen); sha1_result(h,&m[0]); for (j=i*7,k=0;k<20;j++,k++) key[j] += m[k]; } h = sha1_init(); sha1_process_bytes(h,&key[0],237); sha1_result(h,&key[237]); arc4_init(&c->s2cenc); arc4_setkey(&c->s2cenc,&key[0],256,65536); bzero(&key[0],237); for (i=0;i<32;i++) { h = sha1_init(); sha1_process_bytes(h,c->c->keydata,c->c->keylen); sha1_process_bytes(h,&c->crbuf[0],16); sha1_process_bytes(h,&pre_c2s[0],2); if (i) sha1_process_bytes(h,&m[0],20); sha1_process_bytes(h,&post_c2s[0],2); sha1_process_bytes(h,&c->srnd[0],16); sha1_process_bytes(h,c->c->keydata,c->c->keylen); sha1_result(h,&m[0]); for (j=i*7,k=0;k<20;j++,k++) key[j] += m[k]; } h = sha1_init(); sha1_process_bytes(h,&key[0],237); sha1_result(h,&key[237]); arc4_init(&c->c2senc); arc4_setkey(&c->c2senc,&key[0],256,65536); read(rndfd,&c->srnd[0],16); bzero(&c->crbuf[0],16); bzero(&key[0],sizeof(key)); wcrypted(c,&c->srnd[0],16); c->cfill = 0; c->state = CS_RR_B; } static void crypto_step2(CONN *c) { wcrypted(c,&c->crbuf[0],16); c->cfill = 0; c->clen = 16; c->state = CS_RR_C; } static void crypto_step3(CONN *c) { CONN *c2; if (bcmp(&c->crbuf[0],&c->srnd[0],16)) { c->state = CS_DEAD; return; } vprf(1,"%p (%d %s) now up\n",(void *)c,c->fd,c->c->backingfile); for (c2=c->c->conns;c2;c2=c2->link) { if (c2->state == CS_UP) { vprf(1,"killing %p (fd %d)\n",(void *)c2,c2->fd); c2->state = CS_DEAD; } } c->cfill = 0; c->clen = 1; c->state = CS_UP; } static int rtest_conn(int id __attribute__((__unused__)), void *cv) { CONN *c; c = cv; return((c->state != CS_DEAD) && (c->rbptr >= c->rbfill)); } static int wtest_conn(int id __attribute__((__unused__)), void *cv) { CONN *c; c = cv; return(c->oq.q && (c->state != CS_DEAD)); } static void save_data(CLIENT *c, unsigned int bno, const void *data) { int w; w = pwrite(c->bffd,data,512,bno*(off_t)512); if (w < 0) { fprintf(stderr,"%s: %s: write block %u: %s\n",__progname,c->backingfile,bno,strerror(errno)); exit(1); } if (w != 512) { fprintf(stderr,"%s: %s: write block %u: wanted %d, did %d\n",__progname,c->backingfile,bno,512,w); exit(1); } } static void compute_cksum(int ckt, const void *data, void *into) { void *h; switch (ckt) { case CKT_SHA1: h = sha1_init(); sha1_process_bytes(h,data,512); sha1_result(h,into); break; case CKT_SUM_SHA1: { unsigned char s; int i; s = 0; for (i=0;i<512;i++) s += ((const unsigned char *)data)[i]; ((unsigned char *)into)[0] = s; h = sha1_init(); sha1_process_bytes(h,data,512); sha1_result(h,((unsigned char *)into)+1); } break; default: panic("impossible ckt %d",ckt); break; } } static void stop_sums(CONN *c) { CCLIST *cl; if (c->sumid != PL_NOID) { remove_block_id(c->sumid); c->sumid = PL_NOID; for (cl=c->c->conflicts;cl;cl=cl->f_cl) { if (cl->conflict->busy != c) panic("stop_sums bad busy"); cl->conflict->busy = 0; } } } static int sum_block(void *cv) { CONN *c; int n; int i; char buf[256*512]; c = cv; if (c->oq.qlen > 1048576) return(0); if (c->hand >= c->end) { stop_sums(c); return(1); } n = c->bf; if (c->end-c->hand < n) n = c->end - c->hand; i = pread(c->c->bffd,&buf[0],n*512,c->hand*(off_t)512); if (i < 0) { fprintf(stderr,"%s: backing file read error: %s\n",__progname,strerror(errno)); exit(1); } if (i < n*512) bzero(&buf[i],(n*512)-i); for (i=0;ickt,&buf[i*512],&buf[5+(i*c->cklen)]); } buf[0] = LB_SUMS; vprf(2,"%s sending %s %u\n",c->c->backingfile,msg_name(LB_SUMS),c->hand); put_4(&buf[1],c->hand); wcrypted(c,&buf[0],5+(n*20)); c->hand += n; return(1); } static int await_start_sums(void *cv) { CONN *c; CCLIST *cl; c = cv; if (! c->c->conflicts) panic("await_start_sums no conflicts"); for (cl=c->c->conflicts;cl;cl=cl->f_cl) if (cl->conflict->busy) return(0); remove_block_id(c->sumid); c->sumid = add_block_fn(&sum_block,c); for (cl=c->c->conflicts;cl;cl=cl->f_cl) cl->conflict->busy = c; return(1); } static void start_sums(CONN *c) { CCLIST *cl; c->start = get_4(&c->crbuf[1]); c->hand = c->start; c->end = c->hand + get_4(&c->crbuf[5]); c->bf = c->crbuf[9]; c->ckt = c->crbuf[10]; switch (c->ckt) { case CKT_SHA1: c->cklen = 20; break; case CKT_SUM_SHA1: c->cklen = 21; break; default: fprintf(stderr,"%s: (%s) unknown checksum type 0x%02x\n",__progname,c->c->backingfile,c->ckt); c->state = CS_DEAD; return; break; } do <"blocked"> { for (cl=c->c->conflicts;cl;cl=cl->f_cl) { if (cl->conflict->busy) { if (cl->conflict->busy == c) panic("duplicate busy"); if ( (cl->conflict->busy->c == c->c) && (cl->conflict->busy->state != CS_DEAD) ) panic("busy conflict"); c->sumid = add_block_fn(&await_start_sums,c); break <"blocked">; } } c->sumid = add_block_fn(&sum_block,c); for (cl=c->c->conflicts;cl;cl=cl->f_cl) cl->conflict->busy = c; } while (0); } static void set_size(CONN *c) { ftruncate(c->c->bffd,get_4(&c->crbuf[1])*(off_t)512); /* ignore errors, in case it's really a disk or some such */ } static void set_status(CONN *c) { free(c->status); c->status = malloc(c->crbuf[1]+1); if (c->crbuf[1]) bcopy(&c->crbuf[2],c->status,c->crbuf[1]); c->status[c->crbuf[1]] = '\0'; } static void rd_conn(int id __attribute__((__unused__)), void *cv) { CONN *c; int r; c = cv; if (c->state == CS_DEAD) return; if (c->rbptr < c->rbfill) return; r = read(c->fd,&c->rbuf[0],sizeof(c->rbuf)); if (r < 0) { switch (errno) { case EWOULDBLOCK: case EINTR: return; break; } fprintf(stderr,"%s: client read error: %s\n",__progname,strerror(errno)); c->state = CS_DEAD; return; } if (r == 0) { if ((c->state != CS_UP) || (c->cfill > 0)) { fprintf(stderr,"%s: unexpected client EOF\n",__progname); } vprf(1,"client EOF fd %d, conn %p, %s\n",c->fd,(void *)c,c->c->backingfile); c->state = CS_DEAD; return; } c->rbfill = r; c->rbptr = 0; } static void wr_conn(int id __attribute__((__unused__)), void *cv) { CONN *c; c = cv; if (c->state == CS_DEAD) return; if (oq_write(&c->oq,c->fd)) { fprintf(stderr,"%s: network write [%s]: %s\n",__progname,c->c->backingfile,strerror(errno)); c->state = CS_DEAD; } } static int block_conn(void *cv) { CONN *c; int l; int r; c = cv; switch (c->state) { case CS_RR_A: case CS_RR_B: case CS_RR_C: l = 16 - c->cfill; break; case CS_UP: l = c->clen - c->cfill; break; case CS_DEAD: return(0); break; default: panic("block_conn bad state 1"); break; } if (c->rbptr >= c->rbfill) return(0); r = c->rbfill - c->rbptr; if (r > l) r = l; switch (c->state) { case CS_RR_A: bcopy(&c->rbuf[c->rbptr],&c->crbuf[c->cfill],r); break; default: arc4_crypt(&c->c2senc,&c->rbuf[c->rbptr],r,&c->crbuf[c->cfill]); break; } c->rbptr += r; c->cfill += r; if (r < l) return(1); switch (c->state) { case CS_RR_A: crypto_step1(c); break; case CS_RR_B: crypto_step2(c); break; case CS_RR_C: crypto_step3(c); break; case CS_UP: vprf(2,"%s msg %s fill %d\n",c->c->backingfile,msg_name(c->crbuf[0]),c->cfill); switch (c->crbuf[0]) { case LB_DATA: if (c->cfill == 1) { c->clen = 517; return(1); } save_data(c->c,get_4(&c->crbuf[1]),&c->crbuf[5]); break; case LB_RQSUMS: if (c->sumid != PL_NOID) { fprintf(stderr,"%s: RQSUMS during another RQSUMS\n",__progname); c->state = CS_DEAD; return(1); } if (c->cfill == 1) { c->clen = 11; return(1); } start_sums(c); break; case LB_STOPSUM: stop_sums(c); vprf(2,"%s sending %s\n",c->c->backingfile,msg_name(abortedmsg[0])); wcrypted(c,&abortedmsg[0],1); break; case LB_SIZE: if (c->cfill == 1) { c->clen = 5; return(1); } set_size(c); break; case LB_STATUS: switch (c->cfill) { case 1: c->clen = 2; return(1); break; case 2: c->clen += c->crbuf[1]; if (c->clen > 2) return(1); /* fall through */ } set_status(c); break; case LB_PING: vprf(2,"%s sending %s\n",c->c->backingfile,msg_name(pongmsg[0])); wcrypted(c,&pongmsg[0],1); break; case LB_PONG: break; default: fprintf(stderr,"%s: bad packet type %02x\n",__progname,c->crbuf[0]); c->state = CS_DEAD; break; } c->cfill = 0; c->clen = 1; break; default: panic("block_conn bad state 2"); break; } return(1); } static void free_conn(CONN *n) { CCLIST *l; for (l=n->c->conflicts;l;l=l->f_cl) { if (l->conflict->busy == n) { l->conflict->busy = 0; } } remove_poll_id(n->id); remove_block_id(n->crid); close(n->fd); if (n->sumid != PL_NOID) remove_block_id(n->sumid); remove_poll_id(n->tmoid); close(n->tmofd); oq_flusho(&n->oq); free(n); } static int weed_conns(void *arg __attribute__((__unused__))) { CLIENT *c; CONN *n; CONN **np; int rv; rv = 0; for (c=config.clients;c;c=c->link) { np = &c->conns; while ((n = *np)) { if (n->state == CS_DEAD) { *np = n->link; free_conn(n); rv = 1; } else { np = &n->link; } } } return(rv); } static void rd_tmo(int id __attribute__((__unused__)), void *cv) { CONN *c; struct timersock_event e; c = cv; read(c->tmofd,&e,sizeof(e)); if (c->state == CS_DEAD) return; if (c->countdown > 0) { c->countdown --; } else { switch (c->state) { case CS_RR_A: case CS_RR_B: case CS_RR_C: vprf(1,"dropping %p fd %d, timeout\n",(void *)c,c->fd); c->state = CS_DEAD; break; case CS_UP: vprf(2,"%s sending %s\n",c->c->backingfile,msg_name(pingmsg[0])); wcrypted(c,&pingmsg[0],1); c->countdown = 10; break; } } } static void acc_accept(int id __attribute__((__unused__)), void *sv) { ACCSOCK *s; int new; struct sockaddr_storage ss; int sslen; int on; s = sv; sslen = sizeof(ss); new = accept(s->fd,(void *)&ss,&sslen); if (new < 0) { switch (errno) { case EWOULDBLOCK: case EINTR: return; break; } fprintf(stderr,"%s: accept (%s): %s\n",__progname,s->txt,strerror(errno)); return; } fcntl(new,F_SETFL,fcntl(new,F_GETFL,0)|O_NONBLOCK); on = 1; setsockopt(new,SOL_SOCKET,SO_KEEPALIVE,&on,sizeof(on)); (*s->a->ops->accept)(s,new,&ss,sslen); } static char *blk_to_str(const void *data, int len) { char *t; t = malloc(len+1); bcopy(data,t,len); t[len] = '\0'; return(t); } static void free_accsock_list(ACCSOCK *l) { ACCSOCK *s; while (l) { s = l; l = s->link; free(s->addr); if (s->fd >= 0) close(s->fd); if (s->id != PL_NOID) remove_poll_id(s->id); free(s->txt); free(s); } } static void free_one_acc(ACC *a) { free_accsock_list(a->socks); free(a->hoststr); free(a->portstr); free(a); } static void free_acc_list(ACC *l) { ACC *a; while (l) { a = l; l = a->link; free_one_acc(a); } } static void free_one_client(CLIENT *c) { CONN *n; CCLIST *cl; free_acc_list(c->accs); while (c->conns) { n = c->conns; c->conns = n->link; free_conn(n); } while ((cl = c->conflicts)) { if (cl->f_cf) cl->f_cf->b_cf = cl->b_cf; (cl->b_cf ? cl->b_cf->f_cf : cl->conflict->clients) = cl->f_cf; if (cl->conflict->busy) panic("free_one_client: busy"); c->conflicts = cl->f_cl; free(cl); } if (c->bffd >= 0) close(c->bffd); free(c->backingfile); free(c->keyfile); free(c->keydata); free(c->tmp); free(c); } static void free_client_list(CLIENT *l) { CLIENT *c; while (l) { c = l; l = l->link; free_one_client(c); } } static void clear_config(CONFIG *cf) { free_client_list(cf->clients); free_acc_list(cf->admin); } static void add_conflict_tag(CLIENT *c, char *tag, int len) { CCLIST *cl; CONFLICT *f; do <"havecf"> { for (f=conflicts;f;f=f->link) { if ((f->taglen == len) && !bcmp(f->tag,tag,len)) break <"havecf">; } f = malloc(sizeof(CONFLICT)); f->tag = malloc(len); bcopy(tag,f->tag,len); f->taglen = len; f->busy = 0; f->clients = 0; f->link = conflicts; conflicts = f; } while (0); cl = malloc(sizeof(CCLIST)); cl->conflict = f; cl->client = c; cl->f_cf = f->clients; cl->b_cf = 0; if (f->clients) f->clients->b_cf = cl; f->clients = cl; cl->f_cl = c->conflicts; cl->b_cl = 0; if (c->conflicts) c->conflicts->b_cl = cl; c->conflicts = cl; } static void err(const char *, ...) __attribute__((__format__(__printf__,1,2))); static 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"); } static void accops_client_dbgprint(FILE *to, ACC *a, CLIENT *c) { ACCSOCK *s; if (a->private != c) fprintf(to,"*** client backlink %p\n",a->private); for (s=a->socks;s;s=s->link) { fprintf(to," sock %p: %d %s\n",(void *)s,s->fd,s->txt); } } static void accops_client_accept( ACCSOCK *s, int new, struct sockaddr_storage *from, int fromlen ) { CLIENT *c; CONN *n; struct itimerval itv; c = s->a->private; n = malloc(sizeof(CONN)); n->link = c->conns; c->conns = n; n->s = s; n->c = c; n->state = CS_RR_A; n->fd = new; n->from = *from; n->fromlen = fromlen; oq_init(&n->oq); n->sumid = PL_NOID; n->tmofd = timer_socket(); if (n->tmofd < 0) { fprintf(stderr,"%s: timer socket: %s\n",__progname,strerror(errno)); exit(1); } itv.it_interval.tv_sec = 60; itv.it_interval.tv_usec = 0; itv.it_value = itv.it_interval; write(n->tmofd,&itv,sizeof(itv)); n->tmoid = add_poll_fd(n->tmofd,&rwtest_always,&rwtest_never,&rd_tmo,0,n); n->countdown = 10; read(rndfd,&n->srnd[0],16); oq_queue_copy(&n->oq,&n->srnd[0],16); n->cfill = 0; n->rbfill = 0; n->rbptr = 0; n->id = add_poll_fd(n->fd,&rtest_conn,&wtest_conn,&rd_conn,&wr_conn,n); n->crid = add_block_fn(&block_conn,n); n->status = 0; vprf(1,"accepted %p fd %d for %s\n",(void *)n,n->fd,c->backingfile); } /* * ACC ops vector for ACCs corresponding to clients. */ static ACCOPS accops_client = { &accops_client_dbgprint, &accops_client_accept }; static int admin_iline(ADMIN *a) { int x; int x0; int (*ckfn)(CLIENT *c); static int ck_all(CLIENT *c __attribute__((__unused__))) { return(1); } static int ck_up(CLIENT *c) { CONN *n; for (n=c->conns;n;n=n->link) if (n->state == CS_UP) return(1); return(0); } for (x=0;(xl)&&FIXisspace(a->b[x]);x++) ; if (x >= a->l) return(1); x0 = x; for (;(xl)&&!FIXisspace(a->b[x]);x++) ; if ((x-x0 == 4) && (!bcmp(a->b+x0,"quit",4) || !bcmp(a->b+x0,"exit",4))) { a->flags |= AF_DEAD; return(0); } if ( ((x-x0 == 1) && (a->b[x0] == '?')) || ((x-x0 == 4) && !bcmp(a->b+x0,"help",4)) ) { oq_queue_point(&a->oq, "?, help Print this help\n" "exit, quit Disconnect\n" "all Print status for all filesystems\n" "up Print status for all filesystems with a connection\n" "drain Block until all queued output has been sent\n" "prompt Change prompt to \n" "echo Print \n" ,-1); return(1); } if ( ((x-x0 == 3) && !bcmp(a->b+x0,"all",3) && ((ckfn=&ck_all),1)) || ((x-x0 == 2) && !bcmp(a->b+x0,"up",2) && ((ckfn=&ck_up),1)) ) { CLIENT *cl; CONN *cn; for (cl=config.clients;cl;cl=cl->link) { if ((ckfn)(cl)) { for (cn=cl->conns;cn;cn=cn->link) { if (cn->state == CS_UP) break; } if (cn) { oq_printf(&a->oq,"%s [%d]: fd %d - %s\n", cl->backingfile,cl->bffd,cn->fd,cn->status?:"(no status)"); } else { oq_printf(&a->oq,"%s [%d]: disconnected\n", cl->backingfile,cl->bffd); } } } return(1); } if ((x-x0 == 5) && !bcmp(a->b+x0,"drain",5)) { a->flags |= AF_DRAIN; return(0); } if ((x-x0 == 6) && !bcmp(a->b+x0,"prompt",6)) { for (;(xl)&&FIXisspace(a->b[x]);x++) ; free(a->prompt); a->prompt = strdup(a->b+x); return(1); } if ((x-x0 == 4) && !bcmp(a->b+x0,"echo",4)) { for (;(xl)&&FIXisspace(a->b[x]);x++) ; oq_queue_copy(&a->oq,a->b+x,a->l-x); oq_queue_point(&a->oq,"\n",1); return(1); } oq_queue_point(&a->oq,"? for help\n",-1); return(1); } static void admin_prompt(ADMIN *a) { oq_queue_copy(&a->oq,a->prompt,-1); } static void accops_admin_dbgprint( FILE *to __attribute__((__unused__)), ACC *a __attribute__((__unused__)), CLIENT *c __attribute__((__unused__)) ) { fprintf(to,"*** not a client acc\n"); } static int rtest_admin(int id __attribute__((__unused__)), void *av) { ADMIN *a; a = av; return(!(a->flags&(AF_DRAIN|AF_DEAD))); } static int wtest_admin(int id __attribute__((__unused__)), void *av) { ADMIN *a; a = av; return(!(a->flags&AF_DEAD) && a->oq.q); } static void rd_admin(int id __attribute__((__unused__)), void *av) { ADMIN *a; char rbuf[64]; int n; int i; a = av; if (a->flags & AF_DEAD) return; n = read(a->fd,&rbuf[0],sizeof(rbuf)); if (n < 0) { switch (errno) { case EINTR: case EWOULDBLOCK: break; default: a->flags |= AF_DEAD; break; } return; } if (n == 0) { a->flags |= AF_DEAD; return; } for (i=0;il >= a->a) a->b = realloc(a->b,a->a=a->l+8); a->b[a->l++] = ch; } switch (rbuf[i]) { case '\n': save('\0'); a->l --; if (admin_iline(a)) admin_prompt(a); if (a->flags & AF_DEAD) return; a->l = 0; break; case '\r': break; default: save(rbuf[i]); break; } } } static void wr_admin(int id __attribute__((__unused__)), void *av) { ADMIN *a; a = av; if (a->flags & AF_DEAD) return; if (oq_write(&a->oq,a->fd)) { a->flags |= AF_DEAD; return; } if ((a->flags & AF_DRAIN) && !a->oq.q) { a->flags &= ~AF_DRAIN; admin_prompt(a); } } static int block_admin(void *av) { ADMIN *a; a = av; if (a->flags & AF_DEAD) { if (a->flink) a->flink->blink = a->blink; if (a->blink) a->blink->flink = a->flink; else admins = a->flink; close(a->fd); free(a->b); oq_flusho(&a->oq); remove_poll_id(a->ioid); remove_block_id(a->bid); free(a->prompt); free(a); return(1); } return(0); } static void accops_admin_accept( ACCSOCK *s __attribute__((__unused__)), int new, struct sockaddr_storage *from, int fromlen ) { ADMIN *a; int on; on = 1; setsockopt(new,SOL_SOCKET,SO_KEEPALIVE,&on,sizeof(on)); a = malloc(sizeof(ADMIN)); a->flink = admins; a->blink = 0; admins = a; if (a->flink) a->flink->blink = a; a->from = *from; a->fromlen = fromlen; a->fd = new; a->b = 0; a->a = 0; a->l = 0; oq_init(&a->oq); a->ioid = add_poll_fd(a->fd,&rtest_admin,&wtest_admin,&rd_admin,&wr_admin,a); a->bid = add_block_fn(&block_admin,a); a->flags = 0; a->prompt = strdup("lbd> "); admin_prompt(a); vprf(1,"accepted admin fd %d, %p\n",new,(void *)a); } /* * ACC ops vector for administrative ACCs. */ static ACCOPS accops_admin = { &accops_admin_dbgprint, &accops_admin_accept }; static ACC *acc_for(char *h, char *p, ACCOPS *ops, void *priv) { ACC *a; a = malloc(sizeof(ACC)); a->hoststr = h; a->portstr = p; a->socks = 0; a->ops = ops; a->private = priv; return(a); } /* * Load the config from the file into in-core data structures. This * creates the clients list, with accs but not accsocks. It also * opens all backing files, but does not lock them. Return value is * one of the LOAD_ constants: * * LOAD_OK * All went well. * LOAD_EMPTY b * The file was completely empty, not even a comment. * LOAD_ERROR * An error occurred, but it was one that can be recovered * from by ignoring the line that provoked it. * LOAD_FATAL * An error occurred, and it is one that cannot be tied to * a specific line (eg, file not found on open). */ static int loadconfig(CONFIG *cf) #define LOAD_OK 1 #define LOAD_EMPTY 2 #define LOAD_ERROR 3 #define LOAD_FATAL 4 { FILE *f; char *l; int alloc; int n; int ch; int i; int x; int rv; typedef struct { int b; int e; } EL; EL *be; int abe; int nbe; CLIENT *lastclient; CLIENT *c; ACC *a; static void savec(int c) { if (n >= alloc) l = realloc(l,alloc=n+16); l[n++] = c; } cf->clients = 0; cf->admin = 0; rv = LOAD_OK; f = fopen(configfile,"r"); if (f == 0) { err("%s: %s",configfile,strerror(errno)); return(LOAD_FATAL); } ch = getc(f); if (ch == EOF) { fclose(f); return(LOAD_EMPTY); } ungetc(ch,f); lastclient = 0; l = 0; alloc = 0; n = 0; abe = 0; be = 0; while (1) { ch = getc(f); if (ch == EOF) { if (ferror(f)) { err("%s: read error: %s",configfile,strerror(errno)); free(l); clear_config(cf); return(LOAD_FATAL); } if (! n) break; ch = '\n'; } if (ch != '\n') { savec(ch); continue; } do <"nextline"> { savec('\0'); n --; nbe = 0; for (x=0;;x++) { if (x >= n) break <"nextline">; if (l[x] == '#') break <"nextline">; if (! FIXisspace(l[x])) break; } while (1) { if (x >= n) break; if (nbe >= abe) be = realloc(be,(abe=nbe+1)*sizeof(*be)); be[nbe].b = x; while ((x < n) && !FIXisspace(l[x])) x ++; be[nbe].e = x; while ((x < n) && FIXisspace(l[x])) x ++; nbe ++; } if (ISV(1)) { printf("line:"); for (i=0;i; } a = acc_for( blk_to_str(&l[be[1].b],be[1].e-be[1].b), blk_to_str(&l[be[2].b],be[2].e-be[2].b), &accops_admin, 0 ); a->link = cf->admin; cf->admin = a; } else { err("%s: unrecognized * line: %s",configfile,l); rv = LOAD_ERROR; lastclient = 0; } break <"nextline">; } if (nbe < 4) { err("%s: too few fields in line: %s",configfile,l); rv = LOAD_ERROR; lastclient = 0; break <"nextline">; } if ( ((be[0].e == be[0].b+1) && (l[be[0].b] == '-')) && ((be[1].e == be[1].b+1) && (l[be[1].b] == '-')) ) { if (lastclient == 0) { err("%s: - - line with no preceding client",configfile); rv = LOAD_ERROR; break <"nextline">; } vprf(1,"using last client\n"); c = lastclient; } else { struct stat stb; int fd; vprf(1,"new client\n"); c = malloc(sizeof(CLIENT)); c->keyfile = blk_to_str(&l[be[1].b],be[1].e-be[1].b); c->accs = 0; if (stat(c->keyfile,&stb) < 0) { err("stat %s: %s",c->keyfile,strerror(errno)); rv = LOAD_ERROR; free(c->keyfile); free(c); break <"nextline">; } if (stb.st_size > 65536) { err("%s: unreasonably large (%llu)",c->keyfile,(unsigned long long int)stb.st_size); rv = LOAD_ERROR; free(c->keyfile); free(c); break <"nextline">; } c->keylen = stb.st_size; c->keydata = malloc(c->keylen+1); fd = open(c->keyfile,O_RDONLY,0); if (fd < 0) { err("%s: %s",c->keyfile,strerror(errno)); rv = LOAD_ERROR; free(c->keydata); free(c->keyfile); free(c); break <"nextline">; } n = read(fd,c->keydata,c->keylen+1); if ( ( (n < 0) && ( err("reading key data from %s: %s", c->keyfile, strerror(errno)),1 ) ) || ( (n > c->keylen) && ( err("%s: file grew (size %d, read %d)", c->keyfile, c->keylen, n),1 ) ) || ( (n != c->keylen) && ( err("reading key data from %s: wanted %d, got %d", c->keyfile, c->keylen, n),1 ) ) ) { rv = LOAD_ERROR; free(c->keydata); free(c->keyfile); free(c); close(fd); break <"nextline">; } close(fd); vprf(1,"key data len %d\n",c->keylen); c->backingfile = blk_to_str(&l[be[0].b],be[0].e-be[0].b); c->bffd = open(c->backingfile,O_RDWR|O_CREAT,0600); if (c->bffd < 0) { err("%s: %s",c->backingfile,strerror(errno)); rv = LOAD_ERROR; free(c->backingfile); free(c->keydata); free(c->keyfile); free(c); break <"nextline">; } vprf(1,"backing file %s, fd %d\n",c->backingfile,c->bffd); c->conflicts = 0; if (nbe < 5) { if ( (be[0].e-be[0].b > 2) && (l[be[0].e-1] >= 'a') && (l[be[0].e-1] <= 'z') && FIXisdigit(l[be[0].e-2]) ) { add_conflict_tag(c,&l[be[0].b],be[0].e-be[0].b-1); } else { vprf(1,"no tag\n"); } } else { for (i=4;iconns = 0; c->tmp = 0; c->link = cf->clients; cf->clients = c; } a = acc_for( blk_to_str(&l[be[2].b],be[2].e-be[2].b), blk_to_str(&l[be[3].b],be[3].e-be[3].b), &accops_client, c ); a->link = c->accs; c->accs = a; vprf(1,"acc %s/%s\n",a->hoststr,a->portstr); } while (0); n = 0; } fclose(f); free(l); return(rv); } static int known_af(const struct sockaddr *sa, int salen) { switch (sa->sa_family) { case AF_INET: return(salen==sizeof(struct sockaddr_in)); break; case AF_INET6: return(salen==sizeof(struct sockaddr_in6)); break; } return(0); } static int same_sockaddr(const struct sockaddr *a, int alen, const struct sockaddr *b, int blen) { if (alen != blen) return(0); if (a->sa_family != b->sa_family) return(0); switch (a->sa_family) { case AF_INET: if (alen != sizeof(struct sockaddr_in)) return(0); 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: if (alen != sizeof(struct sockaddr_in6)) return(0); return( ( ((const struct sockaddr_in6 *)a)->sin6_port == ((const struct sockaddr_in6 *)b)->sin6_port ) && !bcmp( &((const struct sockaddr_in6 *)a)->sin6_addr.s6_addr[0], &((const struct sockaddr_in6 *)a)->sin6_addr.s6_addr[0], 16 ) ); break; } return(0); } static int same_file(const struct stat *a, const struct stat *b) { return( (a->st_dev == b->st_dev) && (a->st_ino == b->st_ino) && (a->st_gen == b->st_gen) ); } static void dump_client_list(CLIENT *c, FILE *to) { ACC *a; ACCSOCK *s; CONN *n; for (;c;c=c->link) { fprintf(to," %p: %s %s (bffd %d, keylen %d)\n",(void *)c,c->backingfile,c->keyfile,c->bffd,c->keylen); for (a=c->accs;a;a=a->link) { fprintf(to," acc %p: %s/%s\n",(void *)a,a->hoststr,a->portstr); for (s=a->socks;s;s=s->link) { fprintf(to," sock %p: fd=%d id=%d txt=%s\n",(void *)s,s->fd,s->id,s->txt); } } for (n=c->conns;n;n=n->link) { printf(" conn %p: s=%p c=%p state=%d[",(void *)n,(void *)n->s,(void *)n->c,n->state); switch (n->state) { case CS_RR_A: printf("RR_A"); break; case CS_RR_B: printf("RR_B"); break; case CS_RR_C: printf("RR_C"); break; case CS_UP: printf("UP"); break; case CS_DEAD: printf("DEAD"); break; default: printf("?""?"); break; } printf("] fd=%d id=%d\n",n->fd,n->id); } } } static int make_accsocks(ACC *a) { ACCSOCK *s; int e; int gnie; struct addrinfo hints; struct addrinfo *ai0; struct addrinfo *ai; char hn[NI_MAXHOST]; char sn[NI_MAXSERV]; hints.ai_flags = AI_PASSIVE; hints.ai_family = PF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = 0; hints.ai_addrlen = 0; hints.ai_canonname = 0; hints.ai_addr = 0; hints.ai_next = 0; e = getaddrinfo(strcmp(a->hoststr,"*")?a->hoststr:0,a->portstr,&hints,&ai0); if (e) { err("%s/%s: %s",a->hoststr,a->portstr,gai_strerror(e)); return(1); } if (! ai0) { err("%s/%s: successful lookup but no addresses?",a->hoststr,a->portstr); return(1); } for (ai=ai0;ai;ai=ai->ai_next) { int fd; if (! known_af(ai->ai_addr,ai->ai_addrlen)) continue; gnie = getnameinfo(ai->ai_addr,ai->ai_addrlen,&hn[0],NI_MAXHOST,&sn[0],NI_MAXSERV,NI_NUMERICHOST|NI_NUMERICSERV|NI_WITHSCOPEID); fd = socket(ai->ai_family,ai->ai_socktype,ai->ai_protocol); if (fd < 0) { e = errno; if (gnie) { err("socket [af %d - can't get address text: %s]: %s",ai->ai_family,strerror(gnie),strerror(e)); } else { err("socket (%s/%s): %s",&hn[0],&sn[0],strerror(e)); } continue; } s = malloc(sizeof(ACCSOCK)); s->link = a->socks; a->socks = s; s->a = a; s->addr = malloc(ai->ai_addrlen); bcopy(ai->ai_addr,s->addr,ai->ai_addrlen); s->addrlen = ai->ai_addrlen; s->fd = fd; s->id = PL_NOID; if (gnie) { asprintf(&s->txt,"[af%d: can't get address text: %s]",ai->ai_family,strerror(gnie)); } else { asprintf(&s->txt,"%s/%s",&hn[0],&sn[0]); } vprf(1,"added accsock %p [fd=%d txt=%s] to acc %p\n",(void *)s,s->fd,s->txt,(void *)a); } if (a->socks == 0) { err("%s/%s: no listening sockets",a->hoststr,a->portstr); return(1); } return(0); } static int maybe_move_accsock(ACCSOCK *old, ACCSOCK *new) { if (same_sockaddr(old->addr,old->addrlen,new->addr,new->addrlen)) { vprf(1,"moving fd %d from %p to %p, replacing %d\n",old->fd,(void *)old,(void *)new,new->fd); if (new->fd >= 0) close(new->fd); new->fd = old->fd; old->fd = -1; if (new->id != PL_NOID) remove_poll_id(new->id); new->id = old->id; old->id = PL_NOID; return(1); } return(0); } static void finish_accsock(ACCSOCK *s) { if (s->id == PL_NOID) { int v; v = 1; vprf(1,"doing bind-&-listen on %d, to %s\n",s->fd,s->txt); setsockopt(s->fd,SOL_SOCKET,SO_REUSEADDR,&v,sizeof(v)); if (bind(s->fd,(void *)s->addr,s->addrlen) < 0) { err("bind %s: %s",s->txt,strerror(errno)); close(s->fd); s->fd = -1; } else { listen(s->fd,10); fcntl(s->fd,F_SETFL,fcntl(s->fd,F_GETFL,0)|O_NONBLOCK); } } else { remove_poll_id(s->id); } s->id = (s->fd >= 0) ? add_poll_fd(s->fd,&rwtest_always,&rwtest_never,&acc_accept,0,s) : PL_NOID; } /* * We have a new config in clients and an old config in oldclients. * Switch over to using the new config. Return value is one of the * LOAD_ constants, as for loadconfig, except that LOAD_EMPTY's * meaning is different: it means that (possibly after discarding * clients because of errors) the new config is empty. The old config * is left untouched in this case, unless fstat() on an old backing * file returns an error. Also, LOAD_FATAL is not possible. * * The reason we restrict ourselves to address families we recognize is * that we need to be able to tell when two addresses match, and to do * that we have to go into the interior of the struct sockaddr. (If * all AFs used structs sockaddr such that one could just bcmp() them * to get a useful equality test, we could do that AF-independently. * But that's not promised, and I think it's even not so.) */ static int new_config(CONFIG *oldcf) { CLIENT **cp; CLIENT *c; CLIENT *oc; ACC **ap; ACC *a; ACC *oa; ACCSOCK *s; ACCSOCK *os; CONN **onp; CONN *on; int i; int rv; if (ISV(1)) { printf("new_config\nold client list:\n"); dump_client_list(oldcf?oldcf->clients:0,stdout); printf("new client list:\n"); dump_client_list(config.clients,stdout); } /* First, allocate a struct stat for each client's backing file. */ for (i=1;i>=0;i--) { if (i) { cp = &config.clients; } else { if (! oldcf) continue; cp = &oldcf->clients; } while ((c = *cp)) { free(c->tmp); c->tmp = malloc(sizeof(struct stat)); if (fstat(c->bffd,c->tmp) < 0) { err("fstat %s: %s (disabling client)",c->backingfile,strerror(errno)); *cp = c->link; free_one_client(c); } else { cp = &c->link; } } } vprf(1,"allocated structs stat\n"); /* Create accsocks for the new config. Also create the sockets but do not bind() or listen(). In the process, discard any references to AFs we don't recognize or can't create sockets for. */ rv = LOAD_OK; cp = &config.clients; while <"nextclient"> ((c = *cp)) { ap = &c->accs; while <"nextacc"> ((a = *ap)) { if (make_accsocks(a)) { rv = LOAD_ERROR; vprf(1,"error, dropping acc %p\n",(void *)a); *ap = a->link; free_one_acc(a); } else { ap = &a->link; } } if (c->accs == 0) { err("%s %s: no listening sockets",c->backingfile,c->keyfile); vprf(1,"deleting client %p\n",(void *)c); *cp = c->link; free_one_client(c); } else { cp = &c->link; } } ap = &config.admin; while <"nextacc"> ((a = *ap)) { if (make_accsocks(a)) { vprf(1,"error, dropping admin acc %p\n",(void *)a); *ap = a->link; free_one_acc(a); } else { ap = &a->link; } } /* If no clients left, bail. */ if ((rv != LOAD_OK) && !config.clients) { vprf(1,"bailing (empty with errors)\n"); return(LOAD_EMPTY); } /* We are now committed to switching configs. */ /* Move over existing connections when possible. */ if (oldcf) { for (oc=oldcf->clients;oc;oc=oc->link) { onp = &oc->conns; while <"nextconn"> ((on = *onp)) { vprf(1,"considering conn %p (s=%p c=%p)\n",(void *)on,(void *)on->s,(void *)on->c); for (c=config.clients;c;c=c->link) { if ( !same_file(c->tmp,on->c->tmp) || (c->keylen != on->c->keylen) || bcmp(c->keydata,on->c->keydata,c->keylen) ) continue; for (a=c->accs;a;a=a->link) { for (s=a->socks;s;s=s->link) { if (same_sockaddr(on->s->addr,on->s->addrlen,s->addr,s->addrlen)) { vprf(1,"moving conn %p (was s=%p c=%p) to s=%p c=%p\n",(void *)on,(void *)on->s,(void *)on->c,(void *)s,(void *)c); *onp = on->link; on->s = s; on->c = c; on->link = c->conns; c->conns = on; continue <"nextconn">; } } } } onp = &on->link; } } } /* Move over existing listening sockets. We want to do this to preserve queues of pending connections if nothing else. The code duplication here is a little annoying. Given the way the loops have to work, though, I don't see a better way short of doing something like implementing generators, which strikes me as overkill. After doing this, the poll IDs may be a bit broken, in that their cookies are the old ACCSOCKs, but that's fixed up below. */ if (oldcf) { for (oc=oldcf->clients;oc;oc=oc->link) { for (oa=oc->accs;oa;oa=oa->link) { for <"nextsock"> (os=oa->socks;os;os=os->link) { vprf(1,"considering accsock %p (acc=%p client=%p)\n",(void *)os,(void *)oa,(void *)oc); for (c=config.clients;c;c=c->link) { for (a=c->accs;a;a=a->link) { for (s=a->socks;s;s=s->link) { if (maybe_move_accsock(os,s)) continue <"nextsock">; } } } } } } for (oa=oldcf->admin;oa;oa=oa->link) { for <"nextsock"> (os=oa->socks;os;os=os->link) { vprf(1,"considering accsock %p (acc=%p admin)\n",(void *)os,(void *)oa); for (a=config.admin;a;a=a->link) { for (s=a->socks;s;s=s->link) { if (maybe_move_accsock(os,s)) continue <"nextsock">; } } } } } /* Move over backing-file file descriptors when possible. */ if (oldcf) { for (oc=oldcf->clients;oc;oc=oc->link) { for (c=config.clients;c;c=c->link) { if (! c->tmp) continue; if ( same_file(oc->tmp,c->tmp) && (oc->keylen == c->keylen) && !bcmp(oc->keydata,c->keydata,c->keylen) ) { vprf(1,"moving bffd %d from %p to %p, replacing %d\n",oc->bffd,(void *)oc,(void *)c,c->bffd); close(c->bffd); c->bffd = oc->bffd; oc->bffd = -1; free(c->tmp); c->tmp = 0; } } } } /* Okay, everything we can move over has been moved over. Create anything that's missing - specifically, finish setting up listening sockets for accsocks that didn't get them moved, lock backing files for which we didn't move a descriptor, and (re)set poll IDs for ACCSOCKs. */ cp = &config.clients; while <"nextclient"> ((c = *cp)) { for (a=c->accs;a;a=a->link) { for (s=a->socks;s;s=s->link) { finish_accsock(s); } } if (c->tmp) { vprf(1,"flocking %d (%s)\n",c->bffd,c->backingfile); if (flock(c->bffd,LOCK_EX|LOCK_NB) < 0) { err("%s is already locked",c->backingfile); *cp = c->link; free_one_client(c); continue <"nextclient">; } } cp = &c->link; } for (a=config.admin;a;a=a->link) { for (s=a->socks;s;s=s->link) { finish_accsock(s); } } if (ISV(1)) { printf("done\nold client list:\n"); dump_client_list(oldcf?oldcf->clients:0,stdout); printf("new client list:\n"); dump_client_list(config.clients,stdout); } if (oldcf) clear_config(oldcf); return(LOAD_OK); } static void weed_conflicts(void) { CONFLICT **cp; CONFLICT *c; for (cp=&conflicts;(c=*cp);) { if (c->clients) { cp = &c->link; } else { if (c->busy) panic("weed_conflicts busy"); *cp = c->link; free(c->tag); free(c); } } } static void setup(void) { int rv; struct rlimit rl; getrlimit(RLIMIT_NOFILE,&rl); if (rl.rlim_cur < rl.rlim_max) { rl.rlim_cur = rl.rlim_max; setrlimit(RLIMIT_NOFILE,&rl); } conflicts = 0; rndfd = open(rnddev,O_RDONLY,0); if (rndfd < 0) { fprintf(stderr,"%s: %s: %s\n",__progname,rnddev,strerror(errno)); exit(1); } signal(SIGPIPE,SIG_IGN); add_block_fn(&weed_conns,0); rv = loadconfig(&config); switch (rv) { default: panic("setup: bad loadconfig return value"); break; case LOAD_OK: break; case LOAD_EMPTY: fprintf(stderr,"%s: %s: empty config\n",__progname,configfile); /* fall through */ case LOAD_ERROR: case LOAD_FATAL: exit(1); break; } rv = new_config(0); switch (rv) { default: panic("setup: bad new_config return value"); break; case LOAD_OK: break; case LOAD_EMPTY: fprintf(stderr,"%s: %s: empty config\n",__progname,configfile); /* fall through */ case LOAD_ERROR: exit(1); break; } weed_conflicts(); admins = 0; } static void sighup_reload(void) { int rv; CONFIG oldconfig; oldconfig = config; rv = loadconfig(&config); switch (rv) { default: panic("sighup_reload: bad loadconfig return value"); break; case LOAD_OK: break; case LOAD_ERROR: if (config.clients) break; if (0) { case LOAD_EMPTY: fprintf(stderr,"%s: %s: empty config\n",__progname,configfile); } case LOAD_FATAL: clear_config(&config); config = oldconfig; return; break; } rv = new_config(&oldconfig); switch (rv) { default: panic("sighup_reload: bad new_config return value"); break; case LOAD_OK: break; case LOAD_EMPTY: fprintf(stderr,"%s: %s: empty config\n",__progname,configfile); clear_config(&config); config = oldconfig; return; break; } weed_conflicts(); } static void create_sighup_pipe(void); /* forward */ static int do_sighup(void *arg __attribute__((__unused__))) { vprf(1,"reloading config\n"); sighup_reload(); remove_block_id(sighup_block_id); sighup_block_id = PL_NOID; create_sighup_pipe(); return(1); } static void close_sighup_pipe(void) { int w; if (sighup_pipe_r >= 0) close(sighup_pipe_r); sighup_pipe_r = -1; w = sighup_pipe_w; /* If a SIGHUP arrives here, we may close the write end twice. But since no other fds can be opened in between, that doesn't hurt anythng; the later close will show EBADF, but we don't mind. */ if (w >= 0) close(w); sighup_pipe_w = -1; if (sighup_pipe_id != PL_NOID) remove_poll_id(sighup_pipe_id); sighup_pipe_id = PL_NOID; } static void rd_sighup(int id __attribute__((__unused__)), void *cv __attribute__((__unused__))) { vprf(1,"SIGHUP received\n"); close_sighup_pipe(); sighup_block_id = add_block_fn(&do_sighup,0); } static void create_sighup_pipe(void) { int p[2]; if (pipe(&p[0]) < 0) { fprintf(stderr,"%s: pipe: %s\n",__progname,strerror(errno)); exit(1); } sighup_pipe_r = p[0]; sighup_pipe_w = p[1]; sighup_pipe_id = add_poll_fd(sighup_pipe_r,&rwtest_always,&rwtest_never,&rd_sighup,0,0); } static void sighup_handler(int sig __attribute__((__unused__))) { int w; if (ISV(1)) write(1,"[SIGHUP]",8); w = sighup_pipe_w; if (w >= 0) { close(w); sighup_pipe_w = -1; } } static void setup_sighup(void) { struct sigaction sa; sa.sa_handler = &sighup_handler; sigemptyset(&sa.sa_mask); sa.sa_flags = 0; sigaction(SIGHUP,&sa,0); create_sighup_pipe(); } static int flush_stdio(void *arg __attribute__((__unused__))) { fflush(0); return(0); } int main(int, char **); int main(int ac, char **av) { handleargs(ac,av); timer_socket_init(); if (ISV(1)) { fflush(stdout); setlinebuf(stdout); } poll_init(); if (ISV(1)) add_block_fn(&flush_stdio,0); setup_sighup(); setup(); while (1) { pre_poll(); if (do_poll() < 0) { if (errno == EINTR) continue; fprintf(stderr,"%s: poll: %s\n",__progname,strerror(errno)); exit(1); } post_poll(); } }