From 3526759e5910efe3543fa5a9e0436e69f6b4021a Mon Sep 17 00:00:00 2001 From: Vladimir Dubrovin <3proxy@3proxy.ru> Date: Tue, 25 Aug 2026 17:21:33 +0300 Subject: [PATCH] implement small http server for admin and testing --- CMakeLists.txt | 8 +- Makefile.FreeBSD | 5 + Makefile.Linux | 7 + Makefile.Solaris | 5 + Makefile.msvc | 3 +- Makefile.unix | 5 + Makefile.watcom | 3 +- Makefile.win | 5 + man/3proxy.cfg.5 | 11 +- rus.3ps | 1 - src/3proxy.c | 3 +- src/Makefile.inc | 7 +- src/acl.c | 31 +-- src/auth.c | 6 + src/common.c | 96 +++++++++ src/conf.c | 155 ++++++++++---- src/httpsrv.c | 517 +++++++++++++++++++++++++++++++++++++++++++++++ src/plugins.c | 7 +- src/proxy.h | 16 +- src/proxymain.c | 12 ++ src/structures.h | 29 +++ src/webadmin.c | 272 ++++++++++--------------- 22 files changed, 950 insertions(+), 254 deletions(-) create mode 100644 src/httpsrv.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 1b324a1..a0adad6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -56,6 +56,7 @@ option(3PROXY_USE_POLL "Use poll() instead of select() (Unix only)" ON) option(3PROXY_USE_WSAPOLL "Use WSAPoll instead of select() (Windows only)" ON) option(3PROXY_USE_NETFILTER "Enable Linux netfilter support (Linux only)" ON) option(3PROXY_USE_UNIX_SOCKETS "Enable Unix domain socket support (Unix only)" ON) +option(3PROXY_USE_HTTPSRV "Build the HTTP server and the admin interface on top of it" ON) if(NOT WIN32 AND NOT APPLE) option(3PROXY_STATIC_LINK "Statically link libraries using -Wl,-Bstatic (Linux/Unix only)" OFF) @@ -185,7 +186,11 @@ elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux") add_compile_definitions(WITH_NETFILTER) endif() - if(3PROXY_USE_UNIX_SOCKETS) + if(3PROXY_USE_HTTPSRV) + add_compile_definitions(WITH_HTTPSRV) +endif() + +if(3PROXY_USE_UNIX_SOCKETS) add_compile_definitions(WITH_UN) endif() @@ -403,6 +408,7 @@ add_library(srv_modules OBJECT src/auto.c src/socks.c src/webadmin.c + src/httpsrv.c src/dnspr.c ) diff --git a/Makefile.FreeBSD b/Makefile.FreeBSD index a6ba7a8..eddee6f 100644 --- a/Makefile.FreeBSD +++ b/Makefile.FreeBSD @@ -24,6 +24,11 @@ LDFLAGS += $(EXTRA_LDFLAGS) # -lpthreads may be reuiured on some platforms instead of -pthreads # -ldl or -lld may be required for some platforms DCFLAGS ?= -fPIC +HTTPSRV ?= true +ifeq ($(HTTPSRV),true) + CFLAGS += -DWITH_HTTPSRV + HTTPSRV_OBJS = srvhttpsrv$(OBJSUFFICS) srvwebadmin$(OBJSUFFICS) +endif DLFLAGS ?= -shared DLSUFFICS = .so LIBS ?= diff --git a/Makefile.Linux b/Makefile.Linux index e399f60..46597b7 100644 --- a/Makefile.Linux +++ b/Makefile.Linux @@ -25,6 +25,13 @@ LDFLAGS += -fno-strict-aliasing -pthread # makefile, including the += above and the STATIC/LIBSTATIC handling below. CFLAGS += $(EXTRA_CFLAGS) LDFLAGS += $(EXTRA_LDFLAGS) +# The HTTP server serves the endpoints declared by http lines. The admin +# interface is built on top of it, so turning it off removes both. +HTTPSRV ?= true +ifeq ($(HTTPSRV),true) + CFLAGS += -DWITH_HTTPSRV + HTTPSRV_OBJS = srvhttpsrv$(OBJSUFFICS) srvwebadmin$(OBJSUFFICS) +endif DLFLAGS ?= -shared DLSUFFICS = .ld.so # -lpthreads may be reuqired on some platforms instead of -pthreads diff --git a/Makefile.Solaris b/Makefile.Solaris index 2def1c9..1fb7693 100644 --- a/Makefile.Solaris +++ b/Makefile.Solaris @@ -14,6 +14,11 @@ COUT = -o ./ LN = $(CC) LDFLAGS = -xO3 DCFLAGS = -fPIC +HTTPSRV ?= true +ifeq ($(HTTPSRV),true) + CFLAGS += -DWITH_HTTPSRV + HTTPSRV_OBJS = srvhttpsrv$(OBJSUFFICS) srvwebadmin$(OBJSUFFICS) +endif DLFLAGS = -shared DLSUFFICS = .ld.so LIBS = -lpthread -lsocket -lnsl -lresolv -ldl diff --git a/Makefile.msvc b/Makefile.msvc index e412722..fa04294 100644 --- a/Makefile.msvc +++ b/Makefile.msvc @@ -18,7 +18,7 @@ SSL_LIBS = wolfssl.lib SSL_DEFS = /D "WITH_SSL" SSL_LIBS = libcrypto.lib libssl.lib !ENDIF -CFLAGS = /nologo /MT /W3 /Ox /GS /EHs- /GA /GF /D "MSVC" /D "WITH_WSAPOLL" /D "NDEBUG" /D "WIN32" $(SSL_DEFS) /D "WITH_PCRE" /D "WITH_ODBC" /D "_CONSOLE" /D "_MBCS" /D "_WIN32" /Fp"proxy.pch" /FD /c $(BUILDDATE) $(VERSION) +CFLAGS = /D "WITH_HTTPSRV" /nologo /MT /W3 /Ox /GS /EHs- /GA /GF /D "MSVC" /D "WITH_WSAPOLL" /D "NDEBUG" /D "WIN32" $(SSL_DEFS) /D "WITH_PCRE" /D "WITH_ODBC" /D "_CONSOLE" /D "_MBCS" /D "_WIN32" /Fp"proxy.pch" /FD /c $(BUILDDATE) $(VERSION) COUT = /Fo LN = link LDFLAGS = /nologo /subsystem:console /incremental:no @@ -40,6 +40,7 @@ MAKEFILE = Makefile.msvc PLUGINS = utf8tocp1251 WindowsAuthentication TrafficPlugin StringsPlugin FilePlugin SSL_OBJS = ssllib$(OBJSUFFICS) ssl$(OBJSUFFICS) PCRE_OBJS = pcre$(OBJSUFFICS) +HTTPSRV_OBJS = srvhttpsrv$(OBJSUFFICS) srvwebadmin$(OBJSUFFICS) VERFILE = 3proxy.res $(VERFILE) VERSIONDEP = 3proxy.res $(VERSIONDEP) AFTERCLEAN = if exist src\*.res (del src\*.res) && if exist src\*.err (del src\*.err) diff --git a/Makefile.unix b/Makefile.unix index 7337396..c0bb8b1 100644 --- a/Makefile.unix +++ b/Makefile.unix @@ -26,6 +26,11 @@ LDFLAGS += $(EXTRA_LDFLAGS) # -lpthreads may be reuqired on some platforms instead of -pthreads # -ldl or -lld may be required for some platforms DCFLAGS ?= -fPIC +HTTPSRV ?= true +ifeq ($(HTTPSRV),true) + CFLAGS += -DWITH_HTTPSRV + HTTPSRV_OBJS = srvhttpsrv$(OBJSUFFICS) srvwebadmin$(OBJSUFFICS) +endif DLFLAGS ?= -shared DLSUFFICS ?= .ld.so LIBS ?= diff --git a/Makefile.watcom b/Makefile.watcom index bfe1843..c9166ab 100644 --- a/Makefile.watcom +++ b/Makefile.watcom @@ -8,7 +8,7 @@ BUILDDIR = ../bin/ PREFIX = 3proxy_ CRYPT_PREFIX = 3proxy_ CC = cl -CFLAGS = /nologo /Ox /MT /D "NOIPV6" /D "NO_UN" /D "NODEBUG" /D "NORADIUS" /D"WATCOM" /D "MSVC" /D "WIN32" /D "_CONSOLE" /D "_MBCS" /D "_WIN32" /D "PRId64=\"I64d\"" /D "PRIu64=\"I64u\"" /D "SCNu64=\"I64u\"" /D "SCNx64=\"I64x\"" /D "SCNd64=\"I64d\"" /D "PRIx64=\"I64x\"" /c $(VERSION) $(BUILDDATE) +CFLAGS = /D "WITH_HTTPSRV" /nologo /Ox /MT /D "NOIPV6" /D "NO_UN" /D "NODEBUG" /D "NORADIUS" /D"WATCOM" /D "MSVC" /D "WIN32" /D "_CONSOLE" /D "_MBCS" /D "_WIN32" /D "PRId64=\"I64d\"" /D "PRIu64=\"I64u\"" /D "SCNu64=\"I64u\"" /D "SCNx64=\"I64x\"" /D "SCNd64=\"I64d\"" /D "PRIx64=\"I64x\"" /c $(VERSION) $(BUILDDATE) COUT = /Fo LN = link LDFLAGS = /nologo /subsystem:console /incremental:no @@ -21,6 +21,7 @@ LIBEXT = .lib LNOUT = /out: EXESUFFICS = .exe OBJSUFFICS = .obj +HTTPSRV_OBJS = srvhttpsrv$(OBJSUFFICS) srvwebadmin$(OBJSUFFICS) DEFINEOPTION = /D COMPFILES = *.pch *.idb *.err REMOVECOMMAND = del 2>NUL >NUL diff --git a/Makefile.win b/Makefile.win index 3c76abc..8d7ba30 100644 --- a/Makefile.win +++ b/Makefile.win @@ -20,6 +20,11 @@ LDFLAGS += -fno-strict-aliasing -mthreads # makefile, including the += above and the STATIC/LIBSTATIC handling below. CFLAGS += $(EXTRA_CFLAGS) LDFLAGS += $(EXTRA_LDFLAGS) +HTTPSRV ?= true +ifeq ($(HTTPSRV),true) + CFLAGS += -DWITH_HTTPSRV + HTTPSRV_OBJS = srvhttpsrv$(OBJSUFFICS) srvwebadmin$(OBJSUFFICS) +endif DLFLAGS ?= -shared DLSUFFICS = .dll LIBS += -lws2_32 -lodbc32 -ladvapi32 -luser32 -lbcrypt diff --git a/man/3proxy.cfg.5 b/man/3proxy.cfg.5 index 6f88c67..b74fe9a 100644 --- a/man/3proxy.cfg.5 +++ b/man/3proxy.cfg.5 @@ -282,16 +282,7 @@ proxy on a client with FTP proxy support. Username format is one of .BR config \fI\fR .br - Path to configuration file to use on 3proxy restart or to save configuration. - -.br -.B writable -.br - ReOpens configuration file for write access via Web interface, -and rereads it. Usually should be first command on config file -but in combination with config -it can be used anywhere to open -alternate config file. Think twice before using it. + Path to configuration file to use on 3proxy restart. .br .B end diff --git a/rus.3ps b/rus.3ps index 9c91621..c5272af 100644 --- a/rus.3ps +++ b/rus.3ps @@ -24,7 +24,6 @@ Content-type: text/html; charset=utf-8\n Счетчики

\n Перезагрузка конфигурации сервера

\n Запущенные сервисы

\n -Настройка сервера\n

%s %s Конфигурация

[end] diff --git a/src/3proxy.c b/src/3proxy.c index d3f3af2..b762a0e 100644 --- a/src/3proxy.c +++ b/src/3proxy.c @@ -28,7 +28,6 @@ void pcre_install(void); FILE * confopen(); extern unsigned char *strings[]; -extern FILE *writable; extern struct counter_header cheader; extern struct counter_record crecord; @@ -537,7 +536,7 @@ int WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int conf.version++; if(res) RETURN(res); - if(!writable){fclose(fp); fp = NULL;} + fclose(fp); fp = NULL; #ifdef _WIN32 diff --git a/src/Makefile.inc b/src/Makefile.inc index 22833a2..532295c 100644 --- a/src/Makefile.inc +++ b/src/Makefile.inc @@ -116,6 +116,9 @@ srvsocks$(OBJSUFFICS): socks.c proxy.h structures.h srvwebadmin$(OBJSUFFICS): webadmin.c proxy.h structures.h $(CC) $(COUT)srvwebadmin$(OBJSUFFICS) $(CFLAGS) webadmin.c +srvhttpsrv$(OBJSUFFICS): httpsrv.c proxy.h structures.h + $(CC) $(COUT)srvhttpsrv$(OBJSUFFICS) $(CFLAGS) httpsrv.c + srvudppm$(OBJSUFFICS): udppm.c proxy.h structures.h $(CC) $(COUT)srvudppm$(OBJSUFFICS) $(CFLAGS) udppm.c @@ -188,6 +191,6 @@ ssl$(OBJSUFFICS): ssl.c structures.h proxy.h ssl.h pcre$(OBJSUFFICS): pcre.c structures.h $(CC) $(COUT)pcre$(OBJSUFFICS) $(CFLAGS) $(DEFINEOPTION)WITH_PCRE pcre.c -$(BUILDDIR)3proxy$(EXESUFFICS): 3proxy$(OBJSUFFICS) mainfunc$(OBJSUFFICS) srvproxy$(OBJSUFFICS) srvpop3p$(OBJSUFFICS) srvimapp$(OBJSUFFICS) srvsmtpp$(OBJSUFFICS) srvftppr$(OBJSUFFICS) srvsocks$(OBJSUFFICS) srvtcppm$(OBJSUFFICS) srvtlspr$(OBJSUFFICS) srvauto$(OBJSUFFICS) srvudppm$(OBJSUFFICS) sockmap$(OBJSUFFICS) udpsockmap$(OBJSUFFICS) sockgetchar$(OBJSUFFICS) common$(OBJSUFFICS) auth$(OBJSUFFICS) acl$(OBJSUFFICS) limiter$(OBJSUFFICS) redirect$(OBJSUFFICS) authradius$(OBJSUFFICS) hash$(OBJSUFFICS) hashtables$(OBJSUFFICS) resolve$(OBJSUFFICS) sql$(OBJSUFFICS) conf$(OBJSUFFICS) log$(OBJSUFFICS) datatypes$(OBJSUFFICS) md4$(OBJSUFFICS) md5$(OBJSUFFICS) blake2$(OBJSUFFICS) 3proxy_crypt$(OBJSUFFICS) base64$(OBJSUFFICS) ftp$(OBJSUFFICS) stringtable$(OBJSUFFICS) srvwebadmin$(OBJSUFFICS) srvdnspr$(OBJSUFFICS) plugins$(OBJSUFFICS) mdhash$(OBJSUFFICS) $(SSL_OBJS) $(PCRE_OBJS) $(COMPATLIBS) $(VERSIONDEP) - $(LN) $(LNOUT)$(BUILDDIR)3proxy$(EXESUFFICS) $(LDFLAGS) $(VERFILE) 3proxy$(OBJSUFFICS) mainfunc$(OBJSUFFICS) auth$(OBJSUFFICS) acl$(OBJSUFFICS) limiter$(OBJSUFFICS) redirect$(OBJSUFFICS) authradius$(OBJSUFFICS) hash$(OBJSUFFICS) hashtables$(OBJSUFFICS) resolve$(OBJSUFFICS) sql$(OBJSUFFICS) conf$(OBJSUFFICS) datatypes$(OBJSUFFICS) srvauto$(OBJSUFFICS) srvproxy$(OBJSUFFICS) srvpop3p$(OBJSUFFICS) srvimapp$(OBJSUFFICS) srvsmtpp$(OBJSUFFICS) srvftppr$(OBJSUFFICS) srvsocks$(OBJSUFFICS) srvtcppm$(OBJSUFFICS) srvtlspr$(OBJSUFFICS) srvudppm$(OBJSUFFICS) sockmap$(OBJSUFFICS) udpsockmap$(OBJSUFFICS) sockgetchar$(OBJSUFFICS) common$(OBJSUFFICS) log$(OBJSUFFICS) 3proxy_crypt$(OBJSUFFICS) md4$(OBJSUFFICS) md5$(OBJSUFFICS) blake2$(OBJSUFFICS) base64$(OBJSUFFICS) ftp$(OBJSUFFICS) stringtable$(OBJSUFFICS) srvwebadmin$(OBJSUFFICS) srvdnspr$(OBJSUFFICS) plugins$(OBJSUFFICS) mdhash$(OBJSUFFICS) $(SSL_OBJS) $(PCRE_OBJS) $(COMPATLIBS) $(LIBS) $(PCRE_LIBS) +$(BUILDDIR)3proxy$(EXESUFFICS): 3proxy$(OBJSUFFICS) mainfunc$(OBJSUFFICS) srvproxy$(OBJSUFFICS) srvpop3p$(OBJSUFFICS) srvimapp$(OBJSUFFICS) srvsmtpp$(OBJSUFFICS) srvftppr$(OBJSUFFICS) srvsocks$(OBJSUFFICS) srvtcppm$(OBJSUFFICS) srvtlspr$(OBJSUFFICS) srvauto$(OBJSUFFICS) srvudppm$(OBJSUFFICS) sockmap$(OBJSUFFICS) udpsockmap$(OBJSUFFICS) sockgetchar$(OBJSUFFICS) common$(OBJSUFFICS) auth$(OBJSUFFICS) acl$(OBJSUFFICS) limiter$(OBJSUFFICS) redirect$(OBJSUFFICS) authradius$(OBJSUFFICS) hash$(OBJSUFFICS) hashtables$(OBJSUFFICS) resolve$(OBJSUFFICS) sql$(OBJSUFFICS) conf$(OBJSUFFICS) log$(OBJSUFFICS) datatypes$(OBJSUFFICS) md4$(OBJSUFFICS) md5$(OBJSUFFICS) blake2$(OBJSUFFICS) 3proxy_crypt$(OBJSUFFICS) base64$(OBJSUFFICS) ftp$(OBJSUFFICS) stringtable$(OBJSUFFICS) $(HTTPSRV_OBJS) srvdnspr$(OBJSUFFICS) plugins$(OBJSUFFICS) mdhash$(OBJSUFFICS) $(SSL_OBJS) $(PCRE_OBJS) $(COMPATLIBS) $(VERSIONDEP) + $(LN) $(LNOUT)$(BUILDDIR)3proxy$(EXESUFFICS) $(LDFLAGS) $(VERFILE) 3proxy$(OBJSUFFICS) mainfunc$(OBJSUFFICS) auth$(OBJSUFFICS) acl$(OBJSUFFICS) limiter$(OBJSUFFICS) redirect$(OBJSUFFICS) authradius$(OBJSUFFICS) hash$(OBJSUFFICS) hashtables$(OBJSUFFICS) resolve$(OBJSUFFICS) sql$(OBJSUFFICS) conf$(OBJSUFFICS) datatypes$(OBJSUFFICS) srvauto$(OBJSUFFICS) srvproxy$(OBJSUFFICS) srvpop3p$(OBJSUFFICS) srvimapp$(OBJSUFFICS) srvsmtpp$(OBJSUFFICS) srvftppr$(OBJSUFFICS) srvsocks$(OBJSUFFICS) srvtcppm$(OBJSUFFICS) srvtlspr$(OBJSUFFICS) srvudppm$(OBJSUFFICS) sockmap$(OBJSUFFICS) udpsockmap$(OBJSUFFICS) sockgetchar$(OBJSUFFICS) common$(OBJSUFFICS) log$(OBJSUFFICS) 3proxy_crypt$(OBJSUFFICS) md4$(OBJSUFFICS) md5$(OBJSUFFICS) blake2$(OBJSUFFICS) base64$(OBJSUFFICS) ftp$(OBJSUFFICS) stringtable$(OBJSUFFICS) $(HTTPSRV_OBJS) srvdnspr$(OBJSUFFICS) plugins$(OBJSUFFICS) mdhash$(OBJSUFFICS) $(SSL_OBJS) $(PCRE_OBJS) $(COMPATLIBS) $(LIBS) $(PCRE_LIBS) diff --git a/src/acl.c b/src/acl.c index 53f50ca..a5674bb 100644 --- a/src/acl.c +++ b/src/acl.c @@ -62,36 +62,7 @@ int ACLmatches(struct ace* acentry, struct clientparam * param){ } while(i > 5 && param->hostname[i-1] == '.') param->hostname[i-1] = 0; for(hstentry = acentry->dstnames; hstentry; hstentry = hstentry->next){ - int lname, lhost; - switch(hstentry->matchtype){ - case 0: -#ifndef _WIN32 - if(strcasestr((char *)param->hostname, (char *)hstentry->name)) match = 1; -#else - if(strstr((char *)param->hostname, (char *)hstentry->name)) match = 1; -#endif - break; - - case 1: - if(!strncasecmp((char *)param->hostname, (char *)hstentry->name, strlen((char *)hstentry->name))) - match = 1; - break; - - case 2: - lname = strlen((char *)hstentry->name); - lhost = strlen((char *)param->hostname); - if(lhost > lname){ - if(!strncasecmp((char *)param->hostname + (lhost - lname), - (char *)hstentry->name, - lname)) - match = 1; - } - break; - - default: - if(!strcasecmp((char *)param->hostname, (char *)hstentry->name)) match = 1; - break; - } + if(patternmatch(hstentry, param->hostname)) match = 1; if(match) break; } } diff --git a/src/auth.c b/src/auth.c index f986c46..a4daedb 100644 --- a/src/auth.c +++ b/src/auth.c @@ -18,7 +18,13 @@ int alwaysauth(struct clientparam * param){ if(conf.connlimiter && !param->connlim && startconnlims(param)) return 10; +#ifdef WITH_HTTPSRV + /* The http server answers the request itself, so authorization must not + try to reach a destination that does not exist. */ + res = (param->srv->service == S_HTTPSRV)? 0 : doconnect(param); +#else res = doconnect(param); +#endif if(!res){ if(conf.bandlimfunc && (conf.bandlimiter||conf.bandlimiterout)){ _3proxy_mutex_lock(&bandlim_mutex); diff --git a/src/common.c b/src/common.c index 1502546..2857205 100644 --- a/src/common.c +++ b/src/common.c @@ -807,6 +807,102 @@ int bindwithrange(struct clientparam *param, SOCKET sock, PROXYSOCKADDRTYPE *sa, return -1; } +/* Host lists in access rules have always accepted name, name*, *name and + *name*, with the leading and trailing star recorded as a match type rather + than kept in the string. The parser and the comparison are here so that + anything else matching a name against a pattern - the http command, and + whatever replaces the star with a regular expression later - behaves the same + way and gains the same syntax at the same time. + */ +int parsepattern(struct hostname *h, unsigned char *arg) +{ + int arglen; + unsigned char *pattern; + + arglen = (int)strlen((char *)arg); + h->matchtype = 3; + pattern = arg; + + if(arglen && pattern[arglen-1] == '*'){ + arglen--; + pattern[arglen] = 0; + h->matchtype ^= MATCHEND; + } + if(arglen && pattern[0] == '*'){ + pattern++; + arglen--; + h->matchtype ^= MATCHBEGIN; + } + + h->name = (unsigned char *)strdup((char *)pattern); + return h->name? 0 : 1; +} + +/* Matches str against a pattern and reports the part a star stood for. Where a + pattern has a star at both ends the trailing one is reported, since that is + the part following the text that was matched. An exact pattern leaves an + empty span. */ +int patternmatchpos(const struct hostname *h, const unsigned char *str, int *start, int *len) +{ + int lname, lstr, pos = 0, match = 0; + char *found; + + if(!h->name || !str) return 0; + + lname = (int)strlen((char *)h->name); + lstr = (int)strlen((char *)str); + + switch(h->matchtype){ + case 0: +#ifndef _WIN32 + found = strcasestr((char *)str, (char *)h->name); +#else + found = strstr((char *)str, (char *)h->name); +#endif + if(found){ + match = 1; + pos = (int)(found - (char *)str) + lname; + } + break; + + case 1: + if(!strncasecmp((char *)str, (char *)h->name, lname)){ + match = 1; + pos = lname; + } + break; + + case 2: + if(lstr >= lname && + !strncasecmp((char *)str + (lstr - lname), (char *)h->name, lname)){ + match = 1; + pos = 0; + if(start) *start = 0; + if(len) *len = lstr - lname; + return 1; + } + break; + + default: + if(!strcasecmp((char *)str, (char *)h->name)){ + match = 1; + pos = lstr; + } + break; + } + + if(!match) return 0; + + if(start) *start = pos; + if(len) *len = lstr - pos; + return 1; +} + +int patternmatch(const struct hostname *h, const unsigned char *str) +{ + return patternmatchpos(h, str, NULL, NULL); +} + int scanaddr(const unsigned char *s, uint32_t * ip, uint32_t * mask) { unsigned d1, d2, d3, d4, m; int res; diff --git a/src/conf.c b/src/conf.c index aac6317..787c93b 100644 --- a/src/conf.c +++ b/src/conf.c @@ -7,6 +7,10 @@ */ #include "proxy.h" + +#ifdef WITH_HTTPSRV +static int addhttprule(char *host, char *url, char *op, char *params); +#endif #include "mdhash.h" #ifdef WITH_SSL void ssl_install(void); @@ -35,7 +39,6 @@ _3proxy_mutex_t config_mutex; int haveerror = 0; int linenum = 0; -FILE *writable; struct counter_header cheader = {"3CF", (time_t)0}; struct counter_record crecord; @@ -60,10 +63,6 @@ FILE * confopen(){ curconf += strlen(chrootp); } #endif - if(writable) { - rewind(writable); - return writable; - } return fopen(curconf, "r"); } @@ -253,12 +252,32 @@ static int h_proxy(int argc, unsigned char ** argv){ childdef.service = S_UDPPM; childdef.helpmessage = " -s single packet UDP service for request/reply (DNS-like) services\n"; } +#ifdef WITH_HTTPSRV else if(!strcmp((char *)argv[0], "admin")) { - childdef.pf = adminchild; + /* The same service as httpsrv, with the administration pages + declared for it. */ + if(addhttprule("*", "/C*", "admin_counters", NULL) || + addhttprule("*", "/R", "admin_reload", NULL) || + addhttprule("*", "/S*", "admin_services", NULL) || + addhttprule("*", "*", "admin", NULL)){ + fprintf(stderr, "Failed to declare the admin pages, line %d\n", linenum); + return 1; + } + childdef.pf = httpsrvchild; childdef.port = 80; childdef.isudp = 0; - childdef.service = S_ADMIN; + childdef.service = S_HTTPSRV; } +#endif +#ifdef WITH_HTTPSRV + else if(!strcmp((char *)argv[0], "httpsrv")) { + childdef.pf = httpsrvchild; + childdef.port = 80; + childdef.isudp = 0; + childdef.service = S_HTTPSRV; + childdef.helpmessage = " HTTP server, /echo describes the connection, /data?size=N returns N bytes\n"; + } +#endif else if(!strcmp((char *)argv[0], "dnspr")) { childdef.pf = dnsprchild; childdef.port = 53; @@ -765,7 +784,6 @@ struct redirdesc redirs[] = { {R_SOCKS5P, "socks5+", sockschild}, {R_SOCKS4B, "socks4b", sockschild}, {R_SOCKS5B, "socks5b", sockschild}, - {R_ADMIN, "admin", adminchild}, {R_EXTIP, "extip", NULL}, {R_EXTPORT, "extport", NULL}, {R_INTPORT, "intport", NULL}, @@ -775,19 +793,53 @@ struct redirdesc redirs[] = { {0, NULL, NULL} }; +#ifdef WITH_HTTPSRV +/* Installs one rule from code, for the pages a service predefines. */ +static int addhttprule(char *host, char *url, char *op, char *params) +{ + struct httprule *rule, *tail; + unsigned char hostbuf[64], urlbuf[128]; + + rule = malloc(sizeof(struct httprule)); + if(!rule) return 1; + memset(rule, 0, sizeof(struct httprule)); + + rule->op = httpopbyname((unsigned char *)op); + if(rule->op < 0){ + free(rule); + return 1; + } + + strcpy((char *)hostbuf, host); + strcpy((char *)urlbuf, url); + if(parsepattern(&rule->host, hostbuf) || parsepattern(&rule->url, urlbuf)){ + free(rule->host.name); + free(rule); + return 1; + } + if(params) rule->params = (unsigned char *)strdup(params); + + if(!conf.httprules) conf.httprules = rule; + else { + for(tail = conf.httprules; tail->next; tail = tail->next); + tail->next = rule; + } + return 0; +} +#endif + /* Parses an inclusive FIRST-LAST local port range into first | last << 16. */ static int parserange(unsigned char *arg, uint32_t *range) { char *end; unsigned long first, last; - errno = 0; first = strtoul((char *)arg, &end, 10); - if(errno || *end != '-' || !first || first > 65535) return 1; + if(end == (char *)arg || *end != '-' || !first || first > 65535) return 1; - errno = 0; - last = strtoul(end + 1, &end, 10); - if(errno || *end || !last || last > 65535 || last < first) return 1; + arg = (unsigned char *)end + 1; + last = strtoul((char *)arg, &end, 10); + if(end == (char *)arg || *end || !last || last > 65535 || last < first) return 1; *range = (uint32_t)first | ((uint32_t)last << 16); return 0; @@ -906,6 +958,50 @@ static int h_parent(int argc, unsigned char **argv){ } +#ifdef WITH_HTTPSRV +/* http [parameters] + Rules are matched in the order they are given, first match wins. */ +static int h_http(int argc, unsigned char **argv){ + struct httprule *rule, *tail; + int op; + + op = httpopbyname(argv[3]); + if(op < 0){ + fprintf(stderr, "Unknown http operation: %s line %d\n", argv[3], linenum); + return(1); + } + + rule = malloc(sizeof(struct httprule)); + if(!rule) return(21); + memset(rule, 0, sizeof(struct httprule)); + rule->op = op; + + if(parsepattern(&rule->host, argv[1]) || parsepattern(&rule->url, argv[2])){ + fprintf(stderr, "No memory for http rule, line %d\n", linenum); + free(rule->host.name); + free(rule); + return(21); + } + + if(argc > 4){ + rule->params = (unsigned char *)strdup((char *)argv[4]); + if(!rule->params){ + free(rule->host.name); + free(rule->url.name); + free(rule); + return(21); + } + } + + if(!conf.httprules) conf.httprules = rule; + else { + for(tail = conf.httprules; tail->next; tail = tail->next); + tail->next = rule; + } + return 0; +} +#endif + static int h_nolog(int argc, unsigned char **argv){ struct ace *acl = NULL; @@ -1044,20 +1140,7 @@ struct ace * make_ace (int argc, unsigned char ** argv){ return(NULL); } memset(hostnamel, 0, sizeof(struct hostname)); - hostnamel->matchtype = 3; - pattern = arg; - if(pattern[arglen-1] == '*'){ - arglen --; - pattern[arglen] = 0; - hostnamel->matchtype ^= MATCHEND; - } - if(pattern[0] == '*'){ - pattern++; - arglen--; - hostnamel->matchtype ^= MATCHBEGIN; - } - hostnamel->name = (unsigned char *) strdup( (char *)pattern); - if(!hostnamel->name) { + if(parsepattern(hostnamel, arg)) { fprintf(stderr, "No memory for ACL entry, line %d\n", linenum); return(NULL); } @@ -1721,7 +1804,13 @@ struct commands commandhandlers[]={ {NULL, "socks", h_proxy, 1, 0}, {NULL, "tcppm", h_proxy, 4, 0}, {NULL, "udppm", h_proxy, 4, 0}, +#ifdef WITH_HTTPSRV {NULL, "admin", h_proxy, 1, 0}, +#endif +#ifdef WITH_HTTPSRV + {NULL, "httpsrv", h_proxy, 1, 0}, + {NULL, "http", h_http, 4, 5}, +#endif {NULL, "dnspr", h_proxy, 1, 0}, {NULL, "internal", h_internal, 2, 2}, {NULL, "external", h_external, 2, 2}, @@ -1969,16 +2058,6 @@ int readconfig(FILE * fp){ if(!strcmp((char *)argv[0], "end") && argc == 1) { break; } - else if(!strcmp((char *)argv[0], "writable") && argc == 1) { - if(!writable){ - writable = freopen(curconf, "r+", fp); - if(!writable){ - fprintf(stderr, "Unable to reopen config for writing: %s\n", curconf); - return 1; - } - } - continue; - } res = 1; for(cm = commandhandlers; cm; cm = cm->next){ @@ -2140,7 +2219,7 @@ int reload (void){ if(error) { freeconf(&conf); } - if(!writable)fclose(fp); + fclose(fp); } _3proxy_mutex_unlock(&config_mutex); return error; diff --git a/src/httpsrv.c b/src/httpsrv.c new file mode 100644 index 0000000..d3928a9 --- /dev/null +++ b/src/httpsrv.c @@ -0,0 +1,517 @@ +/* + 3proxy - HTTP server + + A small HTTP/1.0 server. The request is parsed into a struct httpreq and + handed to a handler chosen from a table by path, so the transport, request + parsing and response helpers are shared and a new endpoint is one row in + httphandlers[] plus a function. + + The handlers built today generate deterministic responses for the regression + tests: /echo describes the connection as seen by the server, which is how a + test tells which source address and port a request arrived from, and /data + produces a requested amount of output. + + Parsing here is deliberately blunt - fixed buffers, bounded reads, no shared + request parser - so that a fault in the code under test cannot be cancelled + out by the same fault in the server used to observe it. +*/ + +#include "proxy.h" + +#ifdef WITH_HTTPSRV + +#include + +#define RETURN(xxx) { param->res = xxx; goto CLEANRET; } + +#define HTTPSRV_LINE 1024 +#define HTTPSRV_BLOCK 8192 +#define HTTPSRV_MAXHDR 64 + + +/* Returns the value of a query parameter, or def when it is missing or not a + number. Values are clamped by the caller, not here. */ +/* Copies a request field, refusing anything that does not fit rather than + storing a shortened copy. strncpy would leave the result unterminated at + exactly the length that overflows - it is only safe with a zeroed struct - + and a truncated path or host is worse than a rejected one, since it would be + matched against the rules as though the client had sent the shorter string. + */ +static int hexval(int c) +{ + if(c >= '0' && c <= '9') return c - '0'; + if(c >= 'a' && c <= 'f') return c - 'a' + 10; + if(c >= 'A' && c <= 'F') return c - 'A' + 10; + return -1; +} + +/* Decodes %XX sequences. A malformed sequence, or one decoding to a NUL that + would cut the path short, is refused rather than passed on. */ +static int urldecode(char *dst, size_t size, const char *src) +{ + size_t o = 0; + + while(*src){ + int c = (unsigned char)*src++; + + if(c == '%'){ + int hi, lo; + + hi = hexval((unsigned char)src[0]); + if(hi < 0) return 1; + lo = hexval((unsigned char)src[1]); + if(lo < 0) return 1; + c = (hi << 4) | lo; + src += 2; + } + + if(!c) return 1; + if(o + 1 >= size) return 1; + dst[o++] = (char)c; + } + + dst[o] = 0; + return 0; +} + +/* Checked after decoding, because the encoded form hides both of these. */ +static int pathunsafe(const char *path) +{ + if(strstr(path, "/..")) return 1; + if(strchr(path, '\r') || strchr(path, '\n')) return 1; + return 0; +} + +static int copyfield(char *dst, size_t size, const char *src) +{ + size_t len = strlen(src); + + if(len >= size) return 1; + memcpy(dst, src, len + 1); + return 0; +} + +static long qparam(const char *query, const char *name, long def) +{ + const char *p; + size_t len; + char *end; + long val; + + if(!query || !*query) return def; + len = strlen(name); + + for(p = query; *p; ){ + if(!strncmp(p, name, len) && p[len] == '='){ + val = strtol(p + len + 1, &end, 10); + if(end == p + len + 1) return def; + return val; + } + p = strchr(p, '&'); + if(!p) break; + p++; + } + return def; +} + +static int httpsrv_send(struct httpreq *r, const char *buf, int len) +{ + return socksend(r->param, r->param->clisock, (unsigned char *)buf, len, + conf.timeouts[STRING_S]) != len; +} + +static int httpsrv_printf(struct httpreq *r, const char *fmt, ...) +{ + char buf[HTTPSRV_LINE]; + int len; + va_list ap; + + va_start(ap, fmt); + len = vsnprintf(buf, sizeof(buf), fmt, ap); + va_end(ap); + + if(len < 0) return 1; + if(len > (int)sizeof(buf) - 1) len = (int)sizeof(buf) - 1; + + return httpsrv_send(r, buf, len); +} + +/* Writes the status line and headers. A negative length asks for chunked + encoding, which is how a response of unknown or deliberately unstated size is + produced. */ +static int httpsrv_head(struct httpreq *r, int status, const char *ctype, long len) +{ + const char *text; + + switch(status){ + case 200: text = "OK"; break; + case 204: text = "No Content"; break; + case 400: text = "Bad Request"; break; + case 404: text = "Not Found"; break; + case 500: text = "Internal Server Error"; break; + case 503: text = "Service Unavailable"; break; + default: text = "Unknown"; break; + } + + if(httpsrv_printf(r, "HTTP/1.0 %d %s\r\n", status, text)) return 1; + if(httpsrv_printf(r, "Content-Type: %s\r\n", ctype)) return 1; + if(len >= 0){ + if(httpsrv_printf(r, "Content-Length: %ld\r\n", len)) return 1; + } + else if(httpsrv_printf(r, "Transfer-Encoding: chunked\r\n")) return 1; + + return httpsrv_printf(r, "Connection: close\r\n\r\n"); +} + +/* Wraps one block as a chunk, a zero length writing the terminating chunk. + Takes the client rather than a request so that anything writing a chunked + response can use it. */ +int httpchunk(struct clientparam *param, const char *buf, int len) +{ + char hdr[16]; + int hlen; + + if(len <= 0){ + return socksend(param, param->clisock, (unsigned char *)"0\r\n\r\n", 5, + conf.timeouts[STRING_S]) != 5; + } + + hlen = sprintf(hdr, "%x\r\n", len); + if(socksend(param, param->clisock, (unsigned char *)hdr, hlen, + conf.timeouts[STRING_S]) != hlen) return 1; + if(socksend(param, param->clisock, (unsigned char *)buf, len, + conf.timeouts[STRING_S]) != len) return 1; + + return socksend(param, param->clisock, (unsigned char *)"\r\n", 2, + conf.timeouts[STRING_S]) != 2; +} + +/* Fills buf with a repeating pattern carrying its own offset, so a truncated or + reordered body is visible in the output rather than looking like a short + read. */ +static void httpsrv_fill(char *buf, int len, unsigned long offset) +{ + int i; + + for(i = 0; i < len; i++){ + unsigned long pos = offset + (unsigned long)i; + + buf[i] = (pos % 64 == 63)? '\n' : (char)('0' + (int)((pos / 64) % 10)); + } +} + +static int op_echo(struct httpreq *r, const unsigned char *params) +{ + struct clientparam *param = r->param; + char addr[64]; + char body[HTTPSRV_LINE * 2]; + int len; + PROXYSOCKADDRTYPE sa; + SASIZETYPE sasize = sizeof(sa); + + memset(&sa, 0, sizeof(sa)); + if(param->srv->so._getpeername(param->sostate, param->clisock, + (struct sockaddr *)&sa, &sasize) || + !myinet_ntop(*SAFAMILY(&sa), SAADDR(&sa), addr, sizeof(addr))){ + strcpy(addr, "unknown"); + } + + len = snprintf(body, sizeof(body), + "peer.addr=%s\n" + "peer.port=%hu\n" + "method=%s\n" + "path=%s\n" + "query=%s\n" + "host=%s\n" + "content.length=%lu\n" + "glob.start=%d\n" + "glob.len=%d\n" + "glob=%.*s\n", + addr, ntohs(*SAPORT(&sa)), r->method, r->path, r->query, + r->host, r->contentlen, r->globstart, r->globlen, + r->globlen, r->path + r->globstart); + + if(len < 0) return 1; + if(len > (int)sizeof(body) - 1) len = (int)sizeof(body) - 1; + + if(httpsrv_head(r, 200, "text/plain", (long)len)) return 1; + return httpsrv_send(r, body, len); +} + +/* /data?size=N&chunked=0|1&status=NNN&block=N&delay=ms + Produces exactly N bytes of body. */ +static int op_data(struct httpreq *r, const unsigned char *params) +{ + char buf[HTTPSRV_BLOCK]; + long size, block, delay, status; + int chunked; + unsigned long sent = 0; + + size = qparam((const char *)params, "size", 0); + size = qparam(r->query, "size", size); + if(size < 0) size = 0; + block = qparam((const char *)params, "block", HTTPSRV_BLOCK); + block = qparam(r->query, "block", block); + if(block < 1 || block > HTTPSRV_BLOCK) block = HTTPSRV_BLOCK; + status = qparam((const char *)params, "status", 200); + status = qparam(r->query, "status", status); + if(status < 100 || status > 599) status = 200; + chunked = qparam(r->query, "chunked", qparam((const char *)params, "chunked", 0)) != 0; + delay = qparam(r->query, "delay", qparam((const char *)params, "delay", 0)); + + if(httpsrv_head(r, (int)status, "application/octet-stream", + chunked? -1 : size)) return 1; + + while(sent < (unsigned long)size){ + int len = (int)block; + + if((unsigned long)len > (unsigned long)size - sent) len = (int)(size - sent); + httpsrv_fill(buf, len, sent); + + if(delay > 0){ +#ifdef _WIN32 + usleep(delay); +#else + usleep(delay * 1000); +#endif + } + + if(chunked){ + if(httpchunk(r->param, buf, len)) return 1; + } + else if(httpsrv_send(r, buf, len)) return 1; + + sent += (unsigned long)len; + } + + if(chunked) return httpchunk(r->param, NULL, 0); + return 0; +} + +static int op_authrequired(struct httpreq *r) +{ + static const char body[] = "authentication required\n"; + + if(httpsrv_printf(r, "HTTP/1.0 401 Authentication Required\r\n" + "WWW-Authenticate: Basic realm=\"3proxy\"\r\n" + "Content-Type: text/plain\r\n" + "Content-Length: %d\r\n" + "Connection: close\r\n\r\n", (int)sizeof(body) - 1)) return 1; + return httpsrv_send(r, body, (int)sizeof(body) - 1); +} + +static int op_forbidden(struct httpreq *r) +{ + static const char body[] = "forbidden\n"; + + if(httpsrv_head(r, 403, "text/plain", (long)sizeof(body) - 1)) return 1; + return httpsrv_send(r, body, (int)sizeof(body) - 1); +} + +static int op_badrequest(struct httpreq *r) +{ + static const char body[] = "bad request\n"; + + if(httpsrv_head(r, 400, "text/plain", (long)sizeof(body) - 1)) return 1; + return httpsrv_send(r, body, (int)sizeof(body) - 1); +} + +static int op_notfound(struct httpreq *r) +{ + static const char body[] = "not found\n"; + + if(httpsrv_head(r, 404, "text/plain", (long)sizeof(body) - 1)) return 1; + return httpsrv_send(r, body, (int)sizeof(body) - 1); +} + +/* Operations an http line can name. The rule supplies the parameters, so the + same operation serves different content on different urls. */ +static struct httpop { + const char *name; + int (*fn)(struct httpreq *, const unsigned char *params); +} httpops[] = { + {"echo", op_echo}, + {"data", op_data}, + {"admin", op_admin}, + {"admin_counters", op_admin_counters}, + {"admin_reload", op_admin_reload}, + {"admin_services", op_admin_services}, + {NULL, NULL} +}; + +void freehttprules(struct httprule *rule) +{ + struct httprule *next; + + while(rule){ + next = rule->next; + if(rule->host.name) free(rule->host.name); + if(rule->url.name) free(rule->url.name); + if(rule->params) free(rule->params); + free(rule); + rule = next; + } +} + +int httpopbyname(const unsigned char *name) +{ + int i; + + for(i = 0; httpops[i].name; i++){ + if(!strcmp((char *)name, httpops[i].name)) return i; + } + return -1; +} + +void * httpsrvchild(struct clientparam *param) +{ + struct httpreq r; + char buf[HTTPSRV_LINE]; + char *sp, *q; + struct httprule *rule; + int i, hdrs = 0; + + memset(&r, 0, sizeof(r)); + r.param = param; + + i = sockgetlinebuf(param, CLIENT, (unsigned char *)buf, sizeof(buf) - 1, '\n', + conf.timeouts[STRING_S]); + if(i < 5) RETURN(701); + buf[i] = 0; + + sp = strchr(buf, ' '); + if(!sp) RETURN(702); + *sp = 0; + if(copyfield(r.method, sizeof(r.method), buf)) RETURN(703); + + if(!strcasecmp(r.method, "GET")) param->operation = HTTP_GET; + else if(!strcasecmp(r.method, "POST")) param->operation = HTTP_POST; + else if(!strcasecmp(r.method, "PUT")) param->operation = HTTP_PUT; + else if(!strcasecmp(r.method, "HEAD")) param->operation = HTTP_HEAD; + else param->operation = HTTP_OTHER; + + while(*++sp == ' '); + q = strchr(sp, ' '); + if(q) *q = 0; + q = sp + strcspn(sp, "\r\n"); + *q = 0; + + q = strchr(sp, '?'); + if(q){ + *q = 0; + if(copyfield(r.query, sizeof(r.query), q + 1)) RETURN(704); + } + { + char decoded[sizeof(r.path)]; + + /* Keep the raw path first so a refused request still records what + was asked for. */ + if(copyfield(r.path, sizeof(r.path), sp)) RETURN(705); + if(urldecode(decoded, sizeof(decoded), sp)) RETURN(707); + if(pathunsafe(decoded)) RETURN(708); + strcpy(r.path, decoded); + } + + while(hdrs++ < HTTPSRV_MAXHDR && + (i = sockgetlinebuf(param, CLIENT, (unsigned char *)buf, sizeof(buf) - 1, + '\n', conf.timeouts[STRING_S])) > 2){ + buf[i] = 0; + if(!strncasecmp(buf, "host:", 5)){ + sp = buf + 5; + while(isspace((unsigned char)*sp)) sp++; + sp[strcspn(sp, "\r\n")] = 0; + if(copyfield(r.host, sizeof(r.host), sp)) RETURN(706); + } + else if(!strncasecmp(buf, "authorization:", 14)){ + char creds[256]; + int clen; + + sp = buf + 14; + while(isspace((unsigned char)*sp)) sp++; + if(strncasecmp(sp, "basic", 5)) continue; + sp += 5; + while(isspace((unsigned char)*sp)) sp++; + sp[strcspn(sp, "\r\n")] = 0; + + clen = de64((unsigned char *)sp, (unsigned char *)creds, sizeof(creds) - 1); + if(clen <= 0) continue; + creds[clen] = 0; + + q = strchr(creds, ':'); + if(q){ + *q = 0; + if(param->password) free(param->password); + param->password = (unsigned char *)strdup(q + 1); + } + if(param->username) free(param->username); + param->username = (unsigned char *)strdup(creds); + } + else if(!strncasecmp(buf, "content-length:", 15)){ + sscanf(buf + 15, "%lu", &r.contentlen); + } + } + + if(r.host[0]){ + char host[sizeof(r.host)]; + char *colon; + + /* Access rules match a bare name, so drop the port the client sent. + An address in brackets keeps its colons. */ + strcpy(host, r.host); + colon = (*host == '[')? strchr(host, ']') : host; + if(colon){ + colon = strchr(colon, ':'); + if(colon) *colon = 0; + } + if(*host == '['){ + memmove(host, host + 1, strlen(host)); + colon = strchr(host, ']'); + if(colon) *colon = 0; + } + + if(param->hostname) free(param->hostname); + param->hostname = (unsigned char *)strdup(host); + } + + /* The request is answered here, so the address it was sent to is the + destination an access rule should match. Authorization skips doconnect + for this service, so naming a destination cannot start a connection. */ + param->req = param->sincl; + + i = (*param->srv->authfunc)(param); + if(i && i != 10){ + /* 4 no credentials, 5 unknown user, 6 wrong password: all of them + should let the client offer credentials again. */ + if(i >= 4 && i <= 6) op_authrequired(&r); + else op_forbidden(&r); + RETURN(i); + } + + for(rule = param->srv->httprules; rule; rule = rule->next){ + if(patternmatch(&rule->host, (unsigned char *)r.host) && + patternmatchpos(&rule->url, (unsigned char *)r.path, + &r.globstart, &r.globlen)){ + httpops[rule->op].fn(&r, rule->params); + RETURN(0); + } + } + op_notfound(&r); + RETURN(404); + +CLEANRET: + if(param->res >= 700 && param->res < 800) op_badrequest(&r); + /* Log the request the way the proxy does: the parameters decide what was + served, so a bare path is not enough to explain a response. */ + { + char logbuf[sizeof(r.method) + sizeof(r.host) + sizeof(r.path) + + sizeof(r.query) + 8]; + + sprintf(logbuf, "%s %s %s%s%s", r.method[0]? r.method : "-", + r.host[0]? r.host : "-", r.path, + r.query[0]? "?" : "", r.query); + dolog(param, (unsigned char *)logbuf); + } + return NULL; +} + +#endif diff --git a/src/plugins.c b/src/plugins.c index 208c1ae..e414059 100644 --- a/src/plugins.c +++ b/src/plugins.c @@ -15,7 +15,9 @@ void decodeurl(unsigned char *s, int allowcr); int parsestr (unsigned char *str, unsigned char **argm, int nitems, unsigned char ** buff, int *inbuf, int *bufsize); struct ace * make_ace (int argc, unsigned char ** argv); extern char * proxy_stringtable[]; +#ifdef WITH_HTTPSRV extern char * admin_stringtable[]; +#endif extern struct schedule * schedule; int start_proxy_thread(struct child * chp); @@ -59,7 +61,6 @@ struct symbol symbols[] = { {symbols+34, "socks", (void *) sockschild}, {symbols+35, "tcppm", (void *) tcppmchild}, {symbols+36, "udppm", (void *) udppmchild}, - {symbols+37, "admin", (void *) adminchild}, {symbols+38, "ftppr", (void *) ftpprchild}, {symbols+39, "smtpp", (void *) smtppchild}, {symbols+40, "auto", (void *) smtppchild}, @@ -121,7 +122,11 @@ struct pluginlink pluginlink = { proxy_stringtable, &schedule, freeacl, +#ifdef WITH_HTTPSRV admin_stringtable, +#else + NULL, +#endif &childdef, start_proxy_thread, freeparam, diff --git a/src/proxy.h b/src/proxy.h index 4841c93..6bad474 100644 --- a/src/proxy.h +++ b/src/proxy.h @@ -353,6 +353,9 @@ int readconfig(FILE * fp); void initcommands(void); int connectwithpoll(struct clientparam *param, SOCKET sock, struct sockaddr *sa, SASIZETYPE size, int to); int bindwithrange(struct clientparam *param, SOCKET sock, PROXYSOCKADDRTYPE *sa, uint32_t range); +int parsepattern(struct hostname *h, unsigned char *arg); +int patternmatch(const struct hostname *h, const unsigned char *str); +int patternmatchpos(const struct hostname *h, const unsigned char *str, int *start, int *len); void applyportranges(struct clientparam * param, struct ace * acentry); @@ -373,7 +376,18 @@ void * sockschild(struct clientparam * param); void * tcppmchild(struct clientparam * param); void * autochild(struct clientparam * param); void * udppmchild(struct clientparam * param); -void * adminchild(struct clientparam * param); +#ifdef WITH_HTTPSRV +int op_admin(struct httpreq *r, const unsigned char *params); +int op_admin_counters(struct httpreq *r, const unsigned char *params); +int op_admin_reload(struct httpreq *r, const unsigned char *params); +int op_admin_services(struct httpreq *r, const unsigned char *params); +#endif +#ifdef WITH_HTTPSRV +void * httpsrvchild(struct clientparam * param); +int httpopbyname(const unsigned char *name); +int httpchunk(struct clientparam *param, const char *buf, int len); +void freehttprules(struct httprule *rule); +#endif void * ftpprchild(struct clientparam * param); void * tlsprchild(struct clientparam * param); /* Child functions return the child to redirect the request to, or NULL if diff --git a/src/proxymain.c b/src/proxymain.c index a09bcaa..2785055 100644 --- a/src/proxymain.c +++ b/src/proxymain.c @@ -413,6 +413,15 @@ int MODULEMAINFUNC (int argc, char** argv){ } #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){ @@ -1335,6 +1344,9 @@ void srvfree(struct srvparam * srv){ } 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); diff --git a/src/structures.h b/src/structures.h index aab1371..b5ca3d4 100644 --- a/src/structures.h +++ b/src/structures.h @@ -216,6 +216,7 @@ typedef enum { S_AUTO, S_TLSPR, S_IMAPP, + S_HTTPSRV, S_ZOMBIE }PROXYSERVICE; @@ -356,6 +357,28 @@ struct hostname { int matchtype; }; +/* A request handed to an http operation. */ +struct httpreq { + struct clientparam *param; + char method[16]; + char path[256]; + char query[512]; + char host[256]; + unsigned long contentlen; + int globstart, globlen; +}; + +/* One "http" line: which host and url it answers for, which operation serves + it and the parameters that operation takes. Patterns use the same syntax and + the same matcher as host lists in access rules. */ +struct httprule { + struct httprule *next; + struct hostname host; + struct hostname url; + int op; + unsigned char *params; +}; + struct ace { struct ace *next; int action; @@ -583,6 +606,9 @@ struct srvparam { struct auth *authenticate; struct pollfd * srvfds; struct ace *acl; +#ifdef WITH_HTTPSRV + struct httprule *httprules; +#endif struct auth *authfuncs; struct filter *filter; unsigned char * logformat; @@ -702,6 +728,9 @@ struct extparam { _3proxy_sem_t threadinit; int *timeouts; struct ace * acl; +#ifdef WITH_HTTPSRV + struct httprule *httprules; +#endif char * conffile; struct bandlim * bandlimiter, *bandlimiterout; struct connlim * connlimiter; diff --git a/src/webadmin.c b/src/webadmin.c index 77277c1..97baf79 100644 --- a/src/webadmin.c +++ b/src/webadmin.c @@ -8,11 +8,12 @@ #include "proxy.h" +#ifdef WITH_HTTPSRV + #define RETURN(xxx) { param->res = xxx; goto CLEANRET; } #define LINESIZE 65536 -extern FILE *writable; FILE * confopen(); extern void decodeurl(unsigned char *s, int filter); @@ -211,8 +212,7 @@ char * admin_stringtable[]={ "         \r\n" "Counters
\r\n" "Reload
\r\n" - "Running Services
\r\n" - "Config\r\n" + "Running Services\r\n" "" "

%s %s configuration

", @@ -367,92 +367,62 @@ static int printiplist(char *buf, int bufsize, struct iplist* ipl, char * delim) return printed; } -void * adminchild(struct clientparam* param) { - int i, res; - char * buf; - char username[256]; - char *sb; - char *req = NULL; - struct printparam pp; - unsigned contentlen = 0; - int isform = 0; - int limited = 0; +/* The admin pages are http operations: the service, request parsing and + authorization belong to httpsrv, and what is left here is the page itself. + A star in the url carries the selector the pages used to read out of the + path, so /C with a star gives D2 or S2 to disable or enable a counter. */ +static char * admin_open(struct printparam *pp, struct clientparam *param) +{ + char *buf; - limited =param->srv->s_option; - pp.inbuf = 0; - pp.cp = param; + pp->inbuf = 0; + pp->cp = param; + + buf = malloc(LINESIZE); + if(!buf) return NULL; + + sprintf(buf, ok, conf.stringtable?(char *)conf.stringtable[2]:"3proxy", + conf.stringtable?(char *)conf.stringtable[2]:"3[APA3A] tiny proxy", + conf.stringtable?(char *)conf.stringtable[3]:""); + printstr(pp, buf); + return buf; +} + +static void admin_close(struct printparam *pp, char *buf) +{ + printstr(pp, tail); + printstr(pp, NULL); + if(buf) free(buf); +} + +int op_admin(struct httpreq *r, const unsigned char *params) +{ + struct printparam pp; + char *buf; + + buf = admin_open(&pp, r->param); + if(!buf) return 1; + printstr(&pp, (char *)conf.stringtable[WEBBANNERS]); + admin_close(&pp, buf); + return 0; +} + +int op_admin_counters(struct httpreq *r, const unsigned char *params) +{ + struct clientparam *param = r->param; + struct printparam pp; + char *buf; + const char *sel; + int limited; + + limited = param->srv->s_option; + /* In limited mode a counter may be looked at but not switched. */ + sel = limited? "" : r->path + r->globstart; + + buf = admin_open(&pp, param); + if(!buf) return 1; - buf = malloc(LINESIZE); - if(!buf) {RETURN(555);} - i = sockgetlinebuf(param, CLIENT, (unsigned char *)buf, LINESIZE - 1, '\n', conf.timeouts[STRING_S]); - if(i<5 || ((buf[0]!='G' || buf[1]!='E' || buf[2]!='T' || buf[3]!=' ' || buf[4]!='/') && - (buf[0]!='P' || buf[1]!='O' || buf[2]!='S' || buf[3]!='T' || buf[4]!=' ' || buf[5]!='/'))) - { - RETURN(701); - } - buf[i] = 0; - sb = strchr(buf+5, ' '); - if(!sb){ - RETURN(702); - } - *sb = 0; - req = strdup(buf + ((*buf == 'P')? 6 : 5)); - while((i = sockgetlinebuf(param, CLIENT, (unsigned char *)buf, LINESIZE - 1, '\n', conf.timeouts[STRING_S])) > 2){ - buf[i] = 0; - if(i > 19 && (!strncasecmp(buf, "authorization", 13))){ - sb = strchr(buf, ':'); - if(!sb)continue; - ++sb; - while(isspace(*sb))sb++; - if(!*sb || strncasecmp(sb, "basic", 5)){ - continue; - } - sb+=5; - while(isspace(*sb))sb++; - i = de64((unsigned char *)sb, (unsigned char *)username, 255); - if(i<=0)continue; - username[i] = 0; - sb = strchr((char *)username, ':'); - if(sb){ - *sb = 0; - if(param->password)free(param->password); - param->password = (unsigned char *)strdup(sb+1); - } - if(param->username) free(param->username); - param->username = (unsigned char *)strdup(username); - continue; - } - else if(i > 15 && (!strncasecmp(buf, "content-length:", 15))){ - sb = buf + 15; - while(isspace(*sb))sb++; - sscanf(sb, "%u", &contentlen); - if(contentlen > LINESIZE*1024) contentlen = 0; - } - else if(i > 13 && (!strncasecmp(buf, "content-type:", 13))){ - sb = buf + 13; - while(isspace(*sb))sb++; - if(!strncasecmp(sb, "x-www-form-urlencoded", 21)) isform = 1; - } - } - param->operation = ADMIN; - if(isform && contentlen) { - printstr(&pp, "HTTP/1.0 100 Continue\r\n\r\n"); - stdpr(&pp, NULL, 0); - } - res = (*param->srv->authfunc)(param); - if(res && res != 10) { - printstr(&pp, authreq); - RETURN(res); - } - if(limited || param->redirected){ - if(*req == 'C') req[1] = 0; - else *req = 0; - } - sprintf(buf, ok, conf.stringtable?(char *)conf.stringtable[2]:"3proxy", conf.stringtable?(char *)conf.stringtable[2]:"3[APA3A] tiny proxy", conf.stringtable?(char *)conf.stringtable[3]:""); - if(*req != 'S') printstr(&pp, buf); - switch(*req){ - case 'C': printstr(&pp, counters); { struct trafcount *cp; @@ -463,8 +433,8 @@ void * adminchild(struct clientparam* param) { if(cp->ace && (limited || param->redirected)){ if(!ACLmatches(cp->ace, param))continue; } - if(req[1] == 'S' && atoi(req+2) == num) cp->disabled=0; - if(req[1] == 'D' && atoi(req+2) == num) cp->disabled=1; + if(sel[0] == 'S' && atoi(sel+1) == num) cp->disabled=0; + if(sel[0] == 'D' && atoi(sel+1) == num) cp->disabled=1; inbuf += sprintf(buf, "%s", cp->ace?aceaction(cp->ace->action):"-"); if(cp->number || cp->comment) inbuf += sprintf(buf+inbuf, "%d/%s" , cp->number, @@ -535,85 +505,55 @@ void * adminchild(struct clientparam* param) { } printstr(&pp, counterstail); - break; - - case 'R': + + admin_close(&pp, buf); + return 0; +} + +int op_admin_reload(struct httpreq *r, const unsigned char *params) +{ + struct printparam pp; + char *buf; + + buf = admin_open(&pp, r->param); + if(!buf) return 1; + + if(r->param->srv->s_option) printstr(&pp, (char *)conf.stringtable[WEBBANNERS]); + else { conf.needreload = 1; printstr(&pp, "

Reload scheduled

"); - break; - case 'S': - { - if(req[1] == 'X'){ - printstr(&pp, style); - break; - } - printstr(&pp, xml); - printval(conf.services, TYPE_SERVER, 0, &pp); - printstr(&pp, postxml); - } - break; - case 'F': - { - FILE *fp; - char buf[256]; + } - fp = confopen(); - if(!fp){ - printstr(&pp, "

Failed to open config file

"); - break; - } - printstr(&pp, "

Please be careful editing config file remotely

"); - printstr(&pp, "

"); - break; - } - case 'U': - { - unsigned l=0; - int error = 0; - - if(!writable || !contentlen || fseek(writable, 0, 0)){ - error = 1; - } - while(l < contentlen && (i = sockgetlinebuf(param, CLIENT, (unsigned char *)buf, (contentlen - l) > LINESIZE - 1?LINESIZE - 1:contentlen - l, '+', conf.timeouts[STRING_S])) > 0){ - if((unsigned)i > (contentlen - l)) i = (contentlen - l); - if(!l){ - if(i<9 || strncasecmp(buf, "conffile=", 9)) error = 1; - } - if(!error){ - buf[i] = 0; - decodeurl((unsigned char *)buf, 1); - fprintf(writable, "%s", l? buf : buf + 9); - } - l += i; - } - if(writable && !error){ - fflush(writable); -#ifndef _WINCE - if(ftruncate(fileno(writable), ftell(writable))){} -#endif - } - printstr(&pp, error? "

Config file is not writable

Make sure you have \"writable\" command in configuration file": - "

Configuration updated

"); - - } - break; - default: - printstr(&pp, (char *)conf.stringtable[WEBBANNERS]); - break; - } - if(*req != 'S') printstr(&pp, tail); - -CLEANRET: - - - printstr(&pp, NULL); - if(buf) free(buf); - dolog(param, (unsigned char *)req); - if(req)free(req); - return (NULL); + admin_close(&pp, buf); + return 0; } + +int op_admin_services(struct httpreq *r, const unsigned char *params) +{ + struct clientparam *param = r->param; + struct printparam pp; + char *buf; + const char *sel; + + if(param->srv->s_option) return op_admin(r, params); + + sel = r->path + r->globstart; + + /* This page is xml, so it carries its own headers instead of the html + wrapper the other pages share. */ + pp.inbuf = 0; + pp.cp = param; + buf = NULL; + + if(sel[0] == 'X') printstr(&pp, style); + else { + printstr(&pp, xml); + printval(conf.services, TYPE_SERVER, 0, &pp); + printstr(&pp, postxml); + } + + printstr(&pp, NULL); + return 0; +} + +#endif