diff --git a/src/3proxy.c b/src/3proxy.c index c229fca..cd44c5b 100644 --- a/src/3proxy.c +++ b/src/3proxy.c @@ -73,13 +73,13 @@ void __stdcall CommandHandler( DWORD dwCommand ) case SERVICE_CONTROL_SHUTDOWN: SetStatus( SERVICE_STOP_PENDING, 0, 1 ); timetoexit = 1; - conf->paused++; + runconf->paused++; /* ServiceMain() reports the service stopped once cyclestep() has seen this and the services are done */ break; case SERVICE_CONTROL_PAUSE: SetStatus( SERVICE_PAUSE_PENDING, 0, 1 ); - conf->paused++; + runconf->paused++; SetStatus( SERVICE_PAUSED, 0, 0 ); break; case SERVICE_CONTROL_CONTINUE: @@ -120,9 +120,11 @@ void mysigusr1 (int sig){ int even = 0; +/* Signals may come while a reload reads the file: they act on the + configuration in force. */ void mysigpause (int sig){ - conf->paused++; + runconf->paused++; even = !even; if(!even){ needreload = 1; @@ -130,7 +132,7 @@ void mysigpause (int sig){ } void mysigterm (int sig){ - conf->paused++; + runconf->paused++; timetoexit = 1; } @@ -554,6 +556,7 @@ int WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nsopen(); /* before a chroot in the configuration takes /proc away */ #endif _3proxy_mutex_init(&config_mutex); + _3proxy_mutex_init(&confref_mutex); _3proxy_mutex_init(&runningservices_mutex); _3proxy_mutex_init(&bandlim_mutex); _3proxy_mutex_init(&connlim_mutex); @@ -581,10 +584,11 @@ int WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int httpsrv_init(); #endif - freeconf(conf); + confbegin(); initcommands(); res = readconfig(fp); conf->version++; + resolvfunc = conf->resolvfunc; /* readconfig() names the failing line, which an exit status would cut to 8 bits: a failed configuration exits with 1 */ diff --git a/src/auth.c b/src/auth.c index ac0b9de..9bdf8e9 100644 --- a/src/auth.c +++ b/src/auth.c @@ -245,28 +245,30 @@ int strongauth(struct clientparam * param){ if (!*param->username) return 4; if (!param->pwtype && *param->password) { - if (pwl_table.ihashtable && hashresolv(&pwl_table, param->username, pass, NULL)) { + struct hashtable *pwl = ¶m->srv->config->pwl_table; + + if (pwl->ihashtable && hashresolv(pwl, param->username, pass, NULL)) { switch(pass[0]){ case CL: { int pwlen = strlen((char *)param->password); if(pwlen > 255) pwlen = 255; - if((unsigned)pwlen < pwl_table.recsize) { - memset(buf, 0, pwl_table.recsize - 1); + if((unsigned)pwlen < pwl->recsize) { + memset(buf, 0, pwl->recsize - 1); memcpy(buf, param->password, pwlen); - if(!ctmemcmp(pass + 1, buf, pwl_table.recsize - 1)) return 0; + if(!ctmemcmp(pass + 1, buf, pwl->recsize - 1)) return 0; } else { mdh_ctx *bctx; unsigned hashsz; unsigned int blen; - hashsz = pwl_table.recsize - 1 < 64 ? pwl_table.recsize - 1 : 64; - memset(buf, 0, pwl_table.recsize - 1); + hashsz = pwl->recsize - 1 < 64 ? pwl->recsize - 1 : 64; + memset(buf, 0, pwl->recsize - 1); bctx = mdh_init(MDH_BLAKE2, hashsz); if(!bctx) return 6; mdh_update(bctx, param->password, pwlen + 1); blen = hashsz; mdh_final(bctx, buf, &blen); mdh_free(bctx); - if(!ctmemcmp(pass + 1, buf, pwl_table.recsize - 1)) return 0; + if(!ctmemcmp(pass + 1, buf, pwl->recsize - 1)) return 0; } return 6; } diff --git a/src/common.c b/src/common.c index e5bc614..b2dffc1 100644 --- a/src/common.c +++ b/src/common.c @@ -228,7 +228,12 @@ const struct extparam confdefaults = { struct extparam confboot = { #include "confinit.h" }; +/* conf is the configuration commands act on: the one being read during a + reload, the one in force otherwise. runconf is the one in force, for + whatever runs meanwhile without a service of its own. */ struct extparam *conf = &confboot; +struct extparam *runconf = &confboot; +_3proxy_mutex_t confref_mutex; char* NULLADDR="\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"; diff --git a/src/conf.c b/src/conf.c index 2b2d94d..7693dbb 100644 --- a/src/conf.c +++ b/src/conf.c @@ -186,21 +186,40 @@ unsigned char * dologname (unsigned char *buf, unsigned char *name, const unsign return buf; } +static int deferred_add(void *service); +static int startservice(void *service); + /* The service is set up here, in the thread reading the configuration, so an error in its options stops the configuration; its own thread then - listens and serves. */ + listens and serves. During a reload it is started only once the whole + file has been read. */ int start_proxy_thread(struct child * chp){ - pthread_t thread; void *service; int res; -#ifdef _WIN32 - HANDLE h; -#endif if((res = mainprepare(chp->argc, (char **)chp->argv, &service))) { fprintf(stderr, "Service not started on line: %d%s\n", linenum, res == 2? ": insufficient memory": res == 3? ": too many filters": res == 4? ": network namespace unavailable":""); return(40); } + switch(deferred_add(service)){ + case 1: + return 0; + case -1: + maindrop(service); + fprintf(stderr, "Service not started on line: %d: insufficient memory\n", linenum); + return(40); + } + return startservice(service); +} + +/* Starts the thread of a prepared service; 40 if it could not be. */ +static int startservice(void *service){ + pthread_t thread; + int res; +#ifdef _WIN32 + HANDLE h; +#endif + _3proxy_mutex_lock(&runningservices_mutex); runningservices++; _3proxy_mutex_unlock(&runningservices_mutex); @@ -244,7 +263,7 @@ static int h_proxy(int argc, unsigned char ** argv){ childdef.helpmessage = " -Xftp - fetch ftp:// URLs\n"; #endif #ifdef NOIPV6 - if(!resolvfunc || (resolvfunc == myresolver && !dns_table.poolsize)){ + if(!conf->resolvfunc || (conf->resolvfunc == myresolver && !dns_table.poolsize)){ fprintf(stderr, "[line %d] Warning: no nserver/nscache configured, proxy may run very slow\n", linenum); } #endif @@ -283,7 +302,7 @@ static int h_proxy(int argc, unsigned char ** argv){ childdef.isudp = 0; childdef.service = S_SOCKS; #ifdef NOIPV6 - if(!resolvfunc || (resolvfunc == myresolver && !dns_table.poolsize)){ + if(!conf->resolvfunc || (conf->resolvfunc == myresolver && !dns_table.poolsize)){ fprintf(stderr, "[line %d] Warning: no nserver/nscache configured, socks may run very slow\n", linenum); } #endif @@ -349,7 +368,7 @@ static int h_proxy(int argc, unsigned char ** argv){ childdef.service = S_DNSPR; childdef.helpmessage = " -s - simple DNS forwarding - do not use 3proxy resolver / name cache\n -Fip - fake: answer all A queries with this IP\n"; #ifndef NOIPV6 - if(!resolvfunc || (resolvfunc == myresolver && !dns_table.poolsize) || resolvfunc == fakeresolver){ + if(!conf->resolvfunc || (conf->resolvfunc == myresolver && !dns_table.poolsize) || conf->resolvfunc == fakeresolver){ fprintf(stderr, "[line %d] Warning: no nserver/nscache configured, dnspr will not work as expected\n", linenum); } #endif @@ -396,7 +415,7 @@ static int h_log(int argc, unsigned char ** argv){ int notchanged = 0; - havelog = 1; + conf->havelog = 1; if(argc > 1 && conf->logtarget && !strcmp((char *)conf->logtarget, (char *)argv[1])) { notchanged = 1; } @@ -620,8 +639,10 @@ static int h_users(int argc, unsigned char **argv){ *arg = 0; pw[0] = (char *)argv[j]; - if (!pwl_table.ihashtable && inithashtable(&pwl_table, 16, 32, 1048576)) - return 3; + if (!conf->pwl_table.ihashtable){ + conf->pwl_table = pwl_template; + if(inithashtable(&conf->pwl_table, 16, 32, 1048576)) return 3; + } memset(pass, 0, sizeof(pass)); if (arg[1] && arg[2] && arg[3] == ':') { pw[1] = (char *)(arg + 4); @@ -646,12 +667,12 @@ static int h_users(int argc, unsigned char **argv){ } l = strlen(pw[1]); if(l > 255) l = 255; - if((unsigned)l >= pwl_table.recsize) { + if((unsigned)l >= conf->pwl_table.recsize) { mdh_ctx *bctx; unsigned hashsz; unsigned int blen; if(*pass != CL) continue; - hashsz = pwl_table.recsize - 1 < 64 ? pwl_table.recsize - 1 : 64; + hashsz = conf->pwl_table.recsize - 1 < 64 ? conf->pwl_table.recsize - 1 : 64; bctx = mdh_init(MDH_BLAKE2, hashsz); if(!bctx) continue; mdh_update(bctx, pw[1], l + 1); @@ -661,7 +682,7 @@ static int h_users(int argc, unsigned char **argv){ } else { memcpy(pass + 1, pw[1], l); } - hashadd(&pwl_table, pw[0], pass, MAX_COUNTER_TIME); + hashadd(&conf->pwl_table, pw[0], pass, MAX_COUNTER_TIME); } return 0; } @@ -723,7 +744,7 @@ static int h_nserver(int argc, unsigned char **argv){ conf->numservers++; } - resolvfunc = myresolver; + conf->resolvfunc = myresolver; return 0; } @@ -742,7 +763,7 @@ static int h_authnserver(int argc, unsigned char **argv){ } static int h_fakeresolve(int argc, unsigned char **argv){ - resolvfunc = fakeresolver; + conf->resolvfunc = fakeresolver; return 0; } @@ -2392,6 +2413,8 @@ void freepwl(struct passwords *pwl){ } +/* Releases what a configuration holds, once nothing uses it. Nothing global + is touched: a configuration may be released while another is in force. */ void freeconf(struct extparam *confp){ struct bandlim * bl; struct bandlim * blout; @@ -2402,12 +2425,8 @@ void freeconf(struct extparam *confp){ int counterd, archiverc; unsigned char **archiver; unsigned char * logformat; - int i; - - - _3proxy_mutex_lock(&tc_mutex); confp->trafcountfunc = NULL; tc = confp->trafcounter; @@ -2430,52 +2449,24 @@ void freeconf(struct extparam *confp){ confp->connlimiter = NULL; _3proxy_mutex_unlock(&connlim_mutex); - destroyhashtable(&pwl_table); - + if(confp->pwl_table.ihashtable) destroyhashtable(&confp->pwl_table); confp->logfunc = lognone; logformat = confp->logformat; confp->logformat = NULL; - confp->rotate = 0; - confp->logtype = NONE; - confp->logtime = curtime = 0; - confp->poolmax = -1; - confp->pooltimeout = 60; - archiverc = confp->archiverc; confp->archiverc = 0; archiver = confp->archiver; confp->archiver = NULL; fm = confp->fmon; confp->fmon = NULL; - confp->bandlimfunc = NULL; - memset(&confp->intsa, 0, sizeof(confp->intsa)); - memset(&confp->extsa, 0, sizeof(confp->extsa)); -#ifndef NOIPV6 - memset(&confp->extsa6, 0, sizeof(confp->extsa6)); - *SAFAMILY(&confp->extsa6) = AF_INET6; -#endif - *SAFAMILY(&confp->intsa) = AF_INET; - *SAFAMILY(&confp->extsa) = AF_INET; - confp->maxchild = DEFAULT_MAXCHILD; - confp->backlog = 0; - resolvfunc = NULL; - conf->numservers = 0; acl = confp->acl; confp->acl = NULL; - usleep(SLEEPTIME); - - { - char * args[] = {"auth", "iponly", NULL}; - h_auth(2, (unsigned char **)args); - } if(tc)dumpcounters(tc,counterd); for(; tc; tc = (struct trafcount *) itfree(tc, tc->next)){ if(tc->comment)free(tc->comment); freeacl(tc->ace); } - - freeacl(acl); for(; bl; bl = (struct bandlim *) itfree(bl, bl->next)) freeacl(bl->ace); for(; blout; blout = (struct bandlim *) itfree(blout, blout->next))freeacl(blout->ace); @@ -2494,14 +2485,110 @@ void freeconf(struct extparam *confp){ for(i = 0; i < archiverc; i++) free(archiver[i]); free(archiver); } - havelog = 0; + if(confp->authfuncs) freeauth(confp->authfuncs); + confp->authfuncs = NULL; + freepwl(confp->pwl); + confp->pwl = NULL; +#ifdef WITH_HTTPSRV + if(confp->httprules) freehttprules(confp->httprules); + confp->httprules = NULL; +#endif + if(confp->logname) free(confp->logname); + confp->logname = NULL; + if(confp->logtarget) free(confp->logtarget); + confp->logtarget = NULL; + if(confp->counterfile) free(confp->counterfile); + confp->counterfile = NULL; + if(confp->conffile) free(confp->conffile); + confp->conffile = NULL; +} + +/* What every reading of the file starts from, before its first command. */ +void confbegin(void){ + char * args[] = {"auth", "iponly", NULL}; + + h_auth(2, (unsigned char **)args); +} + +/* The configuration in force, held until confput(): for what uses one + without a service of its own, the resolver for one. */ +struct extparam * confget(void){ + struct extparam *c; + + _3proxy_mutex_lock(&confref_mutex); + c = runconf; + c->refs++; + _3proxy_mutex_unlock(&confref_mutex); + return c; +} + +void confhold(struct extparam *c){ + _3proxy_mutex_lock(&confref_mutex); + c->refs++; + _3proxy_mutex_unlock(&confref_mutex); +} + +/* The last to let go of a configuration no longer in force releases it. */ +void confput(struct extparam *c){ + int gone; + + _3proxy_mutex_lock(&confref_mutex); + gone = (--c->refs <= 0 && c != runconf); + _3proxy_mutex_unlock(&confref_mutex); + if(gone){ + freeconf(c); + if(c != &confboot) free(c); + } +} + +/* During a reload the services are prepared as their lines are read, but + started only once the whole file has been read without an error. */ +struct deferred { + void *service; + int linenum; + struct deferred *next; +}; +static struct deferred *deferred = NULL; +static int deferring = 0; + +/* Keeps a prepared service for later if a reload is reading the file: 1, + or 0 if none is, and the service is to be started now, -1 for want of + memory. The services start in the order of their lines. */ +static int deferred_add(void *service){ + struct deferred *d, **dp; + + if(!deferring) return 0; + if(!(d = malloc(sizeof(*d)))) return -1; + d->service = service; + d->linenum = linenum; + d->next = NULL; + for(dp = &deferred; *dp; dp = &(*dp)->next); + *dp = d; + return 1; } int reload (void){ FILE *fp; int error = -2; + struct extparam *old = conf, *new; + struct deferred *d; + + if(!(new = malloc(sizeof(*new)))) return -1; + *new = confdefaults; + /* what belongs to the process rather than to one reading of the file: + the open log, which a log command reopens, and the filters, a list + of static entries the TLS, PCRE and transparent code link in once */ + new->conffile = old->conffile; + new->stringtable = old->stringtable; + new->demon = old->demon; + new->stdlog = old->stdlog; + new->filters = old->filters; + new->version = old->version + 1; + new->refs = 1; /* this reload's, until it is done with it */ _3proxy_mutex_lock(&config_mutex); + conf = new; + pluginlink.conf = new; #ifdef WITH_SSL ssl_install(); #endif @@ -2511,19 +2598,56 @@ int reload (void){ #ifdef WITH_TRANSPARENT transparent_install(); #endif - conf->paused++; - freeconf(conf); - conf->paused++; - + confbegin(); + deferring = 1; fp = confopen(); if(fp){ error = readconfig(fp); - conf->version++; - if(error) { - freeconf(conf); - } fclose(fp); } + deferring = 0; + + if(error){ + /* the old configuration stays in force, its services running: + what was prepared for the new one is dropped */ + while((d = deferred)){ + deferred = d->next; + maindrop(d->service); + free(d); + } + old->conffile = new->conffile; + old->stdlog = new->stdlog; + old->filters = new->filters; + new->conffile = NULL; + new->stdlog = NULL; + new->filters = NULL; + new->stringtable = NULL; + conf = old; + pluginlink.conf = old; + _3proxy_mutex_unlock(&config_mutex); + confput(new); + return error; + } + + old->conffile = NULL; + old->stdlog = NULL; + old->filters = NULL; + old->stringtable = NULL; + confhold(old); + _3proxy_mutex_lock(&confref_mutex); + runconf = new; + _3proxy_mutex_unlock(&confref_mutex); + resolvfunc = new->resolvfunc; + /* the old services stop accepting, the new ones start */ + old->paused++; + while((d = deferred)){ + deferred = d->next; + linenum = d->linenum; + startservice(d->service); + free(d); + } _3proxy_mutex_unlock(&config_mutex); - return error; + confput(new); + confput(old); + return 0; } diff --git a/src/datatypes.c b/src/datatypes.c index ad08111..4571716 100644 --- a/src/datatypes.c +++ b/src/datatypes.c @@ -514,7 +514,7 @@ static void * ef_server_next(struct node * node){ static void * ef_server_type(struct node * node){ int service = ((struct srvparam *)node->value) -> service; - return (service>=0 && service < MAX_SERVICE)? (void *)conf->stringtable[SERVICES + service] : (void *)"unknown"; + return (service>=0 && service < MAX_SERVICE && runconf->stringtable)? (void *)runconf->stringtable[SERVICES + service] : (void *)"unknown"; } static void * ef_server_child(struct node * node){ @@ -543,7 +543,7 @@ static void * ef_pool_limit(struct node * node){ } static void * ef_pool_timeout(struct node * node){ - return &conf->pooltimeout; + return &runconf->pooltimeout; } static void * ef_pool_created(struct node * node){ @@ -693,7 +693,7 @@ static void * ef_client_next(struct node * node){ static void * ef_client_type(struct node * node){ int service = ((struct clientparam *)node->value) -> service; - return (service>=0 && service < MAX_SERVICE)? (void *)conf->stringtable[SERVICES + service] : (void *)"unknown"; + return (service>=0 && service < MAX_SERVICE && runconf->stringtable)? (void *)runconf->stringtable[SERVICES + service] : (void *)"unknown"; } static void * ef_client_operation(struct node * node){ diff --git a/src/hashtables.c b/src/hashtables.c index f52b8c9..58a1d91 100644 --- a/src/hashtables.c +++ b/src/hashtables.c @@ -79,4 +79,5 @@ void param2hash_search(const struct hashtable *ht, void *index, uint8_t *hash){ struct hashtable dns_table = {char_index2hash, char_index2hash, 4, 32}; struct hashtable dns6_table = {char_index2hash, char_index2hash, 16, 32}; struct hashtable auth_table = {param2hash_add, param2hash_search, sizeof(struct authcache), 64}; -struct hashtable pwl_table = {char_index2hash, char_index2hash, 64, 64}; +/* users: each configuration has a table of its own, made from this one */ +const struct hashtable pwl_template = {char_index2hash, char_index2hash, 64, 64}; diff --git a/src/log.c b/src/log.c index 6cfca08..b498556 100644 --- a/src/log.c +++ b/src/log.c @@ -11,7 +11,6 @@ #include "proxy.h" _3proxy_mutex_t log_mutex; -int havelog = 0; struct clientparam logparam; @@ -69,7 +68,12 @@ int dobuf2(struct clientparam * param, unsigned char * buf, const unsigned char struct timezone tz; #endif - if(!logparam.srv) srvinit(&logsrv, &logparam); + if(!logparam.srv) { + srvinit(&logsrv, &logparam); + /* not a service: it holds no configuration, and the first one + is never freed, only what it holds */ + logsrv.config = &confboot; + } if(!param) param = &logparam; @@ -189,9 +193,9 @@ int dobuf2(struct clientparam * param, unsigned char * buf, const unsigned char case 'N': if(param->service <= MAX_SERVICE) { - len = (conf->stringtable)? (int)strlen((char *)conf->stringtable[SERVICES + param->service]) : 0; + len = (runconf->stringtable)? (int)strlen((char *)runconf->stringtable[SERVICES + param->service]) : 0; if(len > 20) len = 20; - memcpy(buf+i, (len)?conf->stringtable[SERVICES + param->service]:(unsigned char*)"-", (len)?len:1); + memcpy(buf+i, (len)?runconf->stringtable[SERVICES + param->service]:(unsigned char*)"-", (len)?len:1); i += (len)?len:1; } break; @@ -345,11 +349,11 @@ void logstdout(struct clientparam * param, const unsigned char *s) { /* with no connection, an error from the main thread */ tmpbuf = param? param->tmpbuf : logparam.tmpbuf; dobuf(param, tmpbuf, s, NULL); - log = (param && param->srv && param->srv->stdlog)?param->srv->stdlog:conf->stdlog?conf->stdlog:stdout; + log = (param && param->srv && param->srv->stdlog)?param->srv->stdlog:runconf->stdlog?runconf->stdlog:stdout; if(!param || !param->nolog)if(fprintf(log, "%s\n", tmpbuf) < 0) { perror("printf()"); }; - if(log != conf->stdlog)fflush(log); + if(log != runconf->stdlog)fflush(log); } #ifndef _WIN32 void logsyslog(struct clientparam * param, const unsigned char *s) { diff --git a/src/proxy.c b/src/proxy.c index 65b91a0..e58e31a 100644 --- a/src/proxy.c +++ b/src/proxy.c @@ -1048,7 +1048,7 @@ for(;;){ hlen += sprintf((char*)buf + hlen, "Via: 1.1 "); gethostname((char *)(buf + hlen), 256); hlen += (int)strlen((char *)(buf + hlen)); - hlen += sprintf((char*)buf + hlen, ":%d (%s %s)\r\nX-Forwarded-For: ", (int)ntohs(*SAPORT(¶m->srv->intsa)), conf->stringtable?conf->stringtable[2]:(unsigned char *)"", conf->stringtable?conf->stringtable[3]:(unsigned char *)""); + hlen += sprintf((char*)buf + hlen, ":%d (%s %s)\r\nX-Forwarded-For: ", (int)ntohs(*SAPORT(¶m->srv->intsa)), runconf->stringtable?runconf->stringtable[2]:(unsigned char *)"", runconf->stringtable?runconf->stringtable[3]:(unsigned char *)""); if(anonymous != 2)hlen += myinet_ntop(*SAFAMILY(¶m->sincr), SAADDR(¶m->sincr), (char *)buf + hlen, 128); else { uint32_t tmp = myrand(); diff --git a/src/proxy.h b/src/proxy.h index 710604a..986b8e2 100644 --- a/src/proxy.h +++ b/src/proxy.h @@ -225,7 +225,11 @@ extern time_t basetime; extern int timetoexit, needreload; extern time_t curtime; /* the clock, kept by the main thread */ -extern struct extparam *conf; +extern struct extparam *conf, *runconf; +extern _3proxy_mutex_t confref_mutex; +struct extparam * confget(void); +void confhold(struct extparam *c); +void confput(struct extparam *c); extern const struct extparam confdefaults; extern struct extparam confboot; @@ -383,7 +387,6 @@ int parseconnusername(char *username, struct clientparam *param, int extpasswd, int ACLmatches(struct ace* acentry, struct clientparam * param); int checkACL(struct clientparam * param); char * aceaction (int action); -extern int havelog; uint32_t udpresolve(int af, unsigned char * name, unsigned char * value, uint32_t *retttl, struct clientparam* param, int makeauth); struct ace * copyacl (struct ace *ac); @@ -393,6 +396,7 @@ void freeacl(struct ace *ac); void freeauth(struct auth *); void freefilter(struct filter *filter); void freeconf(struct extparam *confp); +void confbegin(void); struct passwords * copypwl (struct passwords *pwl); void freepwl(struct passwords *pw); int copyfilter(struct filter *, struct srvparam *srv); diff --git a/src/proxymain.c b/src/proxymain.c index eff6ac7..bac67dc 100644 --- a/src/proxymain.c +++ b/src/proxymain.c @@ -148,7 +148,7 @@ static void freeworker(struct clientparam *param){ static unsigned poollimit(void){ unsigned limit; - if(conf->poolmax >= 0) limit = (unsigned)conf->poolmax; + if(runconf->poolmax >= 0) limit = (unsigned)runconf->poolmax; else { limit = 32 * tpool.services; if(limit > tpool.maxchild) limit = tpool.maxchild; @@ -173,7 +173,7 @@ static int poolpark(struct clientparam *param){ tpool.idle = param; tpool.nidle++; param->pooled = 1; - timeout = conf->pooltimeout; + timeout = runconf->pooltimeout; _3proxy_mutex_unlock(&tpool.mutex); if(timeout <= 0) _3proxy_sem_lock(param->wake); @@ -400,7 +400,7 @@ static void srvdiscard(struct srvstart *s){ static void srvunlink(struct srvparam *srv){ if(srv->next)srv->next->prev = srv->prev; if(srv->prev)srv->prev->next = srv->next; - else conf->services = srv->next; + else srv->config->services = srv->next; srv->next = srv->prev = NULL; } #endif @@ -479,6 +479,9 @@ static int srvprepare(struct srvstart *s, int argc, char **argv){ #endif #endif srvinit(srv, defparam); +#ifndef STDMAIN + confhold(srv->config); /* until srvfree() */ +#endif srv->pf = childdef.pf; s->isudp = childdef.isudp; @@ -1486,7 +1489,7 @@ void srvinit(struct srvparam * srv, struct clientparam *param){ memset(srv, 0, sizeof(struct srvparam)); srv->version = conf->version + 1; srv->paused = conf->paused; - srv->logfunc = havelog?conf->logfunc:lognone; + srv->logfunc = conf->havelog?conf->logfunc:lognone; srv->noforce = conf->noforce; srv->logformat = conf->logformat? (unsigned char *)strdup((char *)conf->logformat) : NULL; srv->authfunc = conf->authfunc; @@ -1500,7 +1503,7 @@ void srvinit(struct srvparam * srv, struct clientparam *param){ srv->maxseg = conf->maxseg; srv->delimchar = conf->delimchar; srv->time_start = time(NULL); - if(havelog && conf->logtarget){ + if(conf->havelog && conf->logtarget){ srv->logtarget = (unsigned char *)strdup((char *)conf->logtarget); } srv->srvsock = INVALID_SOCKET; @@ -1631,6 +1634,15 @@ void srvfree(struct srvparam * srv){ #ifndef NOUDPMAIN if(srv->udpbuf) free(srv->udpbuf); if(srv->udpbuf2) free(srv->udpbuf2); +#endif + /* the last service of a configuration no longer in force releases it */ +#ifndef STDMAIN + if(srv->config){ + struct extparam *c = srv->config; + + srv->config = NULL; + confput(c); + } #endif } diff --git a/src/resolve.c b/src/resolve.c index 4df0ede..4c88b5c 100644 --- a/src/resolve.c +++ b/src/resolve.c @@ -15,7 +15,7 @@ static int dnsqmatch(const unsigned char *a, const unsigned char *b, int len){ } -uint32_t udpresolve(int af, unsigned char * name, unsigned char * value, uint32_t *retttl, struct clientparam* param, int makeauth){ +static uint32_t udpresolvec(struct extparam *c, int af, unsigned char * name, unsigned char * value, uint32_t *retttl, struct clientparam* param, int makeauth){ int i,n; uint32_t retval; @@ -26,7 +26,7 @@ uint32_t udpresolve(int af, unsigned char * name, unsigned char * value, uint32_ if((af == AF_INET6) && (retval = hashresolv(&dns6_table, name, value, retttl))) { return retval; } - n = (makeauth && !SAISNULL(&conf->authnserver.addr))? 1 : conf->numservers; + n = (makeauth && !SAISNULL(&c->authnserver.addr))? 1 : c->numservers; for(i=0; iauthnserver.addr)){ - usetcp = conf->authnserver.usetcp; - *SAFAMILY(sinsl) = *SAFAMILY(&conf->authnserver.addr); + if(makeauth && !SAISNULL(&c->authnserver.addr)){ + usetcp = c->authnserver.usetcp; + *SAFAMILY(sinsl) = *SAFAMILY(&c->authnserver.addr); } else { - usetcp = conf->nservers[i].usetcp; - *SAFAMILY(sinsl) = *SAFAMILY(&conf->nservers[i].addr); + usetcp = c->nservers[i].usetcp; + *SAFAMILY(sinsl) = *SAFAMILY(&c->nservers[i].addr); } if((sock=so._socket(so.state, SASOCK(sinsl), usetcp?SOCK_STREAM:SOCK_DGRAM, usetcp?IPPROTO_TCP:IPPROTO_UDP)) == INVALID_SOCKET) break; if(so._bind(so.state, sock,(struct sockaddr *)sinsl,SASIZE(sinsl))){ @@ -63,14 +63,14 @@ uint32_t udpresolve(int af, unsigned char * name, unsigned char * value, uint32_ so._closesocket(so.state, sock); break; } - if(makeauth && !SAISNULL(&conf->authnserver.addr)){ - *sinsr = conf->authnserver.addr; + if(makeauth && !SAISNULL(&c->authnserver.addr)){ + *sinsr = c->authnserver.addr; } else { - *sinsr = conf->nservers[i].addr; + *sinsr = c->nservers[i].addr; } if(usetcp){ - if(connectwithpoll(NULL, sock,(struct sockaddr *)sinsr,SASIZE(sinsr),conf->timeouts[CONNECT_TO])) { + if(connectwithpoll(NULL, sock,(struct sockaddr *)sinsr,SASIZE(sinsr),c->timeouts[CONNECT_TO])) { so._shutdown(so.state, sock, SHUT_RDWR); so._closesocket(so.state, sock); break; @@ -115,13 +115,13 @@ uint32_t udpresolve(int af, unsigned char * name, unsigned char * value, uint32_ len+=2; } - if(socksendto(NULL, sock, (struct sockaddr *)sinsr, buf, len, conf->timeouts[SINGLEBYTE_L]*1000) != len){ + if(socksendto(NULL, sock, (struct sockaddr *)sinsr, buf, len, c->timeouts[SINGLEBYTE_L]*1000) != len){ so._shutdown(so.state, sock, SHUT_RDWR); so._closesocket(so.state, sock); continue; } if(param) param->statscli64 += len; - len = sockrecvfrom(NULL, sock, (struct sockaddr *)&from, buf, 4096, conf->timeouts[DNS_TO]*1000); + len = sockrecvfrom(NULL, sock, (struct sockaddr *)&from, buf, 4096, c->timeouts[DNS_TO]*1000); if(len > 13 && usetcp){ unsigned short us; us = ntohs(*(unsigned short*)buf); @@ -129,7 +129,7 @@ uint32_t udpresolve(int af, unsigned char * name, unsigned char * value, uint32_ buf+=2; if(us > 4096 || us < len) len = 0; else if(us > len){ - if(sockrecvfrom(NULL, sock, (struct sockaddr *)&from, buf+len, us-len, conf->timeouts[DNS_TO]*1000) != us-len) len = 0; + if(sockrecvfrom(NULL, sock, (struct sockaddr *)&from, buf+len, us-len, c->timeouts[DNS_TO]*1000) != us-len) len = 0; else len = us; } } @@ -202,13 +202,32 @@ uint32_t udpresolve(int af, unsigned char * name, unsigned char * value, uint32_ /* a name longer than the field is no name to authenticate */ if(PARAMSTR(param, username, buf + k + 13)) return 0; - return udpresolve(af,param->username, value, NULL, NULL, 2); + return udpresolvec(c, af,param->username, value, NULL, NULL, 2); } } } return 0; } +/* A client resolves with its service's name servers, which its service + holds; anything else with those in force, held while it waits. */ +uint32_t udpresolve(int af, unsigned char * name, unsigned char * value, uint32_t *retttl, struct clientparam* param, int makeauth){ + struct extparam *c; + uint32_t res; + + if(param && param->srv && param->srv->config) + return udpresolvec(param->srv->config, af, name, value, retttl, param, makeauth); +#ifdef STDMAIN + c = conf; + return udpresolvec(c, af, name, value, retttl, param, makeauth); +#else + c = confget(); + res = udpresolvec(c, af, name, value, retttl, param, makeauth); + confput(c); + return res; +#endif +} + uint32_t myresolver(int af, unsigned char * name, unsigned char * value){ return udpresolve(af, name, value, NULL, NULL, 0); } diff --git a/src/structures.h b/src/structures.h index 17e82b0..52b7de9 100644 --- a/src/structures.h +++ b/src/structures.h @@ -909,6 +909,12 @@ struct extparam { struct radserver radiuslist[MAXRADIUS]; int nradservers; char radiussecret[64]; + /* the services which use it and whoever else holds it (confhold()): + freed when none does and it is no longer the one in force */ + int refs; + struct hashtable pwl_table; /* users */ + int havelog; /* log was configured */ + RESOLVFUNC resolvfunc; /* nserver or fakeresolve, in force once committed */ }; struct property { @@ -974,7 +980,7 @@ struct child { extern struct hashtable dns_table; extern struct hashtable dns6_table; extern struct hashtable auth_table; -extern struct hashtable pwl_table; +extern const struct hashtable pwl_template; extern struct hashtable udp_table; struct authcache { diff --git a/src/webadmin.c b/src/webadmin.c index dcf01ce..7f1eb67 100644 --- a/src/webadmin.c +++ b/src/webadmin.c @@ -353,9 +353,9 @@ static char * admin_open(struct printparam *pp, struct clientparam *param) buf = malloc(LINESIZE); if(!buf) return NULL; - sprintf(buf, ok, conf->stringtable?(char *)conf->stringtable[2]:"3proxy", - conf->stringtable?(char *)conf->stringtable[2]:"3[APA3A] tiny proxy", - conf->stringtable?(char *)conf->stringtable[3]:""); + sprintf(buf, ok, runconf->stringtable?(char *)runconf->stringtable[2]:"3proxy", + runconf->stringtable?(char *)runconf->stringtable[2]:"3[APA3A] tiny proxy", + runconf->stringtable?(char *)runconf->stringtable[3]:""); printstr(pp, buf); return buf; } @@ -374,7 +374,7 @@ int op_admin(struct httpreq *r, const unsigned char *params) buf = admin_open(&pp, r->param); if(!buf) return 1; - printstr(&pp, (char *)conf->stringtable[WEBBANNERS]); + printstr(&pp, (char *)runconf->stringtable[WEBBANNERS]); admin_close(&pp, buf); return 0; } @@ -398,7 +398,7 @@ int op_admin_counters(struct httpreq *r, const unsigned char *params) { struct trafcount *cp; int num = 0; - for(cp = conf->trafcounter; cp; cp = cp->next, num++){ + for(cp = runconf->trafcounter; cp; cp = cp->next, num++){ int inbuf = 0; if(cp->ace && (limited || param->redirected)){ @@ -489,7 +489,7 @@ int op_admin_reload(struct httpreq *r, const unsigned char *params) buf = admin_open(&pp, r->param); if(!buf) return 1; - if(r->param->srv->s_option) printstr(&pp, (char *)conf->stringtable[WEBBANNERS]); + if(r->param->srv->s_option) printstr(&pp, (char *)runconf->stringtable[WEBBANNERS]); else { needreload = 1; printstr(&pp, "

Reload scheduled

"); @@ -520,7 +520,10 @@ int op_admin_services(struct httpreq *r, const unsigned char *params) printstr(&pp, ""); printval(&tpool, TYPE_THREADPOOL, 0, &pp); printstr(&pp, "\r\n"); - printval(conf->services, TYPE_SERVER, 0, &pp); + /* services unlink themselves under config_mutex */ + _3proxy_mutex_lock(&config_mutex); + printval(runconf->services, TYPE_SERVER, 0, &pp); + _3proxy_mutex_unlock(&config_mutex); printstr(&pp, postxml); } diff --git a/tests/cases/reload.py b/tests/cases/reload.py new file mode 100644 index 0000000..ac351f2 --- /dev/null +++ b/tests/cases/reload.py @@ -0,0 +1,103 @@ +"""Reloading the configuration: a file with an error leaves the configuration +in force, services and users, as it was; a good one replaces it, while the +connections of the old services finish undisturbed.""" + +import threading +import time + + +def config(origin, proxy, adm, user, extra=""): + return f""" + log + auth iponly + allow * + http data * /data + httpsrv -p{origin} + + flush + users {user} + auth strong + allow * + proxy -p{proxy} + + flush + auth iponly + allow * + admin -p{adm} + {extra} + """ + + +def run(t): + origin = t.free_port() + first = t.free_port() + second = t.free_port() + adm = t.free_port() + + server = t.start("reload", config(origin, first, adm, "alice:CL:one"), + ports=[origin, first, adm]) + url = f"http://127.0.0.1:{origin}/data?size=1000" + proxy = f"127.0.0.1:{first}" + + def as_user(user, password, through=proxy): + return t.http(url, proxy=through, proxy_auth=(user, password)).status + + t.eq(200, as_user("alice", "one"), "the first configuration lets alice in") + + def reload_and_wait(expect_failure=False): + before = server.output() + accepting = before.count("Accepting connections") + t.http(f"http://127.0.0.1:{adm}/R") + deadline = time.time() + 10 + while time.time() < deadline: + out = server.output() + if expect_failure and "Unknown command" in out[len(before):]: + return out + if not expect_failure and out.count("Accepting connections") >= accepting + 3: + return out + time.sleep(0.05) + return server.output() + + # --- a file with an error: nothing changes -------------------------- + t.write_config("reload", config(origin, first, adm, "bob:CL:two", "nosuchcommand")) + out = reload_and_wait(expect_failure=True) + t.contains(out, "Unknown command", "the broken configuration is refused") + time.sleep(1.5) # what a switch would take: the old services stop within a second + t.eq(200, as_user("alice", "one"), "after a failed reload alice still gets in") + t.ne(200, as_user("bob", "two"), "and bob, from the refused file, does not") + + # --- a good file: the new configuration takes over ------------------- + slow = {} + + def download(): + # about two seconds: 2 MB in 64 KB blocks, 60 ms apart + r = t.http(f"http://127.0.0.1:{origin}/data?size=2000000&block=65536&delay=60", + proxy=proxy, proxy_auth=("alice", "one")) + slow["status"], slow["length"] = r.status, r.length + + th = threading.Thread(target=download) + th.start() + time.sleep(0.3) + + t.write_config("reload", config(origin, second, adm, "bob:CL:two")) + reload_and_wait() + # The old services stop accepting within a second; until then both the + # old and the new origin listen, and a connection the old one has queued + # is reset when it closes (a listener is not handed over yet). + time.sleep(1.5) + t.ok("the new services start") if t.wait_port(second) else \ + t.fail("the new services start", f"port {second} open", "closed") + moved = f"127.0.0.1:{second}" + t.eq(200, as_user("bob", "two", moved), "the new configuration lets bob in") + t.ne(200, as_user("alice", "one", moved), "and not alice, who is gone from it") + + # the old service stops accepting, its port closes + deadline = time.time() + 5 + while time.time() < deadline and t.wait_port(first, timeout=0.2): + time.sleep(0.2) + t.ok("the old service stops listening") if not t.wait_port(first, timeout=0.2) else \ + t.fail("the old service stops listening", f"port {first} closed", "open") + + th.join(20) + t.eq(200, slow.get("status"), "a download through the old service finishes") + t.eq(2000000, slow.get("length"), "with the whole answer")