From 5cbc72d2892d50d175e0783e899f769734c326b0 Mon Sep 17 00:00:00 2001 From: Vladimir Dubrovin <3proxy@3proxy.ru> Date: Sun, 18 Oct 2026 16:11:46 +0300 Subject: [PATCH] Split proxymain.c into its parts proxymain.c stays the one unit built for 3proxy and included by each standalone service, and now includes its parts: mainconf.c services from their configuration lines: options, srvprepare(), mainprepare(), srvinit(), srvfree(), and copying and freeing access lists and authentication mainlisten.c listening sockets, shared and taken over on a reload, and reattach() mainclient.c threads for the clients of TCP services, their pool, srvspawn(), releaseparam(), and the filters' hooks maintcp.c the main loop of a TCP service, tcploop(), taken out of srvrun(), with tcpnext() and cblisten() mainudp.c the main loop of a UDP service proxymain.c keeps srvstart, srvrun() and main(), and declares the functions of file scope of the parts so that they can be in any order. The code is moved, not changed, but for tcploop(). The parts are dependencies of the objects that include proxymain.c in Makefile.inc. Co-Authored-By: Claude Opus 5.5 --- src/Makefile.inc | 23 +- src/mainclient.c | 602 ++++++++++ src/mainconf.c | 1125 ++++++++++++++++++ src/mainlisten.c | 552 +++++++++ src/maintcp.c | 217 ++++ src/mainudp.c | 391 +++++++ src/proxymain.c | 2864 +--------------------------------------------- 7 files changed, 2962 insertions(+), 2812 deletions(-) create mode 100644 src/mainclient.c create mode 100644 src/mainconf.c create mode 100644 src/mainlisten.c create mode 100644 src/maintcp.c create mode 100644 src/mainudp.c diff --git a/src/Makefile.inc b/src/Makefile.inc index abd5d8c..ace05c8 100644 --- a/src/Makefile.inc +++ b/src/Makefile.inc @@ -1,3 +1,6 @@ +# proxymain.c and the parts it includes +PROXYMAIN = proxymain.c mainconf.c mainlisten.c mainclient.c maintcp.c mainudp.c + # # 3 proxy common Makefile # @@ -28,32 +31,32 @@ ftp$(OBJSUFFICS): ftp.c proxy.h structures.h sockgetchar$(OBJSUFFICS): sockgetchar.c proxy.h structures.h $(CC) $(CFLAGS) sockgetchar.c -proxy$(OBJSUFFICS): proxy.c proxy.h structures.h proxymain.c +proxy$(OBJSUFFICS): proxy.c proxy.h structures.h $(PROXYMAIN) $(CC) $(CFLAGS) $(DEFINEOPTION)WITHMAIN $(DEFINEOPTION)NOPORTMAP $(DEFINEOPTION)ANONYMOUS $(DEFINEOPTION)NOUDPMAIN proxy.c -pop3p$(OBJSUFFICS): pop3p.c proxy.h structures.h proxymain.c +pop3p$(OBJSUFFICS): pop3p.c proxy.h structures.h $(PROXYMAIN) $(CC) $(CFLAGS) $(DEFINEOPTION)WITHMAIN $(DEFINEOPTION)NOPORTMAP $(DEFINEOPTION)NOUDPMAIN pop3p.c -imapp$(OBJSUFFICS): imapp.c proxy.h structures.h proxymain.c +imapp$(OBJSUFFICS): imapp.c proxy.h structures.h $(PROXYMAIN) $(CC) $(CFLAGS) $(DEFINEOPTION)WITHMAIN $(DEFINEOPTION)NOPORTMAP $(DEFINEOPTION)NOUDPMAIN imapp.c -smtpp$(OBJSUFFICS): smtpp.c proxy.h structures.h proxymain.c +smtpp$(OBJSUFFICS): smtpp.c proxy.h structures.h $(PROXYMAIN) $(CC) $(CFLAGS) $(DEFINEOPTION)WITHMAIN $(DEFINEOPTION)NOPORTMAP $(DEFINEOPTION)NOUDPMAIN smtpp.c -ftppr$(OBJSUFFICS): ftppr.c proxy.h structures.h proxymain.c +ftppr$(OBJSUFFICS): ftppr.c proxy.h structures.h $(PROXYMAIN) $(CC) $(CFLAGS) $(DEFINEOPTION)WITHMAIN $(DEFINEOPTION)NOPORTMAP $(DEFINEOPTION)NOUDPMAIN ftppr.c -tcppm$(OBJSUFFICS): tcppm.c proxy.h structures.h proxymain.c +tcppm$(OBJSUFFICS): tcppm.c proxy.h structures.h $(PROXYMAIN) $(CC) $(CFLAGS) $(DEFINEOPTION)WITHMAIN $(DEFINEOPTION)PORTMAP $(DEFINEOPTION)NOUDPMAIN tcppm.c -udppm$(OBJSUFFICS): udppm.c proxy.h structures.h proxymain.c +udppm$(OBJSUFFICS): udppm.c proxy.h structures.h $(PROXYMAIN) $(CC) $(CFLAGS) $(DEFINEOPTION)WITHMAIN $(DEFINEOPTION)PORTMAP $(DEFINEOPTION)NOTCPMAIN udppm.c -tlspr$(OBJSUFFICS): tlspr.c proxy.h structures.h proxymain.c +tlspr$(OBJSUFFICS): tlspr.c proxy.h structures.h $(PROXYMAIN) $(CC) $(CFLAGS) $(DEFINEOPTION)WITHMAIN $(DEFINEOPTION)PORTMAP $(DEFINEOPTION)NOUDPMAIN tlspr.c -socks$(OBJSUFFICS): socks.c proxy.h structures.h proxymain.c +socks$(OBJSUFFICS): socks.c proxy.h structures.h $(PROXYMAIN) $(CC) $(CFLAGS) $(DEFINEOPTION)WITHMAIN $(DEFINEOPTION)NOPORTMAP $(DEFINEOPTION)NOUDPMAIN socks.c 3proxy$(OBJSUFFICS): 3proxy.c proxy.h structures.h @@ -86,7 +89,7 @@ $(BUILDDIR)$(PREFIX)udppm$(EXESUFFICS): sockmap$(OBJSUFFICS) udpsockmap$(OBJSUFF $(BUILDDIR)$(PREFIX)tlspr$(EXESUFFICS): sockmap$(OBJSUFFICS) sockgetchar$(OBJSUFFICS) tlspr$(OBJSUFFICS) common$(OBJSUFFICS) sem$(OBJSUFFICS) log$(OBJSUFFICS) $(LN) $(LNOUT)$(BUILDDIR)$(PREFIX)tlspr$(EXESUFFICS) $(LDFLAGS) sockmap$(OBJSUFFICS) sockgetchar$(OBJSUFFICS) tlspr$(OBJSUFFICS) log$(OBJSUFFICS) common$(OBJSUFFICS) sem$(OBJSUFFICS) $(LIBS) -mainfunc$(OBJSUFFICS): proxy.h structures.h proxymain.c +mainfunc$(OBJSUFFICS): proxy.h structures.h $(PROXYMAIN) $(CC) $(COUT)mainfunc$(OBJSUFFICS) $(CFLAGS) $(DEFINEOPTION)MODULEMAINFUNC=mainfunc proxymain.c srvproxy$(OBJSUFFICS): proxy.c proxy.h structures.h diff --git a/src/mainclient.c b/src/mainclient.c new file mode 100644 index 0000000..33f5753 --- /dev/null +++ b/src/mainclient.c @@ -0,0 +1,602 @@ +/* + 3APA3A simplest proxy server + (c) 2002-2026 by Vladimir Dubrovin + + please read License Agreement + +*/ + +/* Clients of TCP services: a thread for each, from a pool, from the filters + to the end of the child serving it, and the filters' hooks. + Part of proxymain.c, which includes it. */ + +/* Child functions do not call each other, a child requesting redirection to + another child returns it instead of calling it, to keep the stack flat. + The child which completed the request returns NULL. param is logged by + the child and released here, but not freed: its thread may keep it. + */ +void * childfunc(struct clientparam * param){ + PROXYFUNC pf = param->srv->pf; + int i; + + for(i = 0; pf && i < MAXCHILDREDIRECTS; i++) pf = (PROXYFUNC)(*pf)(param); + if(pf){ + param->res = 101; + dolog(param, (unsigned char *)"Redirection loop"); + } + releaseparam(param); + return NULL; +} + +/* One connection, from the filters to the end of the child which answers + it. The structure is released, not freed, when it returns. */ +static void serveparam(struct clientparam *param){ + int i = -1; +#ifdef MODULEMAINFUNC + if(makefilters(param->srv, param) > CONTINUE){ + releaseparam(param); + return; + } +#endif + if(param->srv->cbsock != INVALID_SOCKET){ + SASIZETYPE size = sizeof(param->sinsr); + struct pollfd fds; + fds.fd = param->srv->cbsock; + fds.events = POLLIN; + fds.revents = 0; + for(i=5+(param->srv->maxchild>>10); i; i--){ + if(param->srv->so._poll(param->sostate, &fds, 1, 1000*CONNBACK_TO)!=1){ + dolog(param, (unsigned char *)"Connect back not received, check connback client"); + i = 0; + break; + } + param->remsock = param->srv->so._accept(param->sostate, param->srv->cbsock, (struct sockaddr*)¶m->sinsr, &size); + if(param->remsock == INVALID_SOCKET) { + dolog(param, (unsigned char *)"Connect back accept() failed"); + continue; + } + { +#ifdef _WIN32 + unsigned long ul=1; + ioctlsocket(param->remsock, FIONBIO, &ul); +#else + fcntl(param->remsock,F_SETFL,O_NONBLOCK | fcntl(param->remsock,F_GETFL)); +#endif + } +#ifndef WITHMAIN + param->req = param->sinsr; + if(param->srv->acl) param->res = checkACL(param); + if(param->res){ + dolog(param, (unsigned char *)"Connect back ACL failed"); + param->srv->so._closesocket(param->sostate, param->remsock); + param->remsock = INVALID_SOCKET; + continue; + } +#endif + if(socksend(param, param->remsock, (unsigned char *)"C", 1, CONNBACK_TO) != 1){ + dolog(param, (unsigned char *)"Connect back sending command failed"); + param->srv->so._closesocket(param->sostate, param->remsock); + param->remsock = INVALID_SOCKET; + continue; + } + + break; + } + } + if(!i){ + param->res = 13; + releaseparam(param); + } + else { + +#ifndef WITHMAIN +#ifndef _WIN32 + sigset_t mask; + sigfillset(&mask); + pthread_sigmask(SIG_SETMASK, &mask, NULL); +#endif +#endif + + if(param->srv->haproxy){ + char buf[128]; + int i; + i = sockgetlinebuf(param, CLIENT, (unsigned char *)buf, sizeof(buf)-1, '\n', param->srv->timeouts[STRING_S]); + if(i > 12 && !strncasecmp(buf, "PROXY TCP", 9)){ + char *token, *token2=NULL; + unsigned short u1=0, u2=0; + buf[i] = 0; + token = strchr(buf, ' '); + if(token) token = strchr(token+1, ' '); + if(token) token++; + if(token) token2 = strchr(token+1, ' '); + if(token2) { + *token2 = 0; + getip46(46, (unsigned char*) token, (struct sockaddr *)¶m->sincr); + token = token2+1; + token2 = strchr(token, ' '); + } + if(token2) { + *token2 = 0; + getip46(46, (unsigned char *) token, (struct sockaddr *)¶m->sincl); + token = token2+1; + token2 = strchr(token, ' '); + } + if(token){ + sscanf(token,"%hu%hu", &u1, &u2); + if(u1) *SAPORT(¶m->sincr) = htons(u1); + if(u2) *SAPORT(¶m->sincl) = htons(u2); + } + } + } + childfunc(param); + } +} + +/* Frees a worker's structure, once its thread is done with it. */ +static void freeworker(struct clientparam *param){ + releaseparam(param); + _3proxy_sem_destroy(param->wake); + free(param); +} + +/* The automatic cap: 32 idle workers for each running service, but never + more than all of them could have connections. */ +static unsigned poollimit(void){ + unsigned limit; + + if(runconf->poolmax >= 0) limit = (unsigned)runconf->poolmax; + else { + limit = 32 * tpool.services; + if(limit > tpool.maxchild) limit = tpool.maxchild; + } + tpool.limit = limit; + return limit; +} + +/* Parks a worker whose connection has ended and waits for the next one. + Returns 0 when the thread is to end instead: on exit, when the pool is + full, when the thread was left in another network namespace, or when it + waited longer than the idle timeout. */ +static int poolpark(struct clientparam *param){ + int timeout; + + _3proxy_mutex_lock(&tpool.mutex); + if(timetoexit || nsbroken() || tpool.nidle >= poollimit()){ + _3proxy_mutex_unlock(&tpool.mutex); + return 0; + } + param->poolnext = tpool.idle; + tpool.idle = param; + tpool.nidle++; + param->pooled = 1; + timeout = runconf->pooltimeout; + _3proxy_mutex_unlock(&tpool.mutex); + + if(timeout <= 0) _3proxy_sem_lock(param->wake); + else if(_3proxy_sem_timedlock(param->wake, (unsigned)timeout * 1000)){ + struct clientparam **pp; + + _3proxy_mutex_lock(&tpool.mutex); + if(param->pooled){ + for(pp = &tpool.idle; *pp != param; pp = &(*pp)->poolnext); + *pp = param->poolnext; + tpool.nidle--; + param->pooled = 0; + _3proxy_mutex_unlock(&tpool.mutex); + return 0; + } + _3proxy_mutex_unlock(&tpool.mutex); + /* taken from the pool as the wait ran out: the connection follows */ + _3proxy_sem_lock(param->wake); + } + return !param->poolexit; +} + +/* Wakes every idle worker to end, when the process exits. */ +void pooldrain(void){ + struct clientparam *param; + + _3proxy_mutex_lock(&tpool.mutex); + while((param = tpool.idle)){ + tpool.idle = param->poolnext; + tpool.nidle--; + param->pooled = 0; + param->poolexit = 1; + _3proxy_sem_unlock(param->wake); + } + _3proxy_mutex_unlock(&tpool.mutex); +} + +#ifdef _WIN32 +DWORD WINAPI threadfunc(LPVOID p) { +#else +void * threadfunc (void *p) { +#endif + struct clientparam *param = (struct clientparam *)p; + + do serveparam(param); + while(poolpark(param)); + freeworker(param); +#ifdef _WIN32 + return 0; +#else + return NULL; +#endif +} + +/* A thread for the client, client_sock for TCP. 0 once it runs, the slot + in hand going with it. */ +static int srvspawn(struct srvstart *s, SOCKET client_sock){ + struct srvparam *srv = &s->srv; + struct clientparam *defparam = &s->defparam; + struct clientparam * newparam = NULL, **pp; + pthread_t thread; + unsigned char buf[256]; + int error = 0, reused; + size_t need; +#ifdef _WIN32 + HANDLE h; + + need = 16384 + srv->stacksize; +#else + need = threadstacksize(srv->stacksize); +#endif + + /* an idle worker if one has the stack this service needs */ + _3proxy_mutex_lock(&tpool.mutex); + for(pp = &tpool.idle; *pp; pp = &(*pp)->poolnext){ + if((*pp)->stacksize >= need){ + newparam = *pp; + *pp = newparam->poolnext; + newparam->pooled = 0; + tpool.nidle--; + tpool.reused++; + break; + } + } + _3proxy_mutex_unlock(&tpool.mutex); + reused = newparam != NULL; + if(!reused && (!(newparam = malloc (sizeof(*defparam))) || _3proxy_sem_init_timed(newparam->wake, 0, 1))){ + if(newparam) free(newparam); + srv->so._closesocket(srv->so.state, client_sock); + defparam->res = 21; + if(!srv->silent)dolog(defparam, (unsigned char *)"Memory Allocation Failed"); + usleep(SLEEPTIME); + return 1; + }; + if(!reused){ + newparam->stacksize = need; + newparam->pooled = newparam->poolexit = 0; + newparam->poolnext = NULL; + } + memcpy(newparam, defparam, offsetof(struct clientparam, filters)); + newparam->clibuf = newparam->clibufdata; + newparam->srvbuf = newparam->srvbufdata; + newparam->clibufsize = newparam->srvbufsize = SRVBUFSIZE(srv); + clearstat(newparam); + if(!s->isudp) newparam->clisock = client_sock; + newparam->prev = newparam->next = NULL; + _3proxy_mutex_lock(&srv->counter_mutex); + if(!srv->child){ + srv->child = newparam; + } + else { + newparam->next = srv->child; + srv->child = srv->child->prev = newparam; + } + /* a worker from the pool is woken below, once it is one of the children */ + if(!reused){ +#ifdef _WIN32 +#ifndef _WINCE + h = (HANDLE)_beginthreadex((LPSECURITY_ATTRIBUTES )NULL, (unsigned)(16384 + srv->stacksize), (void *)threadfunc, (void *) newparam, 0, &thread); +#else + h = (HANDLE)CreateThread((LPSECURITY_ATTRIBUTES )NULL, (unsigned)(16384 + srv->stacksize), (void *)threadfunc, (void *) newparam, 0, &thread); +#endif + if (h) { + CloseHandle(h); + } + else { + sprintf((char *)buf, "_beginthreadex(): %s", _strerror(NULL)); + error = 1; + } +#else + + if ((error = pthread_create(&thread, &s->pa, threadfunc, (void *)newparam))){ + sprintf((char *)buf, "pthread_create(): %s", strerror(error)); + } +#endif + } + if(error){ + if(!srv->silent)dolog(defparam, buf); + if(newparam->prev) newparam->prev->next = newparam->next; + else srv->child = newparam->next; + if(newparam->next) newparam->next->prev = newparam->prev; + if(newparam->clisock != INVALID_SOCKET){ + srv->so._shutdown(srv->so.state, newparam->clisock, SHUT_RDWR); + srv->so._closesocket(srv->so.state, newparam->clisock); + newparam->clisock = INVALID_SOCKET; + } + newparam->srv = NULL; + freeworker(newparam); + } + else { + srv->childcount++; +#ifndef STDMAIN + listenercount(srv, 1); +#endif + if(!reused){ + newparam->workerid = (uint64_t)thread; + _3proxy_mutex_lock(&tpool.mutex); + tpool.created++; + _3proxy_mutex_unlock(&tpool.mutex); + } + newparam->threadid = newparam->workerid; + } + _3proxy_mutex_unlock(&srv->counter_mutex); + if(reused) _3proxy_sem_unlock(newparam->wake); + memset(&defparam->sincl, 0, sizeof(defparam->sincl)); + memset(&defparam->sincr, 0, sizeof(defparam->sincr)); + return error; +} + + +/* Ends a connection: everything it holds is released and it leaves its + service, but the structure itself stays, with nothing in it pointing to + what was freed. After this the service may be gone at any moment, so + param->srv is cleared and nothing here touches it again. */ +void releaseparam(struct clientparam * param) { + if(param->srv){ + if(param->srv->so.freefunc) param->srv->so.freefunc(param->sostate); + } + if(param->clibuf != param->clibufdata) free(param->clibuf); + if(param->srvbuf != param->srvbufdata) free(param->srvbuf); + param->clibuf = param->clibufdata; + param->srvbuf = param->srvbufdata; +#ifdef WITHSPLICE + if(param->pipecli[0] >= 0) close(param->pipecli[0]); + if(param->pipecli[1] >= 0) close(param->pipecli[1]); + if(param->pipesrv[0] >= 0) close(param->pipesrv[0]); + if(param->pipesrv[1] >= 0) close(param->pipesrv[1]); + param->pipecli[0] = param->pipecli[1] = param->pipesrv[0] = param->pipesrv[1] = -1; +#endif + /* Not shut down on Windows: there shutting down receiving resets the + connection as soon as the peer sends anything more, and close() + alone sends whatever is queued before its FIN. Elsewhere it stays: + on the BSDs it discards what is left unread, so the close that + follows is not turned into a reset. */ + if(param->srv) { + if(param->ctrlsocksrv != INVALID_SOCKET && param->ctrlsocksrv != param->remsock) { +#ifndef _WIN32 + param->srv->so._shutdown(param->sostate, param->ctrlsocksrv, SHUT_RDWR); +#endif + param->srv->so._closesocket(param->sostate, param->ctrlsocksrv); + } + if(param->ctrlsock != INVALID_SOCKET && param->ctrlsock != param->clisock) { +#ifndef _WIN32 + param->srv->so._shutdown(param->sostate, param->ctrlsock, SHUT_RDWR); +#endif + param->srv->so._closesocket(param->sostate, param->ctrlsock); + } + if(param->remsock != INVALID_SOCKET) { +#ifndef _WIN32 + param->srv->so._shutdown(param->sostate, param->remsock, SHUT_RDWR); +#endif + param->srv->so._closesocket(param->sostate, param->remsock); + } + if(param->clisock != INVALID_SOCKET) { +#ifndef _WIN32 + param->srv->so._shutdown(param->sostate, param->clisock, SHUT_RDWR); +#endif + param->srv->so._closesocket(param->sostate, param->clisock); + } + param->ctrlsocksrv = param->ctrlsock = param->remsock = param->clisock = INVALID_SOCKET; + } +#ifndef STDMAIN + while(param->nfilters > 0){ + param->nfilters--; + if(param->filters[param->nfilters].filter->filter_clear) + (*param->filters[param->nfilters].filter->filter_clear)(param->filters[param->nfilters].data); + } + if(param->connlim) stopconnlims(param); + /* a slot held in the newest service on the listener after a reload */ + if(param->newsrv){ + slotrelease(param->newsrv); + param->newsrv = NULL; + } +#endif + /* the sockets were closed and the filters cleared with it */ + param->sostate = NULL; + /* Leaving the service comes last. Once its last child is gone, srvfree() + frees the service along with its socket functions, filters and mutex, + and a close above may have lingered for seconds before getting here. */ + if(param->srv){ + _3proxy_mutex_lock(¶m->srv->counter_mutex); + if(param->prev || param->next || param->srv->child == param){ + if(param->prev){ + param->prev->next = param->next; + } + else + param->srv->child = param->next; + if(param->next){ + param->next->prev = param->prev; + } + (param->srv->childcount)--; +#ifndef STDMAIN + listenercount(param->srv, -1); +#endif + /* under the mutex too: srvfree() may destroy the semaphore as + soon as the mutex shows the last child gone */ + _3proxy_sem_unlock(param->srv->childslots); + } + _3proxy_mutex_unlock(¶m->srv->counter_mutex); + param->prev = param->next = NULL; + param->srv = NULL; + } +} + +void freeparam(struct clientparam * param) { + releaseparam(param); + free(param); +} + +FILTER_ACTION handleconnectflt(struct clientparam *cparam){ +#ifndef STDMAIN + FILTER_ACTION action; + int i; + + for(i=0; inconnectfilters ;i++){ + action = (*cparam->connectfilters[i]->filter->filter_connect)(cparam->connectfilters[i]->data, cparam); + if(action!=CONTINUE) return action; + } +#endif + return PASS; +} + + +/* A buffer filter given a buffer too small for what it makes of it hands + back one of its own, allocated with malloc, and leaves the one it was given + alone: nothing refers to that one any more, so it is freed here, unless it + is one of the connection's own buffers. The caller owns whatever *buf_p + points to on return. */ +static void filterswap(struct clientparam *param, unsigned char **cur, unsigned char *now){ + if(now != *cur){ + if(*cur != param->clibufdata && *cur != param->srvbufdata && *cur != param->tmpbuf) free(*cur); + *cur = now; + } +} + +#ifndef STDMAIN +FILTER_ACTION makefilters (struct srvparam *srv, struct clientparam *param){ + FILTER_ACTION res=PASS; + FILTER_ACTION action; + int i; + + if(!srv->nfilters) return PASS; + + for(i=0; infilters; i++){ + if(!srv->filter[i].filter_client)continue; + /* the slot may hold what the connection before left in it */ + param->filters[param->nfilters].data = NULL; + action = (*srv->filter[i].filter_client)(srv->filter[i].data, param, ¶m->filters[param->nfilters].data); + if(action == PASS) continue; + if(action > CONTINUE) { + /* a filter refusing the connection is not kept, but it may + have made a state for it */ + if(srv->filter[i].filter_clear && param->filters[param->nfilters].data) + (*srv->filter[i].filter_clear)(param->filters[param->nfilters].data); + return action; + } + param->filters[param->nfilters].filter = srv->filter + i; + if(srv->filter[i].filter_request)param->reqfilters[param->nreqfilters++] = param->filters + param->nfilters; + if(srv->filter[i].filter_connect)param->connectfilters[param->nconnectfilters++] = param->filters + param->nfilters; + if(srv->filter[i].filter_afterauth)param->afterauthfilters[param->nafterauthfilters++] = param->filters + param->nfilters; + if(srv->filter[i].filter_header_cli)param->hdrfilterscli[param->nhdrfilterscli++] = param->filters + param->nfilters; + if(srv->filter[i].filter_header_srv)param->hdrfilterssrv[param->nhdrfilterssrv++] = param->filters + param->nfilters; + if(srv->filter[i].filter_predata)param->predatfilters[param->npredatfilters++] = param->filters + param->nfilters; + if(srv->filter[i].filter_data_cli)param->datfilterscli[param->ndatfilterscli++] = param->filters + param->nfilters; + if(srv->filter[i].filter_data_srv)param->datfilterssrv[param->ndatfilterssrv++] = param->filters + param->nfilters; + param->nfilters++; + } + return res; +} + +#endif + +#ifndef STDMAIN +FILTER_ACTION handleafterauthflt(struct clientparam *cparam){ +#ifndef STDMAIN + FILTER_ACTION action; + int i; + + for(i=0; inafterauthfilters ;i++){ + action = (*cparam->afterauthfilters[i]->filter->filter_afterauth)(cparam->afterauthfilters[i]->data, cparam); + if(action!=CONTINUE) return action; + } +#endif + return PASS; +} + + +FILTER_ACTION handlereqfilters(struct clientparam *param, unsigned char ** buf_p, int * bufsize_p, int offset, int * length_p){ + FILTER_ACTION action; + int i; + unsigned char *cur = *buf_p; + + for(i=0; inreqfilters; i++){ + action = (*param->reqfilters[i]->filter->filter_request)(param->reqfilters[i]->data, param, buf_p, bufsize_p, offset, length_p); + filterswap(param, &cur, *buf_p); + if(action!=CONTINUE) return action; + } + return PASS; +} + +FILTER_ACTION handlehdrfilterssrv(struct clientparam *param, unsigned char ** buf_p, int * bufsize_p, int offset, int * length_p){ + FILTER_ACTION action; + int i; + unsigned char *cur = *buf_p; + + for(i=0; inhdrfilterssrv; i++){ + action = (*param->hdrfilterssrv[i]->filter->filter_header_srv)(param->hdrfilterssrv[i]->data, param, buf_p, bufsize_p, offset, length_p); + filterswap(param, &cur, *buf_p); + if(action!=CONTINUE) return action; + } + return PASS; +} + +FILTER_ACTION handlehdrfilterscli(struct clientparam *param, unsigned char ** buf_p, int * bufsize_p, int offset, int * length_p){ + FILTER_ACTION action; + int i; + unsigned char *cur = *buf_p; + + for(i = 0; i < param->nhdrfilterscli; i++){ + action = (*param->hdrfilterscli[i]->filter->filter_header_cli)(param->hdrfilterscli[i]->data, param, buf_p, bufsize_p, offset, length_p); + filterswap(param, &cur, *buf_p); + if(action!=CONTINUE) return action; + } + return PASS; +} + +#endif + +FILTER_ACTION handlepredatflt(struct clientparam *cparam){ +#ifndef STDMAIN + FILTER_ACTION action; + int i; + + if(cparam->predatdone) return PASS; + cparam->predatdone = 1; + for(i=0; inpredatfilters ;i++){ + action = (*cparam->predatfilters[i]->filter->filter_predata)(cparam->predatfilters[i]->data, cparam); + if(action!=CONTINUE) return action; + } +#endif + return PASS; +} + +FILTER_ACTION handledatfltcli(struct clientparam *cparam, unsigned char ** buf_p, int * bufsize_p, int offset, int * length_p){ +#ifndef STDMAIN + FILTER_ACTION action; + int i; + unsigned char *cur = *buf_p; + + for(i=0; indatfilterscli ;i++){ + action = (*cparam->datfilterscli[i]->filter->filter_data_cli)(cparam->datfilterscli[i]->data, cparam, buf_p, bufsize_p, offset, length_p); + filterswap(cparam, &cur, *buf_p); + if(action!=CONTINUE) return action; + } +#endif + return PASS; +} + +FILTER_ACTION handledatfltsrv(struct clientparam *cparam, unsigned char ** buf_p, int * bufsize_p, int offset, int * length_p){ + FILTER_ACTION action; + int i; + unsigned char *cur = *buf_p; + + for(i=0; indatfilterssrv; i++){ + action = (*cparam->datfilterssrv[i]->filter->filter_data_srv)(cparam->datfilterssrv[i]->data, cparam, buf_p, bufsize_p, offset, length_p); + filterswap(cparam, &cur, *buf_p); + if(action!=CONTINUE) return action; + } + return PASS; +} diff --git a/src/mainconf.c b/src/mainconf.c new file mode 100644 index 0000000..8e76be7 --- /dev/null +++ b/src/mainconf.c @@ -0,0 +1,1125 @@ +/* + 3APA3A simplest proxy server + (c) 2002-2026 by Vladimir Dubrovin + + please read License Agreement + +*/ + +/* Services from their configuration lines: options parsed, a service + prepared, set up and freed, the access lists and authentication of a + service copied from the configuration and freed. + Part of proxymain.c, which includes it. */ + +#ifdef _WIN32 +/* Present since Windows 7 (SO_PORT_SCALABILITY) and Windows 10 / Server 2019 + (SO_REUSE_UNICASTPORT), define them if the SDK is older so the options can + still be requested. setsockopt() just fails on a system which does not + support them and the failure is ignored. + */ +#ifndef SO_PORT_SCALABILITY +#define SO_PORT_SCALABILITY 0x3006 +#endif +#ifndef SO_REUSE_UNICASTPORT +#define SO_REUSE_UNICASTPORT 0x3007 +#endif +#endif + +struct socketoptions sockopts[] = { +#ifdef TCP_NODELAY + {TCP_NODELAY, "TCP_NODELAY"}, +#endif +#ifdef TCP_CORK + {TCP_CORK, "TCP_CORK"}, +#endif +#ifdef TCP_DEFER_ACCEPT + {TCP_DEFER_ACCEPT, "TCP_DEFER_ACCEPT"}, +#endif +#ifdef TCP_QUICKACK + {TCP_QUICKACK, "TCP_QUICKACK"}, +#endif +#ifdef TCP_TIMESTAMPS + {TCP_TIMESTAMPS, "TCP_TIMESTAMPS"}, +#endif +#ifdef SO_REUSEADDR + {SO_REUSEADDR, "SO_REUSEADDR"}, +#endif +#ifdef SO_REUSEPORT + {SO_REUSEPORT, "SO_REUSEPORT"}, +#endif +#ifdef SO_EXCLUSIVEADDRUSE + {SO_EXCLUSIVEADDRUSE, "SO_EXCLUSIVEADDRUSE"}, +#endif +#ifdef SO_PORT_SCALABILITY + {SO_PORT_SCALABILITY, "SO_PORT_SCALABILITY"}, +#endif +#ifdef SO_REUSE_UNICASTPORT + {SO_REUSE_UNICASTPORT, "SO_REUSE_UNICASTPORT"}, +#endif +#ifdef SO_KEEPALIVE + {SO_KEEPALIVE, "SO_KEEPALIVE"}, +#endif +#ifdef SO_DONTROUTE + {SO_DONTROUTE, "SO_DONTROUTE"}, +#endif +#ifdef IP_TRANSPARENT + {IP_TRANSPARENT, "IP_TRANSPARENT"}, +#endif +#ifdef TCP_FASTOPEN + {TCP_FASTOPEN, "TCP_FASTOPEN"}, +#endif +#ifdef TCP_FASTOPEN_CONNECT + {TCP_FASTOPEN_CONNECT, "TCP_FASTOPEN_CONNECT"}, +#endif +#ifdef TCP_MAXSEG + {TCP_MAXSEG, "TCP_MAXSEG"}, +#endif + {0, NULL} +}; + +char optsbuf[1024]; + +char * printopts(char *sep){ + int i=0, pos=0; + for(; sockopts[i].optname; i++)pos += sprintf(optsbuf+pos,"%s%s",i?sep:"",sockopts[i].optname); + return optsbuf; +} + + +/* A comma separated list of options, each NAME or NAME=VALUE; a name alone + stands for 1, or for TCP_MAXSEG for the value of maxseg. Options are added + to what is already set, so a default is changed only by naming it, and + turned off with NAME=0. An unknown name or a value which is not a number + makes the whole list fail. */ +int getopts(const char *s, struct sockoptset *opts){ + while(*s){ + const char *end = strchr(s, ','); + const char *eq; + size_t len; + int i, value = 1; + + if(!end) end = s + strlen(s); + eq = memchr(s, '=', (size_t)(end - s)); + len = (size_t)((eq? eq : end) - s); + if(eq){ + char *num; + + if(!len) return -1; + value = (int)strtol(eq + 1, &num, 0); + if(num == eq + 1 || num != end) return -1; + } + else if(len) { +#ifdef TCP_MAXSEG + if(len == 10 && !strncmp(s, "TCP_MAXSEG", 10)) value = 0; +#endif + } + if(len){ + for(i = 0; sockopts[i].optname; i++) + if(strlen(sockopts[i].optname) == len && !strncmp(s, sockopts[i].optname, len)) break; + if(!sockopts[i].optname || i >= MAXSOCKOPTS) return -1; + opts->set |= (1u << i); + opts->value[i] = value; + } + s = *end? end + 1 : end; + } + return 0; +} + +void setopts(SOCKET s, const struct sockoptset *opts, int maxseg){ + int i, value; + + for(i = 0; sockopts[i].optname && i < MAXSOCKOPTS; i++){ + if(!(opts->set & (1u << i))) continue; + value = opts->value[i]; +#ifdef TCP_MAXSEG + if(sockopts[i].opt == TCP_MAXSEG && *sockopts[i].optname == 'T'){ + if(!value) value = maxseg; + if(!value) continue; + } +#endif + setsockopt(s, *sockopts[i].optname == 'T'? IPPROTO_TCP: +#ifdef SOL_IP + *sockopts[i].optname == 'I'? SOL_IP: +#endif + SOL_SOCKET, sockopts[i].opt, (char *)&value, sizeof(value)); + } +} + +/* Releases what the preparation and the run took, s itself excepted. */ +static void srvdiscard(struct srvstart *s){ + srvfree(&s->srv); + if(s->cbc_string) free(s->cbc_string); + if(s->cbl_string) free(s->cbl_string); + if(s->fp) fclose(s->fp); +} + +#ifndef STDMAIN +static void srvunlink(struct srvparam *srv){ + if(srv->next)srv->next->prev = srv->prev; + if(srv->prev)srv->prev->next = srv->next; + else srv->config->services = srv->next; + srv->next = srv->prev = NULL; +} +#endif + +/* 0, or 1 for wrong options, 2 for want of memory. Whatever fails, s can + be passed to srvdiscard(). */ +static int srvprepare(struct srvstart *s, int argc, char **argv){ + struct srvparam *srv = &s->srv; + struct clientparam *defparam = &s->defparam; + int i=0; +#if defined STDMAIN && !defined _WIN32 + SASIZETYPE size; +#endif + int error = 0; + char *hostname=NULL; +#if !defined(PORTMAP) || !defined(NOPORTMAP) + char loghelp[] = +#ifdef STDMAIN +#ifndef _WIN32 + " -I inetd mode (requires real socket, doesn't work with TTY)\n" + " -l@IDENT log to syslog IDENT\n" +#endif + " -d go to background (daemon)\n" +#else + " -u never ask for username\n" + " -u2 always ask for username\n" +#endif +#if defined SO_BINDTODEVICE || defined IP_BOUND_IF + " -Di(DEVICENAME) bind internal interface to device, e.g. eth1\n" + " -De(DEVICENAME) bind external interface to device, e.g. eth1\n" +#endif +#ifdef WITHSPLICE + " -s[N] Use splice(): -s1 server to client, -s2 client to server, -s3 both\n" + " directions; -s (-s7, the default) both, -s5 or -s6 one, only where it\n" + " pays (large transfers); -s0 never. Not with data filters\n" + " :MIN,STREAK,SMALL tune where it pays (-s:16384,2,4096): splice after\n" + " STREAK reads in a row of MIN bytes or more (SMALL from a TLS socket),\n" + " copy again after more than STREAK splices of less than SMALL; a body of\n" + " known length is spliced if MIN or more of it is left\n" + " -C connection closed by any of the sides terminates the session, by default\n" + " the session is kept until both sides close it (TCP half-close)\n" + " -U(0-3) (for socks) what to do when the destination changes within an UDP\n" + " association: 1 - log, 2 - authorize, 3 - both (default), 0 (same as -U) -\n" + " neither. The first destination is always authorized and logged\n" +#endif + "-g(GRACE_TRAFF,GRACE_NUM,GRACE_DELAY) - delay GRACE_DELAY milliseconds before polling if average polling size below GRACE_TRAFF bytes and GRACE_NUM read operations in single directions are detected within 1 second to minimize polling\n" + " -fFORMAT logging format (see documentation)\n" + " -l log to stderr\n" + " -lFILENAME log to FILENAME\n" + " -b(BUFSIZE) size of network buffer, at most and by default 16384\n" + " -S(STACKSIZE) value to add to default client thread stack size\n" + " -t be silent (do not log service start/stop)\n" + "\n" + " -iIP ip address or internal interface (clients are expected to connect)\n" + " -eIP ip address or external interface (outgoing connection will have this)\n" + " -rHOST:PORT Use IP:port for connect back proxy instead of listen port\n" + " -RHOST:PORT Use PORT to listen connect back proxy connection to pass data to\n" + " -4 Use IPv4 for outgoing connections\n" + " -6 Use IPv6 for outgoing connections\n" + " -46 Prefer IPv4 for outgoing connections, use both IPv4 and IPv6\n" + " -64 Prefer IPv6 for outgoing connections, use both IPv4 and IPv6\n" + " -ocOPTIONS, -osOPTIONS, -olOPTIONS, -orOPTIONS -oROPTIONS - options for\n" + " to-client (oc), to-server (os), listening (ol) socket, connect back client\n" + " (or) socket, connect back server (oR) listening socket, a comma separated\n" + " list of NAME or NAME=VALUE (NAME=0 turns an option off); TCP_NODELAY is\n" + " set on to-client and to-server sockets by default\n" + " where possible options are: "; +#endif + + s->cbc_string = s->cbl_string = NULL; + s->fp = NULL; + s->iscbc = s->iscbl = 0; +#ifdef STDMAIN +#ifndef _WIN32 + s->inetd = 0; +#endif +#endif + srvinit(srv, defparam); +#ifndef STDMAIN + confhold(srv->config); /* until srvfree() */ +#endif + srv->pf = childdef.pf; + s->isudp = childdef.isudp; + s->udp = childdef.udp; + + /* Default socket options, which -oc, -os, -ol and -oR change by naming + them, NAME=0 to turn one off. + + Nagle's algorithm holds back a short write until the previous one is + acknowledged, and a peer delays its acknowledgement, which stalls every + reply written in more than one piece. + + SO_REUSEADDR is not set on Windows: it is not needed to rebind a + listening port there, and it only allows another local process to bind + the same address and port, with undefined behaviour as to which socket + receives the connections. Use -olSO_EXCLUSIVEADDRUSE to prevent that + instead. + */ + (void)getopts("TCP_NODELAY", &srv->clisockopts); + (void)getopts("TCP_NODELAY", &srv->srvsockopts); +#ifndef _WIN32 + (void)getopts("SO_REUSEADDR", &srv->lissockopts); + (void)getopts("SO_REUSEADDR", &srv->cbssockopts); +#endif +#ifdef SO_REUSEPORT + (void)getopts("SO_REUSEPORT", &srv->lissockopts); + (void)getopts("SO_REUSEPORT", &srv->cbssockopts); +#endif +#ifndef NOUDPMAIN + if(s->isudp) { + srv->udpbuf = malloc(UDPBUFSIZE); + srv->udpbuf2 = malloc(UDPBUFSIZE); + if(!srv->udpbuf || !srv->udpbuf2) return 2; + } +#endif + srv->service = defparam->service = childdef.service; + +#ifdef WITH_HTTPSRV + /* http lines accumulate until a service claims them, so each httpsrv takes + the rules written above it and the next one starts empty. */ + if(srv->service == S_HTTPSRV){ + srv->httprules = conf->httprules; + conf->httprules = NULL; + } +#endif + +#ifndef STDMAIN + if(conf->acl){ + srv->acl = copyacl(conf->acl); + if(!srv->acl) return 2; + } + + if(conf->authfuncs){ + srv->authfuncs = copyauth(conf->authfuncs); + if(!srv->authfuncs) return 2; + } +#else + srv->needuser = 0; + _3proxy_mutex_init(&log_mutex); +#endif + + for (i=1; idemon)daemonize(); + conf->demon = 1; + break; +#if defined SO_BINDTODEVICE || defined IP_BOUND_IF + case 'D': + if(argv[i][2] == 'i') srv->ibindtodevice = strdup(argv[i] + 3); + else srv->obindtodevice = strdup(argv[i] + 3); + break; +#endif + case 'l': + srv->logfunc = logstdout; + if(srv->logtarget) free(srv->logtarget); + srv->logtarget = (unsigned char *)strdup(argv[i] + 2); + if(argv[i][2]) { + if(argv[i][2]=='@'){ + +#ifdef STDMAIN +#ifndef _WIN32 + openlog(argv[i]+3, LOG_PID, LOG_DAEMON); + srv->logfunc = logsyslog; +#endif +#endif + + } + else { + s->fp = fopen(argv[i] + 2, "a"); + if (s->fp) { + srv->stdlog = s->fp; + } + } + + } + break; + case 'i': +#ifdef WITH_UN + if(!strncmp((char *)argv[i]+2, "unix:", 5)){ + make_un((unsigned char *)argv[i] + 7, (struct sockaddr_un *)&srv->intsa); + } + else +#endif + getip46(46, (unsigned char *)argv[i]+2, (struct sockaddr *)&srv->intsa); + break; + case 'e': + { +#ifndef NOIPV6 + PROXYSOCKADDRTYPE sa6; + memset(&sa6, 0, sizeof(sa6)); + error = !getip46(46, (unsigned char *)argv[i]+2, (struct sockaddr *)&sa6); + if(!error) { + if (*SAFAMILY(&sa6)==AF_INET) srv->extsa = sa6; + else srv->extsa6 = sa6; + } +#else + error = !getip46(46, (unsigned char *)argv[i]+2, (struct sockaddr *)&srv->extsa); +#endif + } + break; + case 'N': + if(argv[i][2] == 'e') getip46(46, (unsigned char *)argv[i]+3, (struct sockaddr *)&srv->extNat); + else if(argv[i][2] == 'i') getip46(46, (unsigned char *)argv[i]+3, (struct sockaddr *)&srv->intNat); + else getip46(46, (unsigned char *)argv[i]+2, (struct sockaddr *)&srv->extNat); + break; + case 'n': +#ifdef __linux__ + if(argv[i][2] == 'i') { if(srv->inetns) free(srv->inetns); srv->inetns = strdup(argv[i] + 3); } + else if(argv[i][2] == 'e') { if(srv->onetns) free(srv->onetns); srv->onetns = strdup(argv[i] + 3); } +#endif + break; + case 'p': + *SAPORT(&srv->intsa) = htons(atoi(argv[i]+2)); + break; + case 'P': + srv->targetport = ntohs(atoi(argv[i]+2)); + break; + case '4': + case '6': + srv->family = atoi(argv[i]+1); + break; + case 'b': + srv->bufsize = atoi(argv[i]+2); + break; +#ifdef STDMAIN +#ifndef _WIN32 + case 'I': + size = sizeof(defparam->sincl); + if(srv->so._getsockname(srv->so.state, 0, (struct sockaddr*)&defparam->sincl, &size) || + *SAFAMILY(&defparam->sincl) != AF_INET) error = 1; + + else s->inetd = 1; + break; +#endif +#endif + case 'f': + if(srv->logformat)free(srv->logformat); + srv->logformat = (unsigned char *)strdup(argv[i] + 2); + break; + case 't': + srv->silent = 1; + break; + case 'h': + hostname = argv[i] + 2; + break; + case 'H': + srv->haproxy=1; + break; + case 'c': + srv->requirecert = 1; + if(isdigit(argv[i][2])) srv->requirecert = atoi(argv[i]+2); + break; + case 'r': + s->cbc_string = (unsigned char *)strdup(argv[i] + 2); + s->iscbc = 1; + break; + case 'R': + s->cbl_string = (unsigned char *)strdup(argv[i] + 2); + s->iscbl = 1; + break; + case 'u': + srv->needuser = 0; + if(*(argv[i] + 2)) srv->needuser = atoi(argv[i] + 2); + break; + case 'x': + srv->nostarttls = 1; + break; + case 'X': + if(!strncasecmp(argv[i]+2, "imap", 4)) srv->srvstarttls = S_IMAPP; + else if(!strncasecmp(argv[i]+2, "pop3", 4)) srv->srvstarttls = S_POP3P; + else if(!strncasecmp(argv[i]+2, "smtp", 4)) srv->srvstarttls = S_SMTPP; +#ifdef WITH_FTP + /* The http proxy fetches ftp:// only where it was asked to: + the protocol brings a second connection and a listing to + build, which a service serving http has no use for. */ + else if(!strncasecmp(argv[i]+2, "ftp", 3)) srv->allowftp = 1; +#endif + else error = 1; + break; + case 'F': + { + PROXYSOCKADDRTYPE fsa; + memset(&fsa, 0, sizeof(fsa)); + if(!getip46(46, (unsigned char *)argv[i]+2, (struct sockaddr *)&fsa)) error = 1; + else if(*SAFAMILY(&fsa) == AF_INET) srv->fakeip = *(uint32_t *)SAADDR(&fsa); +#ifndef NOIPV6 + else if(*SAFAMILY(&fsa) == AF_INET6) memcpy(srv->fakeip6, SAADDR(&fsa), 16); +#endif + else error = 1; + } + break; + case 'T': + srv->transparent = 1; + break; + case 'S': + srv->stacksize = atoi(argv[i]+2); + break; + case 'a': + srv->anonymous = 1 + atoi(argv[i]+2); + break; + case 'g': + sscanf(argv[i]+2, "%d,%d,%d", &srv->gracetraf, &srv->gracenum, &srv->gracedelay); + break; + case 'C': + srv->halfclose = *(argv[i]+2)? atoi(argv[i]+2) : 0; + break; + case 'U': + srv->udpauth = *(argv[i]+2)? atoi(argv[i]+2) : 0; + break; + case 's': +#ifdef WITHSPLICE + /* admin is an httpsrv, where -s is the limited mode */ + if(s->isudp || srv->service == S_ADMIN || srv->service == S_HTTPSRV) +#endif + srv->s_option = 1 + atoi(argv[i]+2); +#ifdef WITHSPLICE + else { + /* -s[N][:MIN,STREAK,SMALL] */ + char *heur = strchr(argv[i]+2, ':'); + + srv->usesplice = ((argv[i][2] && argv[i][2] != ':')? atoi(argv[i]+2) : + SPLICE_DOWNLOAD|SPLICE_UPLOAD|SPLICE_AUTO) & + (SPLICE_DOWNLOAD|SPLICE_UPLOAD|SPLICE_AUTO); + if(heur){ + int min = 0, streak = 0, small = 0; + + sscanf(heur+1, "%d,%d,%d", &min, &streak, &small); + if(min > 0) srv->splicemin = min; + if(streak > 0) srv->splicestreak = streak; + if(small > 0) srv->splicesmall = small; + } + } +#endif + break; + case 'o': + switch(argv[i][2]){ + case 's': + error = getopts(argv[i]+3, &srv->srvsockopts); + break; + case 'c': + error = getopts(argv[i]+3, &srv->clisockopts); + break; + case 'l': + error = getopts(argv[i]+3, &srv->lissockopts); + break; + case 'r': + error = getopts(argv[i]+3, &srv->cbcsockopts); + break; + case 'R': + error = getopts(argv[i]+3, &srv->cbssockopts); + break; + default: + error = 1; + } + if(!error) break; + default: + error = 1; + break; + } + } + else break; + } + + +#ifndef STDMAIN + if(childdef.port) { +#endif +#ifndef PORTMAP + if (error || i!=argc) { + fprintf(stderr, "%s of %s\n" + "Usage: %s options\n" + "Available options are:\n" + "%s\n" + "\t%s\n" + " -pPORT - service port to accept connections\n" + "%s" + "\tExample: %s -i127.0.0.1\n\n" + "%s", + argv[0], + conf->stringtable?(char *)conf->stringtable[3]: VERSION " (" BUILDDATE ")", + argv[0], loghelp, printopts("\n\t"), childdef.helpmessage, argv[0], +#ifdef STDMAIN + copyright +#else + "" +#endif + ); + return (1); + } +#endif +#ifndef STDMAIN + } + else { +#endif +#ifndef NOPORTMAP + /* the port of a Unix socket target is ignored, 0 as the manual has it */ + if (error || argc != i+3 || *argv[i]=='-'|| (*SAPORT(&srv->intsa) = htons((uint16_t)atoi(argv[i])))==0 || ((srv->targetport = htons((uint16_t)atoi(argv[i+2])))==0 +#ifdef WITH_UN + && strncmp(argv[i+1], "unix:", 5) +#endif + )) { + fprintf(stderr, "%s of %s\n" + "Usage: %s options" + " [-e] " + " \n" + "Available options are:\n" + "%s\n" + "\t%s\n" + "%s" + "\tExample: %s -d -i127.0.0.1 6666 serv.somehost.ru 6666\n\n" + "%s", + argv[0], + conf->stringtable?(char *)conf->stringtable[3]: VERSION " (" BUILDDATE ")", + argv[0], loghelp, printopts("\n\t"), childdef.helpmessage, argv[0], +#ifdef STDMAIN + copyright +#else + "" +#endif + ); + return (1); + } + srv->target = (unsigned char *)strdup(argv[i+1]); +#endif +#ifndef STDMAIN + } + +#else +#ifndef _WIN32 + /* main() serves the one connection, as the service was set up so far */ + if(s->inetd) return 0; +#endif +#endif + + + srvinit2(srv, defparam); + if(!*SAFAMILY(&srv->intsa)) *SAFAMILY(&srv->intsa) = AF_INET; + if(!*SAPORT(&srv->intsa)) *SAPORT(&srv->intsa) = htons(childdef.port); + *SAFAMILY(&srv->extsa) = AF_INET; +#ifndef NOIPV6 + *SAFAMILY(&srv->extsa6) = AF_INET6; +#endif + if(hostname)parsehostname(hostname, defparam, childdef.port); + +#ifdef __linux__ + /* Opened here, where the configuration is read, before a chroot can hide + the paths, and each entered once to be sure it is a network namespace + this process may use: a mistake shows now, not on a client's connection. */ + if(srv->inetns || srv->onetns){ + if(nsopen()) return 4; + if(srv->inetns && (srv->i_nsfd = open(srv->inetns, O_RDONLY | O_CLOEXEC)) < 0) return 4; + if(srv->onetns && (srv->o_nsfd = open(srv->onetns, O_RDONLY | O_CLOEXEC)) < 0) return 4; + if(srv->i_nsfd >= 0 && (setns(srv->i_nsfd, CLONE_NEWNET) || setns(mainnsfd, CLONE_NEWNET))) return 4; + if(srv->o_nsfd >= 0 && (setns(srv->o_nsfd, CLONE_NEWNET) || setns(mainnsfd, CLONE_NEWNET))) return 4; + } +#endif + +#ifndef STDMAIN + if((error = copyfilter(conf->filters, srv))) return error; +#endif + return 0; +} + +#ifndef STDMAIN +static int samestr(const char *a, const char *b){ + return (!a && !b) || (a && b && !strcmp(a, b)); +} + +/* Services of one configuration on one address share its socket: they + must be of one type, with the same socket options. 5 if a service of + another type listens there, 6 if one with other options, else 0. + Services connecting back to one place need not be alike. */ +static int sameaddress(struct srvstart *s){ + struct srvparam *srv = &s->srv, *o; + + if(!s->isudp && s->iscbc) return 0; + for(o = conf->services; o; o = o->next){ + struct srvstart *os = (struct srvstart *)o; + + if(os->isudp != s->isudp || (!os->isudp && os->iscbc)) continue; +#ifdef __linux__ + if(!samestr(o->inetns, srv->inetns)) continue; +#endif +#if defined SO_BINDTODEVICE || defined IP_BOUND_IF + if(!samestr(o->ibindtodevice, srv->ibindtodevice)) continue; +#endif + if(SASIZE(&o->intsa) != SASIZE(&srv->intsa) || memcmp(&o->intsa, &srv->intsa, SASIZE(&srv->intsa))) continue; + if(o->service != srv->service) return 5; + if(o->maxseg != srv->maxseg || memcmp(&o->lissockopts, &srv->lissockopts, sizeof(srv->lissockopts))) return 6; + } + return 0; +} + +int mainprepare(int argc, char** argv, void **service){ + struct srvstart *s; + int res; + + *service = NULL; + if(!(s = malloc(sizeof(*s)))) return 2; + if((res = srvprepare(s, argc, argv)) || (res = sameaddress(s))){ + srvdiscard(s); + free(s); + return res; + } + /* listed only once it is sure to run: the thread reading the + configuration links and unlinks it here, the service unlinks itself */ + if(!conf->services){ + conf->services = &s->srv; + } + else { + s->srv.next = conf->services; + conf->services = conf->services->prev = &s->srv; + } + *service = s; + return 0; +} + +int mainrun(void *service){ + int res = srvrun((struct srvstart *)service); + + free(service); + return res; +} + +#endif + +#ifndef STDMAIN +/* A prepared service which could not be started. */ +void maindrop(void *service){ + struct srvstart *s = (struct srvstart *)service; + + srvunlink(&s->srv); + srvdiscard(s); + free(s); +} + +#endif + + +void srvinit(struct srvparam * srv, struct clientparam *param){ + + memset(srv, 0, sizeof(struct srvparam)); + srv->version = conf->version; + srv->paused = conf->paused; + 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; + srv->maxchild = conf->maxchild; + srv->backlog = conf->backlog; + srv->stacksize = conf->stacksize; + srv->config = conf; + memcpy(srv->timeouts, conf->timeouts, sizeof(srv->timeouts)); + srv->filtermaxsize = conf->filtermaxsize; + srv->parentretries = conf->parentretries; + srv->maxseg = conf->maxseg; + srv->delimchar = conf->delimchar; + srv->time_start = time(NULL); + if(conf->havelog && conf->logtarget){ + srv->logtarget = (unsigned char *)strdup((char *)conf->logtarget); + } + srv->srvsock = INVALID_SOCKET; + srv->logdumpsrv = conf->logdumpsrv; + srv->logdumpcli = conf->logdumpcli; + srv->cbsock = INVALID_SOCKET; + srv->needuser = 1; +#ifdef __linux__ + srv->i_nsfd = srv->o_nsfd = -1; +#endif +#ifdef WITHSPLICE + /* where it pays: large transfers, -s0 to never */ + srv->usesplice = SPLICE_DOWNLOAD|SPLICE_UPLOAD|SPLICE_AUTO; + srv->splicemin = SPLICE_MIN; + srv->splicestreak = SPLICE_STREAK; + srv->splicesmall = SPLICE_SMALL; +#endif + srv->halfclose = 1; + srv->udpauth = 3; + memset(param, 0, sizeof(struct clientparam)); + param->srv = srv; + param->version = srv->version; + param->paused = srv->paused; + param->remsock = param->clisock = param->ctrlsock = param->ctrlsocksrv = INVALID_SOCKET; + param->pipecli[0] = param->pipecli[1] = param->pipesrv[0] = param->pipesrv[1] = -1; + param->climark = -1; + *SAFAMILY(¶m->req) = *SAFAMILY(¶m->sinsl) = *SAFAMILY(¶m->sinsr) = *SAFAMILY(¶m->sincr) = *SAFAMILY(¶m->sincl) = AF_INET; + _3proxy_mutex_init(&srv->counter_mutex); + (void)_3proxy_sem_init_timed(srv->childslots, srv->maxchild, srv->maxchild); + srv->intsa = conf->intsa; + srv->extsa = conf->extsa; +#ifndef NOIPV6 + srv->extsa6 = conf->extsa6; +#endif + srv->so = so; + srv->authcachetime = conf->authcachetime; + srv->authcachetype = conf->authcachetype; +} + +void srvinit2(struct srvparam * srv, struct clientparam *param){ + if(srv->logformat){ + char *s; + if(*srv->logformat == '-' && (s = strchr((char *)srv->logformat + 1, '+')) && s[1]){ + unsigned char* logformat = srv->logformat; + + *s = 0; + srv->nonprintable = (unsigned char *)strdup((char *)srv->logformat + 1); + srv->replace = s[1]; + srv->logformat = (unsigned char *)strdup(s + 2); + *s = '+'; + free(logformat); + } + } + memset(¶m->sinsl, 0, sizeof(param->sinsl)); + memset(¶m->sinsr, 0, sizeof(param->sinsr)); + memset(¶m->req, 0, sizeof(param->req)); + *SAFAMILY(¶m->sinsl) = AF_INET; + *SAFAMILY(¶m->sinsr) = AF_INET; + *SAFAMILY(¶m->req) = AF_INET; + param->sincr = param->sincl = srv->intsa; +#ifndef NOIPV6 + if (srv->family == 6 || srv->family == 64) param->sinsr = srv->extsa6; + else +#endif + param->sinsr = srv->extsa; +} + +void srvfree(struct srvparam * srv){ + int i; + +#ifndef STDMAIN + /* a listening socket kept as a listener goes on if a service of a later + configuration took it over */ + if(srv->listener){ + srv->srvsock = INVALID_SOCKET; + listenerstop(srv); + } +#endif + if(srv->srvsock != INVALID_SOCKET) { + so._closesocket(srv->so.state, srv->srvsock); +#ifdef WITH_UN + ununlink(&srv->intsa, srv->undev, srv->unino); +#endif + } + srv->srvsock = INVALID_SOCKET; + if(srv->cbsock != INVALID_SOCKET) so._closesocket(srv->so.state, srv->cbsock); + srv->cbsock = INVALID_SOCKET; + srv->service = S_ZOMBIE; + /* Every child returns its slot as the last thing it does with the service, + so once all of them are back no child is left. A child posts its slot + holding the mutex: taking the mutex once more waits for the last one to + have let go of both before they are destroyed. */ + for(i = srv->maxchild; i; i--) _3proxy_sem_lock(srv->childslots); + _3proxy_mutex_lock(&srv->counter_mutex); + _3proxy_mutex_unlock(&srv->counter_mutex); +#ifndef STDMAIN + if(srv->filter){ + while(srv->nfilters){ + srv->nfilters--; + if(srv->filter[srv->nfilters].filter_close){ + (*srv->filter[srv->nfilters].filter_close)(srv->filter[srv->nfilters].data); + } + } + free(srv->filter); + } + + if(srv->acl)freeacl(srv->acl); +#ifdef WITH_HTTPSRV + if(srv->httprules)freehttprules(srv->httprules); +#endif + if(srv->authfuncs)freeauth(srv->authfuncs); +#endif + _3proxy_mutex_destroy(&srv->counter_mutex); + _3proxy_sem_destroy(srv->childslots); + if(srv->target) free(srv->target); + if(srv->logtarget) free(srv->logtarget); + if(srv->logformat) free(srv->logformat); + if(srv->nonprintable) free(srv->nonprintable); +#if defined SO_BINDTODEVICE || defined IP_BOUND_IF + if(srv->ibindtodevice) free(srv->ibindtodevice); + if(srv->obindtodevice) free(srv->obindtodevice); +#endif +#ifdef __linux__ + if(srv->inetns) free(srv->inetns); + if(srv->onetns) free(srv->onetns); + if(srv->i_nsfd >= 0) { close(srv->i_nsfd); srv->i_nsfd = -1; } + if(srv->o_nsfd >= 0) { close(srv->o_nsfd); srv->o_nsfd = -1; } +#endif + if(srv->so.freefunc) srv->so.freefunc(srv->so.state); +#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->listener) listenerrelease(srv); + if(srv->config){ + struct extparam *c = srv->config; + + srv->config = NULL; + confput(c); + } +#endif +} + +#ifndef STDMAIN +static void * itcopy (void * from, size_t size){ + void * ret; + if(!from) return NULL; + ret = malloc(size); + if(ret) memcpy(ret, from, size); + return ret; +} + + +struct auth * copyauth (struct auth * authfuncs){ + struct auth * newauth = NULL; + + newauth = itcopy(authfuncs, sizeof(struct auth)); + for( authfuncs=newauth; authfuncs; authfuncs = authfuncs->next){ + if(authfuncs->next){ + authfuncs->next = itcopy(authfuncs->next, sizeof(struct auth)); + if(!authfuncs->next)break; + } + } + if(authfuncs){ + freeauth(newauth); + return NULL; + } + return newauth; +} + +struct ace * copyacl (struct ace *ac){ + struct ace * ret = NULL; + struct iplist *ipl; + struct portlist *pl; + struct userlist *ul; + struct chain *ch; + struct period *pel; + struct hostname *hst; + + ret = itcopy(ac, sizeof(struct ace)); + for( ac = ret; ac; ac = ac->next){ + if(ac->src){ + ac->src = itcopy(ac->src, sizeof(struct iplist)); + if(!ac->src) goto ERRORSRC; + for(ipl = ac->src; ipl->next; ipl = ipl->next){ + ipl->next = itcopy(ipl->next, sizeof(struct iplist)); + if(!ipl->next) goto ERRORSRC; + } + } + if(ac->dst){ + ac->dst = itcopy(ac->dst, sizeof(struct iplist)); + if(!ac->dst) goto ERRORDST; + for(ipl = ac->dst; ipl->next; ipl = ipl->next){ + ipl->next = itcopy(ipl->next, sizeof(struct iplist)); + if(!ipl->next) goto ERRORDST; + } + } + if(ac->ports){ + ac->ports = itcopy(ac->ports, sizeof(struct portlist)); + if(!ac->ports) goto ERRORPORTS; + for(pl = ac->ports; pl->next; pl = pl->next){ + pl->next = itcopy(pl->next, sizeof(struct portlist)); + if(!pl->next) goto ERRORPORTS; + } + } + if(ac->periods){ + ac->periods = itcopy(ac->periods, sizeof(struct period)); + if(!ac->periods) goto ERRORPERIODS; + for(pel = ac->periods; pel->next; pel = pel->next){ + pel->next = itcopy(pel->next, sizeof(struct period)); + if(!pel->next) goto ERRORPERIODS; + } + } + if(ac->users){ + ac->users = itcopy(ac->users, sizeof(struct userlist)); + if(!ac->users) goto ERRORUSERS; + for(ul = ac->users; ul; ul = ul->next){ + if(ul->user) { + ul->user = (unsigned char*)strdup((char *)ul->user); + if(!ul->user) { + ul->next = NULL; + goto ERRORUSERS; + } + } + if(ul->next){ + ul->next = itcopy(ul->next, sizeof(struct userlist)); + if(!ul->next) goto ERRORUSERS; + } + } + } + if(ac->dstnames){ + ac->dstnames = itcopy(ac->dstnames, sizeof(struct hostname)); + if(!ac->dstnames) goto ERRORDSTNAMES; + for(hst = ac->dstnames; hst; hst = hst->next){ + if(hst->name) { + hst->name = (unsigned char*)strdup((char *)hst->name); + if(!hst->name) { + hst->re = NULL; + hst->next = NULL; + goto ERRORDSTNAMES; + } + } + /* each copy compiles its own pattern, freed with it */ + if(hst->re){ +#ifdef WITH_PCRE + char err[256]; + + hst->re = pcre_pattern_compile(hst->name, err, sizeof(err)); +#else + hst->re = NULL; +#endif + if(!hst->re) { + hst->next = NULL; + goto ERRORDSTNAMES; + } + } + if(hst->next){ + hst->next = itcopy(hst->next, sizeof(struct hostname)); + if(!hst->next) goto ERRORDSTNAMES; + } + } + } + if(ac->chains){ + ac->chains = itcopy(ac->chains, sizeof(struct chain)); + if(!ac->chains) goto ERRORCHAINS; + for(ch = ac->chains; ch; ch = ch->next){ + if(ch->extuser){ + ch->extuser = (unsigned char*)strdup((char *)ch->extuser); + if(!ch->extuser){ + ch->extpass = NULL; + ch->exthost = NULL; + ch->next = NULL; + goto ERRORCHAINS; + } + } + if(ch->extpass){ + ch->extpass = (unsigned char*)strdup((char *)ch->extpass); + if(!ch->extpass){ + ch->exthost = NULL; + ch->next = NULL; + goto ERRORCHAINS; + } + } + if(ch->exthost){ + ch->exthost = (unsigned char*)strdup((char *)ch->exthost); + if(!ch->exthost){ + ch->next = NULL; + goto ERRORCHAINS; + } + + } + if(ch->next){ + ch->next = itcopy(ch->next, sizeof(struct chain)); + if(!ch->next) goto ERRORNEXT; + } + } + } + if(ac->next){ + ac->next = itcopy(ac->next, sizeof(struct ace)); + if(!ac->next) goto ERRORCHAINS; + } + } + if(!ac) return ret; +ERRORSRC: + ac->dst = NULL; +ERRORDST: + ac->ports = NULL; +ERRORPORTS: + ac->periods = NULL; +ERRORPERIODS: + ac->users = NULL; +ERRORUSERS: + ac->dstnames = NULL; +ERRORDSTNAMES: + ac->chains = NULL; +ERRORCHAINS: + ac->next = NULL; +ERRORNEXT: + freeacl(ret); + return NULL; + +} + + +/* Returns 2 when out of memory, 3 when there are more filters than a + connection has room for: a service started without some of its filters + could pass what one of them was there to stop. */ +int copyfilter (struct filter *filter, struct srvparam *srv){ + int nfilters = 0; + + if(!filter) return 0; + for(srv->filter = filter; srv->filter; srv->filter = srv->filter->next) nfilters++; + srv->filter = malloc(sizeof(struct filter) * nfilters); + if(!srv->filter) return 2; + + for(; filter; filter = filter->next){ + void *data = NULL; + + if(!filter->filter_open || !(data = (*filter->filter_open)(filter->data, srv))) continue; + if(srv->nfilters >= MAX_FILTERS){ + if(filter->filter_close) (*filter->filter_close)(data); + return 3; + } + + srv->filter[srv->nfilters] = *filter; + srv->filter[srv->nfilters].data = data; + if(srv->nfilters>0)srv->filter[srv->nfilters - 1].next = srv->filter + srv->nfilters; + srv->nfilters++; + if(filter->filter_request)srv->nreqfilters++; + if(filter->filter_connect)srv->nconnectfilters++; + if(filter->filter_afterauth)srv->nafterauthfilters++; + if(filter->filter_header_srv)srv->nhdrfilterssrv++; + if(filter->filter_header_cli)srv->nhdrfilterscli++; + if(filter->filter_predata)srv->npredatfilters++; + if(filter->filter_data_srv)srv->ndatfilterssrv++; + if(filter->filter_data_cli)srv->ndatfilterscli++; + } + return 0; +} + +#endif + +#ifndef STDMAIN +void * itfree(void *data, void * retval){ + free(data); + return retval; +} + +void freeauth(struct auth * authfuncs){ + for(; authfuncs; authfuncs = (struct auth *)itfree(authfuncs, authfuncs->next)); +} + +void freeacl(struct ace *ac){ + struct iplist *ipl; + struct portlist *pl; + struct userlist *ul; + struct chain *ch; + struct period *pel; + struct hostname *hst; + for(; ac; ac = (struct ace *) itfree(ac, ac->next)){ + for(ipl = ac->src; ipl; ipl = (struct iplist *)itfree(ipl, ipl->next)); + for(ipl = ac->dst; ipl; ipl = (struct iplist *)itfree(ipl,ipl->next)); + for(pl = ac->ports; pl; pl = (struct portlist *)itfree(pl, pl->next)); + for(pel = ac->periods; pel; pel = (struct period *)itfree(pel, pel->next)); + for(ul = ac->users; ul; ul = (struct userlist *)itfree(ul, ul->next)){ + if(ul->user)free(ul->user); + } + for(hst = ac->dstnames; hst; hst = (struct hostname *)itfree(hst, hst->next)){ + if(hst->name)free(hst->name); +#ifdef WITH_PCRE + if(hst->re)pcre_pattern_free(hst->re); +#endif + } + for(ch = ac->chains; ch; ch = (struct chain *) itfree(ch, ch->next)){ + if(ch->extuser) free(ch->extuser); + if(ch->extpass) free(ch->extpass); + if(ch->exthost) free(ch->exthost); + } + } +} + +#endif diff --git a/src/mainlisten.c b/src/mainlisten.c new file mode 100644 index 0000000..fd682ab --- /dev/null +++ b/src/mainlisten.c @@ -0,0 +1,552 @@ +/* + 3APA3A simplest proxy server + (c) 2002-2026 by Vladimir Dubrovin + + please read License Agreement + +*/ + +/* Listening sockets, shared by the services of a configuration on one + address and taken over by those of the next one on a reload, and the + clients of the old services going by the new rules. + Part of proxymain.c, which includes it. */ + +/* The listening socket. A UDP one also receives the datagrams of new + clients, so it is their client socket too. 0, or the code to exit with. */ +#ifndef STDMAIN +/* A listening socket, shared by the services of a configuration on the + same address, port, network namespace and device, all accepting on it. + It outlives the services that opened it when, on a reload, services of + the new configuration on the same address with the same bind-time + options take it over. The old services stop accepting once their + configuration is no longer in force; whoever lets go of the socket last + closes it. A UDP service's socket is the way its sessions answer too: + the old sessions go on answering through it, the new services reading + what comes, the sessions found by client address whichever service made + them. */ +struct listener { + struct listener *next; + SOCKET sock; + int isudp; + /* a service connecting back (-r) has no socket to share, but its + clients find the newest service through a listener all the same: + one without a socket, for the place it connects to */ + char *cbdest; + PROXYSOCKADDRTYPE sa; + char *netns, *device; + struct sockoptset opts; + int maxseg; + uint64_t undev, unino; /* its file, for a Unix socket on a path */ + int optversion; /* the configuration whose options are set */ + /* the services accepting on it, linked by lisnext */ + struct srvparam *owners; + int users; /* the services holding it: those accepting, + and those which did until their last client + is gone, whose clients look up the newest + service through it after a reload */ + /* held while the first of its services opens the socket, binding + which may take long: the others then use what it opened */ + _3proxy_mutex_t open_mutex; + /* its clients, whichever of its services, old or new, they came to: + those connected now, and those done with */ + uint64_t clients, processed; + /* the traffic of its clients as logged: from the server (%I) and + from the client (%O) */ + uint64_t trafin, trafout; + _3proxy_mutex_t count_mutex; + /* a UDP service's sessions on a reload, for the newest service on the + listener to adopt */ + struct udpsession *udphand; +}; + +/* A client of srv comes (1) or goes (-1). */ +static void listenercount(struct srvparam *srv, int delta){ + struct listener *l = srv->listener; + + if(!l) return; + _3proxy_mutex_lock(&l->count_mutex); + if(delta > 0) l->clients++; + else { + l->clients--; + l->processed++; + } + _3proxy_mutex_unlock(&l->count_mutex); +} + +/* The traffic of a client, about to be cleared once logged, counted for + its listener. */ +void listenertraffic(struct clientparam *param){ + struct listener *l = param->srv->listener; + + if(!l) return; + _3proxy_mutex_lock(&l->count_mutex); + l->trafin += param->statssrv64; + l->trafout += param->statscli64; + _3proxy_mutex_unlock(&l->count_mutex); +} + +static uint64_t nocount = 0; + +/* The counters of a running service's listener, for the admin pages; the + listener lasts as long as the service. */ +uint64_t *listenerclients(struct srvparam *srv){ + return srv->listener? &srv->listener->clients : &nocount; +} + +uint64_t *listenerprocessed(struct srvparam *srv){ + return srv->listener? &srv->listener->processed : &nocount; +} + +uint64_t *listenertrafin(struct srvparam *srv){ + return srv->listener? &srv->listener->trafin : &nocount; +} + +uint64_t *listenertrafout(struct srvparam *srv){ + return srv->listener? &srv->listener->trafout : &nocount; +} +static struct listener *listeners = NULL; +_3proxy_mutex_t listener_mutex; + +static void listenerstop(struct srvparam *srv); +#ifndef NOUDPMAIN +static void udpendall(struct udpsession *list, int res); +#endif +static void takeopts(struct listener *l, struct srvparam *srv); +static void listenerrelease(struct srvparam *srv); +#endif + +#ifdef WITH_UN +/* A Unix socket bound to a path: its file is noted, as a service of a + newer configuration may bind the same path anew before this socket is + closed, when the file is the other's, which closing must not remove. */ +static void unbound(PROXYSOCKADDRTYPE *sa, uint64_t *dev, uint64_t *ino){ + struct stat st; + + if(*SAFAMILY(sa) == AF_UNIX && *SAADDR(sa) && !stat((char *)SAADDR(sa), &st)){ + *dev = (uint64_t)st.st_dev; + *ino = (uint64_t)st.st_ino; + } +} + +static void ununlink(PROXYSOCKADDRTYPE *sa, uint64_t dev, uint64_t ino){ + struct stat st; + + if(*SAFAMILY(sa) == AF_UNIX && *SAADDR(sa) && !stat((char *)SAADDR(sa), &st) && + (uint64_t)st.st_dev == dev && (uint64_t)st.st_ino == ino) + unlink((char *)SAADDR(sa)); +} +#endif + +/* The listening socket of the service: its listener's, opened by the first + of the listener's services to get here. */ +static int srvlisten(struct srvstart *s){ +#ifndef STDMAIN + struct srvparam *srv = &s->srv; + struct clientparam *defparam = &s->defparam; + struct listener *l = srv->listener; + SOCKET sock; + unsigned char buf[256]; + int res, taken; + + if(l){ + _3proxy_mutex_lock(&l->open_mutex); + _3proxy_mutex_lock(&listener_mutex); + sock = l->sock; + _3proxy_mutex_unlock(&listener_mutex); + if(sock == INVALID_SOCKET){ + if(!(res = srvopen(s))){ + _3proxy_mutex_lock(&listener_mutex); + l->sock = srv->srvsock; + l->optversion = srv->version; + l->undev = srv->undev; + l->unino = srv->unino; + _3proxy_mutex_unlock(&listener_mutex); + } + else if(srv->srvsock != INVALID_SOCKET){ + so._closesocket(so.state, srv->srvsock); + srv->srvsock = INVALID_SOCKET; + } + _3proxy_mutex_unlock(&l->open_mutex); + return res; + } + /* listening already: taken over from a service of the configuration + before, its options to be set, or opened by another service of + this one; only the backlog may be new */ + taken = l->optversion != srv->version; + if(taken) takeopts(l, srv); + _3proxy_mutex_unlock(&l->open_mutex); + srv->srvsock = sock; +#ifndef NOUDPMAIN + if(s->isudp) defparam->clisock = sock; + else +#endif + if(srv->so._listen (srv->so.state, sock, srv->backlog?srv->backlog : 1+(srv->maxchild>>3))==-1) { + srv->srvsock = INVALID_SOCKET; + sprintf((char *)buf, "listen(): %s", strerror(errno)); + if(!srv->silent)dolog(defparam, buf); + return -4; + } + if(!srv->silent){ + sprintf((char *)buf, "Accepting connections, %s [%"PRIu64"/%"PRIu64"]", taken? "taken over" : "shared", + (uint64_t)getpid(), (uint64_t)pthread_self()); + dolog(defparam, buf); + } + srv->fds.fd = sock; + srv->fds.events = POLLIN; + return 0; + } +#endif + return srvopen(s); +} + +/* Opens, binds and listens on the service's socket. */ +static int srvopen(struct srvstart *s){ + struct srvparam *srv = &s->srv; + struct clientparam *defparam = &s->defparam; + SOCKET sock = INVALID_SOCKET; + unsigned sleeptime; + unsigned char buf[256]; +#ifdef _WIN32 + unsigned long ul = 1; +#endif + +#ifndef NOTCPMAIN + if(!s->isudp){ + sock=nssocket(srv, &srv->so, srv->so.state, NS_IN, SASOCK(&srv->intsa), SOCK_STREAM, +#ifdef WITH_UN + *SAFAMILY(&srv->intsa) == AF_UNIX? 0 : +#endif + IPPROTO_TCP + ); + } +#endif +#ifndef NOUDPMAIN + if(s->isudp){ + sock=nssocket(srv, &srv->so, srv->so.state, NS_IN, SASOCK(&srv->intsa), SOCK_DGRAM, IPPROTO_UDP); + } +#endif + if( sock == INVALID_SOCKET) { + perror("socket()"); + return -2; + } + setopts(sock, &srv->lissockopts, srv->maxseg); +#ifdef _WIN32 + ioctlsocket(sock, FIONBIO, &ul); +#else + fcntl(sock,F_SETFL,O_NONBLOCK | fcntl(sock,F_GETFL)); +#endif + srv->srvsock = sock; +#if defined SO_BINDTODEVICE || defined IP_BOUND_IF + if(bindtodevice(&srv->so, srv->so.state, sock, srv->ibindtodevice, *SAFAMILY(&srv->intsa))) { + dolog(defparam, (unsigned char *)"failed to bind device"); + return -12; + } +#endif +#ifdef WITH_UN + if(*SAFAMILY(&srv->intsa) == AF_UNIX){ + struct sockaddr_un *sun = (struct sockaddr_un *)&srv->intsa; + if(*sun->sun_path)unlink(sun->sun_path); + } +#endif + for(sleeptime = SLEEPTIME * 100; srv->so._bind(srv->so.state, sock, (struct sockaddr*)&srv->intsa, SASIZE(&srv->intsa))==-1; usleep(sleeptime)) { + sprintf((char *)buf, "bind(): %s", strerror(errno)); + if(!srv->silent)dolog(defparam, buf); + sleeptime = (sleeptime<<1); + if(!sleeptime) return -3; + } +#ifdef WITH_UN + unbound(&srv->intsa, &srv->undev, &srv->unino); +#endif +#ifndef NOTCPMAIN + if(!s->isudp){ + if(srv->so._listen (srv->so.state, sock, srv->backlog?srv->backlog : 1+(srv->maxchild>>3))==-1) { + sprintf((char *)buf, "listen(): %s", strerror(errno)); + if(!srv->silent)dolog(defparam, buf); + return -4; + } + } +#endif +#ifndef NOUDPMAIN + if(s->isudp) defparam->clisock = sock; +#endif + srv->fds.fd = sock; + srv->fds.events = POLLIN; + + if(!srv->silent){ + sprintf((char *)buf, "Accepting connections [%"PRIu64"/%"PRIu64"]", (uint64_t)getpid(), (uint64_t)pthread_self()); + dolog(defparam, buf); + } + return 0; +} + +#ifndef STDMAIN +/* -ol options that act only when set before bind(): a listening socket + differing in them is another socket. The rest are set on a socket taken + over, those no longer asked for turned off. */ +static int bindtimeopt(const char *name){ + return !strncmp(name, "SO_REUSE", 8) || !strcmp(name, "SO_EXCLUSIVEADDRUSE") || + !strcmp(name, "SO_PORT_SCALABILITY") || !strcmp(name, "IP_TRANSPARENT") || + !strcmp(name, "TCP_FASTOPEN"); +} + +/* Whether a socket with the options of l can be made one with those of srv: + the same bind-time ones, with the same values; TCP_MAXSEG, whose default + is not 0, cannot be turned off again. */ +static int sameopts(struct listener *l, struct srvparam *srv){ + const struct sockoptset *a = &l->opts, *b = &srv->lissockopts; + int i; + + for(i = 0; sockopts[i].optname && i < MAXSOCKOPTS; i++){ + unsigned bit = 1u << i; + + if(bindtimeopt(sockopts[i].optname)){ + if((a->set & bit) != (b->set & bit)) return 0; + if((a->set & bit) && a->value[i] != b->value[i]) return 0; + } +#ifdef TCP_MAXSEG + else if(sockopts[i].opt == TCP_MAXSEG && *sockopts[i].optname == 'T' && + (a->set & bit) && !(b->set & bit)) return 0; +#endif + } + return 1; +} + +/* Sets the options of srv on the socket of l taken over: those given, and + off those which l had and srv has not. */ +static void takeopts(struct listener *l, struct srvparam *srv){ + struct sockoptset off; + int i; + + memset(&off, 0, sizeof(off)); + for(i = 0; sockopts[i].optname && i < MAXSOCKOPTS; i++){ + unsigned bit = 1u << i; + + if((l->opts.set & bit) && !(srv->lissockopts.set & bit) && !bindtimeopt(sockopts[i].optname)) + off.set |= bit; + } + if(off.set) setopts(l->sock, &off, 0); + setopts(l->sock, &srv->lissockopts, srv->maxseg); + _3proxy_mutex_lock(&listener_mutex); + l->opts = srv->lissockopts; + l->maxseg = srv->maxseg; + _3proxy_mutex_unlock(&listener_mutex); +} + +static int samelistener(struct listener *l, struct srvparam *srv, int isudp){ + if(l->isudp != isudp || l->cbdest) return 0; +#ifdef __linux__ + if(!samestr(l->netns, srv->inetns)) return 0; +#endif +#if defined SO_BINDTODEVICE || defined IP_BOUND_IF + if(!samestr(l->device, srv->ibindtodevice)) return 0; +#endif + return SASIZE(&l->sa) == SASIZE(&srv->intsa) && + !memcmp(&l->sa, &srv->intsa, SASIZE(&srv->intsa)) && + sameopts(l, srv); +} + +/* Whether s goes on listener l: one connecting back (-r) on the + pseudo-listener of the place it connects to, any other on the socket for + its address. */ +static int listenerfor(struct listener *l, struct srvstart *s){ + if(!s->isudp && s->iscbc) return l->cbdest && s->cbc_string && !strcmp(l->cbdest, (char *)s->cbc_string); + return !l->cbdest && samelistener(l, &s->srv, s->isudp); +} + +static int listenerhas(struct listener *l, struct extparam *config){ + struct srvparam *o; + + for(o = l->owners; o; o = o->lisnext) if(o->config == config) return 1; + return 0; +} + +/* A prepared service, before it starts and before, on a reload, the old + services are told to stop: it joins the listener another service of its + configuration has for the same address, or takes over one an older + configuration's services accept on, one of a service of its type first. + Otherwise it gets a listener of its own, its socket opened when it + starts. A service connecting back has a pseudo-listener without a socket + for the place it connects to, which several of its services may share + too. Without memory for a listener the service keeps its socket to + itself. */ +void mainclaim(void *service){ + struct srvstart *s = (struct srvstart *)service; + struct srvparam *srv = &s->srv; + struct listener *l, *found = NULL, *newl; + + /* made beforehand, as the mutex is held from finding a listener to + joining it, lest its last service let go of it meanwhile */ + if((newl = malloc(sizeof(*newl)))){ + memset(newl, 0, sizeof(*newl)); + newl->sock = INVALID_SOCKET; + newl->isudp = s->isudp; + newl->maxseg = srv->maxseg; + newl->sa = srv->intsa; + newl->opts = srv->lissockopts; + newl->optversion = srv->version; + if((!s->isudp && s->iscbc && s->cbc_string && !(newl->cbdest = strdup((char *)s->cbc_string))) +#ifdef __linux__ + || (srv->inetns && !(newl->netns = strdup(srv->inetns))) +#endif +#if defined SO_BINDTODEVICE || defined IP_BOUND_IF + || (srv->ibindtodevice && !(newl->device = strdup(srv->ibindtodevice))) +#endif + ){ + free(newl->cbdest); + free(newl->netns); + free(newl->device); + free(newl); + newl = NULL; + } + else { + _3proxy_mutex_init(&newl->open_mutex); + _3proxy_mutex_init(&newl->count_mutex); + } + } + _3proxy_mutex_lock(&listener_mutex); + for(l = listeners; l; l = l->next){ + if(!l->owners || !listenerfor(l, s)) continue; + if(listenerhas(l, srv->config)){ + found = l; + break; + } + if(!found || (l->owners->service == srv->service && found->owners->service != srv->service)) + found = l; + } + if(!found && (found = newl)){ + newl = NULL; + found->next = listeners; + listeners = found; + } + if(found){ + srv->lisnext = found->owners; + found->owners = srv; + found->users++; + srv->listener = found; + } + _3proxy_mutex_unlock(&listener_mutex); + if(newl){ + _3proxy_mutex_destroy(&newl->open_mutex); + _3proxy_mutex_destroy(&newl->count_mutex); + free(newl->cbdest); + free(newl->netns); + free(newl->device); + free(newl); + } +} + +/* A service stops accepting: once the last of the listener's services has, + the socket is closed, so that connections do not queue up on it. */ +static void listenerstop(struct srvparam *srv){ + struct listener *l = srv->listener; + struct srvparam **op; + SOCKET sock = INVALID_SOCKET; + struct udpsession *hand = NULL; + + _3proxy_mutex_lock(&listener_mutex); + for(op = &l->owners; *op; op = &(*op)->lisnext) if(*op == srv){ + *op = srv->lisnext; + srv->lisnext = NULL; + break; + } + if(!l->owners){ + sock = l->sock; + l->sock = INVALID_SOCKET; + /* sessions handed over with no service left to adopt them */ + hand = l->udphand; + l->udphand = NULL; + } + _3proxy_mutex_unlock(&listener_mutex); +#ifndef NOUDPMAIN + udpendall(hand, 26); +#endif + if(sock == INVALID_SOCKET) return; + so._closesocket(so.state, sock); +#ifdef WITH_UN + ununlink(&l->sa, l->undev, l->unino); +#endif +} + +/* A service lets go of its listener once its last client is gone, having + stopped accepting before; the last to let go frees it. */ +static void listenerrelease(struct srvparam *srv){ + struct listener *l = srv->listener, **lp; + int last; + + srv->listener = NULL; + _3proxy_mutex_lock(&listener_mutex); + last = !--l->users; + if(last) for(lp = &listeners; *lp; lp = &(*lp)->next) if(*lp == l) { + *lp = l->next; + break; + } + _3proxy_mutex_unlock(&listener_mutex); + if(!last) return; + _3proxy_mutex_destroy(&l->open_mutex); + _3proxy_mutex_destroy(&l->count_mutex); + if(l->netns) free(l->netns); + if(l->device) free(l->device); + if(l->cbdest) free(l->cbdest); + free(l); +} + +/* Gives back a client's slot in a service it was not a child of. */ +static void slotrelease(struct srvparam *srv){ + _3proxy_mutex_lock(&srv->counter_mutex); + _3proxy_sem_unlock(srv->childslots); + _3proxy_mutex_unlock(&srv->counter_mutex); +} + +/* After a reload, unless noforce: a client goes by the rules of a newest + service of its type on its listener, now accepting there, re-authenticated + as it if auth is set. It takes one of that service's maxconn slots, + which counts it there and keeps the service, and its configuration, + until the client is done; a slot it held in a service before is given + back. 0 to go on, or the code to end the connection with: 26 if no + service of its type is there now, 27 if none of those has a slot free, + or what the authentication says. */ +int reattach(struct clientparam *param, int auth){ + struct srvparam *cur = PSRV(param), *newest, *orig, *o; + int version = runconf->version, res, newv = 0; + + /* without a listener (connecting back) nothing replaces it */ + if(!cur->listener){ + param->version = version; + return 0; + } + _3proxy_mutex_lock(&listener_mutex); + for(o = cur->listener->owners; o; o = o->lisnext) if(o->version > newv) newv = o->version; + if(newv <= cur->version){ + /* the old services have not let go of the socket yet */ + _3proxy_mutex_unlock(&listener_mutex); + return 0; + } + /* one of the newest services of the client's type with a slot free; + param->service: the old service is a zombie by now. While it accepts + such a service is not freed, nor after the client has its slot. */ + res = 26; + newest = NULL; + for(o = cur->listener->owners; o; o = o->lisnext){ + if(o->version != newv || o->service != param->service) continue; + if(!_3proxy_sem_timedlock(o->childslots, 0)){ + newest = o; + break; + } + res = 27; + } + _3proxy_mutex_unlock(&listener_mutex); + if(!newest) return res; + if(param->newsrv) slotrelease(param->newsrv); + param->newsrv = newest; + param->version = version; + if(!auth) return 0; + /* authenticated as the newest service's client: its access lists, users, + limits and counters */ + orig = param->srv; + param->srv = newest; + res = (*newest->authfunc)(param); + param->srv = orig; + return res; +} + +#endif diff --git a/src/maintcp.c b/src/maintcp.c new file mode 100644 index 0000000..a5bf1f9 --- /dev/null +++ b/src/maintcp.c @@ -0,0 +1,217 @@ +/* + 3APA3A simplest proxy server + (c) 2002-2026 by Vladimir Dubrovin + + please read License Agreement + +*/ + +/* The main loop of a TCP service: a slot of maxchild, then a connection + accepted, or made back for -r, given a thread. + Part of proxymain.c, which includes it. */ + +#ifndef NOTCPMAIN +/* -R: the socket the far side of a TCP service connects back to. */ +static int cblisten(struct srvstart *s){ + struct srvparam *srv = &s->srv; + struct clientparam *defparam = &s->defparam; + PROXYSOCKADDRTYPE cbsa; + + parsehost(srv->family, s->cbl_string, (struct sockaddr *)&cbsa); + if((srv->cbsock=nssocket(srv, &srv->so, srv->so.state, NS_OUT, SASOCK(&cbsa), SOCK_STREAM, IPPROTO_TCP))==INVALID_SOCKET) { + dolog(defparam, (unsigned char *)"Failed to allocate connect back socket"); + return -6; + } + setopts(srv->cbsock, &srv->cbssockopts, srv->maxseg); + + if(srv->so._bind(srv->so.state, srv->cbsock, (struct sockaddr*)&cbsa, SASIZE(&cbsa))==-1) { + dolog(defparam, (unsigned char *)"Failed to bind connect back socket"); + return -7; + } + if(srv->so._listen(srv->so.state, srv->cbsock, 1 + (srv->maxchild>>4))==-1) { + dolog(defparam, (unsigned char *)"Failed to listen connect back socket"); + return -8; + } + return 0; +} + +#endif + +/* What getting the next client came to. */ +enum { NEXT_CLIENT, NEXT_RETRY, NEXT_STOP }; + +#ifndef NOTCPMAIN +/* The next TCP client: accepted, or with -r connected to and asked to + go ahead. */ +static int tcpnext(struct srvstart *s, SOCKET *client, int *nlog){ + struct srvparam *srv = &s->srv; + struct clientparam *defparam = &s->defparam; + SOCKET new_sock; + SASIZETYPE size; + unsigned char buf[256]; + int error = 0, opt = 1; +#ifndef _WIN32 + struct linger lg; +#else + unsigned long ul = 1; +#endif + + size = sizeof(defparam->sincr); + if(s->iscbc){ + new_sock=nssocket(&s->srv, &so, so.state, NS_OUT, SASOCK(&defparam->sincr), SOCK_STREAM, IPPROTO_TCP); + if(new_sock == INVALID_SOCKET){ + usleep(SLEEPTIME); + return NEXT_RETRY; + } + setopts(new_sock, &srv->cbcsockopts, srv->maxseg); + + parsehost(srv->family, s->cbc_string, (struct sockaddr *)&defparam->sincr); +#if defined SO_BINDTODEVICE || defined IP_BOUND_IF + /* as for a listening socket, an interface that cannot be + bound to is not worth retrying */ + if(bindtodevice(&so, so.state, new_sock, srv->ibindtodevice, *SAFAMILY(&defparam->sincr))) { + dolog(defparam, (unsigned char *)"failed to bind device"); + so._closesocket(so.state, new_sock); + return NEXT_STOP; + } +#endif + if(connectwithpoll(NULL, new_sock,(struct sockaddr *)&defparam->sincr,SASIZE(&defparam->sincr),CONNBACK_TO)) { + so._closesocket(so.state, new_sock); + usleep(SLEEPTIME); + return NEXT_RETRY; + } + + if(sockrecvfrom(NULL, new_sock, NULL, buf, 1, 60*1000) != 1 || *buf!='C') { + so._closesocket(so.state, new_sock); + usleep(SLEEPTIME); + return NEXT_RETRY; + } + } + else { + new_sock = srv->so._accept(srv->so.state, srv->srvsock, (struct sockaddr*)&defparam->sincr, &size); + if(new_sock == INVALID_SOCKET){ +#ifdef _WIN32 + switch(WSAGetLastError()){ + case WSAEMFILE: + case WSAENOBUFS: + case WSAENETDOWN: + usleep(SLEEPTIME * 10); + break; + case WSAEINTR: + error = 1; + break; + default: + break; + } + +#else + switch (errno){ +#ifdef EMFILE + case EMFILE: +#endif +#ifdef ENFILE + case ENFILE: +#endif +#ifdef ENOBUFS + case ENOBUFS: +#endif +#ifdef ENOMEM + case ENOMEM: +#endif + usleep(SLEEPTIME * 10); + break; + + default: + break; + } +#endif + (*nlog)++; + if(!srv->silent && (error || *nlog > 5000)) { + sprintf((char *)buf, "accept(): %s", strerror(errno)); + dolog(defparam, buf); + *nlog = 0; + } + return NEXT_RETRY; + } + setopts(new_sock, &srv->clisockopts, srv->maxseg); + } + size = sizeof(defparam->sincl); + if(srv->so._getsockname(srv->so.state, new_sock, (struct sockaddr *)&defparam->sincl, &size)){ + sprintf((char *)buf, "getsockname(): %s", strerror(errno)); + if(!srv->silent)dolog(defparam, buf); + srv->so._closesocket(srv->so.state, new_sock); + return NEXT_RETRY; + } +#ifdef WITH_UN + if(*SAFAMILY(&defparam->sincl) == AF_UNIX) defparam->sincr = defparam->sincl; +#endif +#ifdef _WIN32 + ioctlsocket(new_sock, FIONBIO, &ul); +#else + fcntl(new_sock,F_SETFL,O_NONBLOCK | fcntl(new_sock,F_GETFL)); +#endif +#ifndef _WIN32 + /* Not on Windows: there closesocket() on a non-blocking socket with a linger + timeout fails with WSAEWOULDBLOCK while data is queued, leaving the socket + open. Without it the close completes in the background, data first. */ + lg.l_onoff = 1; + lg.l_linger = srv->timeouts[STRING_L]; + srv->so._setsockopt(srv->so.state, new_sock, SOL_SOCKET, SO_LINGER, (char *)&lg, sizeof(lg)); +#endif + srv->so._setsockopt(srv->so.state, new_sock, SOL_SOCKET, SO_OOBINLINE, (char *)&opt, sizeof(int)); + *client = new_sock; + return NEXT_CLIENT; +} +#endif + +#ifndef NOTCPMAIN +/* Clients until the service is paused: a connection is only taken with a + slot of maxchild in hand, which goes to its child; a slot not handed on is + kept for the next one, in *haveslot, and given back by the caller. */ +static int tcploop(struct srvstart *s, int *haveslot){ + struct srvparam *srv = &s->srv; + struct clientparam *defparam = &s->defparam; + SOCKET new_sock = INVALID_SOCKET; + int error = 0, next, nfull = 0; + int nlog = 5000; + unsigned char buf[256]; + /* -r: a TCP service connects out for each client instead of listening */ + int connectback = s->iscbc; + + for (;;) { + for(;;){ + /* A connection is only taken with a slot of maxchild in hand, which + goes to its child; a slot not handed on is kept for the next one. + The wait is timed to notice a reload. */ + while(!(*haveslot) && srv->config->paused == srv->paused){ + if(!_3proxy_sem_timedlock(srv->childslots, 1000)){ + (*haveslot) = 1; + nfull = 0; + break; + } + if(!srv->silent && !(nfull++ % 5)) { + sprintf((char *)buf, "Warning: too many connected clients (%d/%d)", srv->childcount, srv->maxchild); + dolog(defparam, buf); + } + } + if (connectback) break; + if (srv->config->paused != srv->paused) break; + error = srv->so._poll(srv->so.state, &srv->fds, 1, 1000); + if (error >= 1) break; + if (error == 0) continue; + if (errno != EAGAIN && errno != EINTR) { + sprintf((char *)buf, "poll(): %s/%d", strerror(errno), errno); + if(!srv->silent)dolog(defparam, buf); + break; + } + } + if((srv->config->paused != srv->paused) || (error < 0)) break; + error = 0; + next = tcpnext(s, &new_sock, &nlog); + if(next == NEXT_STOP) break; + if(next == NEXT_RETRY) continue; + if(!srvspawn(s, new_sock)) (*haveslot) = 0; + } + return 0; +} +#endif diff --git a/src/mainudp.c b/src/mainudp.c new file mode 100644 index 0000000..028d1fa --- /dev/null +++ b/src/mainudp.c @@ -0,0 +1,391 @@ +/* + 3APA3A simplest proxy server + (c) 2002-2026 by Vladimir Dubrovin + + please read License Agreement + +*/ + +/* The main loop of a UDP service: no thread for a client. + Part of proxymain.c, which includes it. */ + +#ifndef NOUDPMAIN +/* A UDP service's loop, in the service's thread: the datagrams of its + clients come on the service's socket, those of their destinations on the + sockets of their sessions, and every answer to a client leaves from the + service's socket. No thread for a client. */ +struct udpstate { + struct udploop u; + const struct udpservice *svc; + struct udpsession *head; + int count; + struct hashtable keys; /* a session by its client's address */ + struct pollfd *fds; + struct udpsession **of; /* the session of each of fds, NULL: the service */ + int nalloc; + int nfull; +}; + +static void udpkey2hash(const struct hashtable *ht, void *index, uint8_t *hash){ + PROXYSOCKADDRTYPE *sa = (PROXYSOCKADDRTYPE *)index; + uint32_t m1, m2; + + m1 = murmurhash3(SAADDR(sa), SAADDRLEN(sa), 0x3a3a3a3a); + m2 = murmurhash3(SAPORT(sa), 2, m1); + memcpy(hash, &m1, 4); + memcpy(hash+4, &m2, 4); +} + +int udpanswer(struct udploop *u, struct clientparam *param, unsigned char *buf, int len){ + return param->srv->so._sendto(param->sostate, param->srv->srvsock, (char *)buf, len, 0, + (struct sockaddr *)¶m->sincr, SASIZE(¶m->sincr)); +} + +/* A session among its service's children, counted there and, if it is new, + on the listener. */ +static void udpjoin(struct srvparam *srv, struct clientparam *param, int new){ + param->prev = param->next = NULL; + _3proxy_mutex_lock(&srv->counter_mutex); + if(srv->child){ + param->next = srv->child; + srv->child->prev = param; + } + srv->child = param; + srv->childcount++; +#ifndef STDMAIN + if(new) listenercount(srv, 1); +#endif + _3proxy_mutex_unlock(&srv->counter_mutex); +} + +/* A new client: a slot of maxchild and a param, as a thread for it would + have; NULL without a slot or memory. */ +static struct udpsession *udpnew(struct udpstate *st){ + struct srvparam *srv = st->u.srv; + struct udpsession *ss; + struct clientparam *param = NULL; + + if(_3proxy_sem_timedlock(srv->childslots, 0)){ + if(!srv->silent && !(st->nfull++ % 100)){ + char buf[128]; + + sprintf(buf, "Warning: too many connected clients (%d/%d), datagram dropped", srv->childcount, srv->maxchild); + dolog(st->u.defparam, (unsigned char *)buf); + } + return NULL; + } + st->nfull = 0; + if(!(ss = calloc(1, sizeof(*ss))) || !(param = malloc(sizeof(*param)))){ + free(ss); + _3proxy_sem_unlock(srv->childslots); + return NULL; + } + memset(param, 0, sizeof(*param)); + memcpy(param, st->u.defparam, offsetof(struct clientparam, filters)); + param->clibuf = param->clibufdata; + param->srvbuf = param->srvbufdata; + param->clibufsize = param->srvbufsize = SRVBUFSIZE(srv); + clearstat(param); + param->clisock = param->remsock = param->ctrlsock = param->ctrlsocksrv = INVALID_SOCKET; + udpjoin(srv, param, 1); + ss->param = param; + ss->svc = st->svc; +#ifdef MODULEMAINFUNC + if(makefilters(srv, param) > CONTINUE){ + freeparam(param); + free(ss); + return NULL; + } +#endif + return ss; +} + +/* A session ends: logged by its service, its sockets closed, its slot + given back. Its own service may be gone after. */ +static void udpend(struct udpsession *ss){ + struct clientparam *param = ss->param; + + if(ss->svc->end) (*ss->svc->end)(NULL, ss); + param->clisock = INVALID_SOCKET; + freeparam(param); + free(ss); +} + +static void udpendall(struct udpsession *list, int res){ + struct udpsession *ss; + + while((ss = list)){ + list = ss->next; + if(!ss->param->res) ss->param->res = res; + udpend(ss); + } +} + +static void udpadd(struct udpstate *st, struct udpsession *ss){ + ss->prev = NULL; + ss->next = st->head; + if(st->head) st->head->prev = ss; + st->head = ss; + st->count++; + ss->keyed = st->svc->keyed; + if(ss->keyed) hashadd(&st->keys, &ss->param->sincr, &ss, MAX_COUNTER_TIME); +} + +static void udpremove(struct udpstate *st, struct udpsession *ss){ + if(ss->prev) ss->prev->next = ss->next; + else st->head = ss->next; + if(ss->next) ss->next->prev = ss->prev; + ss->next = ss->prev = NULL; + st->count--; + if(ss->keyed) hashdelete(&st->keys, &ss->param->sincr); +} + +static void udprefresh(struct udpsession *ss, time_t now){ + if(ss->idle) ss->deadline = now + ss->idle; +} + +#ifndef STDMAIN +/* On a reload: the sessions go to the listener, for the newest service on + it to adopt; if it has none, the last service to stop on it ends them. */ +static void udphandover(struct udpstate *st){ + struct listener *l = st->u.srv->listener; + struct udpsession *ss, *last = NULL; + + for(ss = st->head; ss; ss = ss->next){ + if(ss->keyed) hashdelete(&st->keys, &ss->param->sincr); + ss->keyed = 0; + last = ss; + } + if(!last) return; + if(!l){ + udpendall(st->head, 26); + st->head = NULL; + return; + } + _3proxy_mutex_lock(&listener_mutex); + last->next = l->udphand; + if(l->udphand) l->udphand->prev = last; + l->udphand = st->head; + _3proxy_mutex_unlock(&listener_mutex); + st->head = NULL; + st->count = 0; +} + +/* Sessions of an older service on the listener become this one's: a slot + here, the old one given back, its rules and limits, re-authenticated; 26 + if it is another kind of service, 27 without a slot. */ +static void udpadopt(struct udpstate *st){ + struct srvparam *srv = st->u.srv, *old; + struct listener *l = srv->listener; + struct udpsession *list, *ss; + struct clientparam *param; + int res; + + if(!l || !l->udphand) return; + _3proxy_mutex_lock(&listener_mutex); + list = l->udphand; + l->udphand = NULL; + _3proxy_mutex_unlock(&listener_mutex); + while((ss = list)){ + list = ss->next; + ss->next = ss->prev = NULL; + param = ss->param; + old = param->srv; + if(old == srv) { udpadd(st, ss); continue; } + if(param->service != srv->service){ + param->res = 26; + udpend(ss); + continue; + } + if(_3proxy_sem_timedlock(srv->childslots, 0)){ + param->res = 27; + udpend(ss); + continue; + } + /* what belongs to the old service and configuration let go */ + while(param->nfilters > 0){ + param->nfilters--; + if(param->filters[param->nfilters].filter->filter_clear) + (*param->filters[param->nfilters].filter->filter_clear)(param->filters[param->nfilters].data); + } + if(param->connlim) stopconnlims(param); + param->bandlimfunc = NULL; + param->bandlims[0] = param->bandlimsout[0] = NULL; + param->trafcountfunc = NULL; + param->maxtrafin64 = param->maxtrafout64 = 0; + _3proxy_mutex_lock(&old->counter_mutex); + if(param->prev) param->prev->next = param->next; + else old->child = param->next; + if(param->next) param->next->prev = param->prev; + old->childcount--; + _3proxy_sem_unlock(old->childslots); + _3proxy_mutex_unlock(&old->counter_mutex); + param->srv = srv; + param->sostate = srv->so.state; + param->version = srv->version; + udpjoin(srv, param, 0); + ss->svc = st->svc; +#ifdef MODULEMAINFUNC + if(makefilters(srv, param) > CONTINUE){ + param->res = 26; + udpend(ss); + continue; + } +#endif + if((res = (*srv->authfunc)(param))){ + param->res = res; + udpend(ss); + continue; + } + udpadd(st, ss); + } +} +#endif + +#ifndef STDMAIN +/* After a reload, while an older service of this kind still accepts on the + listener, it goes on serving the service's socket alone, until it hands + its sessions over: a client is not given a session in both. */ +static int udpolder(struct srvparam *srv){ + struct srvparam *o; + int older = 0; + + if(!srv->listener) return 0; + _3proxy_mutex_lock(&listener_mutex); + for(o = srv->listener->owners; o; o = o->lisnext) + if(o != srv && o->version < srv->version && o->service == srv->service) older = 1; + _3proxy_mutex_unlock(&listener_mutex); + return older; +} +#endif + +/* The datagrams waiting on the service's socket: to a session, or a new + one. Some at a time, so that the sessions get their turn. */ +static void udpclients(struct udpstate *st, time_t now){ + struct srvparam *srv = st->u.srv; + struct clientparam *defparam = st->u.defparam; + struct udpsession *ss; + SASIZETYPE size; + int len, n; + + for(n = 0; n < 64; n++){ + size = sizeof(defparam->sincr); + len = srv->so._recvfrom(srv->so.state, srv->srvsock, (char *)srv->udpbuf, UDPBUFSIZE, 0, + (struct sockaddr *)&defparam->sincr, &size); + if(len < 0){ +#ifdef _WIN32 + /* an answer to a client was refused (ICMP): reported here */ + if(WSAGetLastError() == WSAECONNRESET) continue; +#endif + break; + } + if(st->svc->keyed && hashresolv(&st->keys, &defparam->sincr, &ss, NULL)){ + if(ss->done) continue; + udprefresh(ss, now); + if(st->svc->client && (*st->svc->client)(&st->u, ss, srv->udpbuf, len)) ss->done = 1; + continue; + } + if(!(ss = udpnew(st))) continue; + ss->param->sincr = defparam->sincr; + udpadd(st, ss); + if((*st->svc->first)(&st->u, ss, srv->udpbuf, len)) ss->done = 1; + else if(!ss->deadline || ss->idle) udprefresh(ss, now); + } +} + +static int udploop(struct srvstart *s){ + struct udpstate st; + struct srvparam *srv = &s->srv; + struct udpsession *ss, *next; + time_t now; + int i, n, nfds, timeo, res = 0, wait = 0; + + memset(&st, 0, sizeof(st)); + st.u.srv = srv; + st.u.defparam = &s->defparam; + st.u.buf = srv->udpbuf2; + st.svc = s->udp; + st.keys.index2hash_add = udpkey2hash; + st.keys.index2hash_search = udpkey2hash; + st.keys.recsize = sizeof(struct udpsession *); + st.keys.hash_size = 8; + if(st.svc->keyed && inithashtable(&st.keys, 64, 256, 1048576)) return 21; + for(;;){ + if(srv->config->paused != srv->paused) break; +#ifndef STDMAIN + udpadopt(&st); +#endif + if(st.nalloc < 2 * st.count + 1){ + int want = 2 * st.count + 64; + struct pollfd *f = realloc(st.fds, want * sizeof(*f)); + + if(f) st.fds = f; + if(f && (st.of = realloc(st.of, want * sizeof(*st.of)))) st.nalloc = want; + else { res = 21; break; } + } + now = time(NULL); + timeo = 1000; +#ifndef STDMAIN + /* waiting for an older service's sessions: soon looked for again */ + if((wait = udpolder(srv))) timeo = 20; +#endif + nfds = 0; + st.fds[nfds].fd = srv->srvsock; + st.fds[nfds].events = wait? 0 : POLLIN; + st.fds[nfds].revents = 0; + st.of[nfds++] = NULL; + for(ss = st.head; ss; ss = ss->next){ + if(ss->deadline && (ss->deadline - now) * 1000 < timeo) + timeo = ss->deadline > now? (int)(ss->deadline - now) * 1000 : 0; + if(ss->param->remsock != INVALID_SOCKET){ + st.fds[nfds].fd = ss->param->remsock; + st.fds[nfds].events = POLLIN; + st.fds[nfds].revents = 0; + st.of[nfds++] = ss; + } + /* a parent's control connection: its end ends the session */ + if(ss->param->ctrlsocksrv != INVALID_SOCKET && ss->param->ctrlsocksrv != ss->param->remsock){ + st.fds[nfds].fd = ss->param->ctrlsocksrv; + st.fds[nfds].events = POLLIN; + st.fds[nfds].revents = 0; + st.of[nfds++] = ss; + } + } + n = srv->so._poll(srv->so.state, st.fds, nfds, timeo); + if(n < 0 && errno != EINTR && errno != EAGAIN){ + res = 91; + break; + } + now = time(NULL); + for(i = 1; n > 0 && i < nfds; i++){ + ss = st.of[i]; + if(!st.fds[i].revents || ss->done) continue; + if(st.fds[i].fd == ss->param->remsock){ + udprefresh(ss, now); + if((*st.svc->server)(&st.u, ss)) ss->done = 1; + } + else ss->done = 1; + } + if(n > 0 && !wait && st.fds[0].revents) udpclients(&st, now); + for(ss = st.head; ss; ss = next){ + next = ss->next; + if(!ss->done && ss->deadline && ss->deadline <= now){ + ss->done = 1; + ss->param->res = st.svc->timeoutres; + } + if(ss->done){ + udpremove(&st, ss); + udpend(ss); + } + } + } +#ifndef STDMAIN + if(srv->config->paused != srv->paused) udphandover(&st); +#endif + udpendall(st.head, 26); + if(st.svc->keyed) destroyhashtable(&st.keys); + free(st.fds); + free(st.of); + return res; +} +#endif diff --git a/src/proxymain.c b/src/proxymain.c index e991a4b..000b849 100644 --- a/src/proxymain.c +++ b/src/proxymain.c @@ -11,354 +11,6 @@ #include #endif -/* Child functions do not call each other, a child requesting redirection to - another child returns it instead of calling it, to keep the stack flat. - The child which completed the request returns NULL. param is logged by - the child and released here, but not freed: its thread may keep it. - */ -void * childfunc(struct clientparam * param){ - PROXYFUNC pf = param->srv->pf; - int i; - - for(i = 0; pf && i < MAXCHILDREDIRECTS; i++) pf = (PROXYFUNC)(*pf)(param); - if(pf){ - param->res = 101; - dolog(param, (unsigned char *)"Redirection loop"); - } - releaseparam(param); - return NULL; -} - -/* One connection, from the filters to the end of the child which answers - it. The structure is released, not freed, when it returns. */ -static void serveparam(struct clientparam *param){ - int i = -1; -#ifdef MODULEMAINFUNC - if(makefilters(param->srv, param) > CONTINUE){ - releaseparam(param); - return; - } -#endif - if(param->srv->cbsock != INVALID_SOCKET){ - SASIZETYPE size = sizeof(param->sinsr); - struct pollfd fds; - fds.fd = param->srv->cbsock; - fds.events = POLLIN; - fds.revents = 0; - for(i=5+(param->srv->maxchild>>10); i; i--){ - if(param->srv->so._poll(param->sostate, &fds, 1, 1000*CONNBACK_TO)!=1){ - dolog(param, (unsigned char *)"Connect back not received, check connback client"); - i = 0; - break; - } - param->remsock = param->srv->so._accept(param->sostate, param->srv->cbsock, (struct sockaddr*)¶m->sinsr, &size); - if(param->remsock == INVALID_SOCKET) { - dolog(param, (unsigned char *)"Connect back accept() failed"); - continue; - } - { -#ifdef _WIN32 - unsigned long ul=1; - ioctlsocket(param->remsock, FIONBIO, &ul); -#else - fcntl(param->remsock,F_SETFL,O_NONBLOCK | fcntl(param->remsock,F_GETFL)); -#endif - } -#ifndef WITHMAIN - param->req = param->sinsr; - if(param->srv->acl) param->res = checkACL(param); - if(param->res){ - dolog(param, (unsigned char *)"Connect back ACL failed"); - param->srv->so._closesocket(param->sostate, param->remsock); - param->remsock = INVALID_SOCKET; - continue; - } -#endif - if(socksend(param, param->remsock, (unsigned char *)"C", 1, CONNBACK_TO) != 1){ - dolog(param, (unsigned char *)"Connect back sending command failed"); - param->srv->so._closesocket(param->sostate, param->remsock); - param->remsock = INVALID_SOCKET; - continue; - } - - break; - } - } - if(!i){ - param->res = 13; - releaseparam(param); - } - else { - -#ifndef WITHMAIN -#ifndef _WIN32 - sigset_t mask; - sigfillset(&mask); - pthread_sigmask(SIG_SETMASK, &mask, NULL); -#endif -#endif - - if(param->srv->haproxy){ - char buf[128]; - int i; - i = sockgetlinebuf(param, CLIENT, (unsigned char *)buf, sizeof(buf)-1, '\n', param->srv->timeouts[STRING_S]); - if(i > 12 && !strncasecmp(buf, "PROXY TCP", 9)){ - char *token, *token2=NULL; - unsigned short u1=0, u2=0; - buf[i] = 0; - token = strchr(buf, ' '); - if(token) token = strchr(token+1, ' '); - if(token) token++; - if(token) token2 = strchr(token+1, ' '); - if(token2) { - *token2 = 0; - getip46(46, (unsigned char*) token, (struct sockaddr *)¶m->sincr); - token = token2+1; - token2 = strchr(token, ' '); - } - if(token2) { - *token2 = 0; - getip46(46, (unsigned char *) token, (struct sockaddr *)¶m->sincl); - token = token2+1; - token2 = strchr(token, ' '); - } - if(token){ - sscanf(token,"%hu%hu", &u1, &u2); - if(u1) *SAPORT(¶m->sincr) = htons(u1); - if(u2) *SAPORT(¶m->sincl) = htons(u2); - } - } - } - childfunc(param); - } -} - -/* Frees a worker's structure, once its thread is done with it. */ -static void freeworker(struct clientparam *param){ - releaseparam(param); - _3proxy_sem_destroy(param->wake); - free(param); -} - -/* The automatic cap: 32 idle workers for each running service, but never - more than all of them could have connections. */ -static unsigned poollimit(void){ - unsigned limit; - - if(runconf->poolmax >= 0) limit = (unsigned)runconf->poolmax; - else { - limit = 32 * tpool.services; - if(limit > tpool.maxchild) limit = tpool.maxchild; - } - tpool.limit = limit; - return limit; -} - -/* Parks a worker whose connection has ended and waits for the next one. - Returns 0 when the thread is to end instead: on exit, when the pool is - full, when the thread was left in another network namespace, or when it - waited longer than the idle timeout. */ -static int poolpark(struct clientparam *param){ - int timeout; - - _3proxy_mutex_lock(&tpool.mutex); - if(timetoexit || nsbroken() || tpool.nidle >= poollimit()){ - _3proxy_mutex_unlock(&tpool.mutex); - return 0; - } - param->poolnext = tpool.idle; - tpool.idle = param; - tpool.nidle++; - param->pooled = 1; - timeout = runconf->pooltimeout; - _3proxy_mutex_unlock(&tpool.mutex); - - if(timeout <= 0) _3proxy_sem_lock(param->wake); - else if(_3proxy_sem_timedlock(param->wake, (unsigned)timeout * 1000)){ - struct clientparam **pp; - - _3proxy_mutex_lock(&tpool.mutex); - if(param->pooled){ - for(pp = &tpool.idle; *pp != param; pp = &(*pp)->poolnext); - *pp = param->poolnext; - tpool.nidle--; - param->pooled = 0; - _3proxy_mutex_unlock(&tpool.mutex); - return 0; - } - _3proxy_mutex_unlock(&tpool.mutex); - /* taken from the pool as the wait ran out: the connection follows */ - _3proxy_sem_lock(param->wake); - } - return !param->poolexit; -} - -/* Wakes every idle worker to end, when the process exits. */ -void pooldrain(void){ - struct clientparam *param; - - _3proxy_mutex_lock(&tpool.mutex); - while((param = tpool.idle)){ - tpool.idle = param->poolnext; - tpool.nidle--; - param->pooled = 0; - param->poolexit = 1; - _3proxy_sem_unlock(param->wake); - } - _3proxy_mutex_unlock(&tpool.mutex); -} - -#ifdef _WIN32 -DWORD WINAPI threadfunc(LPVOID p) { -#else -void * threadfunc (void *p) { -#endif - struct clientparam *param = (struct clientparam *)p; - - do serveparam(param); - while(poolpark(param)); - freeworker(param); -#ifdef _WIN32 - return 0; -#else - return NULL; -#endif -} - -#ifdef _WIN32 -/* Present since Windows 7 (SO_PORT_SCALABILITY) and Windows 10 / Server 2019 - (SO_REUSE_UNICASTPORT), define them if the SDK is older so the options can - still be requested. setsockopt() just fails on a system which does not - support them and the failure is ignored. - */ -#ifndef SO_PORT_SCALABILITY -#define SO_PORT_SCALABILITY 0x3006 -#endif -#ifndef SO_REUSE_UNICASTPORT -#define SO_REUSE_UNICASTPORT 0x3007 -#endif -#endif - -struct socketoptions sockopts[] = { -#ifdef TCP_NODELAY - {TCP_NODELAY, "TCP_NODELAY"}, -#endif -#ifdef TCP_CORK - {TCP_CORK, "TCP_CORK"}, -#endif -#ifdef TCP_DEFER_ACCEPT - {TCP_DEFER_ACCEPT, "TCP_DEFER_ACCEPT"}, -#endif -#ifdef TCP_QUICKACK - {TCP_QUICKACK, "TCP_QUICKACK"}, -#endif -#ifdef TCP_TIMESTAMPS - {TCP_TIMESTAMPS, "TCP_TIMESTAMPS"}, -#endif -#ifdef SO_REUSEADDR - {SO_REUSEADDR, "SO_REUSEADDR"}, -#endif -#ifdef SO_REUSEPORT - {SO_REUSEPORT, "SO_REUSEPORT"}, -#endif -#ifdef SO_EXCLUSIVEADDRUSE - {SO_EXCLUSIVEADDRUSE, "SO_EXCLUSIVEADDRUSE"}, -#endif -#ifdef SO_PORT_SCALABILITY - {SO_PORT_SCALABILITY, "SO_PORT_SCALABILITY"}, -#endif -#ifdef SO_REUSE_UNICASTPORT - {SO_REUSE_UNICASTPORT, "SO_REUSE_UNICASTPORT"}, -#endif -#ifdef SO_KEEPALIVE - {SO_KEEPALIVE, "SO_KEEPALIVE"}, -#endif -#ifdef SO_DONTROUTE - {SO_DONTROUTE, "SO_DONTROUTE"}, -#endif -#ifdef IP_TRANSPARENT - {IP_TRANSPARENT, "IP_TRANSPARENT"}, -#endif -#ifdef TCP_FASTOPEN - {TCP_FASTOPEN, "TCP_FASTOPEN"}, -#endif -#ifdef TCP_FASTOPEN_CONNECT - {TCP_FASTOPEN_CONNECT, "TCP_FASTOPEN_CONNECT"}, -#endif -#ifdef TCP_MAXSEG - {TCP_MAXSEG, "TCP_MAXSEG"}, -#endif - {0, NULL} -}; - -char optsbuf[1024]; - -char * printopts(char *sep){ - int i=0, pos=0; - for(; sockopts[i].optname; i++)pos += sprintf(optsbuf+pos,"%s%s",i?sep:"",sockopts[i].optname); - return optsbuf; -} - - -/* A comma separated list of options, each NAME or NAME=VALUE; a name alone - stands for 1, or for TCP_MAXSEG for the value of maxseg. Options are added - to what is already set, so a default is changed only by naming it, and - turned off with NAME=0. An unknown name or a value which is not a number - makes the whole list fail. */ -int getopts(const char *s, struct sockoptset *opts){ - while(*s){ - const char *end = strchr(s, ','); - const char *eq; - size_t len; - int i, value = 1; - - if(!end) end = s + strlen(s); - eq = memchr(s, '=', (size_t)(end - s)); - len = (size_t)((eq? eq : end) - s); - if(eq){ - char *num; - - if(!len) return -1; - value = (int)strtol(eq + 1, &num, 0); - if(num == eq + 1 || num != end) return -1; - } - else if(len) { -#ifdef TCP_MAXSEG - if(len == 10 && !strncmp(s, "TCP_MAXSEG", 10)) value = 0; -#endif - } - if(len){ - for(i = 0; sockopts[i].optname; i++) - if(strlen(sockopts[i].optname) == len && !strncmp(s, sockopts[i].optname, len)) break; - if(!sockopts[i].optname || i >= MAXSOCKOPTS) return -1; - opts->set |= (1u << i); - opts->value[i] = value; - } - s = *end? end + 1 : end; - } - return 0; -} - -void setopts(SOCKET s, const struct sockoptset *opts, int maxseg){ - int i, value; - - for(i = 0; sockopts[i].optname && i < MAXSOCKOPTS; i++){ - if(!(opts->set & (1u << i))) continue; - value = opts->value[i]; -#ifdef TCP_MAXSEG - if(sockopts[i].opt == TCP_MAXSEG && *sockopts[i].optname == 'T'){ - if(!value) value = maxseg; - if(!value) continue; - } -#endif - setsockopt(s, *sockopts[i].optname == 'T'? IPPROTO_TCP: -#ifdef SOL_IP - *sockopts[i].optname == 'I'? SOL_IP: -#endif - SOL_SOCKET, sockopts[i].opt, (char *)&value, sizeof(value)); - } -} - #ifndef MODULEMAINFUNC #define STDMAIN @@ -386,1417 +38,79 @@ struct srvstart { #endif }; -/* Releases what the preparation and the run took, s itself excepted. */ -static void srvdiscard(struct srvstart *s){ - srvfree(&s->srv); - if(s->cbc_string) free(s->cbc_string); - if(s->cbl_string) free(s->cbl_string); - if(s->fp) fclose(s->fp); -} +struct listener; +struct udpstate; -#ifndef STDMAIN -static void srvunlink(struct srvparam *srv){ - if(srv->next)srv->next->prev = srv->prev; - if(srv->prev)srv->prev->next = srv->next; - else srv->config->services = srv->next; - srv->next = srv->prev = NULL; -} +/* proxymain.c is one unit, built for 3proxy (MODULEMAINFUNC) and, included + by a service's own file (WITHMAIN), for a standalone service; its parts: + mainconf.c services from their configuration lines + mainlisten.c listening sockets, shared and taken over on a reload + mainclient.c threads for the clients of TCP services, and filters + maintcp.c the main loop of a TCP service + mainudp.c the main loop of a UDP service, in its one thread + Their functions of file scope are declared here, so that they can be in + any order. */ +static void srvdiscard(struct srvstart *s); +static void srvunlink(struct srvparam *srv); +static int srvprepare(struct srvstart *s, int argc, char **argv); +static int samestr(const char *a, const char *b); +static int sameaddress(struct srvstart *s); +static void * itcopy (void * from, size_t size); +static void listenercount(struct srvparam *srv, int delta); +static void unbound(PROXYSOCKADDRTYPE *sa, uint64_t *dev, uint64_t *ino); +static void ununlink(PROXYSOCKADDRTYPE *sa, uint64_t dev, uint64_t ino); +static int srvlisten(struct srvstart *s); +static int srvopen(struct srvstart *s); +static int bindtimeopt(const char *name); +static int sameopts(struct listener *l, struct srvparam *srv); +static void takeopts(struct listener *l, struct srvparam *srv); +static int samelistener(struct listener *l, struct srvparam *srv, int isudp); +static int listenerfor(struct listener *l, struct srvstart *s); +static int listenerhas(struct listener *l, struct extparam *config); +static void listenerstop(struct srvparam *srv); +static void listenerrelease(struct srvparam *srv); +static void slotrelease(struct srvparam *srv); +static void serveparam(struct clientparam *param); +static void freeworker(struct clientparam *param); +static unsigned poollimit(void); +static int poolpark(struct clientparam *param); +static int srvspawn(struct srvstart *s, SOCKET client_sock); +static void filterswap(struct clientparam *param, unsigned char **cur, unsigned char *now); +static int cblisten(struct srvstart *s); +static int tcpnext(struct srvstart *s, SOCKET *client, int *nlog); +static void udpkey2hash(const struct hashtable *ht, void *index, uint8_t *hash); +static void udpjoin(struct srvparam *srv, struct clientparam *param, int new); +static struct udpsession *udpnew(struct udpstate *st); +static void udpend(struct udpsession *ss); +static void udpendall(struct udpsession *list, int res); +static void udpadd(struct udpstate *st, struct udpsession *ss); +static void udpremove(struct udpstate *st, struct udpsession *ss); +static void udprefresh(struct udpsession *ss, time_t now); +static void udphandover(struct udpstate *st); +static void udpadopt(struct udpstate *st); +static int udpolder(struct srvparam *srv); +static void udpclients(struct udpstate *st, time_t now); +static int udploop(struct srvstart *s); +#ifndef NOTCPMAIN +static int tcploop(struct srvstart *s, int *haveslot); #endif +static int srvrun(struct srvstart *s); -/* 0, or 1 for wrong options, 2 for want of memory. Whatever fails, s can - be passed to srvdiscard(). */ -static int srvprepare(struct srvstart *s, int argc, char **argv){ - struct srvparam *srv = &s->srv; - struct clientparam *defparam = &s->defparam; - int i=0; -#if defined STDMAIN && !defined _WIN32 - SASIZETYPE size; -#endif - int error = 0; - char *hostname=NULL; -#if !defined(PORTMAP) || !defined(NOPORTMAP) - char loghelp[] = -#ifdef STDMAIN -#ifndef _WIN32 - " -I inetd mode (requires real socket, doesn't work with TTY)\n" - " -l@IDENT log to syslog IDENT\n" -#endif - " -d go to background (daemon)\n" -#else - " -u never ask for username\n" - " -u2 always ask for username\n" -#endif -#if defined SO_BINDTODEVICE || defined IP_BOUND_IF - " -Di(DEVICENAME) bind internal interface to device, e.g. eth1\n" - " -De(DEVICENAME) bind external interface to device, e.g. eth1\n" -#endif -#ifdef WITHSPLICE - " -s[N] Use splice(): -s1 server to client, -s2 client to server, -s3 both\n" - " directions; -s (-s7, the default) both, -s5 or -s6 one, only where it\n" - " pays (large transfers); -s0 never. Not with data filters\n" - " :MIN,STREAK,SMALL tune where it pays (-s:16384,2,4096): splice after\n" - " STREAK reads in a row of MIN bytes or more (SMALL from a TLS socket),\n" - " copy again after more than STREAK splices of less than SMALL; a body of\n" - " known length is spliced if MIN or more of it is left\n" - " -C connection closed by any of the sides terminates the session, by default\n" - " the session is kept until both sides close it (TCP half-close)\n" - " -U(0-3) (for socks) what to do when the destination changes within an UDP\n" - " association: 1 - log, 2 - authorize, 3 - both (default), 0 (same as -U) -\n" - " neither. The first destination is always authorized and logged\n" -#endif - "-g(GRACE_TRAFF,GRACE_NUM,GRACE_DELAY) - delay GRACE_DELAY milliseconds before polling if average polling size below GRACE_TRAFF bytes and GRACE_NUM read operations in single directions are detected within 1 second to minimize polling\n" - " -fFORMAT logging format (see documentation)\n" - " -l log to stderr\n" - " -lFILENAME log to FILENAME\n" - " -b(BUFSIZE) size of network buffer, at most and by default 16384\n" - " -S(STACKSIZE) value to add to default client thread stack size\n" - " -t be silent (do not log service start/stop)\n" - "\n" - " -iIP ip address or internal interface (clients are expected to connect)\n" - " -eIP ip address or external interface (outgoing connection will have this)\n" - " -rHOST:PORT Use IP:port for connect back proxy instead of listen port\n" - " -RHOST:PORT Use PORT to listen connect back proxy connection to pass data to\n" - " -4 Use IPv4 for outgoing connections\n" - " -6 Use IPv6 for outgoing connections\n" - " -46 Prefer IPv4 for outgoing connections, use both IPv4 and IPv6\n" - " -64 Prefer IPv6 for outgoing connections, use both IPv4 and IPv6\n" - " -ocOPTIONS, -osOPTIONS, -olOPTIONS, -orOPTIONS -oROPTIONS - options for\n" - " to-client (oc), to-server (os), listening (ol) socket, connect back client\n" - " (or) socket, connect back server (oR) listening socket, a comma separated\n" - " list of NAME or NAME=VALUE (NAME=0 turns an option off); TCP_NODELAY is\n" - " set on to-client and to-server sockets by default\n" - " where possible options are: "; -#endif - - s->cbc_string = s->cbl_string = NULL; - s->fp = NULL; - s->iscbc = s->iscbl = 0; -#ifdef STDMAIN -#ifndef _WIN32 - s->inetd = 0; -#endif -#endif - srvinit(srv, defparam); -#ifndef STDMAIN - confhold(srv->config); /* until srvfree() */ -#endif - srv->pf = childdef.pf; - s->isudp = childdef.isudp; - s->udp = childdef.udp; - - /* Default socket options, which -oc, -os, -ol and -oR change by naming - them, NAME=0 to turn one off. - - Nagle's algorithm holds back a short write until the previous one is - acknowledged, and a peer delays its acknowledgement, which stalls every - reply written in more than one piece. - - SO_REUSEADDR is not set on Windows: it is not needed to rebind a - listening port there, and it only allows another local process to bind - the same address and port, with undefined behaviour as to which socket - receives the connections. Use -olSO_EXCLUSIVEADDRUSE to prevent that - instead. - */ - (void)getopts("TCP_NODELAY", &srv->clisockopts); - (void)getopts("TCP_NODELAY", &srv->srvsockopts); -#ifndef _WIN32 - (void)getopts("SO_REUSEADDR", &srv->lissockopts); - (void)getopts("SO_REUSEADDR", &srv->cbssockopts); -#endif -#ifdef SO_REUSEPORT - (void)getopts("SO_REUSEPORT", &srv->lissockopts); - (void)getopts("SO_REUSEPORT", &srv->cbssockopts); -#endif -#ifndef NOUDPMAIN - if(s->isudp) { - srv->udpbuf = malloc(UDPBUFSIZE); - srv->udpbuf2 = malloc(UDPBUFSIZE); - if(!srv->udpbuf || !srv->udpbuf2) return 2; - } -#endif - srv->service = defparam->service = childdef.service; - -#ifdef WITH_HTTPSRV - /* http lines accumulate until a service claims them, so each httpsrv takes - the rules written above it and the next one starts empty. */ - if(srv->service == S_HTTPSRV){ - srv->httprules = conf->httprules; - conf->httprules = NULL; - } -#endif - -#ifndef STDMAIN - if(conf->acl){ - srv->acl = copyacl(conf->acl); - if(!srv->acl) return 2; - } - - if(conf->authfuncs){ - srv->authfuncs = copyauth(conf->authfuncs); - if(!srv->authfuncs) return 2; - } -#else - srv->needuser = 0; - _3proxy_mutex_init(&log_mutex); -#endif - - for (i=1; idemon)daemonize(); - conf->demon = 1; - break; -#if defined SO_BINDTODEVICE || defined IP_BOUND_IF - case 'D': - if(argv[i][2] == 'i') srv->ibindtodevice = strdup(argv[i] + 3); - else srv->obindtodevice = strdup(argv[i] + 3); - break; -#endif - case 'l': - srv->logfunc = logstdout; - if(srv->logtarget) free(srv->logtarget); - srv->logtarget = (unsigned char *)strdup(argv[i] + 2); - if(argv[i][2]) { - if(argv[i][2]=='@'){ - -#ifdef STDMAIN -#ifndef _WIN32 - openlog(argv[i]+3, LOG_PID, LOG_DAEMON); - srv->logfunc = logsyslog; -#endif -#endif - - } - else { - s->fp = fopen(argv[i] + 2, "a"); - if (s->fp) { - srv->stdlog = s->fp; - } - } - - } - break; - case 'i': -#ifdef WITH_UN - if(!strncmp((char *)argv[i]+2, "unix:", 5)){ - make_un((unsigned char *)argv[i] + 7, (struct sockaddr_un *)&srv->intsa); - } - else -#endif - getip46(46, (unsigned char *)argv[i]+2, (struct sockaddr *)&srv->intsa); - break; - case 'e': - { -#ifndef NOIPV6 - PROXYSOCKADDRTYPE sa6; - memset(&sa6, 0, sizeof(sa6)); - error = !getip46(46, (unsigned char *)argv[i]+2, (struct sockaddr *)&sa6); - if(!error) { - if (*SAFAMILY(&sa6)==AF_INET) srv->extsa = sa6; - else srv->extsa6 = sa6; - } -#else - error = !getip46(46, (unsigned char *)argv[i]+2, (struct sockaddr *)&srv->extsa); -#endif - } - break; - case 'N': - if(argv[i][2] == 'e') getip46(46, (unsigned char *)argv[i]+3, (struct sockaddr *)&srv->extNat); - else if(argv[i][2] == 'i') getip46(46, (unsigned char *)argv[i]+3, (struct sockaddr *)&srv->intNat); - else getip46(46, (unsigned char *)argv[i]+2, (struct sockaddr *)&srv->extNat); - break; - case 'n': -#ifdef __linux__ - if(argv[i][2] == 'i') { if(srv->inetns) free(srv->inetns); srv->inetns = strdup(argv[i] + 3); } - else if(argv[i][2] == 'e') { if(srv->onetns) free(srv->onetns); srv->onetns = strdup(argv[i] + 3); } -#endif - break; - case 'p': - *SAPORT(&srv->intsa) = htons(atoi(argv[i]+2)); - break; - case 'P': - srv->targetport = ntohs(atoi(argv[i]+2)); - break; - case '4': - case '6': - srv->family = atoi(argv[i]+1); - break; - case 'b': - srv->bufsize = atoi(argv[i]+2); - break; -#ifdef STDMAIN -#ifndef _WIN32 - case 'I': - size = sizeof(defparam->sincl); - if(srv->so._getsockname(srv->so.state, 0, (struct sockaddr*)&defparam->sincl, &size) || - *SAFAMILY(&defparam->sincl) != AF_INET) error = 1; - - else s->inetd = 1; - break; -#endif -#endif - case 'f': - if(srv->logformat)free(srv->logformat); - srv->logformat = (unsigned char *)strdup(argv[i] + 2); - break; - case 't': - srv->silent = 1; - break; - case 'h': - hostname = argv[i] + 2; - break; - case 'H': - srv->haproxy=1; - break; - case 'c': - srv->requirecert = 1; - if(isdigit(argv[i][2])) srv->requirecert = atoi(argv[i]+2); - break; - case 'r': - s->cbc_string = (unsigned char *)strdup(argv[i] + 2); - s->iscbc = 1; - break; - case 'R': - s->cbl_string = (unsigned char *)strdup(argv[i] + 2); - s->iscbl = 1; - break; - case 'u': - srv->needuser = 0; - if(*(argv[i] + 2)) srv->needuser = atoi(argv[i] + 2); - break; - case 'x': - srv->nostarttls = 1; - break; - case 'X': - if(!strncasecmp(argv[i]+2, "imap", 4)) srv->srvstarttls = S_IMAPP; - else if(!strncasecmp(argv[i]+2, "pop3", 4)) srv->srvstarttls = S_POP3P; - else if(!strncasecmp(argv[i]+2, "smtp", 4)) srv->srvstarttls = S_SMTPP; -#ifdef WITH_FTP - /* The http proxy fetches ftp:// only where it was asked to: - the protocol brings a second connection and a listing to - build, which a service serving http has no use for. */ - else if(!strncasecmp(argv[i]+2, "ftp", 3)) srv->allowftp = 1; -#endif - else error = 1; - break; - case 'F': - { - PROXYSOCKADDRTYPE fsa; - memset(&fsa, 0, sizeof(fsa)); - if(!getip46(46, (unsigned char *)argv[i]+2, (struct sockaddr *)&fsa)) error = 1; - else if(*SAFAMILY(&fsa) == AF_INET) srv->fakeip = *(uint32_t *)SAADDR(&fsa); -#ifndef NOIPV6 - else if(*SAFAMILY(&fsa) == AF_INET6) memcpy(srv->fakeip6, SAADDR(&fsa), 16); -#endif - else error = 1; - } - break; - case 'T': - srv->transparent = 1; - break; - case 'S': - srv->stacksize = atoi(argv[i]+2); - break; - case 'a': - srv->anonymous = 1 + atoi(argv[i]+2); - break; - case 'g': - sscanf(argv[i]+2, "%d,%d,%d", &srv->gracetraf, &srv->gracenum, &srv->gracedelay); - break; - case 'C': - srv->halfclose = *(argv[i]+2)? atoi(argv[i]+2) : 0; - break; - case 'U': - srv->udpauth = *(argv[i]+2)? atoi(argv[i]+2) : 0; - break; - case 's': -#ifdef WITHSPLICE - /* admin is an httpsrv, where -s is the limited mode */ - if(s->isudp || srv->service == S_ADMIN || srv->service == S_HTTPSRV) -#endif - srv->s_option = 1 + atoi(argv[i]+2); -#ifdef WITHSPLICE - else { - /* -s[N][:MIN,STREAK,SMALL] */ - char *heur = strchr(argv[i]+2, ':'); - - srv->usesplice = ((argv[i][2] && argv[i][2] != ':')? atoi(argv[i]+2) : - SPLICE_DOWNLOAD|SPLICE_UPLOAD|SPLICE_AUTO) & - (SPLICE_DOWNLOAD|SPLICE_UPLOAD|SPLICE_AUTO); - if(heur){ - int min = 0, streak = 0, small = 0; - - sscanf(heur+1, "%d,%d,%d", &min, &streak, &small); - if(min > 0) srv->splicemin = min; - if(streak > 0) srv->splicestreak = streak; - if(small > 0) srv->splicesmall = small; - } - } -#endif - break; - case 'o': - switch(argv[i][2]){ - case 's': - error = getopts(argv[i]+3, &srv->srvsockopts); - break; - case 'c': - error = getopts(argv[i]+3, &srv->clisockopts); - break; - case 'l': - error = getopts(argv[i]+3, &srv->lissockopts); - break; - case 'r': - error = getopts(argv[i]+3, &srv->cbcsockopts); - break; - case 'R': - error = getopts(argv[i]+3, &srv->cbssockopts); - break; - default: - error = 1; - } - if(!error) break; - default: - error = 1; - break; - } - } - else break; - } - - -#ifndef STDMAIN - if(childdef.port) { -#endif -#ifndef PORTMAP - if (error || i!=argc) { - fprintf(stderr, "%s of %s\n" - "Usage: %s options\n" - "Available options are:\n" - "%s\n" - "\t%s\n" - " -pPORT - service port to accept connections\n" - "%s" - "\tExample: %s -i127.0.0.1\n\n" - "%s", - argv[0], - conf->stringtable?(char *)conf->stringtable[3]: VERSION " (" BUILDDATE ")", - argv[0], loghelp, printopts("\n\t"), childdef.helpmessage, argv[0], -#ifdef STDMAIN - copyright -#else - "" -#endif - ); - return (1); - } -#endif -#ifndef STDMAIN - } - else { -#endif -#ifndef NOPORTMAP - /* the port of a Unix socket target is ignored, 0 as the manual has it */ - if (error || argc != i+3 || *argv[i]=='-'|| (*SAPORT(&srv->intsa) = htons((uint16_t)atoi(argv[i])))==0 || ((srv->targetport = htons((uint16_t)atoi(argv[i+2])))==0 -#ifdef WITH_UN - && strncmp(argv[i+1], "unix:", 5) -#endif - )) { - fprintf(stderr, "%s of %s\n" - "Usage: %s options" - " [-e] " - " \n" - "Available options are:\n" - "%s\n" - "\t%s\n" - "%s" - "\tExample: %s -d -i127.0.0.1 6666 serv.somehost.ru 6666\n\n" - "%s", - argv[0], - conf->stringtable?(char *)conf->stringtable[3]: VERSION " (" BUILDDATE ")", - argv[0], loghelp, printopts("\n\t"), childdef.helpmessage, argv[0], -#ifdef STDMAIN - copyright -#else - "" -#endif - ); - return (1); - } - srv->target = (unsigned char *)strdup(argv[i+1]); -#endif -#ifndef STDMAIN - } - -#else -#ifndef _WIN32 - /* main() serves the one connection, as the service was set up so far */ - if(s->inetd) return 0; -#endif -#endif - - - srvinit2(srv, defparam); - if(!*SAFAMILY(&srv->intsa)) *SAFAMILY(&srv->intsa) = AF_INET; - if(!*SAPORT(&srv->intsa)) *SAPORT(&srv->intsa) = htons(childdef.port); - *SAFAMILY(&srv->extsa) = AF_INET; -#ifndef NOIPV6 - *SAFAMILY(&srv->extsa6) = AF_INET6; -#endif - if(hostname)parsehostname(hostname, defparam, childdef.port); - -#ifdef __linux__ - /* Opened here, where the configuration is read, before a chroot can hide - the paths, and each entered once to be sure it is a network namespace - this process may use: a mistake shows now, not on a client's connection. */ - if(srv->inetns || srv->onetns){ - if(nsopen()) return 4; - if(srv->inetns && (srv->i_nsfd = open(srv->inetns, O_RDONLY | O_CLOEXEC)) < 0) return 4; - if(srv->onetns && (srv->o_nsfd = open(srv->onetns, O_RDONLY | O_CLOEXEC)) < 0) return 4; - if(srv->i_nsfd >= 0 && (setns(srv->i_nsfd, CLONE_NEWNET) || setns(mainnsfd, CLONE_NEWNET))) return 4; - if(srv->o_nsfd >= 0 && (setns(srv->o_nsfd, CLONE_NEWNET) || setns(mainnsfd, CLONE_NEWNET))) return 4; - } -#endif - -#ifndef STDMAIN - if((error = copyfilter(conf->filters, srv))) return error; -#endif - return 0; -} /* NOUDPMAIN and NOTCPMAIN leave out what only UDP or only TCP services need, for a standalone service of the other kind. */ -/* The listening socket. A UDP one also receives the datagrams of new - clients, so it is their client socket too. 0, or the code to exit with. */ -#ifndef STDMAIN -/* A listening socket, shared by the services of a configuration on the - same address, port, network namespace and device, all accepting on it. - It outlives the services that opened it when, on a reload, services of - the new configuration on the same address with the same bind-time - options take it over. The old services stop accepting once their - configuration is no longer in force; whoever lets go of the socket last - closes it. A UDP service's socket is the way its sessions answer too: - the old sessions go on answering through it, the new services reading - what comes, the sessions found by client address whichever service made - them. */ -struct listener { - struct listener *next; - SOCKET sock; - int isudp; - /* a service connecting back (-r) has no socket to share, but its - clients find the newest service through a listener all the same: - one without a socket, for the place it connects to */ - char *cbdest; - PROXYSOCKADDRTYPE sa; - char *netns, *device; - struct sockoptset opts; - int maxseg; - uint64_t undev, unino; /* its file, for a Unix socket on a path */ - int optversion; /* the configuration whose options are set */ - /* the services accepting on it, linked by lisnext */ - struct srvparam *owners; - int users; /* the services holding it: those accepting, - and those which did until their last client - is gone, whose clients look up the newest - service through it after a reload */ - /* held while the first of its services opens the socket, binding - which may take long: the others then use what it opened */ - _3proxy_mutex_t open_mutex; - /* its clients, whichever of its services, old or new, they came to: - those connected now, and those done with */ - uint64_t clients, processed; - /* the traffic of its clients as logged: from the server (%I) and - from the client (%O) */ - uint64_t trafin, trafout; - _3proxy_mutex_t count_mutex; - /* a UDP service's sessions on a reload, for the newest service on the - listener to adopt */ - struct udpsession *udphand; -}; +#include "mainconf.c" +#include "mainlisten.c" +#include "mainclient.c" +#include "maintcp.c" +#include "mainudp.c" -/* A client of srv comes (1) or goes (-1). */ -static void listenercount(struct srvparam *srv, int delta){ - struct listener *l = srv->listener; - - if(!l) return; - _3proxy_mutex_lock(&l->count_mutex); - if(delta > 0) l->clients++; - else { - l->clients--; - l->processed++; - } - _3proxy_mutex_unlock(&l->count_mutex); -} - -/* The traffic of a client, about to be cleared once logged, counted for - its listener. */ -void listenertraffic(struct clientparam *param){ - struct listener *l = param->srv->listener; - - if(!l) return; - _3proxy_mutex_lock(&l->count_mutex); - l->trafin += param->statssrv64; - l->trafout += param->statscli64; - _3proxy_mutex_unlock(&l->count_mutex); -} - -static uint64_t nocount = 0; - -/* The counters of a running service's listener, for the admin pages; the - listener lasts as long as the service. */ -uint64_t *listenerclients(struct srvparam *srv){ - return srv->listener? &srv->listener->clients : &nocount; -} - -uint64_t *listenerprocessed(struct srvparam *srv){ - return srv->listener? &srv->listener->processed : &nocount; -} - -uint64_t *listenertrafin(struct srvparam *srv){ - return srv->listener? &srv->listener->trafin : &nocount; -} - -uint64_t *listenertrafout(struct srvparam *srv){ - return srv->listener? &srv->listener->trafout : &nocount; -} -static struct listener *listeners = NULL; -_3proxy_mutex_t listener_mutex; - -static void listenerstop(struct srvparam *srv); -#ifndef NOUDPMAIN -static void udpendall(struct udpsession *list, int res); -#endif -static void takeopts(struct listener *l, struct srvparam *srv); -static void listenerrelease(struct srvparam *srv); -#endif - -static int srvopen(struct srvstart *s); - -#ifdef WITH_UN -/* A Unix socket bound to a path: its file is noted, as a service of a - newer configuration may bind the same path anew before this socket is - closed, when the file is the other's, which closing must not remove. */ -static void unbound(PROXYSOCKADDRTYPE *sa, uint64_t *dev, uint64_t *ino){ - struct stat st; - - if(*SAFAMILY(sa) == AF_UNIX && *SAADDR(sa) && !stat((char *)SAADDR(sa), &st)){ - *dev = (uint64_t)st.st_dev; - *ino = (uint64_t)st.st_ino; - } -} - -static void ununlink(PROXYSOCKADDRTYPE *sa, uint64_t dev, uint64_t ino){ - struct stat st; - - if(*SAFAMILY(sa) == AF_UNIX && *SAADDR(sa) && !stat((char *)SAADDR(sa), &st) && - (uint64_t)st.st_dev == dev && (uint64_t)st.st_ino == ino) - unlink((char *)SAADDR(sa)); -} -#endif - -/* The listening socket of the service: its listener's, opened by the first - of the listener's services to get here. */ -static int srvlisten(struct srvstart *s){ -#ifndef STDMAIN - struct srvparam *srv = &s->srv; - struct clientparam *defparam = &s->defparam; - struct listener *l = srv->listener; - SOCKET sock; - unsigned char buf[256]; - int res, taken; - - if(l){ - _3proxy_mutex_lock(&l->open_mutex); - _3proxy_mutex_lock(&listener_mutex); - sock = l->sock; - _3proxy_mutex_unlock(&listener_mutex); - if(sock == INVALID_SOCKET){ - if(!(res = srvopen(s))){ - _3proxy_mutex_lock(&listener_mutex); - l->sock = srv->srvsock; - l->optversion = srv->version; - l->undev = srv->undev; - l->unino = srv->unino; - _3proxy_mutex_unlock(&listener_mutex); - } - else if(srv->srvsock != INVALID_SOCKET){ - so._closesocket(so.state, srv->srvsock); - srv->srvsock = INVALID_SOCKET; - } - _3proxy_mutex_unlock(&l->open_mutex); - return res; - } - /* listening already: taken over from a service of the configuration - before, its options to be set, or opened by another service of - this one; only the backlog may be new */ - taken = l->optversion != srv->version; - if(taken) takeopts(l, srv); - _3proxy_mutex_unlock(&l->open_mutex); - srv->srvsock = sock; -#ifndef NOUDPMAIN - if(s->isudp) defparam->clisock = sock; - else -#endif - if(srv->so._listen (srv->so.state, sock, srv->backlog?srv->backlog : 1+(srv->maxchild>>3))==-1) { - srv->srvsock = INVALID_SOCKET; - sprintf((char *)buf, "listen(): %s", strerror(errno)); - if(!srv->silent)dolog(defparam, buf); - return -4; - } - if(!srv->silent){ - sprintf((char *)buf, "Accepting connections, %s [%"PRIu64"/%"PRIu64"]", taken? "taken over" : "shared", - (uint64_t)getpid(), (uint64_t)pthread_self()); - dolog(defparam, buf); - } - srv->fds.fd = sock; - srv->fds.events = POLLIN; - return 0; - } -#endif - return srvopen(s); -} - -/* Opens, binds and listens on the service's socket. */ -static int srvopen(struct srvstart *s){ - struct srvparam *srv = &s->srv; - struct clientparam *defparam = &s->defparam; - SOCKET sock = INVALID_SOCKET; - unsigned sleeptime; - unsigned char buf[256]; -#ifdef _WIN32 - unsigned long ul = 1; -#endif - -#ifndef NOTCPMAIN - if(!s->isudp){ - sock=nssocket(srv, &srv->so, srv->so.state, NS_IN, SASOCK(&srv->intsa), SOCK_STREAM, -#ifdef WITH_UN - *SAFAMILY(&srv->intsa) == AF_UNIX? 0 : -#endif - IPPROTO_TCP - ); - } -#endif -#ifndef NOUDPMAIN - if(s->isudp){ - sock=nssocket(srv, &srv->so, srv->so.state, NS_IN, SASOCK(&srv->intsa), SOCK_DGRAM, IPPROTO_UDP); - } -#endif - if( sock == INVALID_SOCKET) { - perror("socket()"); - return -2; - } - setopts(sock, &srv->lissockopts, srv->maxseg); -#ifdef _WIN32 - ioctlsocket(sock, FIONBIO, &ul); -#else - fcntl(sock,F_SETFL,O_NONBLOCK | fcntl(sock,F_GETFL)); -#endif - srv->srvsock = sock; -#if defined SO_BINDTODEVICE || defined IP_BOUND_IF - if(bindtodevice(&srv->so, srv->so.state, sock, srv->ibindtodevice, *SAFAMILY(&srv->intsa))) { - dolog(defparam, (unsigned char *)"failed to bind device"); - return -12; - } -#endif -#ifdef WITH_UN - if(*SAFAMILY(&srv->intsa) == AF_UNIX){ - struct sockaddr_un *sun = (struct sockaddr_un *)&srv->intsa; - if(*sun->sun_path)unlink(sun->sun_path); - } -#endif - for(sleeptime = SLEEPTIME * 100; srv->so._bind(srv->so.state, sock, (struct sockaddr*)&srv->intsa, SASIZE(&srv->intsa))==-1; usleep(sleeptime)) { - sprintf((char *)buf, "bind(): %s", strerror(errno)); - if(!srv->silent)dolog(defparam, buf); - sleeptime = (sleeptime<<1); - if(!sleeptime) return -3; - } -#ifdef WITH_UN - unbound(&srv->intsa, &srv->undev, &srv->unino); -#endif -#ifndef NOTCPMAIN - if(!s->isudp){ - if(srv->so._listen (srv->so.state, sock, srv->backlog?srv->backlog : 1+(srv->maxchild>>3))==-1) { - sprintf((char *)buf, "listen(): %s", strerror(errno)); - if(!srv->silent)dolog(defparam, buf); - return -4; - } - } -#endif -#ifndef NOUDPMAIN - if(s->isudp) defparam->clisock = sock; -#endif - srv->fds.fd = sock; - srv->fds.events = POLLIN; - - if(!srv->silent){ - sprintf((char *)buf, "Accepting connections [%"PRIu64"/%"PRIu64"]", (uint64_t)getpid(), (uint64_t)pthread_self()); - dolog(defparam, buf); - } - return 0; -} - -#ifndef NOTCPMAIN -/* -R: the socket the far side of a TCP service connects back to. */ -static int cblisten(struct srvstart *s){ - struct srvparam *srv = &s->srv; - struct clientparam *defparam = &s->defparam; - PROXYSOCKADDRTYPE cbsa; - - parsehost(srv->family, s->cbl_string, (struct sockaddr *)&cbsa); - if((srv->cbsock=nssocket(srv, &srv->so, srv->so.state, NS_OUT, SASOCK(&cbsa), SOCK_STREAM, IPPROTO_TCP))==INVALID_SOCKET) { - dolog(defparam, (unsigned char *)"Failed to allocate connect back socket"); - return -6; - } - setopts(srv->cbsock, &srv->cbssockopts, srv->maxseg); - - if(srv->so._bind(srv->so.state, srv->cbsock, (struct sockaddr*)&cbsa, SASIZE(&cbsa))==-1) { - dolog(defparam, (unsigned char *)"Failed to bind connect back socket"); - return -7; - } - if(srv->so._listen(srv->so.state, srv->cbsock, 1 + (srv->maxchild>>4))==-1) { - dolog(defparam, (unsigned char *)"Failed to listen connect back socket"); - return -8; - } - return 0; -} - -#endif - -/* What getting the next client came to. */ -enum { NEXT_CLIENT, NEXT_RETRY, NEXT_STOP }; - -#ifndef NOTCPMAIN -/* The next TCP client: accepted, or with -r connected to and asked to - go ahead. */ -static int tcpnext(struct srvstart *s, SOCKET *client, int *nlog){ - struct srvparam *srv = &s->srv; - struct clientparam *defparam = &s->defparam; - SOCKET new_sock; - SASIZETYPE size; - unsigned char buf[256]; - int error = 0, opt = 1; -#ifndef _WIN32 - struct linger lg; -#else - unsigned long ul = 1; -#endif - - size = sizeof(defparam->sincr); - if(s->iscbc){ - new_sock=nssocket(&s->srv, &so, so.state, NS_OUT, SASOCK(&defparam->sincr), SOCK_STREAM, IPPROTO_TCP); - if(new_sock == INVALID_SOCKET){ - usleep(SLEEPTIME); - return NEXT_RETRY; - } - setopts(new_sock, &srv->cbcsockopts, srv->maxseg); - - parsehost(srv->family, s->cbc_string, (struct sockaddr *)&defparam->sincr); -#if defined SO_BINDTODEVICE || defined IP_BOUND_IF - /* as for a listening socket, an interface that cannot be - bound to is not worth retrying */ - if(bindtodevice(&so, so.state, new_sock, srv->ibindtodevice, *SAFAMILY(&defparam->sincr))) { - dolog(defparam, (unsigned char *)"failed to bind device"); - so._closesocket(so.state, new_sock); - return NEXT_STOP; - } -#endif - if(connectwithpoll(NULL, new_sock,(struct sockaddr *)&defparam->sincr,SASIZE(&defparam->sincr),CONNBACK_TO)) { - so._closesocket(so.state, new_sock); - usleep(SLEEPTIME); - return NEXT_RETRY; - } - - if(sockrecvfrom(NULL, new_sock, NULL, buf, 1, 60*1000) != 1 || *buf!='C') { - so._closesocket(so.state, new_sock); - usleep(SLEEPTIME); - return NEXT_RETRY; - } - } - else { - new_sock = srv->so._accept(srv->so.state, srv->srvsock, (struct sockaddr*)&defparam->sincr, &size); - if(new_sock == INVALID_SOCKET){ -#ifdef _WIN32 - switch(WSAGetLastError()){ - case WSAEMFILE: - case WSAENOBUFS: - case WSAENETDOWN: - usleep(SLEEPTIME * 10); - break; - case WSAEINTR: - error = 1; - break; - default: - break; - } - -#else - switch (errno){ -#ifdef EMFILE - case EMFILE: -#endif -#ifdef ENFILE - case ENFILE: -#endif -#ifdef ENOBUFS - case ENOBUFS: -#endif -#ifdef ENOMEM - case ENOMEM: -#endif - usleep(SLEEPTIME * 10); - break; - - default: - break; - } -#endif - (*nlog)++; - if(!srv->silent && (error || *nlog > 5000)) { - sprintf((char *)buf, "accept(): %s", strerror(errno)); - dolog(defparam, buf); - *nlog = 0; - } - return NEXT_RETRY; - } - setopts(new_sock, &srv->clisockopts, srv->maxseg); - } - size = sizeof(defparam->sincl); - if(srv->so._getsockname(srv->so.state, new_sock, (struct sockaddr *)&defparam->sincl, &size)){ - sprintf((char *)buf, "getsockname(): %s", strerror(errno)); - if(!srv->silent)dolog(defparam, buf); - srv->so._closesocket(srv->so.state, new_sock); - return NEXT_RETRY; - } -#ifdef WITH_UN - if(*SAFAMILY(&defparam->sincl) == AF_UNIX) defparam->sincr = defparam->sincl; -#endif -#ifdef _WIN32 - ioctlsocket(new_sock, FIONBIO, &ul); -#else - fcntl(new_sock,F_SETFL,O_NONBLOCK | fcntl(new_sock,F_GETFL)); -#endif -#ifndef _WIN32 - /* Not on Windows: there closesocket() on a non-blocking socket with a linger - timeout fails with WSAEWOULDBLOCK while data is queued, leaving the socket - open. Without it the close completes in the background, data first. */ - lg.l_onoff = 1; - lg.l_linger = srv->timeouts[STRING_L]; - srv->so._setsockopt(srv->so.state, new_sock, SOL_SOCKET, SO_LINGER, (char *)&lg, sizeof(lg)); -#endif - srv->so._setsockopt(srv->so.state, new_sock, SOL_SOCKET, SO_OOBINLINE, (char *)&opt, sizeof(int)); - *client = new_sock; - return NEXT_CLIENT; -} -#endif - -#ifndef NOUDPMAIN -/* A UDP service's loop, in the service's thread: the datagrams of its - clients come on the service's socket, those of their destinations on the - sockets of their sessions, and every answer to a client leaves from the - service's socket. No thread for a client. */ -struct udpstate { - struct udploop u; - const struct udpservice *svc; - struct udpsession *head; - int count; - struct hashtable keys; /* a session by its client's address */ - struct pollfd *fds; - struct udpsession **of; /* the session of each of fds, NULL: the service */ - int nalloc; - int nfull; -}; - -static void udpkey2hash(const struct hashtable *ht, void *index, uint8_t *hash){ - PROXYSOCKADDRTYPE *sa = (PROXYSOCKADDRTYPE *)index; - uint32_t m1, m2; - - m1 = murmurhash3(SAADDR(sa), SAADDRLEN(sa), 0x3a3a3a3a); - m2 = murmurhash3(SAPORT(sa), 2, m1); - memcpy(hash, &m1, 4); - memcpy(hash+4, &m2, 4); -} - -int udpanswer(struct udploop *u, struct clientparam *param, unsigned char *buf, int len){ - return param->srv->so._sendto(param->sostate, param->srv->srvsock, (char *)buf, len, 0, - (struct sockaddr *)¶m->sincr, SASIZE(¶m->sincr)); -} - -/* A session among its service's children, counted there and, if it is new, - on the listener. */ -static void udpjoin(struct srvparam *srv, struct clientparam *param, int new){ - param->prev = param->next = NULL; - _3proxy_mutex_lock(&srv->counter_mutex); - if(srv->child){ - param->next = srv->child; - srv->child->prev = param; - } - srv->child = param; - srv->childcount++; -#ifndef STDMAIN - if(new) listenercount(srv, 1); -#endif - _3proxy_mutex_unlock(&srv->counter_mutex); -} - -/* A new client: a slot of maxchild and a param, as a thread for it would - have; NULL without a slot or memory. */ -static struct udpsession *udpnew(struct udpstate *st){ - struct srvparam *srv = st->u.srv; - struct udpsession *ss; - struct clientparam *param = NULL; - - if(_3proxy_sem_timedlock(srv->childslots, 0)){ - if(!srv->silent && !(st->nfull++ % 100)){ - char buf[128]; - - sprintf(buf, "Warning: too many connected clients (%d/%d), datagram dropped", srv->childcount, srv->maxchild); - dolog(st->u.defparam, (unsigned char *)buf); - } - return NULL; - } - st->nfull = 0; - if(!(ss = calloc(1, sizeof(*ss))) || !(param = malloc(sizeof(*param)))){ - free(ss); - _3proxy_sem_unlock(srv->childslots); - return NULL; - } - memset(param, 0, sizeof(*param)); - memcpy(param, st->u.defparam, offsetof(struct clientparam, filters)); - param->clibuf = param->clibufdata; - param->srvbuf = param->srvbufdata; - param->clibufsize = param->srvbufsize = SRVBUFSIZE(srv); - clearstat(param); - param->clisock = param->remsock = param->ctrlsock = param->ctrlsocksrv = INVALID_SOCKET; - udpjoin(srv, param, 1); - ss->param = param; - ss->svc = st->svc; -#ifdef MODULEMAINFUNC - if(makefilters(srv, param) > CONTINUE){ - freeparam(param); - free(ss); - return NULL; - } -#endif - return ss; -} - -/* A session ends: logged by its service, its sockets closed, its slot - given back. Its own service may be gone after. */ -static void udpend(struct udpsession *ss){ - struct clientparam *param = ss->param; - - if(ss->svc->end) (*ss->svc->end)(NULL, ss); - param->clisock = INVALID_SOCKET; - freeparam(param); - free(ss); -} - -static void udpendall(struct udpsession *list, int res){ - struct udpsession *ss; - - while((ss = list)){ - list = ss->next; - if(!ss->param->res) ss->param->res = res; - udpend(ss); - } -} - -static void udpadd(struct udpstate *st, struct udpsession *ss){ - ss->prev = NULL; - ss->next = st->head; - if(st->head) st->head->prev = ss; - st->head = ss; - st->count++; - ss->keyed = st->svc->keyed; - if(ss->keyed) hashadd(&st->keys, &ss->param->sincr, &ss, MAX_COUNTER_TIME); -} - -static void udpremove(struct udpstate *st, struct udpsession *ss){ - if(ss->prev) ss->prev->next = ss->next; - else st->head = ss->next; - if(ss->next) ss->next->prev = ss->prev; - ss->next = ss->prev = NULL; - st->count--; - if(ss->keyed) hashdelete(&st->keys, &ss->param->sincr); -} - -static void udprefresh(struct udpsession *ss, time_t now){ - if(ss->idle) ss->deadline = now + ss->idle; -} - -#ifndef STDMAIN -/* On a reload: the sessions go to the listener, for the newest service on - it to adopt; if it has none, the last service to stop on it ends them. */ -static void udphandover(struct udpstate *st){ - struct listener *l = st->u.srv->listener; - struct udpsession *ss, *last = NULL; - - for(ss = st->head; ss; ss = ss->next){ - if(ss->keyed) hashdelete(&st->keys, &ss->param->sincr); - ss->keyed = 0; - last = ss; - } - if(!last) return; - if(!l){ - udpendall(st->head, 26); - st->head = NULL; - return; - } - _3proxy_mutex_lock(&listener_mutex); - last->next = l->udphand; - if(l->udphand) l->udphand->prev = last; - l->udphand = st->head; - _3proxy_mutex_unlock(&listener_mutex); - st->head = NULL; - st->count = 0; -} - -/* Sessions of an older service on the listener become this one's: a slot - here, the old one given back, its rules and limits, re-authenticated; 26 - if it is another kind of service, 27 without a slot. */ -static void udpadopt(struct udpstate *st){ - struct srvparam *srv = st->u.srv, *old; - struct listener *l = srv->listener; - struct udpsession *list, *ss; - struct clientparam *param; - int res; - - if(!l || !l->udphand) return; - _3proxy_mutex_lock(&listener_mutex); - list = l->udphand; - l->udphand = NULL; - _3proxy_mutex_unlock(&listener_mutex); - while((ss = list)){ - list = ss->next; - ss->next = ss->prev = NULL; - param = ss->param; - old = param->srv; - if(old == srv) { udpadd(st, ss); continue; } - if(param->service != srv->service){ - param->res = 26; - udpend(ss); - continue; - } - if(_3proxy_sem_timedlock(srv->childslots, 0)){ - param->res = 27; - udpend(ss); - continue; - } - /* what belongs to the old service and configuration let go */ - while(param->nfilters > 0){ - param->nfilters--; - if(param->filters[param->nfilters].filter->filter_clear) - (*param->filters[param->nfilters].filter->filter_clear)(param->filters[param->nfilters].data); - } - if(param->connlim) stopconnlims(param); - param->bandlimfunc = NULL; - param->bandlims[0] = param->bandlimsout[0] = NULL; - param->trafcountfunc = NULL; - param->maxtrafin64 = param->maxtrafout64 = 0; - _3proxy_mutex_lock(&old->counter_mutex); - if(param->prev) param->prev->next = param->next; - else old->child = param->next; - if(param->next) param->next->prev = param->prev; - old->childcount--; - _3proxy_sem_unlock(old->childslots); - _3proxy_mutex_unlock(&old->counter_mutex); - param->srv = srv; - param->sostate = srv->so.state; - param->version = srv->version; - udpjoin(srv, param, 0); - ss->svc = st->svc; -#ifdef MODULEMAINFUNC - if(makefilters(srv, param) > CONTINUE){ - param->res = 26; - udpend(ss); - continue; - } -#endif - if((res = (*srv->authfunc)(param))){ - param->res = res; - udpend(ss); - continue; - } - udpadd(st, ss); - } -} -#endif - -#ifndef STDMAIN -/* After a reload, while an older service of this kind still accepts on the - listener, it goes on serving the service's socket alone, until it hands - its sessions over: a client is not given a session in both. */ -static int udpolder(struct srvparam *srv){ - struct srvparam *o; - int older = 0; - - if(!srv->listener) return 0; - _3proxy_mutex_lock(&listener_mutex); - for(o = srv->listener->owners; o; o = o->lisnext) - if(o != srv && o->version < srv->version && o->service == srv->service) older = 1; - _3proxy_mutex_unlock(&listener_mutex); - return older; -} -#endif - -/* The datagrams waiting on the service's socket: to a session, or a new - one. Some at a time, so that the sessions get their turn. */ -static void udpclients(struct udpstate *st, time_t now){ - struct srvparam *srv = st->u.srv; - struct clientparam *defparam = st->u.defparam; - struct udpsession *ss; - SASIZETYPE size; - int len, n; - - for(n = 0; n < 64; n++){ - size = sizeof(defparam->sincr); - len = srv->so._recvfrom(srv->so.state, srv->srvsock, (char *)srv->udpbuf, UDPBUFSIZE, 0, - (struct sockaddr *)&defparam->sincr, &size); - if(len < 0){ -#ifdef _WIN32 - /* an answer to a client was refused (ICMP): reported here */ - if(WSAGetLastError() == WSAECONNRESET) continue; -#endif - break; - } - if(st->svc->keyed && hashresolv(&st->keys, &defparam->sincr, &ss, NULL)){ - if(ss->done) continue; - udprefresh(ss, now); - if(st->svc->client && (*st->svc->client)(&st->u, ss, srv->udpbuf, len)) ss->done = 1; - continue; - } - if(!(ss = udpnew(st))) continue; - ss->param->sincr = defparam->sincr; - udpadd(st, ss); - if((*st->svc->first)(&st->u, ss, srv->udpbuf, len)) ss->done = 1; - else if(!ss->deadline || ss->idle) udprefresh(ss, now); - } -} - -static int udploop(struct srvstart *s){ - struct udpstate st; - struct srvparam *srv = &s->srv; - struct udpsession *ss, *next; - time_t now; - int i, n, nfds, timeo, res = 0, wait = 0; - - memset(&st, 0, sizeof(st)); - st.u.srv = srv; - st.u.defparam = &s->defparam; - st.u.buf = srv->udpbuf2; - st.svc = s->udp; - st.keys.index2hash_add = udpkey2hash; - st.keys.index2hash_search = udpkey2hash; - st.keys.recsize = sizeof(struct udpsession *); - st.keys.hash_size = 8; - if(st.svc->keyed && inithashtable(&st.keys, 64, 256, 1048576)) return 21; - for(;;){ - if(srv->config->paused != srv->paused) break; -#ifndef STDMAIN - udpadopt(&st); -#endif - if(st.nalloc < 2 * st.count + 1){ - int want = 2 * st.count + 64; - struct pollfd *f = realloc(st.fds, want * sizeof(*f)); - - if(f) st.fds = f; - if(f && (st.of = realloc(st.of, want * sizeof(*st.of)))) st.nalloc = want; - else { res = 21; break; } - } - now = time(NULL); - timeo = 1000; -#ifndef STDMAIN - /* waiting for an older service's sessions: soon looked for again */ - if((wait = udpolder(srv))) timeo = 20; -#endif - nfds = 0; - st.fds[nfds].fd = srv->srvsock; - st.fds[nfds].events = wait? 0 : POLLIN; - st.fds[nfds].revents = 0; - st.of[nfds++] = NULL; - for(ss = st.head; ss; ss = ss->next){ - if(ss->deadline && (ss->deadline - now) * 1000 < timeo) - timeo = ss->deadline > now? (int)(ss->deadline - now) * 1000 : 0; - if(ss->param->remsock != INVALID_SOCKET){ - st.fds[nfds].fd = ss->param->remsock; - st.fds[nfds].events = POLLIN; - st.fds[nfds].revents = 0; - st.of[nfds++] = ss; - } - /* a parent's control connection: its end ends the session */ - if(ss->param->ctrlsocksrv != INVALID_SOCKET && ss->param->ctrlsocksrv != ss->param->remsock){ - st.fds[nfds].fd = ss->param->ctrlsocksrv; - st.fds[nfds].events = POLLIN; - st.fds[nfds].revents = 0; - st.of[nfds++] = ss; - } - } - n = srv->so._poll(srv->so.state, st.fds, nfds, timeo); - if(n < 0 && errno != EINTR && errno != EAGAIN){ - res = 91; - break; - } - now = time(NULL); - for(i = 1; n > 0 && i < nfds; i++){ - ss = st.of[i]; - if(!st.fds[i].revents || ss->done) continue; - if(st.fds[i].fd == ss->param->remsock){ - udprefresh(ss, now); - if((*st.svc->server)(&st.u, ss)) ss->done = 1; - } - else ss->done = 1; - } - if(n > 0 && !wait && st.fds[0].revents) udpclients(&st, now); - for(ss = st.head; ss; ss = next){ - next = ss->next; - if(!ss->done && ss->deadline && ss->deadline <= now){ - ss->done = 1; - ss->param->res = st.svc->timeoutres; - } - if(ss->done){ - udpremove(&st, ss); - udpend(ss); - } - } - } -#ifndef STDMAIN - if(srv->config->paused != srv->paused) udphandover(&st); -#endif - udpendall(st.head, 26); - if(st.svc->keyed) destroyhashtable(&st.keys); - free(st.fds); - free(st.of); - return res; -} -#endif - -/* A thread for the client, client_sock for TCP. 0 once it runs, the slot - in hand going with it. */ -static int srvspawn(struct srvstart *s, SOCKET client_sock){ - struct srvparam *srv = &s->srv; - struct clientparam *defparam = &s->defparam; - struct clientparam * newparam = NULL, **pp; - pthread_t thread; - unsigned char buf[256]; - int error = 0, reused; - size_t need; -#ifdef _WIN32 - HANDLE h; - - need = 16384 + srv->stacksize; -#else - need = threadstacksize(srv->stacksize); -#endif - - /* an idle worker if one has the stack this service needs */ - _3proxy_mutex_lock(&tpool.mutex); - for(pp = &tpool.idle; *pp; pp = &(*pp)->poolnext){ - if((*pp)->stacksize >= need){ - newparam = *pp; - *pp = newparam->poolnext; - newparam->pooled = 0; - tpool.nidle--; - tpool.reused++; - break; - } - } - _3proxy_mutex_unlock(&tpool.mutex); - reused = newparam != NULL; - if(!reused && (!(newparam = malloc (sizeof(*defparam))) || _3proxy_sem_init_timed(newparam->wake, 0, 1))){ - if(newparam) free(newparam); - srv->so._closesocket(srv->so.state, client_sock); - defparam->res = 21; - if(!srv->silent)dolog(defparam, (unsigned char *)"Memory Allocation Failed"); - usleep(SLEEPTIME); - return 1; - }; - if(!reused){ - newparam->stacksize = need; - newparam->pooled = newparam->poolexit = 0; - newparam->poolnext = NULL; - } - memcpy(newparam, defparam, offsetof(struct clientparam, filters)); - newparam->clibuf = newparam->clibufdata; - newparam->srvbuf = newparam->srvbufdata; - newparam->clibufsize = newparam->srvbufsize = SRVBUFSIZE(srv); - clearstat(newparam); - if(!s->isudp) newparam->clisock = client_sock; - newparam->prev = newparam->next = NULL; - _3proxy_mutex_lock(&srv->counter_mutex); - if(!srv->child){ - srv->child = newparam; - } - else { - newparam->next = srv->child; - srv->child = srv->child->prev = newparam; - } - /* a worker from the pool is woken below, once it is one of the children */ - if(!reused){ -#ifdef _WIN32 -#ifndef _WINCE - h = (HANDLE)_beginthreadex((LPSECURITY_ATTRIBUTES )NULL, (unsigned)(16384 + srv->stacksize), (void *)threadfunc, (void *) newparam, 0, &thread); -#else - h = (HANDLE)CreateThread((LPSECURITY_ATTRIBUTES )NULL, (unsigned)(16384 + srv->stacksize), (void *)threadfunc, (void *) newparam, 0, &thread); -#endif - if (h) { - CloseHandle(h); - } - else { - sprintf((char *)buf, "_beginthreadex(): %s", _strerror(NULL)); - error = 1; - } -#else - - if ((error = pthread_create(&thread, &s->pa, threadfunc, (void *)newparam))){ - sprintf((char *)buf, "pthread_create(): %s", strerror(error)); - } -#endif - } - if(error){ - if(!srv->silent)dolog(defparam, buf); - if(newparam->prev) newparam->prev->next = newparam->next; - else srv->child = newparam->next; - if(newparam->next) newparam->next->prev = newparam->prev; - if(newparam->clisock != INVALID_SOCKET){ - srv->so._shutdown(srv->so.state, newparam->clisock, SHUT_RDWR); - srv->so._closesocket(srv->so.state, newparam->clisock); - newparam->clisock = INVALID_SOCKET; - } - newparam->srv = NULL; - freeworker(newparam); - } - else { - srv->childcount++; -#ifndef STDMAIN - listenercount(srv, 1); -#endif - if(!reused){ - newparam->workerid = (uint64_t)thread; - _3proxy_mutex_lock(&tpool.mutex); - tpool.created++; - _3proxy_mutex_unlock(&tpool.mutex); - } - newparam->threadid = newparam->workerid; - } - _3proxy_mutex_unlock(&srv->counter_mutex); - if(reused) _3proxy_sem_unlock(newparam->wake); - memset(&defparam->sincl, 0, sizeof(defparam->sincl)); - memset(&defparam->sincr, 0, sizeof(defparam->sincr)); - return error; -} static int srvrun(struct srvstart *s){ struct srvparam *srv = &s->srv; struct clientparam *defparam = &s->defparam; - SOCKET new_sock = INVALID_SOCKET; - int error = 0, res = 0, next; - unsigned char buf[256]; - int haveslot = 0, nfull = 0; -#ifndef NOTCPMAIN - int nlog = 5000; -#endif + int res = 0, haveslot = 0; /* -r: a TCP service connects out for each client instead of listening */ int connectback = !s->isudp && s->iscbc; #ifndef _WIN32 @@ -1826,44 +140,9 @@ static int srvrun(struct srvstart *s){ if(s->isudp) res = udploop(s); else #endif - for (;;) { - for(;;){ - /* A connection is only taken with a slot of maxchild in hand, which - goes to its child; a slot not handed on is kept for the next one. - The wait is timed to notice a reload. */ - while(!haveslot && srv->config->paused == srv->paused){ - if(!_3proxy_sem_timedlock(srv->childslots, 1000)){ - haveslot = 1; - nfull = 0; - break; - } - if(!srv->silent && !(nfull++ % 5)) { - sprintf((char *)buf, "Warning: too many connected clients (%d/%d)", srv->childcount, srv->maxchild); - dolog(defparam, buf); - } - } - if (connectback) break; - if (srv->config->paused != srv->paused) break; - error = srv->so._poll(srv->so.state, &srv->fds, 1, 1000); - if (error >= 1) break; - if (error == 0) continue; - if (errno != EAGAIN && errno != EINTR) { - sprintf((char *)buf, "poll(): %s/%d", strerror(errno), errno); - if(!srv->silent)dolog(defparam, buf); - break; - } - } - if((srv->config->paused != srv->paused) || (error < 0)) break; - error = 0; #ifndef NOTCPMAIN - next = tcpnext(s, &new_sock, &nlog); -#else - next = NEXT_STOP; + res = tcploop(s, &haveslot); #endif - if(next == NEXT_STOP) break; - if(next == NEXT_RETRY) continue; - if(!srvspawn(s, new_sock)) haveslot = 0; - } _3proxy_mutex_lock(&tpool.mutex); tpool.services--; tpool.maxchild -= srv->maxchild; @@ -1963,1025 +242,6 @@ int WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int return res; } -#else - -static int samestr(const char *a, const char *b){ - return (!a && !b) || (a && b && !strcmp(a, b)); -} - -/* Services of one configuration on one address share its socket: they - must be of one type, with the same socket options. 5 if a service of - another type listens there, 6 if one with other options, else 0. - Services connecting back to one place need not be alike. */ -static int sameaddress(struct srvstart *s){ - struct srvparam *srv = &s->srv, *o; - - if(!s->isudp && s->iscbc) return 0; - for(o = conf->services; o; o = o->next){ - struct srvstart *os = (struct srvstart *)o; - - if(os->isudp != s->isudp || (!os->isudp && os->iscbc)) continue; -#ifdef __linux__ - if(!samestr(o->inetns, srv->inetns)) continue; -#endif -#if defined SO_BINDTODEVICE || defined IP_BOUND_IF - if(!samestr(o->ibindtodevice, srv->ibindtodevice)) continue; -#endif - if(SASIZE(&o->intsa) != SASIZE(&srv->intsa) || memcmp(&o->intsa, &srv->intsa, SASIZE(&srv->intsa))) continue; - if(o->service != srv->service) return 5; - if(o->maxseg != srv->maxseg || memcmp(&o->lissockopts, &srv->lissockopts, sizeof(srv->lissockopts))) return 6; - } - return 0; -} - -int mainprepare(int argc, char** argv, void **service){ - struct srvstart *s; - int res; - - *service = NULL; - if(!(s = malloc(sizeof(*s)))) return 2; - if((res = srvprepare(s, argc, argv)) || (res = sameaddress(s))){ - srvdiscard(s); - free(s); - return res; - } - /* listed only once it is sure to run: the thread reading the - configuration links and unlinks it here, the service unlinks itself */ - if(!conf->services){ - conf->services = &s->srv; - } - else { - s->srv.next = conf->services; - conf->services = conf->services->prev = &s->srv; - } - *service = s; - return 0; -} - -int mainrun(void *service){ - int res = srvrun((struct srvstart *)service); - - free(service); - return res; -} - -/* -ol options that act only when set before bind(): a listening socket - differing in them is another socket. The rest are set on a socket taken - over, those no longer asked for turned off. */ -static int bindtimeopt(const char *name){ - return !strncmp(name, "SO_REUSE", 8) || !strcmp(name, "SO_EXCLUSIVEADDRUSE") || - !strcmp(name, "SO_PORT_SCALABILITY") || !strcmp(name, "IP_TRANSPARENT") || - !strcmp(name, "TCP_FASTOPEN"); -} - -/* Whether a socket with the options of l can be made one with those of srv: - the same bind-time ones, with the same values; TCP_MAXSEG, whose default - is not 0, cannot be turned off again. */ -static int sameopts(struct listener *l, struct srvparam *srv){ - const struct sockoptset *a = &l->opts, *b = &srv->lissockopts; - int i; - - for(i = 0; sockopts[i].optname && i < MAXSOCKOPTS; i++){ - unsigned bit = 1u << i; - - if(bindtimeopt(sockopts[i].optname)){ - if((a->set & bit) != (b->set & bit)) return 0; - if((a->set & bit) && a->value[i] != b->value[i]) return 0; - } -#ifdef TCP_MAXSEG - else if(sockopts[i].opt == TCP_MAXSEG && *sockopts[i].optname == 'T' && - (a->set & bit) && !(b->set & bit)) return 0; -#endif - } - return 1; -} - -/* Sets the options of srv on the socket of l taken over: those given, and - off those which l had and srv has not. */ -static void takeopts(struct listener *l, struct srvparam *srv){ - struct sockoptset off; - int i; - - memset(&off, 0, sizeof(off)); - for(i = 0; sockopts[i].optname && i < MAXSOCKOPTS; i++){ - unsigned bit = 1u << i; - - if((l->opts.set & bit) && !(srv->lissockopts.set & bit) && !bindtimeopt(sockopts[i].optname)) - off.set |= bit; - } - if(off.set) setopts(l->sock, &off, 0); - setopts(l->sock, &srv->lissockopts, srv->maxseg); - _3proxy_mutex_lock(&listener_mutex); - l->opts = srv->lissockopts; - l->maxseg = srv->maxseg; - _3proxy_mutex_unlock(&listener_mutex); -} - -static int samelistener(struct listener *l, struct srvparam *srv, int isudp){ - if(l->isudp != isudp || l->cbdest) return 0; -#ifdef __linux__ - if(!samestr(l->netns, srv->inetns)) return 0; -#endif -#if defined SO_BINDTODEVICE || defined IP_BOUND_IF - if(!samestr(l->device, srv->ibindtodevice)) return 0; -#endif - return SASIZE(&l->sa) == SASIZE(&srv->intsa) && - !memcmp(&l->sa, &srv->intsa, SASIZE(&srv->intsa)) && - sameopts(l, srv); -} - -/* Whether s goes on listener l: one connecting back (-r) on the - pseudo-listener of the place it connects to, any other on the socket for - its address. */ -static int listenerfor(struct listener *l, struct srvstart *s){ - if(!s->isudp && s->iscbc) return l->cbdest && s->cbc_string && !strcmp(l->cbdest, (char *)s->cbc_string); - return !l->cbdest && samelistener(l, &s->srv, s->isudp); -} - -static int listenerhas(struct listener *l, struct extparam *config){ - struct srvparam *o; - - for(o = l->owners; o; o = o->lisnext) if(o->config == config) return 1; - return 0; -} - -/* A prepared service, before it starts and before, on a reload, the old - services are told to stop: it joins the listener another service of its - configuration has for the same address, or takes over one an older - configuration's services accept on, one of a service of its type first. - Otherwise it gets a listener of its own, its socket opened when it - starts. A service connecting back has a pseudo-listener without a socket - for the place it connects to, which several of its services may share - too. Without memory for a listener the service keeps its socket to - itself. */ -void mainclaim(void *service){ - struct srvstart *s = (struct srvstart *)service; - struct srvparam *srv = &s->srv; - struct listener *l, *found = NULL, *newl; - - /* made beforehand, as the mutex is held from finding a listener to - joining it, lest its last service let go of it meanwhile */ - if((newl = malloc(sizeof(*newl)))){ - memset(newl, 0, sizeof(*newl)); - newl->sock = INVALID_SOCKET; - newl->isudp = s->isudp; - newl->maxseg = srv->maxseg; - newl->sa = srv->intsa; - newl->opts = srv->lissockopts; - newl->optversion = srv->version; - if((!s->isudp && s->iscbc && s->cbc_string && !(newl->cbdest = strdup((char *)s->cbc_string))) -#ifdef __linux__ - || (srv->inetns && !(newl->netns = strdup(srv->inetns))) -#endif -#if defined SO_BINDTODEVICE || defined IP_BOUND_IF - || (srv->ibindtodevice && !(newl->device = strdup(srv->ibindtodevice))) -#endif - ){ - free(newl->cbdest); - free(newl->netns); - free(newl->device); - free(newl); - newl = NULL; - } - else { - _3proxy_mutex_init(&newl->open_mutex); - _3proxy_mutex_init(&newl->count_mutex); - } - } - _3proxy_mutex_lock(&listener_mutex); - for(l = listeners; l; l = l->next){ - if(!l->owners || !listenerfor(l, s)) continue; - if(listenerhas(l, srv->config)){ - found = l; - break; - } - if(!found || (l->owners->service == srv->service && found->owners->service != srv->service)) - found = l; - } - if(!found && (found = newl)){ - newl = NULL; - found->next = listeners; - listeners = found; - } - if(found){ - srv->lisnext = found->owners; - found->owners = srv; - found->users++; - srv->listener = found; - } - _3proxy_mutex_unlock(&listener_mutex); - if(newl){ - _3proxy_mutex_destroy(&newl->open_mutex); - _3proxy_mutex_destroy(&newl->count_mutex); - free(newl->cbdest); - free(newl->netns); - free(newl->device); - free(newl); - } -} - -/* A service stops accepting: once the last of the listener's services has, - the socket is closed, so that connections do not queue up on it. */ -static void listenerstop(struct srvparam *srv){ - struct listener *l = srv->listener; - struct srvparam **op; - SOCKET sock = INVALID_SOCKET; - struct udpsession *hand = NULL; - - _3proxy_mutex_lock(&listener_mutex); - for(op = &l->owners; *op; op = &(*op)->lisnext) if(*op == srv){ - *op = srv->lisnext; - srv->lisnext = NULL; - break; - } - if(!l->owners){ - sock = l->sock; - l->sock = INVALID_SOCKET; - /* sessions handed over with no service left to adopt them */ - hand = l->udphand; - l->udphand = NULL; - } - _3proxy_mutex_unlock(&listener_mutex); -#ifndef NOUDPMAIN - udpendall(hand, 26); -#endif - if(sock == INVALID_SOCKET) return; - so._closesocket(so.state, sock); -#ifdef WITH_UN - ununlink(&l->sa, l->undev, l->unino); -#endif -} - -/* A service lets go of its listener once its last client is gone, having - stopped accepting before; the last to let go frees it. */ -static void listenerrelease(struct srvparam *srv){ - struct listener *l = srv->listener, **lp; - int last; - - srv->listener = NULL; - _3proxy_mutex_lock(&listener_mutex); - last = !--l->users; - if(last) for(lp = &listeners; *lp; lp = &(*lp)->next) if(*lp == l) { - *lp = l->next; - break; - } - _3proxy_mutex_unlock(&listener_mutex); - if(!last) return; - _3proxy_mutex_destroy(&l->open_mutex); - _3proxy_mutex_destroy(&l->count_mutex); - if(l->netns) free(l->netns); - if(l->device) free(l->device); - if(l->cbdest) free(l->cbdest); - free(l); -} - -/* Gives back a client's slot in a service it was not a child of. */ -static void slotrelease(struct srvparam *srv){ - _3proxy_mutex_lock(&srv->counter_mutex); - _3proxy_sem_unlock(srv->childslots); - _3proxy_mutex_unlock(&srv->counter_mutex); -} - -/* After a reload, unless noforce: a client goes by the rules of a newest - service of its type on its listener, now accepting there, re-authenticated - as it if auth is set. It takes one of that service's maxconn slots, - which counts it there and keeps the service, and its configuration, - until the client is done; a slot it held in a service before is given - back. 0 to go on, or the code to end the connection with: 26 if no - service of its type is there now, 27 if none of those has a slot free, - or what the authentication says. */ -int reattach(struct clientparam *param, int auth){ - struct srvparam *cur = PSRV(param), *newest, *orig, *o; - int version = runconf->version, res, newv = 0; - - /* without a listener (connecting back) nothing replaces it */ - if(!cur->listener){ - param->version = version; - return 0; - } - _3proxy_mutex_lock(&listener_mutex); - for(o = cur->listener->owners; o; o = o->lisnext) if(o->version > newv) newv = o->version; - if(newv <= cur->version){ - /* the old services have not let go of the socket yet */ - _3proxy_mutex_unlock(&listener_mutex); - return 0; - } - /* one of the newest services of the client's type with a slot free; - param->service: the old service is a zombie by now. While it accepts - such a service is not freed, nor after the client has its slot. */ - res = 26; - newest = NULL; - for(o = cur->listener->owners; o; o = o->lisnext){ - if(o->version != newv || o->service != param->service) continue; - if(!_3proxy_sem_timedlock(o->childslots, 0)){ - newest = o; - break; - } - res = 27; - } - _3proxy_mutex_unlock(&listener_mutex); - if(!newest) return res; - if(param->newsrv) slotrelease(param->newsrv); - param->newsrv = newest; - param->version = version; - if(!auth) return 0; - /* authenticated as the newest service's client: its access lists, users, - limits and counters */ - orig = param->srv; - param->srv = newest; - res = (*newest->authfunc)(param); - param->srv = orig; - return res; -} - -/* A prepared service which could not be started. */ -void maindrop(void *service){ - struct srvstart *s = (struct srvstart *)service; - - srvunlink(&s->srv); - srvdiscard(s); - free(s); -} - #endif -void srvinit(struct srvparam * srv, struct clientparam *param){ - - memset(srv, 0, sizeof(struct srvparam)); - srv->version = conf->version; - srv->paused = conf->paused; - 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; - srv->maxchild = conf->maxchild; - srv->backlog = conf->backlog; - srv->stacksize = conf->stacksize; - srv->config = conf; - memcpy(srv->timeouts, conf->timeouts, sizeof(srv->timeouts)); - srv->filtermaxsize = conf->filtermaxsize; - srv->parentretries = conf->parentretries; - srv->maxseg = conf->maxseg; - srv->delimchar = conf->delimchar; - srv->time_start = time(NULL); - if(conf->havelog && conf->logtarget){ - srv->logtarget = (unsigned char *)strdup((char *)conf->logtarget); - } - srv->srvsock = INVALID_SOCKET; - srv->logdumpsrv = conf->logdumpsrv; - srv->logdumpcli = conf->logdumpcli; - srv->cbsock = INVALID_SOCKET; - srv->needuser = 1; -#ifdef __linux__ - srv->i_nsfd = srv->o_nsfd = -1; -#endif -#ifdef WITHSPLICE - /* where it pays: large transfers, -s0 to never */ - srv->usesplice = SPLICE_DOWNLOAD|SPLICE_UPLOAD|SPLICE_AUTO; - srv->splicemin = SPLICE_MIN; - srv->splicestreak = SPLICE_STREAK; - srv->splicesmall = SPLICE_SMALL; -#endif - srv->halfclose = 1; - srv->udpauth = 3; - memset(param, 0, sizeof(struct clientparam)); - param->srv = srv; - param->version = srv->version; - param->paused = srv->paused; - param->remsock = param->clisock = param->ctrlsock = param->ctrlsocksrv = INVALID_SOCKET; - param->pipecli[0] = param->pipecli[1] = param->pipesrv[0] = param->pipesrv[1] = -1; - param->climark = -1; - *SAFAMILY(¶m->req) = *SAFAMILY(¶m->sinsl) = *SAFAMILY(¶m->sinsr) = *SAFAMILY(¶m->sincr) = *SAFAMILY(¶m->sincl) = AF_INET; - _3proxy_mutex_init(&srv->counter_mutex); - (void)_3proxy_sem_init_timed(srv->childslots, srv->maxchild, srv->maxchild); - srv->intsa = conf->intsa; - srv->extsa = conf->extsa; -#ifndef NOIPV6 - srv->extsa6 = conf->extsa6; -#endif - srv->so = so; - srv->authcachetime = conf->authcachetime; - srv->authcachetype = conf->authcachetype; -} - -void srvinit2(struct srvparam * srv, struct clientparam *param){ - if(srv->logformat){ - char *s; - if(*srv->logformat == '-' && (s = strchr((char *)srv->logformat + 1, '+')) && s[1]){ - unsigned char* logformat = srv->logformat; - - *s = 0; - srv->nonprintable = (unsigned char *)strdup((char *)srv->logformat + 1); - srv->replace = s[1]; - srv->logformat = (unsigned char *)strdup(s + 2); - *s = '+'; - free(logformat); - } - } - memset(¶m->sinsl, 0, sizeof(param->sinsl)); - memset(¶m->sinsr, 0, sizeof(param->sinsr)); - memset(¶m->req, 0, sizeof(param->req)); - *SAFAMILY(¶m->sinsl) = AF_INET; - *SAFAMILY(¶m->sinsr) = AF_INET; - *SAFAMILY(¶m->req) = AF_INET; - param->sincr = param->sincl = srv->intsa; -#ifndef NOIPV6 - if (srv->family == 6 || srv->family == 64) param->sinsr = srv->extsa6; - else -#endif - param->sinsr = srv->extsa; -} - -void srvfree(struct srvparam * srv){ - int i; - -#ifndef STDMAIN - /* a listening socket kept as a listener goes on if a service of a later - configuration took it over */ - if(srv->listener){ - srv->srvsock = INVALID_SOCKET; - listenerstop(srv); - } -#endif - if(srv->srvsock != INVALID_SOCKET) { - so._closesocket(srv->so.state, srv->srvsock); -#ifdef WITH_UN - ununlink(&srv->intsa, srv->undev, srv->unino); -#endif - } - srv->srvsock = INVALID_SOCKET; - if(srv->cbsock != INVALID_SOCKET) so._closesocket(srv->so.state, srv->cbsock); - srv->cbsock = INVALID_SOCKET; - srv->service = S_ZOMBIE; - /* Every child returns its slot as the last thing it does with the service, - so once all of them are back no child is left. A child posts its slot - holding the mutex: taking the mutex once more waits for the last one to - have let go of both before they are destroyed. */ - for(i = srv->maxchild; i; i--) _3proxy_sem_lock(srv->childslots); - _3proxy_mutex_lock(&srv->counter_mutex); - _3proxy_mutex_unlock(&srv->counter_mutex); -#ifndef STDMAIN - if(srv->filter){ - while(srv->nfilters){ - srv->nfilters--; - if(srv->filter[srv->nfilters].filter_close){ - (*srv->filter[srv->nfilters].filter_close)(srv->filter[srv->nfilters].data); - } - } - free(srv->filter); - } - - if(srv->acl)freeacl(srv->acl); -#ifdef WITH_HTTPSRV - if(srv->httprules)freehttprules(srv->httprules); -#endif - if(srv->authfuncs)freeauth(srv->authfuncs); -#endif - _3proxy_mutex_destroy(&srv->counter_mutex); - _3proxy_sem_destroy(srv->childslots); - if(srv->target) free(srv->target); - if(srv->logtarget) free(srv->logtarget); - if(srv->logformat) free(srv->logformat); - if(srv->nonprintable) free(srv->nonprintable); -#if defined SO_BINDTODEVICE || defined IP_BOUND_IF - if(srv->ibindtodevice) free(srv->ibindtodevice); - if(srv->obindtodevice) free(srv->obindtodevice); -#endif -#ifdef __linux__ - if(srv->inetns) free(srv->inetns); - if(srv->onetns) free(srv->onetns); - if(srv->i_nsfd >= 0) { close(srv->i_nsfd); srv->i_nsfd = -1; } - if(srv->o_nsfd >= 0) { close(srv->o_nsfd); srv->o_nsfd = -1; } -#endif - if(srv->so.freefunc) srv->so.freefunc(srv->so.state); -#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->listener) listenerrelease(srv); - if(srv->config){ - struct extparam *c = srv->config; - - srv->config = NULL; - confput(c); - } -#endif -} - - -/* Ends a connection: everything it holds is released and it leaves its - service, but the structure itself stays, with nothing in it pointing to - what was freed. After this the service may be gone at any moment, so - param->srv is cleared and nothing here touches it again. */ -void releaseparam(struct clientparam * param) { - if(param->srv){ - if(param->srv->so.freefunc) param->srv->so.freefunc(param->sostate); - } - if(param->clibuf != param->clibufdata) free(param->clibuf); - if(param->srvbuf != param->srvbufdata) free(param->srvbuf); - param->clibuf = param->clibufdata; - param->srvbuf = param->srvbufdata; -#ifdef WITHSPLICE - if(param->pipecli[0] >= 0) close(param->pipecli[0]); - if(param->pipecli[1] >= 0) close(param->pipecli[1]); - if(param->pipesrv[0] >= 0) close(param->pipesrv[0]); - if(param->pipesrv[1] >= 0) close(param->pipesrv[1]); - param->pipecli[0] = param->pipecli[1] = param->pipesrv[0] = param->pipesrv[1] = -1; -#endif - /* Not shut down on Windows: there shutting down receiving resets the - connection as soon as the peer sends anything more, and close() - alone sends whatever is queued before its FIN. Elsewhere it stays: - on the BSDs it discards what is left unread, so the close that - follows is not turned into a reset. */ - if(param->srv) { - if(param->ctrlsocksrv != INVALID_SOCKET && param->ctrlsocksrv != param->remsock) { -#ifndef _WIN32 - param->srv->so._shutdown(param->sostate, param->ctrlsocksrv, SHUT_RDWR); -#endif - param->srv->so._closesocket(param->sostate, param->ctrlsocksrv); - } - if(param->ctrlsock != INVALID_SOCKET && param->ctrlsock != param->clisock) { -#ifndef _WIN32 - param->srv->so._shutdown(param->sostate, param->ctrlsock, SHUT_RDWR); -#endif - param->srv->so._closesocket(param->sostate, param->ctrlsock); - } - if(param->remsock != INVALID_SOCKET) { -#ifndef _WIN32 - param->srv->so._shutdown(param->sostate, param->remsock, SHUT_RDWR); -#endif - param->srv->so._closesocket(param->sostate, param->remsock); - } - if(param->clisock != INVALID_SOCKET) { -#ifndef _WIN32 - param->srv->so._shutdown(param->sostate, param->clisock, SHUT_RDWR); -#endif - param->srv->so._closesocket(param->sostate, param->clisock); - } - param->ctrlsocksrv = param->ctrlsock = param->remsock = param->clisock = INVALID_SOCKET; - } -#ifndef STDMAIN - while(param->nfilters > 0){ - param->nfilters--; - if(param->filters[param->nfilters].filter->filter_clear) - (*param->filters[param->nfilters].filter->filter_clear)(param->filters[param->nfilters].data); - } - if(param->connlim) stopconnlims(param); - /* a slot held in the newest service on the listener after a reload */ - if(param->newsrv){ - slotrelease(param->newsrv); - param->newsrv = NULL; - } -#endif - /* the sockets were closed and the filters cleared with it */ - param->sostate = NULL; - /* Leaving the service comes last. Once its last child is gone, srvfree() - frees the service along with its socket functions, filters and mutex, - and a close above may have lingered for seconds before getting here. */ - if(param->srv){ - _3proxy_mutex_lock(¶m->srv->counter_mutex); - if(param->prev || param->next || param->srv->child == param){ - if(param->prev){ - param->prev->next = param->next; - } - else - param->srv->child = param->next; - if(param->next){ - param->next->prev = param->prev; - } - (param->srv->childcount)--; -#ifndef STDMAIN - listenercount(param->srv, -1); -#endif - /* under the mutex too: srvfree() may destroy the semaphore as - soon as the mutex shows the last child gone */ - _3proxy_sem_unlock(param->srv->childslots); - } - _3proxy_mutex_unlock(¶m->srv->counter_mutex); - param->prev = param->next = NULL; - param->srv = NULL; - } -} - -void freeparam(struct clientparam * param) { - releaseparam(param); - free(param); -} - -FILTER_ACTION handleconnectflt(struct clientparam *cparam){ -#ifndef STDMAIN - FILTER_ACTION action; - int i; - - for(i=0; inconnectfilters ;i++){ - action = (*cparam->connectfilters[i]->filter->filter_connect)(cparam->connectfilters[i]->data, cparam); - if(action!=CONTINUE) return action; - } -#endif - return PASS; -} - - -/* A buffer filter given a buffer too small for what it makes of it hands - back one of its own, allocated with malloc, and leaves the one it was given - alone: nothing refers to that one any more, so it is freed here, unless it - is one of the connection's own buffers. The caller owns whatever *buf_p - points to on return. */ -static void filterswap(struct clientparam *param, unsigned char **cur, unsigned char *now){ - if(now != *cur){ - if(*cur != param->clibufdata && *cur != param->srvbufdata && *cur != param->tmpbuf) free(*cur); - *cur = now; - } -} - -#ifndef STDMAIN -static void * itcopy (void * from, size_t size){ - void * ret; - if(!from) return NULL; - ret = malloc(size); - if(ret) memcpy(ret, from, size); - return ret; -} - - -struct auth * copyauth (struct auth * authfuncs){ - struct auth * newauth = NULL; - - newauth = itcopy(authfuncs, sizeof(struct auth)); - for( authfuncs=newauth; authfuncs; authfuncs = authfuncs->next){ - if(authfuncs->next){ - authfuncs->next = itcopy(authfuncs->next, sizeof(struct auth)); - if(!authfuncs->next)break; - } - } - if(authfuncs){ - freeauth(newauth); - return NULL; - } - return newauth; -} - -struct ace * copyacl (struct ace *ac){ - struct ace * ret = NULL; - struct iplist *ipl; - struct portlist *pl; - struct userlist *ul; - struct chain *ch; - struct period *pel; - struct hostname *hst; - - ret = itcopy(ac, sizeof(struct ace)); - for( ac = ret; ac; ac = ac->next){ - if(ac->src){ - ac->src = itcopy(ac->src, sizeof(struct iplist)); - if(!ac->src) goto ERRORSRC; - for(ipl = ac->src; ipl->next; ipl = ipl->next){ - ipl->next = itcopy(ipl->next, sizeof(struct iplist)); - if(!ipl->next) goto ERRORSRC; - } - } - if(ac->dst){ - ac->dst = itcopy(ac->dst, sizeof(struct iplist)); - if(!ac->dst) goto ERRORDST; - for(ipl = ac->dst; ipl->next; ipl = ipl->next){ - ipl->next = itcopy(ipl->next, sizeof(struct iplist)); - if(!ipl->next) goto ERRORDST; - } - } - if(ac->ports){ - ac->ports = itcopy(ac->ports, sizeof(struct portlist)); - if(!ac->ports) goto ERRORPORTS; - for(pl = ac->ports; pl->next; pl = pl->next){ - pl->next = itcopy(pl->next, sizeof(struct portlist)); - if(!pl->next) goto ERRORPORTS; - } - } - if(ac->periods){ - ac->periods = itcopy(ac->periods, sizeof(struct period)); - if(!ac->periods) goto ERRORPERIODS; - for(pel = ac->periods; pel->next; pel = pel->next){ - pel->next = itcopy(pel->next, sizeof(struct period)); - if(!pel->next) goto ERRORPERIODS; - } - } - if(ac->users){ - ac->users = itcopy(ac->users, sizeof(struct userlist)); - if(!ac->users) goto ERRORUSERS; - for(ul = ac->users; ul; ul = ul->next){ - if(ul->user) { - ul->user = (unsigned char*)strdup((char *)ul->user); - if(!ul->user) { - ul->next = NULL; - goto ERRORUSERS; - } - } - if(ul->next){ - ul->next = itcopy(ul->next, sizeof(struct userlist)); - if(!ul->next) goto ERRORUSERS; - } - } - } - if(ac->dstnames){ - ac->dstnames = itcopy(ac->dstnames, sizeof(struct hostname)); - if(!ac->dstnames) goto ERRORDSTNAMES; - for(hst = ac->dstnames; hst; hst = hst->next){ - if(hst->name) { - hst->name = (unsigned char*)strdup((char *)hst->name); - if(!hst->name) { - hst->re = NULL; - hst->next = NULL; - goto ERRORDSTNAMES; - } - } - /* each copy compiles its own pattern, freed with it */ - if(hst->re){ -#ifdef WITH_PCRE - char err[256]; - - hst->re = pcre_pattern_compile(hst->name, err, sizeof(err)); -#else - hst->re = NULL; -#endif - if(!hst->re) { - hst->next = NULL; - goto ERRORDSTNAMES; - } - } - if(hst->next){ - hst->next = itcopy(hst->next, sizeof(struct hostname)); - if(!hst->next) goto ERRORDSTNAMES; - } - } - } - if(ac->chains){ - ac->chains = itcopy(ac->chains, sizeof(struct chain)); - if(!ac->chains) goto ERRORCHAINS; - for(ch = ac->chains; ch; ch = ch->next){ - if(ch->extuser){ - ch->extuser = (unsigned char*)strdup((char *)ch->extuser); - if(!ch->extuser){ - ch->extpass = NULL; - ch->exthost = NULL; - ch->next = NULL; - goto ERRORCHAINS; - } - } - if(ch->extpass){ - ch->extpass = (unsigned char*)strdup((char *)ch->extpass); - if(!ch->extpass){ - ch->exthost = NULL; - ch->next = NULL; - goto ERRORCHAINS; - } - } - if(ch->exthost){ - ch->exthost = (unsigned char*)strdup((char *)ch->exthost); - if(!ch->exthost){ - ch->next = NULL; - goto ERRORCHAINS; - } - - } - if(ch->next){ - ch->next = itcopy(ch->next, sizeof(struct chain)); - if(!ch->next) goto ERRORNEXT; - } - } - } - if(ac->next){ - ac->next = itcopy(ac->next, sizeof(struct ace)); - if(!ac->next) goto ERRORCHAINS; - } - } - if(!ac) return ret; -ERRORSRC: - ac->dst = NULL; -ERRORDST: - ac->ports = NULL; -ERRORPORTS: - ac->periods = NULL; -ERRORPERIODS: - ac->users = NULL; -ERRORUSERS: - ac->dstnames = NULL; -ERRORDSTNAMES: - ac->chains = NULL; -ERRORCHAINS: - ac->next = NULL; -ERRORNEXT: - freeacl(ret); - return NULL; - -} - - -/* Returns 2 when out of memory, 3 when there are more filters than a - connection has room for: a service started without some of its filters - could pass what one of them was there to stop. */ -int copyfilter (struct filter *filter, struct srvparam *srv){ - int nfilters = 0; - - if(!filter) return 0; - for(srv->filter = filter; srv->filter; srv->filter = srv->filter->next) nfilters++; - srv->filter = malloc(sizeof(struct filter) * nfilters); - if(!srv->filter) return 2; - - for(; filter; filter = filter->next){ - void *data = NULL; - - if(!filter->filter_open || !(data = (*filter->filter_open)(filter->data, srv))) continue; - if(srv->nfilters >= MAX_FILTERS){ - if(filter->filter_close) (*filter->filter_close)(data); - return 3; - } - - srv->filter[srv->nfilters] = *filter; - srv->filter[srv->nfilters].data = data; - if(srv->nfilters>0)srv->filter[srv->nfilters - 1].next = srv->filter + srv->nfilters; - srv->nfilters++; - if(filter->filter_request)srv->nreqfilters++; - if(filter->filter_connect)srv->nconnectfilters++; - if(filter->filter_afterauth)srv->nafterauthfilters++; - if(filter->filter_header_srv)srv->nhdrfilterssrv++; - if(filter->filter_header_cli)srv->nhdrfilterscli++; - if(filter->filter_predata)srv->npredatfilters++; - if(filter->filter_data_srv)srv->ndatfilterssrv++; - if(filter->filter_data_cli)srv->ndatfilterscli++; - } - return 0; -} - -FILTER_ACTION makefilters (struct srvparam *srv, struct clientparam *param){ - FILTER_ACTION res=PASS; - FILTER_ACTION action; - int i; - - if(!srv->nfilters) return PASS; - - for(i=0; infilters; i++){ - if(!srv->filter[i].filter_client)continue; - /* the slot may hold what the connection before left in it */ - param->filters[param->nfilters].data = NULL; - action = (*srv->filter[i].filter_client)(srv->filter[i].data, param, ¶m->filters[param->nfilters].data); - if(action == PASS) continue; - if(action > CONTINUE) { - /* a filter refusing the connection is not kept, but it may - have made a state for it */ - if(srv->filter[i].filter_clear && param->filters[param->nfilters].data) - (*srv->filter[i].filter_clear)(param->filters[param->nfilters].data); - return action; - } - param->filters[param->nfilters].filter = srv->filter + i; - if(srv->filter[i].filter_request)param->reqfilters[param->nreqfilters++] = param->filters + param->nfilters; - if(srv->filter[i].filter_connect)param->connectfilters[param->nconnectfilters++] = param->filters + param->nfilters; - if(srv->filter[i].filter_afterauth)param->afterauthfilters[param->nafterauthfilters++] = param->filters + param->nfilters; - if(srv->filter[i].filter_header_cli)param->hdrfilterscli[param->nhdrfilterscli++] = param->filters + param->nfilters; - if(srv->filter[i].filter_header_srv)param->hdrfilterssrv[param->nhdrfilterssrv++] = param->filters + param->nfilters; - if(srv->filter[i].filter_predata)param->predatfilters[param->npredatfilters++] = param->filters + param->nfilters; - if(srv->filter[i].filter_data_cli)param->datfilterscli[param->ndatfilterscli++] = param->filters + param->nfilters; - if(srv->filter[i].filter_data_srv)param->datfilterssrv[param->ndatfilterssrv++] = param->filters + param->nfilters; - param->nfilters++; - } - return res; -} - -void * itfree(void *data, void * retval){ - free(data); - return retval; -} - -void freeauth(struct auth * authfuncs){ - for(; authfuncs; authfuncs = (struct auth *)itfree(authfuncs, authfuncs->next)); -} - -void freeacl(struct ace *ac){ - struct iplist *ipl; - struct portlist *pl; - struct userlist *ul; - struct chain *ch; - struct period *pel; - struct hostname *hst; - for(; ac; ac = (struct ace *) itfree(ac, ac->next)){ - for(ipl = ac->src; ipl; ipl = (struct iplist *)itfree(ipl, ipl->next)); - for(ipl = ac->dst; ipl; ipl = (struct iplist *)itfree(ipl,ipl->next)); - for(pl = ac->ports; pl; pl = (struct portlist *)itfree(pl, pl->next)); - for(pel = ac->periods; pel; pel = (struct period *)itfree(pel, pel->next)); - for(ul = ac->users; ul; ul = (struct userlist *)itfree(ul, ul->next)){ - if(ul->user)free(ul->user); - } - for(hst = ac->dstnames; hst; hst = (struct hostname *)itfree(hst, hst->next)){ - if(hst->name)free(hst->name); -#ifdef WITH_PCRE - if(hst->re)pcre_pattern_free(hst->re); -#endif - } - for(ch = ac->chains; ch; ch = (struct chain *) itfree(ch, ch->next)){ - if(ch->extuser) free(ch->extuser); - if(ch->extpass) free(ch->extpass); - if(ch->exthost) free(ch->exthost); - } - } -} - -FILTER_ACTION handleafterauthflt(struct clientparam *cparam){ -#ifndef STDMAIN - FILTER_ACTION action; - int i; - - for(i=0; inafterauthfilters ;i++){ - action = (*cparam->afterauthfilters[i]->filter->filter_afterauth)(cparam->afterauthfilters[i]->data, cparam); - if(action!=CONTINUE) return action; - } -#endif - return PASS; -} - - -FILTER_ACTION handlereqfilters(struct clientparam *param, unsigned char ** buf_p, int * bufsize_p, int offset, int * length_p){ - FILTER_ACTION action; - int i; - unsigned char *cur = *buf_p; - - for(i=0; inreqfilters; i++){ - action = (*param->reqfilters[i]->filter->filter_request)(param->reqfilters[i]->data, param, buf_p, bufsize_p, offset, length_p); - filterswap(param, &cur, *buf_p); - if(action!=CONTINUE) return action; - } - return PASS; -} - -FILTER_ACTION handlehdrfilterssrv(struct clientparam *param, unsigned char ** buf_p, int * bufsize_p, int offset, int * length_p){ - FILTER_ACTION action; - int i; - unsigned char *cur = *buf_p; - - for(i=0; inhdrfilterssrv; i++){ - action = (*param->hdrfilterssrv[i]->filter->filter_header_srv)(param->hdrfilterssrv[i]->data, param, buf_p, bufsize_p, offset, length_p); - filterswap(param, &cur, *buf_p); - if(action!=CONTINUE) return action; - } - return PASS; -} - -FILTER_ACTION handlehdrfilterscli(struct clientparam *param, unsigned char ** buf_p, int * bufsize_p, int offset, int * length_p){ - FILTER_ACTION action; - int i; - unsigned char *cur = *buf_p; - - for(i = 0; i < param->nhdrfilterscli; i++){ - action = (*param->hdrfilterscli[i]->filter->filter_header_cli)(param->hdrfilterscli[i]->data, param, buf_p, bufsize_p, offset, length_p); - filterswap(param, &cur, *buf_p); - if(action!=CONTINUE) return action; - } - return PASS; -} - -#endif - -FILTER_ACTION handlepredatflt(struct clientparam *cparam){ -#ifndef STDMAIN - FILTER_ACTION action; - int i; - - if(cparam->predatdone) return PASS; - cparam->predatdone = 1; - for(i=0; inpredatfilters ;i++){ - action = (*cparam->predatfilters[i]->filter->filter_predata)(cparam->predatfilters[i]->data, cparam); - if(action!=CONTINUE) return action; - } -#endif - return PASS; -} - -FILTER_ACTION handledatfltcli(struct clientparam *cparam, unsigned char ** buf_p, int * bufsize_p, int offset, int * length_p){ -#ifndef STDMAIN - FILTER_ACTION action; - int i; - unsigned char *cur = *buf_p; - - for(i=0; indatfilterscli ;i++){ - action = (*cparam->datfilterscli[i]->filter->filter_data_cli)(cparam->datfilterscli[i]->data, cparam, buf_p, bufsize_p, offset, length_p); - filterswap(cparam, &cur, *buf_p); - if(action!=CONTINUE) return action; - } -#endif - return PASS; -} - -FILTER_ACTION handledatfltsrv(struct clientparam *cparam, unsigned char ** buf_p, int * bufsize_p, int offset, int * length_p){ - FILTER_ACTION action; - int i; - unsigned char *cur = *buf_p; - - for(i=0; indatfilterssrv; i++){ - action = (*cparam->datfilterssrv[i]->filter->filter_data_srv)(cparam->datfilterssrv[i]->data, cparam, buf_p, bufsize_p, offset, length_p); - filterswap(cparam, &cur, *buf_p); - if(action!=CONTINUE) return action; - } - return PASS; -} - -