implement small http server for admin and testing

This commit is contained in:
Vladimir Dubrovin 2026-08-25 17:21:33 +03:00
parent a3b40e6176
commit 3526759e59
22 changed files with 950 additions and 254 deletions

View File

@ -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_WSAPOLL "Use WSAPoll instead of select() (Windows only)" ON)
option(3PROXY_USE_NETFILTER "Enable Linux netfilter support (Linux 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_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) if(NOT WIN32 AND NOT APPLE)
option(3PROXY_STATIC_LINK "Statically link libraries using -Wl,-Bstatic (Linux/Unix only)" OFF) option(3PROXY_STATIC_LINK "Statically link libraries using -Wl,-Bstatic (Linux/Unix only)" OFF)
@ -185,6 +186,10 @@ elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux")
add_compile_definitions(WITH_NETFILTER) add_compile_definitions(WITH_NETFILTER)
endif() endif()
if(3PROXY_USE_HTTPSRV)
add_compile_definitions(WITH_HTTPSRV)
endif()
if(3PROXY_USE_UNIX_SOCKETS) if(3PROXY_USE_UNIX_SOCKETS)
add_compile_definitions(WITH_UN) add_compile_definitions(WITH_UN)
endif() endif()
@ -403,6 +408,7 @@ add_library(srv_modules OBJECT
src/auto.c src/auto.c
src/socks.c src/socks.c
src/webadmin.c src/webadmin.c
src/httpsrv.c
src/dnspr.c src/dnspr.c
) )

View File

@ -24,6 +24,11 @@ LDFLAGS += $(EXTRA_LDFLAGS)
# -lpthreads may be reuiured on some platforms instead of -pthreads # -lpthreads may be reuiured on some platforms instead of -pthreads
# -ldl or -lld may be required for some platforms # -ldl or -lld may be required for some platforms
DCFLAGS ?= -fPIC DCFLAGS ?= -fPIC
HTTPSRV ?= true
ifeq ($(HTTPSRV),true)
CFLAGS += -DWITH_HTTPSRV
HTTPSRV_OBJS = srvhttpsrv$(OBJSUFFICS) srvwebadmin$(OBJSUFFICS)
endif
DLFLAGS ?= -shared DLFLAGS ?= -shared
DLSUFFICS = .so DLSUFFICS = .so
LIBS ?= LIBS ?=

View File

@ -25,6 +25,13 @@ LDFLAGS += -fno-strict-aliasing -pthread
# makefile, including the += above and the STATIC/LIBSTATIC handling below. # makefile, including the += above and the STATIC/LIBSTATIC handling below.
CFLAGS += $(EXTRA_CFLAGS) CFLAGS += $(EXTRA_CFLAGS)
LDFLAGS += $(EXTRA_LDFLAGS) 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 DLFLAGS ?= -shared
DLSUFFICS = .ld.so DLSUFFICS = .ld.so
# -lpthreads may be reuqired on some platforms instead of -pthreads # -lpthreads may be reuqired on some platforms instead of -pthreads

View File

@ -14,6 +14,11 @@ COUT = -o ./
LN = $(CC) LN = $(CC)
LDFLAGS = -xO3 LDFLAGS = -xO3
DCFLAGS = -fPIC DCFLAGS = -fPIC
HTTPSRV ?= true
ifeq ($(HTTPSRV),true)
CFLAGS += -DWITH_HTTPSRV
HTTPSRV_OBJS = srvhttpsrv$(OBJSUFFICS) srvwebadmin$(OBJSUFFICS)
endif
DLFLAGS = -shared DLFLAGS = -shared
DLSUFFICS = .ld.so DLSUFFICS = .ld.so
LIBS = -lpthread -lsocket -lnsl -lresolv -ldl LIBS = -lpthread -lsocket -lnsl -lresolv -ldl

View File

@ -18,7 +18,7 @@ SSL_LIBS = wolfssl.lib
SSL_DEFS = /D "WITH_SSL" SSL_DEFS = /D "WITH_SSL"
SSL_LIBS = libcrypto.lib libssl.lib SSL_LIBS = libcrypto.lib libssl.lib
!ENDIF !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 COUT = /Fo
LN = link LN = link
LDFLAGS = /nologo /subsystem:console /incremental:no LDFLAGS = /nologo /subsystem:console /incremental:no
@ -40,6 +40,7 @@ MAKEFILE = Makefile.msvc
PLUGINS = utf8tocp1251 WindowsAuthentication TrafficPlugin StringsPlugin FilePlugin PLUGINS = utf8tocp1251 WindowsAuthentication TrafficPlugin StringsPlugin FilePlugin
SSL_OBJS = ssllib$(OBJSUFFICS) ssl$(OBJSUFFICS) SSL_OBJS = ssllib$(OBJSUFFICS) ssl$(OBJSUFFICS)
PCRE_OBJS = pcre$(OBJSUFFICS) PCRE_OBJS = pcre$(OBJSUFFICS)
HTTPSRV_OBJS = srvhttpsrv$(OBJSUFFICS) srvwebadmin$(OBJSUFFICS)
VERFILE = 3proxy.res $(VERFILE) VERFILE = 3proxy.res $(VERFILE)
VERSIONDEP = 3proxy.res $(VERSIONDEP) VERSIONDEP = 3proxy.res $(VERSIONDEP)
AFTERCLEAN = if exist src\*.res (del src\*.res) && if exist src\*.err (del src\*.err) AFTERCLEAN = if exist src\*.res (del src\*.res) && if exist src\*.err (del src\*.err)

View File

@ -26,6 +26,11 @@ LDFLAGS += $(EXTRA_LDFLAGS)
# -lpthreads may be reuqired on some platforms instead of -pthreads # -lpthreads may be reuqired on some platforms instead of -pthreads
# -ldl or -lld may be required for some platforms # -ldl or -lld may be required for some platforms
DCFLAGS ?= -fPIC DCFLAGS ?= -fPIC
HTTPSRV ?= true
ifeq ($(HTTPSRV),true)
CFLAGS += -DWITH_HTTPSRV
HTTPSRV_OBJS = srvhttpsrv$(OBJSUFFICS) srvwebadmin$(OBJSUFFICS)
endif
DLFLAGS ?= -shared DLFLAGS ?= -shared
DLSUFFICS ?= .ld.so DLSUFFICS ?= .ld.so
LIBS ?= LIBS ?=

View File

@ -8,7 +8,7 @@ BUILDDIR = ../bin/
PREFIX = 3proxy_ PREFIX = 3proxy_
CRYPT_PREFIX = 3proxy_ CRYPT_PREFIX = 3proxy_
CC = cl 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 COUT = /Fo
LN = link LN = link
LDFLAGS = /nologo /subsystem:console /incremental:no LDFLAGS = /nologo /subsystem:console /incremental:no
@ -21,6 +21,7 @@ LIBEXT = .lib
LNOUT = /out: LNOUT = /out:
EXESUFFICS = .exe EXESUFFICS = .exe
OBJSUFFICS = .obj OBJSUFFICS = .obj
HTTPSRV_OBJS = srvhttpsrv$(OBJSUFFICS) srvwebadmin$(OBJSUFFICS)
DEFINEOPTION = /D DEFINEOPTION = /D
COMPFILES = *.pch *.idb *.err COMPFILES = *.pch *.idb *.err
REMOVECOMMAND = del 2>NUL >NUL REMOVECOMMAND = del 2>NUL >NUL

View File

@ -20,6 +20,11 @@ LDFLAGS += -fno-strict-aliasing -mthreads
# makefile, including the += above and the STATIC/LIBSTATIC handling below. # makefile, including the += above and the STATIC/LIBSTATIC handling below.
CFLAGS += $(EXTRA_CFLAGS) CFLAGS += $(EXTRA_CFLAGS)
LDFLAGS += $(EXTRA_LDFLAGS) LDFLAGS += $(EXTRA_LDFLAGS)
HTTPSRV ?= true
ifeq ($(HTTPSRV),true)
CFLAGS += -DWITH_HTTPSRV
HTTPSRV_OBJS = srvhttpsrv$(OBJSUFFICS) srvwebadmin$(OBJSUFFICS)
endif
DLFLAGS ?= -shared DLFLAGS ?= -shared
DLSUFFICS = .dll DLSUFFICS = .dll
LIBS += -lws2_32 -lodbc32 -ladvapi32 -luser32 -lbcrypt LIBS += -lws2_32 -lodbc32 -ladvapi32 -luser32 -lbcrypt

View File

@ -282,16 +282,7 @@ proxy on a client with FTP proxy support. Username format is one of
.BR config .BR config
\fI<path>\fR \fI<path>\fR
.br .br
Path to configuration file to use on 3proxy restart or to save configuration. Path to configuration file to use on 3proxy restart.
.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.
.br .br
.B end .B end

View File

@ -24,7 +24,6 @@ Content-type: text/html; charset=utf-8\n
<A HREF='/C'>Счетчики</A><br><br>\n <A HREF='/C'>Счетчики</A><br><br>\n
<A HREF='/R'>Перезагрузка конфигурации сервера</A><br><br>\n <A HREF='/R'>Перезагрузка конфигурации сервера</A><br><br>\n
<A HREF='/S'>Запущенные сервисы</A><br><br>\n <A HREF='/S'>Запущенные сервисы</A><br><br>\n
<A HREF='/F'>Настройка сервера</A>\n
</td><td> </td><td>
<h2>%s %s Конфигурация</h2> <h2>%s %s Конфигурация</h2>
[end] [end]

View File

@ -28,7 +28,6 @@ void pcre_install(void);
FILE * confopen(); FILE * confopen();
extern unsigned char *strings[]; extern unsigned char *strings[];
extern FILE *writable;
extern struct counter_header cheader; extern struct counter_header cheader;
extern struct counter_record crecord; extern struct counter_record crecord;
@ -537,7 +536,7 @@ int WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int
conf.version++; conf.version++;
if(res) RETURN(res); if(res) RETURN(res);
if(!writable){fclose(fp); fp = NULL;} fclose(fp); fp = NULL;
#ifdef _WIN32 #ifdef _WIN32

View File

@ -116,6 +116,9 @@ srvsocks$(OBJSUFFICS): socks.c proxy.h structures.h
srvwebadmin$(OBJSUFFICS): webadmin.c proxy.h structures.h srvwebadmin$(OBJSUFFICS): webadmin.c proxy.h structures.h
$(CC) $(COUT)srvwebadmin$(OBJSUFFICS) $(CFLAGS) webadmin.c $(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 srvudppm$(OBJSUFFICS): udppm.c proxy.h structures.h
$(CC) $(COUT)srvudppm$(OBJSUFFICS) $(CFLAGS) udppm.c $(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 pcre$(OBJSUFFICS): pcre.c structures.h
$(CC) $(COUT)pcre$(OBJSUFFICS) $(CFLAGS) $(DEFINEOPTION)WITH_PCRE pcre.c $(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) $(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) srvwebadmin$(OBJSUFFICS) srvdnspr$(OBJSUFFICS) plugins$(OBJSUFFICS) mdhash$(OBJSUFFICS) $(SSL_OBJS) $(PCRE_OBJS) $(COMPATLIBS) $(LIBS) $(PCRE_LIBS) $(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)

View File

@ -62,36 +62,7 @@ int ACLmatches(struct ace* acentry, struct clientparam * param){
} }
while(i > 5 && param->hostname[i-1] == '.') param->hostname[i-1] = 0; while(i > 5 && param->hostname[i-1] == '.') param->hostname[i-1] = 0;
for(hstentry = acentry->dstnames; hstentry; hstentry = hstentry->next){ for(hstentry = acentry->dstnames; hstentry; hstentry = hstentry->next){
int lname, lhost; if(patternmatch(hstentry, param->hostname)) match = 1;
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(match) break; if(match) break;
} }
} }

View File

@ -18,7 +18,13 @@ int alwaysauth(struct clientparam * param){
if(conf.connlimiter && !param->connlim && startconnlims(param)) return 10; 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); res = doconnect(param);
#endif
if(!res){ if(!res){
if(conf.bandlimfunc && (conf.bandlimiter||conf.bandlimiterout)){ if(conf.bandlimfunc && (conf.bandlimiter||conf.bandlimiterout)){
_3proxy_mutex_lock(&bandlim_mutex); _3proxy_mutex_lock(&bandlim_mutex);

View File

@ -807,6 +807,102 @@ int bindwithrange(struct clientparam *param, SOCKET sock, PROXYSOCKADDRTYPE *sa,
return -1; 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) { int scanaddr(const unsigned char *s, uint32_t * ip, uint32_t * mask) {
unsigned d1, d2, d3, d4, m; unsigned d1, d2, d3, d4, m;
int res; int res;

View File

@ -7,6 +7,10 @@
*/ */
#include "proxy.h" #include "proxy.h"
#ifdef WITH_HTTPSRV
static int addhttprule(char *host, char *url, char *op, char *params);
#endif
#include "mdhash.h" #include "mdhash.h"
#ifdef WITH_SSL #ifdef WITH_SSL
void ssl_install(void); void ssl_install(void);
@ -35,7 +39,6 @@ _3proxy_mutex_t config_mutex;
int haveerror = 0; int haveerror = 0;
int linenum = 0; int linenum = 0;
FILE *writable;
struct counter_header cheader = {"3CF", (time_t)0}; struct counter_header cheader = {"3CF", (time_t)0};
struct counter_record crecord; struct counter_record crecord;
@ -60,10 +63,6 @@ FILE * confopen(){
curconf += strlen(chrootp); curconf += strlen(chrootp);
} }
#endif #endif
if(writable) {
rewind(writable);
return writable;
}
return fopen(curconf, "r"); return fopen(curconf, "r");
} }
@ -253,12 +252,32 @@ static int h_proxy(int argc, unsigned char ** argv){
childdef.service = S_UDPPM; childdef.service = S_UDPPM;
childdef.helpmessage = " -s single packet UDP service for request/reply (DNS-like) services\n"; childdef.helpmessage = " -s single packet UDP service for request/reply (DNS-like) services\n";
} }
#ifdef WITH_HTTPSRV
else if(!strcmp((char *)argv[0], "admin")) { 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.port = 80;
childdef.isudp = 0; 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")) { else if(!strcmp((char *)argv[0], "dnspr")) {
childdef.pf = dnsprchild; childdef.pf = dnsprchild;
childdef.port = 53; childdef.port = 53;
@ -765,7 +784,6 @@ struct redirdesc redirs[] = {
{R_SOCKS5P, "socks5+", sockschild}, {R_SOCKS5P, "socks5+", sockschild},
{R_SOCKS4B, "socks4b", sockschild}, {R_SOCKS4B, "socks4b", sockschild},
{R_SOCKS5B, "socks5b", sockschild}, {R_SOCKS5B, "socks5b", sockschild},
{R_ADMIN, "admin", adminchild},
{R_EXTIP, "extip", NULL}, {R_EXTIP, "extip", NULL},
{R_EXTPORT, "extport", NULL}, {R_EXTPORT, "extport", NULL},
{R_INTPORT, "intport", NULL}, {R_INTPORT, "intport", NULL},
@ -775,19 +793,53 @@ struct redirdesc redirs[] = {
{0, NULL, NULL} {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. */ /* Parses an inclusive FIRST-LAST local port range into first | last << 16. */
static int parserange(unsigned char *arg, uint32_t *range) static int parserange(unsigned char *arg, uint32_t *range)
{ {
char *end; char *end;
unsigned long first, last; unsigned long first, last;
errno = 0;
first = strtoul((char *)arg, &end, 10); 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; arg = (unsigned char *)end + 1;
last = strtoul(end + 1, &end, 10); last = strtoul((char *)arg, &end, 10);
if(errno || *end || !last || last > 65535 || last < first) return 1; if(end == (char *)arg || *end || !last || last > 65535 || last < first) return 1;
*range = (uint32_t)first | ((uint32_t)last << 16); *range = (uint32_t)first | ((uint32_t)last << 16);
return 0; return 0;
@ -906,6 +958,50 @@ static int h_parent(int argc, unsigned char **argv){
} }
#ifdef WITH_HTTPSRV
/* http <hostname> <url> <operation> [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){ static int h_nolog(int argc, unsigned char **argv){
struct ace *acl = NULL; struct ace *acl = NULL;
@ -1044,20 +1140,7 @@ struct ace * make_ace (int argc, unsigned char ** argv){
return(NULL); return(NULL);
} }
memset(hostnamel, 0, sizeof(struct hostname)); memset(hostnamel, 0, sizeof(struct hostname));
hostnamel->matchtype = 3; if(parsepattern(hostnamel, arg)) {
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) {
fprintf(stderr, "No memory for ACL entry, line %d\n", linenum); fprintf(stderr, "No memory for ACL entry, line %d\n", linenum);
return(NULL); return(NULL);
} }
@ -1721,7 +1804,13 @@ struct commands commandhandlers[]={
{NULL, "socks", h_proxy, 1, 0}, {NULL, "socks", h_proxy, 1, 0},
{NULL, "tcppm", h_proxy, 4, 0}, {NULL, "tcppm", h_proxy, 4, 0},
{NULL, "udppm", h_proxy, 4, 0}, {NULL, "udppm", h_proxy, 4, 0},
#ifdef WITH_HTTPSRV
{NULL, "admin", h_proxy, 1, 0}, {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, "dnspr", h_proxy, 1, 0},
{NULL, "internal", h_internal, 2, 2}, {NULL, "internal", h_internal, 2, 2},
{NULL, "external", h_external, 2, 2}, {NULL, "external", h_external, 2, 2},
@ -1969,16 +2058,6 @@ int readconfig(FILE * fp){
if(!strcmp((char *)argv[0], "end") && argc == 1) { if(!strcmp((char *)argv[0], "end") && argc == 1) {
break; 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; res = 1;
for(cm = commandhandlers; cm; cm = cm->next){ for(cm = commandhandlers; cm; cm = cm->next){
@ -2140,7 +2219,7 @@ int reload (void){
if(error) { if(error) {
freeconf(&conf); freeconf(&conf);
} }
if(!writable)fclose(fp); fclose(fp);
} }
_3proxy_mutex_unlock(&config_mutex); _3proxy_mutex_unlock(&config_mutex);
return error; return error;

517
src/httpsrv.c Normal file
View File

@ -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 <stdarg.h>
#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

View File

@ -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); 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); struct ace * make_ace (int argc, unsigned char ** argv);
extern char * proxy_stringtable[]; extern char * proxy_stringtable[];
#ifdef WITH_HTTPSRV
extern char * admin_stringtable[]; extern char * admin_stringtable[];
#endif
extern struct schedule * schedule; extern struct schedule * schedule;
int start_proxy_thread(struct child * chp); int start_proxy_thread(struct child * chp);
@ -59,7 +61,6 @@ struct symbol symbols[] = {
{symbols+34, "socks", (void *) sockschild}, {symbols+34, "socks", (void *) sockschild},
{symbols+35, "tcppm", (void *) tcppmchild}, {symbols+35, "tcppm", (void *) tcppmchild},
{symbols+36, "udppm", (void *) udppmchild}, {symbols+36, "udppm", (void *) udppmchild},
{symbols+37, "admin", (void *) adminchild},
{symbols+38, "ftppr", (void *) ftpprchild}, {symbols+38, "ftppr", (void *) ftpprchild},
{symbols+39, "smtpp", (void *) smtppchild}, {symbols+39, "smtpp", (void *) smtppchild},
{symbols+40, "auto", (void *) smtppchild}, {symbols+40, "auto", (void *) smtppchild},
@ -121,7 +122,11 @@ struct pluginlink pluginlink = {
proxy_stringtable, proxy_stringtable,
&schedule, &schedule,
freeacl, freeacl,
#ifdef WITH_HTTPSRV
admin_stringtable, admin_stringtable,
#else
NULL,
#endif
&childdef, &childdef,
start_proxy_thread, start_proxy_thread,
freeparam, freeparam,

View File

@ -353,6 +353,9 @@ int readconfig(FILE * fp);
void initcommands(void); void initcommands(void);
int connectwithpoll(struct clientparam *param, SOCKET sock, struct sockaddr *sa, SASIZETYPE size, int to); 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 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); void applyportranges(struct clientparam * param, struct ace * acentry);
@ -373,7 +376,18 @@ void * sockschild(struct clientparam * param);
void * tcppmchild(struct clientparam * param); void * tcppmchild(struct clientparam * param);
void * autochild(struct clientparam * param); void * autochild(struct clientparam * param);
void * udppmchild(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 * ftpprchild(struct clientparam * param);
void * tlsprchild(struct clientparam * param); void * tlsprchild(struct clientparam * param);
/* Child functions return the child to redirect the request to, or NULL if /* Child functions return the child to redirect the request to, or NULL if

View File

@ -414,6 +414,15 @@ int MODULEMAINFUNC (int argc, char** argv){
#endif #endif
srv.service = defparam.service = childdef.service; 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 #ifndef STDMAIN
if(conf.acl){ if(conf.acl){
srv.acl = copyacl(conf.acl); srv.acl = copyacl(conf.acl);
@ -1335,6 +1344,9 @@ void srvfree(struct srvparam * srv){
} }
if(srv->acl)freeacl(srv->acl); if(srv->acl)freeacl(srv->acl);
#ifdef WITH_HTTPSRV
if(srv->httprules)freehttprules(srv->httprules);
#endif
if(srv->authfuncs)freeauth(srv->authfuncs); if(srv->authfuncs)freeauth(srv->authfuncs);
#endif #endif
_3proxy_mutex_destroy(&srv->counter_mutex); _3proxy_mutex_destroy(&srv->counter_mutex);

View File

@ -216,6 +216,7 @@ typedef enum {
S_AUTO, S_AUTO,
S_TLSPR, S_TLSPR,
S_IMAPP, S_IMAPP,
S_HTTPSRV,
S_ZOMBIE S_ZOMBIE
}PROXYSERVICE; }PROXYSERVICE;
@ -356,6 +357,28 @@ struct hostname {
int matchtype; 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 {
struct ace *next; struct ace *next;
int action; int action;
@ -583,6 +606,9 @@ struct srvparam {
struct auth *authenticate; struct auth *authenticate;
struct pollfd * srvfds; struct pollfd * srvfds;
struct ace *acl; struct ace *acl;
#ifdef WITH_HTTPSRV
struct httprule *httprules;
#endif
struct auth *authfuncs; struct auth *authfuncs;
struct filter *filter; struct filter *filter;
unsigned char * logformat; unsigned char * logformat;
@ -702,6 +728,9 @@ struct extparam {
_3proxy_sem_t threadinit; _3proxy_sem_t threadinit;
int *timeouts; int *timeouts;
struct ace * acl; struct ace * acl;
#ifdef WITH_HTTPSRV
struct httprule *httprules;
#endif
char * conffile; char * conffile;
struct bandlim * bandlimiter, *bandlimiterout; struct bandlim * bandlimiter, *bandlimiterout;
struct connlim * connlimiter; struct connlim * connlimiter;

View File

@ -8,11 +8,12 @@
#include "proxy.h" #include "proxy.h"
#ifdef WITH_HTTPSRV
#define RETURN(xxx) { param->res = xxx; goto CLEANRET; } #define RETURN(xxx) { param->res = xxx; goto CLEANRET; }
#define LINESIZE 65536 #define LINESIZE 65536
extern FILE *writable;
FILE * confopen(); FILE * confopen();
extern void decodeurl(unsigned char *s, int filter); extern void decodeurl(unsigned char *s, int filter);
@ -211,8 +212,7 @@ char * admin_stringtable[]={
"&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</h2>\r\n" "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</h2>\r\n"
"<A HREF=\'/C'>Counters</A><br>\r\n" "<A HREF=\'/C'>Counters</A><br>\r\n"
"<A HREF=\'/R'>Reload</A><br>\r\n" "<A HREF=\'/R'>Reload</A><br>\r\n"
"<A HREF=\'/S'>Running Services</A><br>\r\n" "<A HREF=\'/S'>Running Services</A>\r\n"
"<A HREF=\'/F'>Config</A>\r\n"
"</td><td>" "</td><td>"
"<h2>%s %s configuration</h2>", "<h2>%s %s configuration</h2>",
@ -367,92 +367,62 @@ static int printiplist(char *buf, int bufsize, struct iplist* ipl, char * delim)
return printed; return printed;
} }
void * adminchild(struct clientparam* param) { /* The admin pages are http operations: the service, request parsing and
int i, res; 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; char *buf;
char username[256];
char *sb;
char *req = NULL;
struct printparam pp;
unsigned contentlen = 0;
int isform = 0;
int limited = 0;
pp->inbuf = 0;
limited =param->srv->s_option; pp->cp = param;
pp.inbuf = 0;
pp.cp = param;
buf = malloc(LINESIZE); buf = malloc(LINESIZE);
if(!buf) {RETURN(555);} if(!buf) return NULL;
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]!='/') && sprintf(buf, ok, conf.stringtable?(char *)conf.stringtable[2]:"3proxy",
(buf[0]!='P' || buf[1]!='O' || buf[2]!='S' || buf[3]!='T' || buf[4]!=' ' || buf[5]!='/'))) 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)
{ {
RETURN(701); printstr(pp, tail);
printstr(pp, NULL);
if(buf) free(buf);
} }
buf[i] = 0;
sb = strchr(buf+5, ' '); int op_admin(struct httpreq *r, const unsigned char *params)
if(!sb){ {
RETURN(702); 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;
} }
*sb = 0;
req = strdup(buf + ((*buf == 'P')? 6 : 5)); int op_admin_counters(struct httpreq *r, const unsigned char *params)
while((i = sockgetlinebuf(param, CLIENT, (unsigned char *)buf, LINESIZE - 1, '\n', conf.timeouts[STRING_S])) > 2){ {
buf[i] = 0; struct clientparam *param = r->param;
if(i > 19 && (!strncasecmp(buf, "authorization", 13))){ struct printparam pp;
sb = strchr(buf, ':'); char *buf;
if(!sb)continue; const char *sel;
++sb; int limited;
while(isspace(*sb))sb++;
if(!*sb || strncasecmp(sb, "basic", 5)){ limited = param->srv->s_option;
continue; /* In limited mode a counter may be looked at but not switched. */
} sel = limited? "" : r->path + r->globstart;
sb+=5;
while(isspace(*sb))sb++; buf = admin_open(&pp, param);
i = de64((unsigned char *)sb, (unsigned char *)username, 255); if(!buf) return 1;
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); printstr(&pp, counters);
{ {
struct trafcount *cp; struct trafcount *cp;
@ -463,8 +433,8 @@ void * adminchild(struct clientparam* param) {
if(cp->ace && (limited || param->redirected)){ if(cp->ace && (limited || param->redirected)){
if(!ACLmatches(cp->ace, param))continue; if(!ACLmatches(cp->ace, param))continue;
} }
if(req[1] == 'S' && atoi(req+2) == num) cp->disabled=0; if(sel[0] == 'S' && atoi(sel+1) == num) cp->disabled=0;
if(req[1] == 'D' && atoi(req+2) == num) cp->disabled=1; if(sel[0] == 'D' && atoi(sel+1) == num) cp->disabled=1;
inbuf += sprintf(buf, "<tr><td>%s</td><td>", cp->ace?aceaction(cp->ace->action):"-"); inbuf += sprintf(buf, "<tr><td>%s</td><td>", cp->ace?aceaction(cp->ace->action):"-");
if(cp->number || cp->comment) if(cp->number || cp->comment)
inbuf += sprintf(buf+inbuf, "%d/%s</td>" , cp->number, inbuf += sprintf(buf+inbuf, "%d/%s</td>" , cp->number,
@ -535,85 +505,55 @@ void * adminchild(struct clientparam* param) {
} }
printstr(&pp, counterstail); 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; conf.needreload = 1;
printstr(&pp, "<h3>Reload scheduled</h3>"); printstr(&pp, "<h3>Reload scheduled</h3>");
break;
case 'S':
{
if(req[1] == 'X'){
printstr(&pp, style);
break;
} }
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); printstr(&pp, xml);
printval(conf.services, TYPE_SERVER, 0, &pp); printval(conf.services, TYPE_SERVER, 0, &pp);
printstr(&pp, postxml); printstr(&pp, postxml);
} }
break;
case 'F':
{
FILE *fp;
char buf[256];
fp = confopen();
if(!fp){
printstr(&pp, "<h3><font color=\"red\">Failed to open config file</font></h3>");
break;
}
printstr(&pp, "<h3>Please be careful editing config file remotely</h3>");
printstr(&pp, "<form method=\"POST\" action=\"/U\" enctype=\"application/x-www-form-urlencoded\"><textarea cols=\"80\" rows=\"30\" name=\"conffile\">");
while(fgets(buf, 256, fp)){
printstr(&pp, buf);
}
if(!writable) fclose(fp);
printstr(&pp, "</textarea><br><input type=\"Submit\"></form>");
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? "<h3><font color=\"red\">Config file is not writable</font></h3>Make sure you have \"writable\" command in configuration file":
"<h3>Configuration updated</h3>");
}
break;
default:
printstr(&pp, (char *)conf.stringtable[WEBBANNERS]);
break;
}
if(*req != 'S') printstr(&pp, tail);
CLEANRET:
printstr(&pp, NULL); printstr(&pp, NULL);
if(buf) free(buf); return 0;
dolog(param, (unsigned char *)req);
if(req)free(req);
return (NULL);
} }
#endif