mirror of
https://github.com/3proxy/3proxy.git
synced 2026-09-29 16:55:51 +08:00
Compare commits
No commits in common. "137ff3beea1146b789a0cb3f59b2f5cfed2249cf" and "1565c67c135ee77354359a2c72f505a45056151b" have entirely different histories.
137ff3beea
...
1565c67c13
6
.github/workflows/c-cpp-Linux.yml
vendored
6
.github/workflows/c-cpp-Linux.yml
vendored
@ -2,9 +2,9 @@ name: C/C++ CI Linux
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
paths: [ '**.c', '**.h', 'Makefile.Linux', 'tests/**', '.github/configs', '.github/workflows/c-cpp-Linux.yml' ]
|
paths: [ '**.c', '**.h', 'Makefile.Linux', '.github/configs', '.github/workflows/c-cpp-Linux.yml' ]
|
||||||
pull_request:
|
pull_request:
|
||||||
paths: [ "**.c", "**.h", "Makefile.Linux", "tests/**", ".github/configs", ".github/workflows/c-cpp-Linux.yml" ]
|
paths: [ "**.c", "**.h", "Makefile.Linux", ".github/configs", ".github/workflows/c-cpp-Linux.yml" ]
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
@ -28,8 +28,6 @@ jobs:
|
|||||||
run: sudo apt-get update && sudo apt-get install -y libssl-dev libpam-dev libpcre2-dev
|
run: sudo apt-get update && sudo apt-get install -y libssl-dev libpam-dev libpcre2-dev
|
||||||
- name: make
|
- name: make
|
||||||
run: make -f Makefile.Linux
|
run: make -f Makefile.Linux
|
||||||
- name: regression tests
|
|
||||||
run: python3 tests/run.py
|
|
||||||
- name: mkdir
|
- name: mkdir
|
||||||
run: mkdir ~/3proxy
|
run: mkdir ~/3proxy
|
||||||
- name: make install
|
- name: make install
|
||||||
|
|||||||
6
.github/workflows/c-cpp-MacOS.yml
vendored
6
.github/workflows/c-cpp-MacOS.yml
vendored
@ -2,9 +2,9 @@ name: C/C++ CI MacOS
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
paths: [ '**.c', '**.h', 'Makefile.FreeBSD', 'tests/**', '.github/configs', '.github/workflows/c-cpp-MacOS.yml' ]
|
paths: [ '**.c', '**.h', 'Makefile.FreeBSD', '.github/configs', '.github/workflows/c-cpp-MacOS.yml' ]
|
||||||
pull_request:
|
pull_request:
|
||||||
paths: [ "**.c", "**.h", "Makefile.FreeBSD", "tests/**", ".github/configs", ".github/workflows/c-cpp-MacOS.yml" ]
|
paths: [ "**.c", "**.h", "Makefile.FreeBSD", ".github/configs", ".github/workflows/c-cpp-MacOS.yml" ]
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
@ -29,7 +29,5 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
LDFLAGS: "-L/usr/local/lib -L/opt/homebrew/lib -L/opt/homebrew/opt/openssl/lib"
|
LDFLAGS: "-L/usr/local/lib -L/opt/homebrew/lib -L/opt/homebrew/opt/openssl/lib"
|
||||||
CFLAGS: "-I/usr/local/include -I/opt/homebrew/include -I/usr/local/opt/openssl/include -I/opt/homebrew/opt/openssl/include"
|
CFLAGS: "-I/usr/local/include -I/opt/homebrew/include -I/usr/local/opt/openssl/include -I/opt/homebrew/opt/openssl/include"
|
||||||
- name: regression tests
|
|
||||||
run: python3 tests/run.py
|
|
||||||
- name: make clean MacOS
|
- name: make clean MacOS
|
||||||
run: make -f Makefile.FreeBSD clean
|
run: make -f Makefile.FreeBSD clean
|
||||||
|
|||||||
10
.github/workflows/c-cpp-Windows.yml
vendored
10
.github/workflows/c-cpp-Windows.yml
vendored
@ -2,9 +2,9 @@ name: C/C++ CI Windows
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
paths: [ '**.c', '**.h', 'Makefile.msvc', 'tests/**', '.github/configs', '.github/workflows/c-cpp-Windows.yml' ]
|
paths: [ '**.c', '**.h', 'Makefile.msvc', '.github/configs', '.github/workflows/c-cpp-Windows.yml' ]
|
||||||
pull_request:
|
pull_request:
|
||||||
paths: [ "**.c", "**.h", "Makefile.msvc", "tests/**", ".github/configs", ".github/workflows/c-cpp-Windows.yml" ]
|
paths: [ "**.c", "**.h", "Makefile.msvc", ".github/configs", ".github/workflows/c-cpp-Windows.yml" ]
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
@ -27,10 +27,6 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
LDFLAGS: '-L "c:/msys64/mingw64/lib"'
|
LDFLAGS: '-L "c:/msys64/mingw64/lib"'
|
||||||
CFLAGS: '-I "c:/msys64/mingw64/include"'
|
CFLAGS: '-I "c:/msys64/mingw64/include"'
|
||||||
- name: regression tests (MinGW)
|
|
||||||
run: |
|
|
||||||
$env:PATH = "c:\msys64\mingw64\bin;$env:PATH"
|
|
||||||
python tests\run.py --bin bin\3proxy.exe
|
|
||||||
- name: make clean Windows
|
- name: make clean Windows
|
||||||
run: make -f Makefile.win clean
|
run: make -f Makefile.win clean
|
||||||
- name: Add msbuild to PATH
|
- name: Add msbuild to PATH
|
||||||
@ -44,6 +40,4 @@ jobs:
|
|||||||
set "LIB=%LIB%;c:/vcpkg/installed/x64-windows-static/lib;c:/vcpkg/installed/x64-windows/lib"
|
set "LIB=%LIB%;c:/vcpkg/installed/x64-windows-static/lib;c:/vcpkg/installed/x64-windows/lib"
|
||||||
set "INCLUDE=%INCLUDE%;c:/vcpkg/installed/x64-windows-static/include;c:/vcpkg/installed/x64-windows/include"
|
set "INCLUDE=%INCLUDE%;c:/vcpkg/installed/x64-windows-static/include;c:/vcpkg/installed/x64-windows/include"
|
||||||
nmake /F Makefile.msvc WOLFSSL=1 || exit /b 1
|
nmake /F Makefile.msvc WOLFSSL=1 || exit /b 1
|
||||||
set "PATH=%PATH%;c:/vcpkg/installed/x64-windows/bin"
|
|
||||||
python tests\run.py --bin bin\3proxy.exe || exit /b 1
|
|
||||||
nmake /F Makefile.msvc clean
|
nmake /F Makefile.msvc clean
|
||||||
|
|||||||
14
.github/workflows/c-cpp-cmake.yml
vendored
14
.github/workflows/c-cpp-cmake.yml
vendored
@ -2,9 +2,9 @@ name: C/C++ CI cmake
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
paths: [ '**.c', '**.h', '**.cmake', 'CMakeLists.txt', 'tests/**', '.github/configs', '.github/workflows/c-cpp-cmake.yml' ]
|
paths: [ '**.c', '**.h', '**.cmake', 'CMakeLists.txt', '.github/configs', '.github/workflows/c-cpp-cmake.yml' ]
|
||||||
pull_request:
|
pull_request:
|
||||||
paths: [ "**.c", "**.h", "**.cmake", "CMakeLists.txt", "tests/**", ".github/configs", ".github/workflows/c-cpp-cmake.yml" ]
|
paths: [ "**.c", "**.h", "**.cmake", "CMakeLists.txt", ".github/configs", ".github/workflows/c-cpp-cmake.yml" ]
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
@ -43,9 +43,7 @@ jobs:
|
|||||||
cmake --build .
|
cmake --build .
|
||||||
mkdir ~/3proxy
|
mkdir ~/3proxy
|
||||||
DESTDIR=~/3proxy cmake --install .
|
DESTDIR=~/3proxy cmake --install .
|
||||||
cd ..
|
cd .. && rm -rf build/
|
||||||
python3 tests/run.py --bin build/bin/3proxy
|
|
||||||
rm -rf build/
|
|
||||||
- name: make with CMake Win
|
- name: make with CMake Win
|
||||||
if: ${{ startsWith(matrix.target, 'windows') }}
|
if: ${{ startsWith(matrix.target, 'windows') }}
|
||||||
shell: cmd
|
shell: cmd
|
||||||
@ -58,8 +56,6 @@ jobs:
|
|||||||
dir
|
dir
|
||||||
cmake --build .
|
cmake --build .
|
||||||
cd ..
|
cd ..
|
||||||
set "PATH=%PATH%;c:/vcpkg/installed/x64-windows/bin"
|
|
||||||
python tests\run.py || exit /b 1
|
|
||||||
rmdir /s /q build
|
rmdir /s /q build
|
||||||
|
|
||||||
wolfssl:
|
wolfssl:
|
||||||
@ -75,6 +71,4 @@ jobs:
|
|||||||
cd build
|
cd build
|
||||||
cmake ..
|
cmake ..
|
||||||
cmake --build .
|
cmake --build .
|
||||||
cd ..
|
cd .. && rm -rf build/
|
||||||
python3 tests/run.py --bin build/bin/3proxy
|
|
||||||
rm -rf build/
|
|
||||||
|
|||||||
@ -56,7 +56,6 @@ 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)
|
||||||
@ -237,10 +236,6 @@ else()
|
|||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(3PROXY_USE_HTTPSRV)
|
|
||||||
add_compile_definitions(WITH_HTTPSRV)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# Unix domain sockets off: NO_UN also undefines WITH_UN if it arrives from
|
# Unix domain sockets off: NO_UN also undefines WITH_UN if it arrives from
|
||||||
# elsewhere, e.g. CFLAGS
|
# elsewhere, e.g. CFLAGS
|
||||||
if(NOT 3PROXY_USE_UNIX_SOCKETS)
|
if(NOT 3PROXY_USE_UNIX_SOCKETS)
|
||||||
@ -408,7 +403,6 @@ 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
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -24,11 +24,6 @@ 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 ?=
|
||||||
|
|||||||
@ -25,13 +25,6 @@ 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
|
||||||
|
|||||||
@ -14,11 +14,6 @@ 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
|
||||||
|
|||||||
@ -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 = /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)
|
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)
|
||||||
COUT = /Fo
|
COUT = /Fo
|
||||||
LN = link
|
LN = link
|
||||||
LDFLAGS = /nologo /subsystem:console /incremental:no
|
LDFLAGS = /nologo /subsystem:console /incremental:no
|
||||||
@ -40,7 +40,6 @@ 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)
|
||||||
|
|||||||
@ -26,11 +26,6 @@ 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 ?=
|
||||||
|
|||||||
@ -8,7 +8,7 @@ BUILDDIR = ../bin/
|
|||||||
PREFIX = 3proxy_
|
PREFIX = 3proxy_
|
||||||
CRYPT_PREFIX = 3proxy_
|
CRYPT_PREFIX = 3proxy_
|
||||||
CC = cl
|
CC = cl
|
||||||
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)
|
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)
|
||||||
COUT = /Fo
|
COUT = /Fo
|
||||||
LN = link
|
LN = link
|
||||||
LDFLAGS = /nologo /subsystem:console /incremental:no
|
LDFLAGS = /nologo /subsystem:console /incremental:no
|
||||||
@ -21,7 +21,6 @@ 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
|
||||||
|
|||||||
@ -20,11 +20,6 @@ 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
|
||||||
|
|||||||
@ -292,7 +292,16 @@ Include config file</p>
|
|||||||
|
|
||||||
<p style="margin-left:9%; margin-top: 1em"><b>config</b>
|
<p style="margin-left:9%; margin-top: 1em"><b>config</b>
|
||||||
<i><path></i> <br>
|
<i><path></i> <br>
|
||||||
Path to configuration file to use on 3proxy restart.</p>
|
Path to configuration file to use on 3proxy restart or to
|
||||||
|
save configuration.</p>
|
||||||
|
|
||||||
|
<p style="margin-left:9%; margin-top: 1em"><b>writable</b>
|
||||||
|
<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.</p>
|
||||||
|
|
||||||
<p style="margin-left:9%; margin-top: 1em"><b>end</b> <br>
|
<p style="margin-left:9%; margin-top: 1em"><b>end</b> <br>
|
||||||
End of configuration</p>
|
End of configuration</p>
|
||||||
@ -816,31 +825,6 @@ the external address for this request to <i><ip></i>.
|
|||||||
It can be chained with another parent type. It’s
|
It can be chained with another parent type. It’s
|
||||||
useful to set the external IP based on ACL or make it
|
useful to set the external IP based on ACL or make it
|
||||||
random. <b><br>
|
random. <b><br>
|
||||||
extport</b> does not redirect the request; it sets the range
|
|
||||||
the local port of outgoing connections is taken from, given
|
|
||||||
as <i>FIRST-LAST</i> inclusive in place of the port
|
|
||||||
argument, with 0.0.0.0 as the address, for example <b>parent
|
|
||||||
1000 extport 0.0.0.0 40000-40100</b>. Where the system can
|
|
||||||
be asked to pick the port itself (Linux
|
|
||||||
<b>IP_LOCAL_PORT_RANGE</b>) it does, otherwise a port is
|
|
||||||
picked at random from the range and retried if it is already
|
|
||||||
in use, up to ten times. On Linux the range has to lie
|
|
||||||
within <i>net.ipv4.ip_local_port_range</i>, commonly
|
|
||||||
32768-60999: the kernel ignores a range outside it and picks
|
|
||||||
an ordinary ephemeral port instead. If no port in the range
|
|
||||||
can be bound, an ephemeral port is used rather than failing
|
|
||||||
the connection. It can be chained with another parent type,
|
|
||||||
and the access rule it belongs to decides which requests it
|
|
||||||
applies to, so <b>allow * * * * UDPASSOC</b> followed by
|
|
||||||
<b>parent 1000 extport 0.0.0.0 40000-40100</b> limits it to
|
|
||||||
UDP associations. The range is applied when the outgoing
|
|
||||||
connection is made, so a kept alive connection carrying
|
|
||||||
several requests uses the rule that matched when it was
|
|
||||||
opened. <b><br>
|
|
||||||
intport</b> is the same for sockets bound on the side facing
|
|
||||||
the client: the port a UDP association tells the client to
|
|
||||||
send its datagrams to, and the FTP proxy data connection.
|
|
||||||
<b><br>
|
|
||||||
tcp</b> simply redirect connection. TCP is always last in
|
tcp</b> simply redirect connection. TCP is always last in
|
||||||
chain. This type of proxy is a simple TCP redirection, it
|
chain. This type of proxy is a simple TCP redirection, it
|
||||||
does not support parent authentication. <b><br>
|
does not support parent authentication. <b><br>
|
||||||
|
|||||||
@ -282,7 +282,16 @@ 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.
|
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.
|
||||||
|
|
||||||
.br
|
.br
|
||||||
.B end
|
.B end
|
||||||
@ -870,10 +879,6 @@ with probability of 0.7) for outgoing web connections. Chains are only applied t
|
|||||||
type is one of:
|
type is one of:
|
||||||
.br
|
.br
|
||||||
\fBextip\fR does not actually redirect the request; it sets the external address for this request to \fI<ip>\fR. It can be chained with another parent type. It's useful to set the external IP based on ACL or make it random.
|
\fBextip\fR does not actually redirect the request; it sets the external address for this request to \fI<ip>\fR. It can be chained with another parent type. It's useful to set the external IP based on ACL or make it random.
|
||||||
.br
|
|
||||||
\fBextport\fR does not redirect the request; it sets the range the local port of outgoing connections is taken from, given as \fIFIRST-LAST\fR inclusive in place of the port argument, with 0.0.0.0 as the address, for example \fBparent 1000 extport 0.0.0.0 40000-40100\fR. Where the system can be asked to pick the port itself (Linux \fBIP_LOCAL_PORT_RANGE\fR) it does, otherwise a port is picked at random from the range and retried if it is already in use, up to ten times. On Linux the range has to lie within \fInet.ipv4.ip_local_port_range\fR, commonly 32768-60999: the kernel ignores a range outside it and picks an ordinary ephemeral port instead. If no port in the range can be bound, an ephemeral port is used rather than failing the connection. It can be chained with another parent type, and the access rule it belongs to decides which requests it applies to, so \fBallow * * * * UDPASSOC\fR followed by \fBparent 1000 extport 0.0.0.0 40000-40100\fR limits it to UDP associations. The range is applied when the outgoing connection is made, so a kept alive connection carrying several requests uses the rule that matched when it was opened.
|
|
||||||
.br
|
|
||||||
\fBintport\fR is the same for sockets bound on the side facing the client: the port a UDP association tells the client to send its datagrams to, and the FTP proxy data connection.
|
|
||||||
.br
|
.br
|
||||||
\fBtcp\fR simply redirect connection. TCP is always last in chain. This type of proxy is a simple TCP redirection, it does not support parent authentication.
|
\fBtcp\fR simply redirect connection. TCP is always last in chain. This type of proxy is a simple TCP redirection, it does not support parent authentication.
|
||||||
.br
|
.br
|
||||||
|
|||||||
1
rus.3ps
1
rus.3ps
@ -24,6 +24,7 @@ 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]
|
||||||
|
|||||||
@ -28,6 +28,7 @@ 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;
|
||||||
|
|
||||||
@ -536,7 +537,7 @@ int WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int
|
|||||||
conf.version++;
|
conf.version++;
|
||||||
|
|
||||||
if(res) RETURN(res);
|
if(res) RETURN(res);
|
||||||
fclose(fp); fp = NULL;
|
if(!writable){fclose(fp); fp = NULL;}
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
|
|
||||||
|
|||||||
@ -116,9 +116,6 @@ 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
|
||||||
|
|
||||||
@ -191,6 +188,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) $(HTTPSRV_OBJS) 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) 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) $(HTTPSRV_OBJS) 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) srvwebadmin$(OBJSUFFICS) srvdnspr$(OBJSUFFICS) plugins$(OBJSUFFICS) mdhash$(OBJSUFFICS) $(SSL_OBJS) $(PCRE_OBJS) $(COMPATLIBS) $(LIBS) $(PCRE_LIBS)
|
||||||
|
|
||||||
|
|||||||
64
src/acl.c
64
src/acl.c
@ -62,7 +62,36 @@ 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){
|
||||||
if(patternmatch(hstentry, param->hostname)) match = 1;
|
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(match) break;
|
if(match) break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -135,10 +164,7 @@ int checkACL(struct clientparam * param){
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
param->lastace = acentry;
|
param->lastace = acentry;
|
||||||
if(param->preauth) {
|
if(param->preauth) return 2;
|
||||||
applyportranges(param, acentry);
|
|
||||||
return 2;
|
|
||||||
}
|
|
||||||
if((param->operation == UDPASSOC)? (param->ctrlsocksrv != INVALID_SOCKET) : (param->remsock != INVALID_SOCKET)) {
|
if((param->operation == UDPASSOC)? (param->ctrlsocksrv != INVALID_SOCKET) : (param->remsock != INVALID_SOCKET)) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -161,31 +187,3 @@ int checkACL(struct clientparam * param){
|
|||||||
}
|
}
|
||||||
return 3;
|
return 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
char * aceaction (int action){
|
|
||||||
switch (action) {
|
|
||||||
case ALLOW:
|
|
||||||
case REDIRECT:
|
|
||||||
return "allow";
|
|
||||||
case DENY:
|
|
||||||
return "deny";
|
|
||||||
case BANDLIM:
|
|
||||||
return "bandlim";
|
|
||||||
case NOBANDLIM:
|
|
||||||
return "nobandlim";
|
|
||||||
case COUNTIN:
|
|
||||||
return "countin";
|
|
||||||
case NOCOUNTIN:
|
|
||||||
return "nocountin";
|
|
||||||
case COUNTOUT:
|
|
||||||
return "countout";
|
|
||||||
case NOCOUNTOUT:
|
|
||||||
return "nocountout";
|
|
||||||
case COUNTALL:
|
|
||||||
return "countall";
|
|
||||||
case NOCOUNTALL:
|
|
||||||
return "nocountall";
|
|
||||||
default:
|
|
||||||
return "unknown";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@ -18,13 +18,7 @@ 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);
|
||||||
|
|||||||
143
src/common.c
143
src/common.c
@ -182,7 +182,7 @@ int timeouts[12] = {
|
|||||||
EINVAL below it and the thread silently gets the 8M system default stack.
|
EINVAL below it and the thread silently gets the 8M system default stack.
|
||||||
*/
|
*/
|
||||||
size_t threadstacksize(int extra){
|
size_t threadstacksize(int extra){
|
||||||
long size = BASESTACKSIZE + TLSSTACKSIZE + extra;
|
long size = BASESTACKSIZE + extra;
|
||||||
|
|
||||||
if(size < (long)PTHREAD_STACK_MIN) size = (long)PTHREAD_STACK_MIN;
|
if(size < (long)PTHREAD_STACK_MIN) size = (long)PTHREAD_STACK_MIN;
|
||||||
return (size_t)size;
|
return (size_t)size;
|
||||||
@ -746,7 +746,7 @@ int doconnect(struct clientparam * param){
|
|||||||
#ifdef WITH_UN
|
#ifdef WITH_UN
|
||||||
if(*SAFAMILY(¶m->sinsl) != AF_UNIX)
|
if(*SAFAMILY(¶m->sinsl) != AF_UNIX)
|
||||||
#endif
|
#endif
|
||||||
if(bindwithrange(param, param->remsock, ¶m->sinsl, param->extport)==-1) {
|
if(param->srv->so._bind(param->sostate, param->remsock, (struct sockaddr*)¶m->sinsl, SASIZE(¶m->sinsl))==-1) {
|
||||||
return 12;
|
return 12;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -767,145 +767,6 @@ int doconnect(struct clientparam * param){
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Number of ports tried before giving up when the range has to be searched by
|
|
||||||
* hand. The kernel option picks a free port itself and needs no retries. */
|
|
||||||
#define RANGETRIES 10
|
|
||||||
|
|
||||||
/* Bind sock to sa, taking the local port from the range if one is set. The
|
|
||||||
* range is packed as first | last << 16.
|
|
||||||
*
|
|
||||||
* IP_LOCAL_PORT_RANGE leaves the choice to the kernel, which knows which ports
|
|
||||||
* are free. Where the option does not exist, or the kernel refuses it, or the
|
|
||||||
* address family is not one it covers, pick a port at random instead and retry
|
|
||||||
* on failure, since the one picked may already be taken.
|
|
||||||
*/
|
|
||||||
int bindwithrange(struct clientparam *param, SOCKET sock, PROXYSOCKADDRTYPE *sa, uint32_t range)
|
|
||||||
{
|
|
||||||
uint16_t first, last;
|
|
||||||
int i;
|
|
||||||
|
|
||||||
if(!range) return param->srv->so._bind(param->sostate, sock, (struct sockaddr *)sa, SASIZE(sa));
|
|
||||||
|
|
||||||
#ifdef IP_LOCAL_PORT_RANGE
|
|
||||||
if(*SAFAMILY(sa) == AF_INET &&
|
|
||||||
!param->srv->so._setsockopt(param->sostate, sock, IPPROTO_IP, IP_LOCAL_PORT_RANGE,
|
|
||||||
(char *)&range, sizeof(range))){
|
|
||||||
*SAPORT(sa) = 0;
|
|
||||||
return param->srv->so._bind(param->sostate, sock, (struct sockaddr *)sa, SASIZE(sa));
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
first = (uint16_t)(range & 0xffff);
|
|
||||||
last = (uint16_t)(range >> 16);
|
|
||||||
|
|
||||||
for(i = 0; i < RANGETRIES; i++){
|
|
||||||
*SAPORT(sa) = htons((uint16_t)(first + (myrand() % (unsigned)(last - first + 1))));
|
|
||||||
if(!param->srv->so._bind(param->sostate, sock, (struct sockaddr *)sa, SASIZE(sa))) return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Every port tried was taken. Fall back to an ephemeral one, which is
|
|
||||||
what the kernel option above does when it cannot honour the range, so
|
|
||||||
an exhausted range behaves the same way on every platform. */
|
|
||||||
*SAPORT(sa) = 0;
|
|
||||||
return param->srv->so._bind(param->sostate, sock, (struct sockaddr *)sa, SASIZE(sa));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 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;
|
||||||
|
|||||||
188
src/conf.c
188
src/conf.c
@ -7,10 +7,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#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);
|
||||||
@ -39,6 +35,7 @@ _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;
|
||||||
|
|
||||||
@ -63,6 +60,10 @@ FILE * confopen(){
|
|||||||
curconf += strlen(chrootp);
|
curconf += strlen(chrootp);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
if(writable) {
|
||||||
|
rewind(writable);
|
||||||
|
return writable;
|
||||||
|
}
|
||||||
return fopen(curconf, "r");
|
return fopen(curconf, "r");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -157,12 +158,7 @@ int start_proxy_thread(struct child * chp){
|
|||||||
pthread_attr_init(&pa);
|
pthread_attr_init(&pa);
|
||||||
pthread_attr_setstacksize(&pa,threadstacksize(conf.stacksize));
|
pthread_attr_setstacksize(&pa,threadstacksize(conf.stacksize));
|
||||||
pthread_attr_setdetachstate(&pa,PTHREAD_CREATE_DETACHED);
|
pthread_attr_setdetachstate(&pa,PTHREAD_CREATE_DETACHED);
|
||||||
if(pthread_create(&thread, &pa, startsrv, (void *)chp)){
|
pthread_create(&thread, &pa, startsrv, (void *)chp);
|
||||||
pthread_attr_destroy(&pa);
|
|
||||||
fprintf(stderr, "Failed to create service thread on line %d, try to set larger stacksize\n", linenum);
|
|
||||||
_3proxy_sem_unlock(conf.threadinit);
|
|
||||||
return(40);
|
|
||||||
}
|
|
||||||
pthread_attr_destroy(&pa);
|
pthread_attr_destroy(&pa);
|
||||||
#endif
|
#endif
|
||||||
_3proxy_sem_lock(conf.threadinit);
|
_3proxy_sem_lock(conf.threadinit);
|
||||||
@ -257,32 +253,12 @@ 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")) {
|
||||||
/* The same service as httpsrv, with the administration pages
|
childdef.pf = adminchild;
|
||||||
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_HTTPSRV;
|
childdef.service = S_ADMIN;
|
||||||
}
|
}
|
||||||
#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;
|
||||||
@ -789,67 +765,14 @@ 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_INTPORT, "intport", NULL},
|
|
||||||
{R_TLS, "tls", tlsprchild},
|
{R_TLS, "tls", tlsprchild},
|
||||||
{R_HA, "ha", NULL},
|
{R_HA, "ha", NULL},
|
||||||
{R_DNS, "dns", dnsprchild},
|
{R_DNS, "dns", dnsprchild},
|
||||||
{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. */
|
|
||||||
static int parserange(unsigned char *arg, uint32_t *range)
|
|
||||||
{
|
|
||||||
char *end;
|
|
||||||
unsigned long first, last;
|
|
||||||
|
|
||||||
first = strtoul((char *)arg, &end, 10);
|
|
||||||
if(end == (char *)arg || *end != '-' || !first || first > 65535) 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int h_parent(int argc, unsigned char **argv){
|
static int h_parent(int argc, unsigned char **argv){
|
||||||
struct ace *acl = NULL;
|
struct ace *acl = NULL;
|
||||||
struct chain *chains;
|
struct chain *chains;
|
||||||
@ -915,21 +838,7 @@ static int h_parent(int argc, unsigned char **argv){
|
|||||||
*cidr = '/';
|
*cidr = '/';
|
||||||
chains->cidr = atoi(cidr + 1);
|
chains->cidr = atoi(cidr + 1);
|
||||||
}
|
}
|
||||||
if(chains->type == R_EXTPORT || chains->type == R_INTPORT){
|
*SAPORT(&chains->addr) = htons((uint16_t)atoi((char *)argv[4]));
|
||||||
if(!SAISNULL(&chains->addr)){
|
|
||||||
fprintf(stderr, "Chaining error: chain type (%s) sets a local port range, it requires 0.0.0.0 as address on line %d\n", argv[2], linenum);
|
|
||||||
free(chains->exthost);
|
|
||||||
free(chains);
|
|
||||||
return(4);
|
|
||||||
}
|
|
||||||
if(parserange(argv[4], &chains->range)){
|
|
||||||
fprintf(stderr, "Chaining error: bad port range (%s) on line %d\n", argv[4], linenum);
|
|
||||||
free(chains->exthost);
|
|
||||||
free(chains);
|
|
||||||
return(3);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else *SAPORT(&chains->addr) = htons((uint16_t)atoi((char *)argv[4]));
|
|
||||||
switch(chains->type){
|
switch(chains->type){
|
||||||
case R_POP3:
|
case R_POP3:
|
||||||
case R_SMTP:
|
case R_SMTP:
|
||||||
@ -963,50 +872,6 @@ 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;
|
||||||
|
|
||||||
@ -1145,7 +1010,20 @@ 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));
|
||||||
if(parsepattern(hostnamel, arg)) {
|
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) {
|
||||||
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);
|
||||||
}
|
}
|
||||||
@ -1809,13 +1687,7 @@ 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},
|
||||||
@ -2063,6 +1935,16 @@ 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){
|
||||||
@ -2224,7 +2106,7 @@ int reload (void){
|
|||||||
if(error) {
|
if(error) {
|
||||||
freeconf(&conf);
|
freeconf(&conf);
|
||||||
}
|
}
|
||||||
fclose(fp);
|
if(!writable)fclose(fp);
|
||||||
}
|
}
|
||||||
_3proxy_mutex_unlock(&config_mutex);
|
_3proxy_mutex_unlock(&config_mutex);
|
||||||
return error;
|
return error;
|
||||||
|
|||||||
@ -391,6 +391,8 @@ static void * ef_ace_next(struct node * node){
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
char * aceaction (int action);
|
||||||
|
|
||||||
static void * ef_ace_type(struct node * node){
|
static void * ef_ace_type(struct node * node){
|
||||||
return aceaction(((struct ace *)node->value) -> action);
|
return aceaction(((struct ace *)node->value) -> action);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -149,7 +149,7 @@ void * dnsprchild(struct clientparam* param) {
|
|||||||
}
|
}
|
||||||
memset(¶m->sinsl, 0, sizeof(param->sinsl));
|
memset(¶m->sinsl, 0, sizeof(param->sinsl));
|
||||||
*SAFAMILY(¶m->sinsl) = *SAFAMILY(&nservers[0].addr);
|
*SAFAMILY(¶m->sinsl) = *SAFAMILY(&nservers[0].addr);
|
||||||
if(bindwithrange(param, param->remsock, ¶m->sinsl, param->extport)) {
|
if(param->srv->so._bind(param->sostate, param->remsock,(struct sockaddr *)¶m->sinsl,SASIZE(¶m->sinsl))) {
|
||||||
RETURN(819);
|
RETURN(819);
|
||||||
}
|
}
|
||||||
param->sinsr = nservers[0].addr;
|
param->sinsr = nservers[0].addr;
|
||||||
|
|||||||
@ -121,7 +121,7 @@ void * ftpprchild(struct clientparam* param) {
|
|||||||
}
|
}
|
||||||
if ((clidatasock=socket(SASOCK(¶m->sincl), SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET) {RETURN(821);}
|
if ((clidatasock=socket(SASOCK(¶m->sincl), SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET) {RETURN(821);}
|
||||||
*SAPORT(¶m->sincl) = 0;
|
*SAPORT(¶m->sincl) = 0;
|
||||||
if(bindwithrange(param, clidatasock, ¶m->sincl, param->intport)){RETURN(822);}
|
if(param->srv->so._bind(param->sostate, clidatasock, (struct sockaddr *)¶m->sincl, SASIZE(¶m->sincl))){RETURN(822);}
|
||||||
if (pasv) {
|
if (pasv) {
|
||||||
if(param->srv->so._listen(param->sostate, clidatasock, 1)) {RETURN(823);}
|
if(param->srv->so._listen(param->sostate, clidatasock, 1)) {RETURN(823);}
|
||||||
sasize = sizeof(param->sincl);
|
sasize = sizeof(param->sincl);
|
||||||
|
|||||||
517
src/httpsrv.c
517
src/httpsrv.c
@ -1,517 +0,0 @@
|
|||||||
/*
|
|
||||||
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
|
|
||||||
@ -15,9 +15,7 @@ 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);
|
||||||
|
|
||||||
@ -61,6 +59,7 @@ 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},
|
||||||
@ -122,11 +121,7 @@ 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,
|
||||||
|
|||||||
36
src/proxy.h
36
src/proxy.h
@ -135,23 +135,6 @@ void daemonize(void);
|
|||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* wolfSSL reserves around 48K of static thread-local storage. glibc counts
|
|
||||||
that against the thread stack, so pthread_create() fails with EINVAL and
|
|
||||||
no thread starts at all. musl places the block next to the stack instead
|
|
||||||
of inside it and needs nothing extra, and OpenSSL has no static TLS.
|
|
||||||
musl identifies itself by no macro of its own, but it does not define
|
|
||||||
__GLIBC__, which any libc header pulled in above would have set.
|
|
||||||
*/
|
|
||||||
#ifndef TLSSTACKSIZE
|
|
||||||
#if defined(__linux__) && !defined(__GLIBC__)
|
|
||||||
#define TLSSTACKSIZE 0
|
|
||||||
#elif defined(WITH_WOLFSSL)
|
|
||||||
#define TLSSTACKSIZE 49152
|
|
||||||
#else
|
|
||||||
#define TLSSTACKSIZE 0
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef _WIN32
|
#ifndef _WIN32
|
||||||
size_t threadstacksize(int extra);
|
size_t threadstacksize(int extra);
|
||||||
#endif
|
#endif
|
||||||
@ -339,7 +322,6 @@ int parseusername(char *username, struct clientparam *param, int extpasswd);
|
|||||||
int parseconnusername(char *username, struct clientparam *param, int extpasswd, uint16_t port);
|
int parseconnusername(char *username, struct clientparam *param, int extpasswd, uint16_t port);
|
||||||
int ACLmatches(struct ace* acentry, struct clientparam * param);
|
int ACLmatches(struct ace* acentry, struct clientparam * param);
|
||||||
int checkACL(struct clientparam * param);
|
int checkACL(struct clientparam * param);
|
||||||
char * aceaction (int action);
|
|
||||||
extern int havelog;
|
extern int havelog;
|
||||||
uint32_t udpresolve(int af, unsigned char * name, unsigned char * value, uint32_t *retttl, struct clientparam* param, int makeauth);
|
uint32_t udpresolve(int af, unsigned char * name, unsigned char * value, uint32_t *retttl, struct clientparam* param, int makeauth);
|
||||||
|
|
||||||
@ -370,11 +352,6 @@ unsigned char * dologname (unsigned char *buf, unsigned char *name, const unsign
|
|||||||
int readconfig(FILE * fp);
|
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 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);
|
|
||||||
|
|
||||||
|
|
||||||
uint32_t myrand(void);
|
uint32_t myrand(void);
|
||||||
@ -394,18 +371,7 @@ 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);
|
||||||
#ifdef WITH_HTTPSRV
|
void * adminchild(struct clientparam * param);
|
||||||
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
|
||||||
|
|||||||
@ -414,15 +414,6 @@ 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);
|
||||||
@ -1344,9 +1335,6 @@ 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);
|
||||||
|
|||||||
@ -259,20 +259,6 @@ int clientnegotiate(struct chain * redir, struct clientparam * param, struct soc
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* The local port ranges do not depend on the destination, so they can be taken
|
|
||||||
* as soon as a rule matches. UDP ASSOCIATE is authorized before the destination
|
|
||||||
* is known and returns before the chain is walked, which would otherwise leave
|
|
||||||
* the socket the client sends its datagrams to outside the configured range.
|
|
||||||
*/
|
|
||||||
void applyportranges(struct clientparam * param, struct ace * acentry){
|
|
||||||
struct chain *cur;
|
|
||||||
|
|
||||||
for(cur = acentry->chains; cur; cur = cur->next){
|
|
||||||
if(cur->type == R_EXTPORT) param->extport = cur->range;
|
|
||||||
else if(cur->type == R_INTPORT) param->intport = cur->range;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int handleredirect(struct clientparam * param, struct ace * acentry){
|
int handleredirect(struct clientparam * param, struct ace * acentry){
|
||||||
int connected = 0;
|
int connected = 0;
|
||||||
int weight = 1000;
|
int weight = 1000;
|
||||||
@ -299,8 +285,7 @@ int handleredirect(struct clientparam * param, struct ace * acentry){
|
|||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if(cur->type != R_EXTIP && cur->type != R_HA &&
|
if(cur->type != R_EXTIP && cur->type != R_HA) param->redirected++;
|
||||||
cur->type != R_EXTPORT && cur->type != R_INTPORT) param->redirected++;
|
|
||||||
done = 1;
|
done = 1;
|
||||||
if(weight <= 0) {
|
if(weight <= 0) {
|
||||||
weight += 1000;
|
weight += 1000;
|
||||||
@ -308,12 +293,6 @@ int handleredirect(struct clientparam * param, struct ace * acentry){
|
|||||||
r2 = (myrand()%1000);
|
r2 = (myrand()%1000);
|
||||||
}
|
}
|
||||||
if(!connected){
|
if(!connected){
|
||||||
if(cur->type == R_EXTPORT || cur->type == R_INTPORT){
|
|
||||||
if(cur->type == R_EXTPORT) param->extport = cur->range;
|
|
||||||
else param->intport = cur->range;
|
|
||||||
if(cur->next)continue;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
if(cur->type == R_EXTIP){
|
if(cur->type == R_EXTIP){
|
||||||
param->sinsl = cur->addr;
|
param->sinsl = cur->addr;
|
||||||
if(SAISNULL(¶m->sinsl) && (*SAFAMILY(¶m->sincr) == AF_INET || *SAFAMILY(¶m->sincr) == AF_INET6))param->sinsl = param->sincr;
|
if(SAISNULL(¶m->sinsl) && (*SAFAMILY(¶m->sincr) == AF_INET || *SAFAMILY(¶m->sincr) == AF_INET6))param->sinsl = param->sincr;
|
||||||
|
|||||||
@ -218,10 +218,7 @@ void * sockschild(struct clientparam* param) {
|
|||||||
if((res = udpbind(param))) {RETURN(res);}
|
if((res = udpbind(param))) {RETURN(res);}
|
||||||
}
|
}
|
||||||
else if(command == 2) {
|
else if(command == 2) {
|
||||||
if(bindwithrange(param, param->remsock, ¶m->sinsl, param->extport)) {
|
if(param->srv->so._bind(param->sostate, param->remsock,(struct sockaddr *)¶m->sinsl,SASIZE(¶m->sinsl))) {
|
||||||
/* a range has already been searched, retrying on any port would
|
|
||||||
ignore what was asked for */
|
|
||||||
if(param->extport) RETURN (12);
|
|
||||||
*SAPORT(¶m->sinsl) = 0;
|
*SAPORT(¶m->sinsl) = 0;
|
||||||
if(param->srv->so._bind(param->sostate, param->remsock,(struct sockaddr *)¶m->sinsl,SASIZE(¶m->sinsl)))RETURN (12);
|
if(param->srv->so._bind(param->sostate, param->remsock,(struct sockaddr *)¶m->sinsl,SASIZE(¶m->sinsl)))RETURN (12);
|
||||||
#if SOCKSTRACE > 0
|
#if SOCKSTRACE > 0
|
||||||
@ -246,8 +243,7 @@ fflush(stderr);
|
|||||||
#endif
|
#endif
|
||||||
sin = param->sincl;
|
sin = param->sincl;
|
||||||
*SAPORT(&sin) = 0;
|
*SAPORT(&sin) = 0;
|
||||||
/* the port the client is told to send its datagrams to */
|
if(param->srv->so._bind(param->sostate, param->clisock,(struct sockaddr *)&sin,SASIZE(&sin))) {RETURN (12);}
|
||||||
if(bindwithrange(param, param->clisock, &sin, param->intport)) {RETURN (12);}
|
|
||||||
sasize = SASIZE(&sin);
|
sasize = SASIZE(&sin);
|
||||||
param->srv->so._getsockname(param->sostate, param->clisock, (struct sockaddr *)&sin, &sasize);
|
param->srv->so._getsockname(param->sostate, param->clisock, (struct sockaddr *)&sin, &sasize);
|
||||||
#if SOCKSTRACE > 0
|
#if SOCKSTRACE > 0
|
||||||
|
|||||||
@ -216,7 +216,6 @@ typedef enum {
|
|||||||
S_AUTO,
|
S_AUTO,
|
||||||
S_TLSPR,
|
S_TLSPR,
|
||||||
S_IMAPP,
|
S_IMAPP,
|
||||||
S_HTTPSRV,
|
|
||||||
S_ZOMBIE
|
S_ZOMBIE
|
||||||
}PROXYSERVICE;
|
}PROXYSERVICE;
|
||||||
|
|
||||||
@ -314,9 +313,7 @@ typedef enum {
|
|||||||
R_TLS,
|
R_TLS,
|
||||||
R_HA,
|
R_HA,
|
||||||
R_DNS,
|
R_DNS,
|
||||||
R_IMAP,
|
R_IMAP
|
||||||
R_EXTPORT,
|
|
||||||
R_INTPORT
|
|
||||||
} REDIRTYPE;
|
} REDIRTYPE;
|
||||||
|
|
||||||
struct redirdesc {
|
struct redirdesc {
|
||||||
@ -338,8 +335,6 @@ struct chain {
|
|||||||
unsigned char * extpass;
|
unsigned char * extpass;
|
||||||
unsigned short weight;
|
unsigned short weight;
|
||||||
unsigned short cidr;
|
unsigned short cidr;
|
||||||
/* local port range for extport/intport, first in the low half */
|
|
||||||
uint32_t range;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct period {
|
struct period {
|
||||||
@ -357,28 +352,6 @@ 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;
|
||||||
@ -606,9 +579,6 @@ 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;
|
||||||
@ -699,7 +669,6 @@ struct clientparam {
|
|||||||
maxtrafout64;
|
maxtrafout64;
|
||||||
PROXYSOCKADDRTYPE sincl, sincr;
|
PROXYSOCKADDRTYPE sincl, sincr;
|
||||||
PROXYSOCKADDRTYPE sinsl, sinsr, req;
|
PROXYSOCKADDRTYPE sinsl, sinsr, req;
|
||||||
uint32_t extport, intport;
|
|
||||||
|
|
||||||
uint64_t statscli64,
|
uint64_t statscli64,
|
||||||
statssrv64;
|
statssrv64;
|
||||||
@ -728,9 +697,6 @@ 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;
|
||||||
|
|||||||
@ -121,12 +121,8 @@ int udpbind(struct clientparam *param)
|
|||||||
fcntl(s, F_SETFL, O_NONBLOCK | fcntl(s, F_GETFL));
|
fcntl(s, F_SETFL, O_NONBLOCK | fcntl(s, F_GETFL));
|
||||||
#endif
|
#endif
|
||||||
param->remsock = s;
|
param->remsock = s;
|
||||||
if (bindwithrange(param, param->remsock, ¶m->sinsl, param->extport)) {
|
if (param->srv->so._bind(param->sostate, param->remsock,
|
||||||
if (param->extport) {
|
(struct sockaddr *)¶m->sinsl, SASIZE(¶m->sinsl))) {
|
||||||
param->srv->so._closesocket(param->sostate, param->remsock);
|
|
||||||
param->remsock = INVALID_SOCKET;
|
|
||||||
return 12;
|
|
||||||
}
|
|
||||||
*SAPORT(¶m->sinsl) = 0;
|
*SAPORT(¶m->sinsl) = 0;
|
||||||
if (param->srv->so._bind(param->sostate, param->remsock,
|
if (param->srv->so._bind(param->sostate, param->remsock,
|
||||||
(struct sockaddr *)¶m->sinsl, SASIZE(¶m->sinsl))) {
|
(struct sockaddr *)¶m->sinsl, SASIZE(¶m->sinsl))) {
|
||||||
|
|||||||
297
src/webadmin.c
297
src/webadmin.c
@ -8,12 +8,11 @@
|
|||||||
|
|
||||||
#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);
|
||||||
|
|
||||||
@ -25,6 +24,35 @@ struct printparam {
|
|||||||
struct clientparam *cp;
|
struct clientparam *cp;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
char * aceaction (int action){
|
||||||
|
switch (action) {
|
||||||
|
case ALLOW:
|
||||||
|
case REDIRECT:
|
||||||
|
return "allow";
|
||||||
|
case DENY:
|
||||||
|
return "deny";
|
||||||
|
case BANDLIM:
|
||||||
|
return "bandlim";
|
||||||
|
case NOBANDLIM:
|
||||||
|
return "nobandlim";
|
||||||
|
case COUNTIN:
|
||||||
|
return "countin";
|
||||||
|
case NOCOUNTIN:
|
||||||
|
return "nocountin";
|
||||||
|
case COUNTOUT:
|
||||||
|
return "countout";
|
||||||
|
case NOCOUNTOUT:
|
||||||
|
return "nocountout";
|
||||||
|
case COUNTALL:
|
||||||
|
return "countall";
|
||||||
|
case NOCOUNTALL:
|
||||||
|
return "nocountall";
|
||||||
|
default:
|
||||||
|
return "unknown";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static void stdpr(struct printparam* pp, char *buf, int inbuf){
|
static void stdpr(struct printparam* pp, char *buf, int inbuf){
|
||||||
if((pp->inbuf + inbuf > 1024) || !buf) {
|
if((pp->inbuf + inbuf > 1024) || !buf) {
|
||||||
socksend(pp->cp, pp->cp->clisock, (unsigned char *)pp->buf, pp->inbuf, conf.timeouts[STRING_S]);
|
socksend(pp->cp, pp->cp->clisock, (unsigned char *)pp->buf, pp->inbuf, conf.timeouts[STRING_S]);
|
||||||
@ -183,7 +211,8 @@ char * admin_stringtable[]={
|
|||||||
" </h2>\r\n"
|
" </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>\r\n"
|
"<A HREF=\'/S'>Running Services</A><br>\r\n"
|
||||||
|
"<A HREF=\'/F'>Config</A>\r\n"
|
||||||
"</td><td>"
|
"</td><td>"
|
||||||
"<h2>%s %s configuration</h2>",
|
"<h2>%s %s configuration</h2>",
|
||||||
|
|
||||||
@ -338,62 +367,92 @@ static int printiplist(char *buf, int bufsize, struct iplist* ipl, char * delim)
|
|||||||
return printed;
|
return printed;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* The admin pages are http operations: the service, request parsing and
|
void * adminchild(struct clientparam* param) {
|
||||||
authorization belong to httpsrv, and what is left here is the page itself.
|
int i, res;
|
||||||
A star in the url carries the selector the pages used to read out of the
|
char * buf;
|
||||||
path, so /C with a star gives D2 or S2 to disable or enable a counter. */
|
char username[256];
|
||||||
|
char *sb;
|
||||||
|
char *req = NULL;
|
||||||
|
struct printparam pp;
|
||||||
|
unsigned contentlen = 0;
|
||||||
|
int isform = 0;
|
||||||
|
int limited = 0;
|
||||||
|
|
||||||
static char * admin_open(struct printparam *pp, struct clientparam *param)
|
|
||||||
{
|
|
||||||
char *buf;
|
|
||||||
|
|
||||||
pp->inbuf = 0;
|
limited =param->srv->s_option;
|
||||||
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);
|
printstr(&pp, counters);
|
||||||
{
|
{
|
||||||
struct trafcount *cp;
|
struct trafcount *cp;
|
||||||
@ -404,8 +463,8 @@ int op_admin_counters(struct httpreq *r, const unsigned char *params)
|
|||||||
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(sel[0] == 'S' && atoi(sel+1) == num) cp->disabled=0;
|
if(req[1] == 'S' && atoi(req+2) == num) cp->disabled=0;
|
||||||
if(sel[0] == 'D' && atoi(sel+1) == num) cp->disabled=1;
|
if(req[1] == 'D' && atoi(req+2) == 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,
|
||||||
@ -476,55 +535,85 @@ int op_admin_counters(struct httpreq *r, const unsigned char *params)
|
|||||||
|
|
||||||
}
|
}
|
||||||
printstr(&pp, counterstail);
|
printstr(&pp, counterstail);
|
||||||
|
break;
|
||||||
|
|
||||||
admin_close(&pp, buf);
|
case 'R':
|
||||||
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;
|
||||||
|
}
|
||||||
|
printstr(&pp, xml);
|
||||||
|
printval(conf.services, TYPE_SERVER, 0, &pp);
|
||||||
|
printstr(&pp, postxml);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'F':
|
||||||
|
{
|
||||||
|
FILE *fp;
|
||||||
|
char buf[256];
|
||||||
|
|
||||||
admin_close(&pp, buf);
|
fp = confopen();
|
||||||
return 0;
|
if(!fp){
|
||||||
}
|
printstr(&pp, "<h3><font color=\"red\">Failed to open config file</font></h3>");
|
||||||
|
break;
|
||||||
int op_admin_services(struct httpreq *r, const unsigned char *params)
|
}
|
||||||
{
|
printstr(&pp, "<h3>Please be careful editing config file remotely</h3>");
|
||||||
struct clientparam *param = r->param;
|
printstr(&pp, "<form method=\"POST\" action=\"/U\" enctype=\"application/x-www-form-urlencoded\"><textarea cols=\"80\" rows=\"30\" name=\"conffile\">");
|
||||||
struct printparam pp;
|
while(fgets(buf, 256, fp)){
|
||||||
char *buf;
|
printstr(&pp, buf);
|
||||||
const char *sel;
|
}
|
||||||
|
if(!writable) fclose(fp);
|
||||||
if(param->srv->s_option) return op_admin(r, params);
|
printstr(&pp, "</textarea><br><input type=\"Submit\"></form>");
|
||||||
|
break;
|
||||||
sel = r->path + r->globstart;
|
}
|
||||||
|
case 'U':
|
||||||
/* This page is xml, so it carries its own headers instead of the html
|
{
|
||||||
wrapper the other pages share. */
|
unsigned l=0;
|
||||||
pp.inbuf = 0;
|
int error = 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
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
|
#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);
|
||||||
|
if(buf) free(buf);
|
||||||
|
dolog(param, (unsigned char *)req);
|
||||||
|
if(req)free(req);
|
||||||
|
return (NULL);
|
||||||
|
}
|
||||||
|
|||||||
2
tests/.gitignore
vendored
2
tests/.gitignore
vendored
@ -1,2 +0,0 @@
|
|||||||
__pycache__/
|
|
||||||
*.pyc
|
|
||||||
@ -1,54 +0,0 @@
|
|||||||
# Regression tests
|
|
||||||
|
|
||||||
python3 tests/run.py # every case
|
|
||||||
python3 tests/run.py httpsrv # cases whose name matches
|
|
||||||
python3 tests/run.py --bin build/bin/3proxy
|
|
||||||
python3 tests/run.py -v # print every check
|
|
||||||
python3 tests/run.py --keep # keep the configurations and logs
|
|
||||||
|
|
||||||
Python 3.6 or later and a built 3proxy are the only requirements: the suite
|
|
||||||
is standard library throughout, so it runs wherever 3proxy builds. With no
|
|
||||||
`--bin` it looks in `bin/`, then `build/bin/`, then the per-configuration
|
|
||||||
directories a multi-configuration CMake generator uses.
|
|
||||||
|
|
||||||
The proxy under test is also the origin server the tests talk to: the `http`
|
|
||||||
command's `echo` operation reports back how a request arrived - method, path,
|
|
||||||
query, host, and the source port it came from - and `data` generates a body
|
|
||||||
of a requested size, framing, status and pace. So a case can state what a
|
|
||||||
proxy should do to a request and then read off what actually reached the
|
|
||||||
other side.
|
|
||||||
|
|
||||||
## Adding a case
|
|
||||||
|
|
||||||
A case is a module under `cases/` exporting `run(t)`. It writes the
|
|
||||||
configurations it needs, starts them, and says what it expects:
|
|
||||||
|
|
||||||
```python
|
|
||||||
def run(t):
|
|
||||||
srv = t.free_port()
|
|
||||||
t.start("my_case", f"""
|
|
||||||
log
|
|
||||||
auth iponly
|
|
||||||
allow *
|
|
||||||
http * /echo echo
|
|
||||||
httpsrv -p{srv}
|
|
||||||
""", ports=[srv])
|
|
||||||
|
|
||||||
r = t.http(f"http://127.0.0.1:{srv}/echo")
|
|
||||||
t.eq(200, r.status, "the server answers")
|
|
||||||
t.contains(r, "method=GET", "the method is reported")
|
|
||||||
```
|
|
||||||
|
|
||||||
Servers are stopped for you when the case ends, whether or not it passed.
|
|
||||||
|
|
||||||
`t` offers `http()` (direct, through an HTTP proxy, or over a CONNECT
|
|
||||||
tunnel), `socks_http()` and `socks_connect()` for SOCKS4 and SOCKS5,
|
|
||||||
`socks_udp_associate()`, `raw()` for bytes a real client would never send,
|
|
||||||
and `run_config()` for configurations that are meant to be rejected.
|
|
||||||
Assertions are `eq`, `ne`, `contains`, `not_contains`, `in_range`,
|
|
||||||
`not_in_range`, plus `ok`, `fail` and `skip`. `harness.field()` and
|
|
||||||
`int_field()` pull a single line out of an `echo` reply.
|
|
||||||
|
|
||||||
Note that access rules accumulate until `flush`, so a service section that
|
|
||||||
means to stand on its own should start with one - otherwise an earlier
|
|
||||||
`allow *` matches first and the rule under test is never reached.
|
|
||||||
@ -1,69 +0,0 @@
|
|||||||
"""The admin interface, now a set of handlers on the HTTP server."""
|
|
||||||
|
|
||||||
|
|
||||||
def run(t):
|
|
||||||
adm = t.free_port()
|
|
||||||
lim = t.free_port()
|
|
||||||
t.start("admin", f"""
|
|
||||||
log
|
|
||||||
auth iponly
|
|
||||||
allow *
|
|
||||||
countin 1 D 100 * * *
|
|
||||||
countin 2 D 200 * * *
|
|
||||||
admin -p{adm}
|
|
||||||
|
|
||||||
flush
|
|
||||||
auth iponly
|
|
||||||
allow *
|
|
||||||
admin -p{lim} -s1
|
|
||||||
""", ports=[adm, lim])
|
|
||||||
|
|
||||||
url = f"http://127.0.0.1:{adm}"
|
|
||||||
|
|
||||||
# --- the predefined pages -----------------------------------------
|
|
||||||
t.eq(200, t.http(url + "/").status, "the main page")
|
|
||||||
t.eq(200, t.http(url + "/C").status, "the counters page")
|
|
||||||
t.eq(200, t.http(url + "/R").status, "the reload page")
|
|
||||||
t.eq(200, t.http(url + "/S").status, "the services page")
|
|
||||||
|
|
||||||
counters = t.http(url + "/C")
|
|
||||||
t.contains(counters, "countin", "the counters page names the counter type")
|
|
||||||
t.contains(counters, "<tr>", "the counters page renders a table")
|
|
||||||
t.contains(t.http(url + "/R"), "Reload", "the reload page confirms the request")
|
|
||||||
t.contains(t.http(url + "/S"), "<", "the services page returns markup")
|
|
||||||
|
|
||||||
# --- the menu no longer offers the removed config editor -----------
|
|
||||||
main = t.http(url + "/")
|
|
||||||
t.contains(main, "HREF='/C'", "the menu links to the counters")
|
|
||||||
t.contains(main, "HREF='/R'", "the menu links to reload")
|
|
||||||
t.contains(main, "HREF='/S'", "the menu links to the services")
|
|
||||||
t.not_contains(main, "HREF='/F'",
|
|
||||||
"the menu no longer links to the config editor")
|
|
||||||
|
|
||||||
# /F and /U are gone, so they fall through to the catch-all rule
|
|
||||||
t.eq(200, t.http(url + "/F").status, "the removed /F falls through")
|
|
||||||
t.eq(200, t.http(url + "/U").status, "the removed /U falls through")
|
|
||||||
t.contains(t.http(url + "/F"), "configuration", "/F yields the main page")
|
|
||||||
|
|
||||||
# --- counter control through the glob ------------------------------
|
|
||||||
# /C<action><number> is routed by the /C* rule, the action arriving as
|
|
||||||
# the glob
|
|
||||||
t.http(url + "/CD0")
|
|
||||||
t.contains(t.http(url + "/C"), ">NO<", "a counter can be disabled")
|
|
||||||
t.http(url + "/CS0")
|
|
||||||
t.contains(t.http(url + "/C"), ">YES<", "a counter can be enabled again")
|
|
||||||
|
|
||||||
# --- limited mode ---------------------------------------------------
|
|
||||||
limited = f"http://127.0.0.1:{lim}"
|
|
||||||
t.eq(200, t.http(limited + "/").status, "limited mode serves the main page")
|
|
||||||
t.eq(200, t.http(limited + "/C").status, "limited mode serves the counters")
|
|
||||||
t.not_contains(t.http(limited + "/R"), "Reload scheduled",
|
|
||||||
"limited mode refuses a reload")
|
|
||||||
|
|
||||||
# --- the writable command is gone ------------------------------------
|
|
||||||
output = t.run_config("writable", f"""
|
|
||||||
log
|
|
||||||
writable
|
|
||||||
admin -p{t.free_port()}
|
|
||||||
""")
|
|
||||||
t.contains(output, "Unknown command", "the writable command is rejected")
|
|
||||||
@ -1,49 +0,0 @@
|
|||||||
"""Authentication and access rules in front of the HTTP server."""
|
|
||||||
|
|
||||||
|
|
||||||
def run(t):
|
|
||||||
srv = t.free_port()
|
|
||||||
openport = t.free_port()
|
|
||||||
t.start("httpsrv_auth", f"""
|
|
||||||
log
|
|
||||||
http * /echo echo
|
|
||||||
auth strong
|
|
||||||
users alice:CL:secret bob:CL:hunter2
|
|
||||||
allow alice
|
|
||||||
httpsrv -p{srv}
|
|
||||||
|
|
||||||
flush
|
|
||||||
http * /echo echo
|
|
||||||
auth iponly
|
|
||||||
allow *
|
|
||||||
httpsrv -p{openport}
|
|
||||||
""", ports=[srv, openport])
|
|
||||||
|
|
||||||
url = f"http://127.0.0.1:{srv}"
|
|
||||||
|
|
||||||
r = t.http(url + "/echo")
|
|
||||||
t.eq(401, r.status, "no credentials gives 401")
|
|
||||||
t.ne(None, r.header("WWW-Authenticate"),
|
|
||||||
"the 401 carries a WWW-Authenticate header")
|
|
||||||
|
|
||||||
t.eq(200, t.http(url + "/echo", auth=("alice", "secret")).status,
|
|
||||||
"valid credentials pass")
|
|
||||||
t.eq(401, t.http(url + "/echo", auth=("alice", "wrong")).status,
|
|
||||||
"a wrong password gives 401")
|
|
||||||
t.eq(401, t.http(url + "/echo", auth=("nobody", "secret")).status,
|
|
||||||
"an unknown user gives 401")
|
|
||||||
|
|
||||||
# bob authenticates, but no rule admits him
|
|
||||||
t.eq(403, t.http(url + "/echo", auth=("bob", "hunter2")).status,
|
|
||||||
"authenticated but not allowed gives 403")
|
|
||||||
|
|
||||||
# authentication comes before dispatch, so an unmatched URL still needs it
|
|
||||||
t.eq(401, t.http(url + "/nosuchpath").status,
|
|
||||||
"authentication precedes the rule lookup")
|
|
||||||
|
|
||||||
# the second service kept its own iponly authentication
|
|
||||||
t.eq(200, t.http(f"http://127.0.0.1:{openport}/echo").status,
|
|
||||||
"the open service needs no credentials")
|
|
||||||
|
|
||||||
t.contains(t.http(url + "/echo", auth=("alice", "secret")), "path=/echo",
|
|
||||||
"an authenticated request is dispatched")
|
|
||||||
@ -1,78 +0,0 @@
|
|||||||
"""The built-in HTTP server: the echo and data operations."""
|
|
||||||
|
|
||||||
import time
|
|
||||||
|
|
||||||
|
|
||||||
def run(t):
|
|
||||||
srv = t.free_port()
|
|
||||||
t.start("httpsrv_ops", f"""
|
|
||||||
log
|
|
||||||
auth iponly
|
|
||||||
allow *
|
|
||||||
http * /echo* echo
|
|
||||||
http * /data data
|
|
||||||
http * /small data size=64
|
|
||||||
httpsrv -p{srv}
|
|
||||||
""", ports=[srv])
|
|
||||||
|
|
||||||
url = f"http://127.0.0.1:{srv}"
|
|
||||||
|
|
||||||
# --- echo: request introspection ---------------------------------
|
|
||||||
r = t.http(url + "/echo?a=1")
|
|
||||||
t.eq(200, r.status, "echo answers 200")
|
|
||||||
t.contains(r, "method=GET", "echo reports the method")
|
|
||||||
t.contains(r, "path=/echo", "echo reports the path")
|
|
||||||
t.contains(r, "query=a=1", "echo reports the query")
|
|
||||||
t.contains(r, "peer.addr=127.0.0.1", "echo reports the peer address")
|
|
||||||
t.contains(r, f"host=127.0.0.1:{srv}", "echo reports the Host header")
|
|
||||||
|
|
||||||
# the glob is the wildcard-matched tail, which is how admin routes its
|
|
||||||
# sub-pages
|
|
||||||
r = t.http(url + "/echoXYZ")
|
|
||||||
t.contains(r, "glob=XYZ", "echo reports the glob text")
|
|
||||||
t.contains(r, "glob.len=3", "echo reports the glob length")
|
|
||||||
|
|
||||||
# --- data: generated payload -------------------------------------
|
|
||||||
t.eq(1000, t.http(url + "/data?size=1000").length, "data honours size")
|
|
||||||
t.eq(0, t.http(url + "/data?size=0").length, "data size=0 sends an empty body")
|
|
||||||
t.eq(64, t.http(url + "/small").length, "data takes its size from the rule")
|
|
||||||
t.eq(1000, t.http(url + "/small?size=1000").length,
|
|
||||||
"the query overrides the rule parameters")
|
|
||||||
|
|
||||||
# a size past one block exercises the send loop
|
|
||||||
t.eq(70000, t.http(url + "/data?size=70000").length,
|
|
||||||
"data spans several blocks")
|
|
||||||
t.eq(70000, t.http(url + "/data?size=70000&block=1024").length,
|
|
||||||
"data honours the block size")
|
|
||||||
|
|
||||||
# --- status and framing ------------------------------------------
|
|
||||||
t.eq(404, t.http(url + "/data?size=10&status=404").status,
|
|
||||||
"data honours the status")
|
|
||||||
t.eq(503, t.http(url + "/data?size=10&status=503").status,
|
|
||||||
"data returns 503 when asked")
|
|
||||||
t.eq(200, t.http(url + "/data?size=10&status=99").status,
|
|
||||||
"an out-of-range status falls back to 200")
|
|
||||||
|
|
||||||
r = t.http(url + "/data?size=100")
|
|
||||||
t.eq("100", r.header("Content-Length"), "an identity reply sets Content-Length")
|
|
||||||
|
|
||||||
r = t.http(url + "/data?size=100&chunked=1")
|
|
||||||
t.eq("chunked", r.header("Transfer-Encoding"),
|
|
||||||
"a chunked reply sets Transfer-Encoding")
|
|
||||||
t.eq(None, r.header("Content-Length"),
|
|
||||||
"a chunked reply omits Content-Length")
|
|
||||||
t.eq(100, r.length, "a chunked body decodes to the size asked for")
|
|
||||||
t.eq(70000, t.http(url + "/data?size=70000&chunked=1").length,
|
|
||||||
"a chunked body spans several blocks")
|
|
||||||
|
|
||||||
# --- delay --------------------------------------------------------
|
|
||||||
start = time.time()
|
|
||||||
t.http(url + "/data?size=4096&block=1024&delay=100")
|
|
||||||
elapsed = time.time() - start
|
|
||||||
if elapsed >= 0.3:
|
|
||||||
t.ok("delay slows the transfer")
|
|
||||||
else:
|
|
||||||
t.fail("delay slows the transfer", ">=0.3s", f"{elapsed:.2f}s")
|
|
||||||
|
|
||||||
# --- unmatched ----------------------------------------------------
|
|
||||||
t.eq(404, t.http(url + "/nosuchthing").status, "an unmatched URL gives 404")
|
|
||||||
@ -1,64 +0,0 @@
|
|||||||
"""Request parsing: decoding, path safety, malformed and oversized input.
|
|
||||||
|
|
||||||
These go over a raw socket, because a well-behaved client would normalise
|
|
||||||
most of them away before they ever reached the server.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
def run(t):
|
|
||||||
srv = t.free_port()
|
|
||||||
t.start("httpsrv_parsing", f"""
|
|
||||||
log
|
|
||||||
auth iponly
|
|
||||||
allow *
|
|
||||||
http * /echo* echo
|
|
||||||
http * /safe/* echo
|
|
||||||
httpsrv -p{srv}
|
|
||||||
""", ports=[srv])
|
|
||||||
|
|
||||||
def request(path, host="t", extra=""):
|
|
||||||
return t.raw(srv, f"GET {path} HTTP/1.0\r\nHost: {host}\r\n{extra}\r\n")
|
|
||||||
|
|
||||||
# --- percent-decoding ---------------------------------------------
|
|
||||||
reply = request("/%65cho")
|
|
||||||
t.contains(reply, "200 OK", "a percent-encoded path is decoded before matching")
|
|
||||||
t.contains(reply, "path=/echo", "the decoded path is what gets reported")
|
|
||||||
t.contains(request("/echo%20space"), "glob= space",
|
|
||||||
"an encoded space decodes into the glob")
|
|
||||||
|
|
||||||
# --- traversal -----------------------------------------------------
|
|
||||||
for path in ("/safe/../etc/passwd", "/safe/%2e%2e/etc", "/safe/..%2fetc",
|
|
||||||
"/echo/../../x"):
|
|
||||||
t.not_contains(request(path), "200 OK", f"traversal is refused: {path}")
|
|
||||||
|
|
||||||
t.contains(request("/safe/./ok"), "200 OK",
|
|
||||||
"a harmless dot segment is still served")
|
|
||||||
|
|
||||||
# --- injection ------------------------------------------------------
|
|
||||||
t.not_contains(request("/echo%0d%0aInjected:%20yes"), "Injected: yes",
|
|
||||||
"an encoded CRLF cannot inject a header")
|
|
||||||
t.not_contains(request("/echo%00cut"), "200 OK", "an encoded NUL is refused")
|
|
||||||
|
|
||||||
# a header value cannot smuggle a newline into the echoed output
|
|
||||||
reply = request("/echo", host="evil", extra="X-Injected: yes\r\n")
|
|
||||||
t.not_contains(reply, "host=evil\nX-Injected",
|
|
||||||
"header values stay in their own fields")
|
|
||||||
|
|
||||||
# --- malformed ------------------------------------------------------
|
|
||||||
t.not_contains(t.raw(srv, "GARBAGE\r\n\r\n"), "200 OK",
|
|
||||||
"a malformed request line is not served")
|
|
||||||
t.not_contains(t.raw(srv, "GET\r\n\r\n"), "200 OK",
|
|
||||||
"a request line with no URL is not served")
|
|
||||||
|
|
||||||
# an over-long path has to be refused rather than quietly truncated to
|
|
||||||
# something shorter that might match another rule
|
|
||||||
t.not_contains(request("/echo" + "a" * 9000), "200 OK",
|
|
||||||
"an over-long path is refused, not truncated")
|
|
||||||
|
|
||||||
# --- methods --------------------------------------------------------
|
|
||||||
url = f"http://127.0.0.1:{srv}"
|
|
||||||
t.eq(200, t.http(url + "/echo", method="HEAD").status, "HEAD is accepted")
|
|
||||||
r = t.http(url + "/echo", method="POST", body="payload=1",
|
|
||||||
headers={"Content-Type": "application/x-www-form-urlencoded"})
|
|
||||||
t.contains(r, "method=POST", "POST reaches the handler")
|
|
||||||
t.contains(r, "content.length=9", "the POST content length is parsed")
|
|
||||||
@ -1,69 +0,0 @@
|
|||||||
"""Rule dispatch: host and URL patterns, and per-service rule sets."""
|
|
||||||
|
|
||||||
|
|
||||||
def run(t):
|
|
||||||
srv = t.free_port()
|
|
||||||
srv2 = t.free_port()
|
|
||||||
t.start("httpsrv_rules", f"""
|
|
||||||
log
|
|
||||||
auth iponly
|
|
||||||
allow *
|
|
||||||
http * /exact echo
|
|
||||||
http * /pre* echo
|
|
||||||
http * *.suffix echo
|
|
||||||
http * *mid* echo
|
|
||||||
http host.example.com /byhost echo
|
|
||||||
http *.wild.example.com /bywild echo
|
|
||||||
http * /only-first echo
|
|
||||||
httpsrv -p{srv}
|
|
||||||
|
|
||||||
flush
|
|
||||||
auth iponly
|
|
||||||
allow *
|
|
||||||
http * /only-second echo
|
|
||||||
httpsrv -p{srv2}
|
|
||||||
""", ports=[srv, srv2])
|
|
||||||
|
|
||||||
url = f"http://127.0.0.1:{srv}"
|
|
||||||
|
|
||||||
# --- URL patterns -------------------------------------------------
|
|
||||||
t.eq(200, t.http(url + "/exact").status, "an exact URL matches")
|
|
||||||
t.eq(404, t.http(url + "/exactly").status,
|
|
||||||
"an exact URL does not match a longer path")
|
|
||||||
t.eq(200, t.http(url + "/pre").status, "a prefix matches the bare prefix")
|
|
||||||
t.eq(200, t.http(url + "/pretty/deep").status,
|
|
||||||
"a prefix matches a longer path")
|
|
||||||
t.eq(200, t.http(url + "/any.suffix").status, "a suffix matches")
|
|
||||||
t.eq(404, t.http(url + "/any.suffixx").status,
|
|
||||||
"a suffix is anchored at the end")
|
|
||||||
t.eq(200, t.http(url + "/xxmidxx").status, "a substring matches")
|
|
||||||
t.eq(404, t.http(url + "/nomatch").status, "an unmatched URL gives 404")
|
|
||||||
|
|
||||||
# --- host patterns ------------------------------------------------
|
|
||||||
def with_host(path, host):
|
|
||||||
return t.http(url + path, headers={"Host": host})
|
|
||||||
|
|
||||||
t.eq(200, with_host("/byhost", "host.example.com").status,
|
|
||||||
"an exact host matches")
|
|
||||||
t.eq(404, with_host("/byhost", "other.example.com").status,
|
|
||||||
"another host does not match")
|
|
||||||
t.eq(200, with_host("/bywild", "a.wild.example.com").status,
|
|
||||||
"a wildcard host matches")
|
|
||||||
t.eq(404, with_host("/bywild", "a.other.example.com").status,
|
|
||||||
"a wildcard host rejects another domain")
|
|
||||||
|
|
||||||
# the rules are ordered, and the first match wins
|
|
||||||
t.contains(t.http(url + "/exact"), "path=/exact",
|
|
||||||
"the first matching rule handles the request")
|
|
||||||
|
|
||||||
# --- per-service rule sets ----------------------------------------
|
|
||||||
# Rules accumulate until a service starts, which takes them; later rules
|
|
||||||
# belong to the next service only.
|
|
||||||
t.eq(200, t.http(f"http://127.0.0.1:{srv}/only-first").status,
|
|
||||||
"the first service has its own rules")
|
|
||||||
t.eq(404, t.http(f"http://127.0.0.1:{srv}/only-second").status,
|
|
||||||
"the first service does not have the later rules")
|
|
||||||
t.eq(200, t.http(f"http://127.0.0.1:{srv2}/only-second").status,
|
|
||||||
"the second service has its own rules")
|
|
||||||
t.eq(404, t.http(f"http://127.0.0.1:{srv2}/only-first").status,
|
|
||||||
"the second service does not have the earlier rules")
|
|
||||||
@ -1,167 +0,0 @@
|
|||||||
"""extport and intport: binding the local side of a connection to a range.
|
|
||||||
|
|
||||||
Access rules accumulate until "flush": without it an earlier "allow *"
|
|
||||||
matches first and the rule carrying the range is never reached.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from harness import int_field
|
|
||||||
|
|
||||||
|
|
||||||
def _windows():
|
|
||||||
"""Pick port windows this platform will actually honour.
|
|
||||||
|
|
||||||
On Linux the kernel applies IP_LOCAL_PORT_RANGE only within
|
|
||||||
net.ipv4.ip_local_port_range; a window outside it is ignored and an
|
|
||||||
ordinary ephemeral port is used, so a fixed low window would be
|
|
||||||
measuring the kernel's own choice rather than the setting.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
with open("/proc/sys/net/ipv4/ip_local_port_range") as fp:
|
|
||||||
low, high = (int(part) for part in fp.read().split()[:2])
|
|
||||||
except (OSError, ValueError):
|
|
||||||
return (21400, 21449), (21500, 21549)
|
|
||||||
base = low + 1000 if low + 1150 <= high else low
|
|
||||||
return (base, base + 49), (base + 100, base + 149)
|
|
||||||
|
|
||||||
|
|
||||||
(LOW, HIGH), (ILOW, IHIGH) = _windows()
|
|
||||||
|
|
||||||
# below the Linux window on purpose: the kernel ignores such a range
|
|
||||||
UNHONOURED = (21400, 21449)
|
|
||||||
|
|
||||||
|
|
||||||
def run(t):
|
|
||||||
srv = t.free_port()
|
|
||||||
prx = t.free_port()
|
|
||||||
sks = t.free_port()
|
|
||||||
meth = t.free_port()
|
|
||||||
|
|
||||||
t.start("parent_ports", f"""
|
|
||||||
log
|
|
||||||
auth iponly
|
|
||||||
allow *
|
|
||||||
http * /echo* echo
|
|
||||||
httpsrv -p{srv}
|
|
||||||
|
|
||||||
# every outgoing connection binds inside the range
|
|
||||||
flush
|
|
||||||
auth iponly
|
|
||||||
allow *
|
|
||||||
parent 1000 extport 0.0.0.0 {LOW}-{HIGH}
|
|
||||||
proxy -p{prx}
|
|
||||||
|
|
||||||
# the range applies only to CONNECT: an HTTP proxy CONNECT is
|
|
||||||
# HTTP_CONNECT, the bare CONNECT operation being the SOCKS one
|
|
||||||
flush
|
|
||||||
auth iponly
|
|
||||||
allow * * * * HTTP_CONNECT
|
|
||||||
parent 1000 extport 0.0.0.0 {LOW}-{HIGH}
|
|
||||||
allow *
|
|
||||||
proxy -p{meth}
|
|
||||||
|
|
||||||
# socks, for the same setting on another service
|
|
||||||
flush
|
|
||||||
auth iponly
|
|
||||||
allow *
|
|
||||||
parent 1000 extport 0.0.0.0 {LOW}-{HIGH}
|
|
||||||
socks -p{sks}
|
|
||||||
""", ports=[srv, prx, sks, meth])
|
|
||||||
|
|
||||||
origin = f"http://127.0.0.1:{srv}"
|
|
||||||
proxy = f"127.0.0.1:{prx}"
|
|
||||||
|
|
||||||
# --- extport ---------------------------------------------------------
|
|
||||||
# the origin reports the source port it actually saw
|
|
||||||
port = int_field(t.http(origin + "/echo", proxy=proxy), "peer.port")
|
|
||||||
t.in_range(port, LOW, HIGH, "the outgoing connection binds inside the range")
|
|
||||||
|
|
||||||
seen = []
|
|
||||||
for _ in range(5):
|
|
||||||
seen.append(int_field(t.http(origin + "/echo", proxy=proxy), "peer.port"))
|
|
||||||
outside = [p for p in seen if p is None or not LOW <= p <= HIGH]
|
|
||||||
t.eq([], outside, "repeated connections all bind inside the range")
|
|
||||||
|
|
||||||
port = int_field(t.socks_http(f"127.0.0.1:{sks}", origin + "/echo"),
|
|
||||||
"peer.port")
|
|
||||||
t.in_range(port, LOW, HIGH,
|
|
||||||
"socks binds the outgoing connection inside the range")
|
|
||||||
|
|
||||||
# --- per-method scoping ------------------------------------------------
|
|
||||||
method_proxy = f"127.0.0.1:{meth}"
|
|
||||||
port = int_field(t.http(origin + "/echo", proxy=method_proxy, tunnel=True),
|
|
||||||
"peer.port")
|
|
||||||
t.in_range(port, LOW, HIGH, "CONNECT uses the range its rule sets")
|
|
||||||
|
|
||||||
# a plain GET matches the later rule, which sets no range
|
|
||||||
port = int_field(t.http(origin + "/echo", proxy=method_proxy), "peer.port")
|
|
||||||
t.not_in_range(port, LOW, HIGH,
|
|
||||||
"a method outside that rule keeps an ephemeral port")
|
|
||||||
|
|
||||||
# --- a range the platform cannot honour --------------------------------
|
|
||||||
# Linux ignores a range outside net.ipv4.ip_local_port_range, and any
|
|
||||||
# platform can run out of free ports in a range. Either way the
|
|
||||||
# connection falls back to an ephemeral port instead of failing.
|
|
||||||
unhonoured = t.free_port()
|
|
||||||
t.start("parent_unhonoured", f"""
|
|
||||||
log
|
|
||||||
flush
|
|
||||||
auth iponly
|
|
||||||
allow *
|
|
||||||
parent 1000 extport 0.0.0.0 {UNHONOURED[0]}-{UNHONOURED[1]}
|
|
||||||
proxy -p{unhonoured}
|
|
||||||
""", ports=[unhonoured])
|
|
||||||
r = t.http(origin + "/echo", proxy=f"127.0.0.1:{unhonoured}")
|
|
||||||
t.eq(200, r.status, "a range the platform cannot honour still connects")
|
|
||||||
t.ne(None, int_field(r, "peer.port"),
|
|
||||||
"the connection still has a source port")
|
|
||||||
|
|
||||||
# --- intport -----------------------------------------------------------
|
|
||||||
# A UDP association allocates its socket after the destination is known,
|
|
||||||
# so the range has to be applied when the rule matches rather than when
|
|
||||||
# the chain is walked.
|
|
||||||
udps = t.free_port()
|
|
||||||
t.start("parent_intport", f"""
|
|
||||||
log
|
|
||||||
flush
|
|
||||||
auth iponly
|
|
||||||
allow *
|
|
||||||
parent 1000 intport 0.0.0.0 {ILOW}-{IHIGH}
|
|
||||||
socks -p{udps}
|
|
||||||
""", ports=[udps])
|
|
||||||
t.in_range(t.socks_udp_associate(udps), ILOW, IHIGH,
|
|
||||||
"UDP ASSOCIATE binds inside the internal range")
|
|
||||||
|
|
||||||
# without a range the association still works, on an ephemeral port
|
|
||||||
udps2 = t.free_port()
|
|
||||||
t.start("parent_intport_none", f"""
|
|
||||||
log
|
|
||||||
flush
|
|
||||||
auth iponly
|
|
||||||
allow *
|
|
||||||
socks -p{udps2}
|
|
||||||
""", ports=[udps2])
|
|
||||||
t.ne(None, t.socks_udp_associate(udps2),
|
|
||||||
"UDP ASSOCIATE works without a range")
|
|
||||||
|
|
||||||
# --- configuration errors ------------------------------------------------
|
|
||||||
dead = t.free_port()
|
|
||||||
t.contains(t.run_config("badaddr", f"""
|
|
||||||
log
|
|
||||||
allow *
|
|
||||||
parent 1000 extport 127.0.0.1 {LOW}-{HIGH}
|
|
||||||
proxy -p{dead}
|
|
||||||
"""), "requires 0.0.0.0", "a non-zero address with extport is rejected")
|
|
||||||
|
|
||||||
t.contains(t.run_config("badrange", f"""
|
|
||||||
log
|
|
||||||
allow *
|
|
||||||
parent 1000 extport 0.0.0.0 notaport
|
|
||||||
proxy -p{dead}
|
|
||||||
"""), "bad port range", "a malformed range is rejected")
|
|
||||||
|
|
||||||
t.contains(t.run_config("badorder", f"""
|
|
||||||
log
|
|
||||||
allow *
|
|
||||||
parent 1000 extport 0.0.0.0 {HIGH}-{LOW}
|
|
||||||
proxy -p{dead}
|
|
||||||
"""), "bad port range", "a reversed range is rejected")
|
|
||||||
@ -1,108 +0,0 @@
|
|||||||
"""The HTTP proxy, with the built-in server as the origin.
|
|
||||||
|
|
||||||
Access rules accumulate until "flush", so each service section here starts
|
|
||||||
from a clean list.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
def run(t):
|
|
||||||
srv = t.free_port()
|
|
||||||
other = t.free_port()
|
|
||||||
prx = t.free_port()
|
|
||||||
deny = t.free_port()
|
|
||||||
auth = t.free_port()
|
|
||||||
|
|
||||||
t.start("proxy_http", f"""
|
|
||||||
log
|
|
||||||
auth iponly
|
|
||||||
allow *
|
|
||||||
http * /echo* echo
|
|
||||||
http * /data data
|
|
||||||
httpsrv -p{srv}
|
|
||||||
|
|
||||||
# a second origin, used as a destination the rules must keep out
|
|
||||||
flush
|
|
||||||
auth iponly
|
|
||||||
allow *
|
|
||||||
http * /echo* echo
|
|
||||||
httpsrv -p{other}
|
|
||||||
|
|
||||||
# an open proxy
|
|
||||||
flush
|
|
||||||
auth iponly
|
|
||||||
allow *
|
|
||||||
proxy -p{prx}
|
|
||||||
|
|
||||||
# only the first origin is reachable
|
|
||||||
flush
|
|
||||||
auth iponly
|
|
||||||
allow * * * {srv}
|
|
||||||
proxy -p{deny}
|
|
||||||
|
|
||||||
# credentials required
|
|
||||||
flush
|
|
||||||
auth strong
|
|
||||||
users alice:CL:secret
|
|
||||||
allow alice
|
|
||||||
proxy -p{auth}
|
|
||||||
""", ports=[srv, other, prx, deny, auth])
|
|
||||||
|
|
||||||
origin = f"http://127.0.0.1:{srv}"
|
|
||||||
second = f"http://127.0.0.1:{other}"
|
|
||||||
open_proxy = f"127.0.0.1:{prx}"
|
|
||||||
|
|
||||||
# --- plain proxying -------------------------------------------------
|
|
||||||
r = t.http(origin + "/echo", proxy=open_proxy)
|
|
||||||
t.eq(200, r.status, "a GET through the proxy")
|
|
||||||
t.contains(r, "path=/echo", "the origin sees the proxied path")
|
|
||||||
t.contains(r, "peer.addr=127.0.0.1", "the origin sees the proxy as the peer")
|
|
||||||
|
|
||||||
t.eq(10000, t.http(origin + "/data?size=10000", proxy=open_proxy).length,
|
|
||||||
"a sized body survives proxying")
|
|
||||||
t.eq(10000,
|
|
||||||
t.http(origin + "/data?size=10000&chunked=1", proxy=open_proxy).length,
|
|
||||||
"a chunked body survives proxying")
|
|
||||||
t.eq(503, t.http(origin + "/data?size=5&status=503", proxy=open_proxy).status,
|
|
||||||
"the origin status is relayed")
|
|
||||||
|
|
||||||
# --- POST and keep-alive ---------------------------------------------
|
|
||||||
r = t.http(origin + "/echo", proxy=open_proxy, method="POST", body="x=1")
|
|
||||||
t.contains(r, "method=POST", "POST is proxied")
|
|
||||||
|
|
||||||
# two requests on one connection, which may carry different methods
|
|
||||||
conn = t.connection("127.0.0.1", srv, proxy=open_proxy)
|
|
||||||
try:
|
|
||||||
first = t.http(origin + "/echo", proxy=open_proxy, method="POST",
|
|
||||||
body="x=1", conn=conn)
|
|
||||||
second_reply = t.http(origin + "/echo", proxy=open_proxy, conn=conn)
|
|
||||||
t.eq((200, 200), (first.status, second_reply.status),
|
|
||||||
"two requests on one proxied connection")
|
|
||||||
finally:
|
|
||||||
conn.close()
|
|
||||||
|
|
||||||
# --- CONNECT ----------------------------------------------------------
|
|
||||||
t.eq(200, t.http(origin + "/echo", proxy=open_proxy, tunnel=True).status,
|
|
||||||
"CONNECT tunnels to the origin")
|
|
||||||
|
|
||||||
# --- access control ----------------------------------------------------
|
|
||||||
denying = f"127.0.0.1:{deny}"
|
|
||||||
t.eq(200, t.http(origin + "/echo", proxy=denying).status,
|
|
||||||
"the permitted destination is reachable")
|
|
||||||
t.ne(200, t.http(second + "/echo", proxy=denying).status,
|
|
||||||
"a destination outside the rules is refused")
|
|
||||||
t.ne(200, t.http(second + "/echo", proxy=denying, tunnel=True).status,
|
|
||||||
"CONNECT to a destination outside the rules is refused")
|
|
||||||
# the open proxy still reaches it, so the refusal came from the rules
|
|
||||||
t.eq(200, t.http(second + "/echo", proxy=open_proxy).status,
|
|
||||||
"the same destination is reachable through the open proxy")
|
|
||||||
|
|
||||||
# --- proxy authentication -----------------------------------------------
|
|
||||||
needs_auth = f"127.0.0.1:{auth}"
|
|
||||||
t.eq(407, t.http(origin + "/echo", proxy=needs_auth).status,
|
|
||||||
"the proxy demands credentials")
|
|
||||||
t.eq(200, t.http(origin + "/echo", proxy=needs_auth,
|
|
||||||
proxy_auth=("alice", "secret")).status,
|
|
||||||
"valid proxy credentials pass")
|
|
||||||
t.eq(407, t.http(origin + "/echo", proxy=needs_auth,
|
|
||||||
proxy_auth=("alice", "wrong")).status,
|
|
||||||
"wrong proxy credentials are refused")
|
|
||||||
@ -1,57 +0,0 @@
|
|||||||
"""The SOCKS proxy, reaching the built-in server."""
|
|
||||||
|
|
||||||
|
|
||||||
def run(t):
|
|
||||||
srv = t.free_port()
|
|
||||||
sks = t.free_port()
|
|
||||||
sauth = t.free_port()
|
|
||||||
|
|
||||||
t.start("socks", f"""
|
|
||||||
log
|
|
||||||
auth iponly
|
|
||||||
allow *
|
|
||||||
http * /echo* echo
|
|
||||||
http * /data data
|
|
||||||
httpsrv -p{srv}
|
|
||||||
|
|
||||||
flush
|
|
||||||
auth iponly
|
|
||||||
allow *
|
|
||||||
socks -p{sks}
|
|
||||||
|
|
||||||
flush
|
|
||||||
auth strong
|
|
||||||
users alice:CL:secret
|
|
||||||
allow alice
|
|
||||||
socks -p{sauth}
|
|
||||||
""", ports=[srv, sks, sauth])
|
|
||||||
|
|
||||||
origin = f"http://127.0.0.1:{srv}"
|
|
||||||
plain = f"127.0.0.1:{sks}"
|
|
||||||
guarded = f"127.0.0.1:{sauth}"
|
|
||||||
|
|
||||||
# --- SOCKS5 ---------------------------------------------------------
|
|
||||||
r = t.socks_http(plain, origin + "/echo")
|
|
||||||
t.eq(200, r.status, "a SOCKS5 connection")
|
|
||||||
t.contains(r, "path=/echo", "the origin sees the request made over SOCKS5")
|
|
||||||
t.eq(10000, t.socks_http(plain, origin + "/data?size=10000").length,
|
|
||||||
"a body survives SOCKS5")
|
|
||||||
|
|
||||||
# resolution delegated to the proxy
|
|
||||||
t.eq(200, t.socks_http(plain, f"http://localhost:{srv}/echo",
|
|
||||||
remote_dns=True).status,
|
|
||||||
"SOCKS5 resolves the hostname itself")
|
|
||||||
|
|
||||||
# --- SOCKS4 -----------------------------------------------------------
|
|
||||||
t.eq(200, t.socks_http(plain, origin + "/echo", socks4=True).status,
|
|
||||||
"a SOCKS4 connection")
|
|
||||||
|
|
||||||
# --- authentication ----------------------------------------------------
|
|
||||||
t.eq(200, t.socks_http(guarded, origin + "/echo",
|
|
||||||
auth=("alice", "secret")).status,
|
|
||||||
"valid SOCKS5 credentials pass")
|
|
||||||
t.ne(None, t.socks_connect(guarded, "127.0.0.1", srv,
|
|
||||||
auth=("alice", "wrong")),
|
|
||||||
"wrong SOCKS5 credentials are refused")
|
|
||||||
t.ne(None, t.socks_connect(guarded, "127.0.0.1", srv),
|
|
||||||
"SOCKS5 without credentials is refused")
|
|
||||||
476
tests/harness.py
476
tests/harness.py
@ -1,476 +0,0 @@
|
|||||||
"""Support code for the 3proxy regression tests.
|
|
||||||
|
|
||||||
Everything here is standard library, so the suite runs wherever 3proxy
|
|
||||||
builds: no shell, no curl, no netcat.
|
|
||||||
|
|
||||||
A test case is a module under tests/cases/ exporting run(t). It writes the
|
|
||||||
configurations it needs, starts them, and states what it expects:
|
|
||||||
|
|
||||||
def run(t):
|
|
||||||
srv = t.free_port()
|
|
||||||
t.start("echo", f'''
|
|
||||||
log
|
|
||||||
auth iponly
|
|
||||||
allow *
|
|
||||||
http * /echo echo
|
|
||||||
httpsrv -p{srv}
|
|
||||||
''', ports=[srv])
|
|
||||||
r = t.http(f"http://127.0.0.1:{srv}/echo")
|
|
||||||
t.eq(200, r.status, "the server answers")
|
|
||||||
"""
|
|
||||||
|
|
||||||
import base64
|
|
||||||
import http.client
|
|
||||||
import os
|
|
||||||
import socket
|
|
||||||
import struct
|
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
import textwrap
|
|
||||||
import time
|
|
||||||
|
|
||||||
|
|
||||||
class Response:
|
|
||||||
"""A reply, or the reason there wasn't one."""
|
|
||||||
|
|
||||||
def __init__(self, status=None, body=b"", headers=None, error=None):
|
|
||||||
self.status = status
|
|
||||||
self.body = body
|
|
||||||
self.headers = headers or {}
|
|
||||||
self.error = error
|
|
||||||
|
|
||||||
@property
|
|
||||||
def text(self):
|
|
||||||
return self.body.decode("utf-8", "replace")
|
|
||||||
|
|
||||||
@property
|
|
||||||
def length(self):
|
|
||||||
return len(self.body)
|
|
||||||
|
|
||||||
def header(self, name):
|
|
||||||
for k, v in self.headers.items():
|
|
||||||
if k.lower() == name.lower():
|
|
||||||
return v
|
|
||||||
return None
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
if self.error:
|
|
||||||
return f"<no reply: {self.error}>"
|
|
||||||
return f"<{self.status}, {len(self.body)} bytes>"
|
|
||||||
|
|
||||||
|
|
||||||
class Server:
|
|
||||||
"""A running 3proxy, with the configuration it was given."""
|
|
||||||
|
|
||||||
def __init__(self, name, path, proc, logfile):
|
|
||||||
self.name = name
|
|
||||||
self.path = path
|
|
||||||
self.proc = proc
|
|
||||||
self.logfile = logfile
|
|
||||||
|
|
||||||
def output(self):
|
|
||||||
try:
|
|
||||||
with open(self.logfile, "rb") as fp:
|
|
||||||
return fp.read().decode("utf-8", "replace")
|
|
||||||
except OSError:
|
|
||||||
return ""
|
|
||||||
|
|
||||||
def stop(self):
|
|
||||||
if self.proc.poll() is None:
|
|
||||||
self.proc.terminate()
|
|
||||||
try:
|
|
||||||
self.proc.wait(timeout=5)
|
|
||||||
except subprocess.TimeoutExpired:
|
|
||||||
self.proc.kill()
|
|
||||||
self.proc.wait(timeout=5)
|
|
||||||
|
|
||||||
|
|
||||||
class Failure(Exception):
|
|
||||||
"""Raised when a case cannot go on, e.g. a server refused to start."""
|
|
||||||
|
|
||||||
|
|
||||||
class Tester:
|
|
||||||
"""The API a case runs against: start servers, make requests, assert."""
|
|
||||||
|
|
||||||
def __init__(self, binary, tmpdir, case):
|
|
||||||
self.binary = binary
|
|
||||||
self.tmpdir = tmpdir
|
|
||||||
self.case = case
|
|
||||||
self.servers = []
|
|
||||||
self.checks = []
|
|
||||||
self.timeout = 10
|
|
||||||
self._skipped = 0
|
|
||||||
|
|
||||||
# ---- servers -----------------------------------------------------
|
|
||||||
|
|
||||||
def free_port(self):
|
|
||||||
"""A port nothing is listening on. Closed again before it is used,
|
|
||||||
which is racy in principle and reliable enough in practice."""
|
|
||||||
s = socket.socket()
|
|
||||||
try:
|
|
||||||
s.bind(("127.0.0.1", 0))
|
|
||||||
return s.getsockname()[1]
|
|
||||||
finally:
|
|
||||||
s.close()
|
|
||||||
|
|
||||||
def write_config(self, name, config):
|
|
||||||
path = os.path.join(self.tmpdir, name + ".cfg")
|
|
||||||
text = textwrap.dedent(config).strip() + "\n"
|
|
||||||
# newline="" keeps the line endings as written, rather than letting
|
|
||||||
# Windows turn them into CRLF behind the parser's back
|
|
||||||
with open(path, "w", newline="") as fp:
|
|
||||||
fp.write(text)
|
|
||||||
return path
|
|
||||||
|
|
||||||
def start(self, name, config, ports=()):
|
|
||||||
"""Write a configuration, run it, and wait for its ports to open."""
|
|
||||||
path = self.write_config(name, config)
|
|
||||||
logfile = os.path.join(self.tmpdir, name + ".out")
|
|
||||||
with open(logfile, "wb") as out:
|
|
||||||
proc = subprocess.Popen([self.binary, path], stdout=out,
|
|
||||||
stderr=subprocess.STDOUT)
|
|
||||||
server = Server(name, path, proc, logfile)
|
|
||||||
self.servers.append(server)
|
|
||||||
|
|
||||||
for port in ports:
|
|
||||||
if not self.wait_port(port):
|
|
||||||
code = proc.poll()
|
|
||||||
if code is None:
|
|
||||||
died = "the process is still running"
|
|
||||||
else:
|
|
||||||
died = f"the process exited with code {code}"
|
|
||||||
if os.name == "nt" and code is not None and code & 0xFFFFFFFF == 0xC0000135:
|
|
||||||
died += " (a DLL it needs was not found)"
|
|
||||||
raise Failure(
|
|
||||||
f"{name} never listened on port {port}: {died}\n"
|
|
||||||
f"--- configuration ---\n{open(path).read()}"
|
|
||||||
f"--- output ---\n{server.output()}")
|
|
||||||
return server
|
|
||||||
|
|
||||||
def run_config(self, name, config):
|
|
||||||
"""Run a configuration expected to be rejected; return its output."""
|
|
||||||
path = self.write_config(name, config)
|
|
||||||
done = subprocess.run([self.binary, path], stdout=subprocess.PIPE,
|
|
||||||
stderr=subprocess.STDOUT, timeout=15)
|
|
||||||
return done.stdout.decode("utf-8", "replace")
|
|
||||||
|
|
||||||
def wait_port(self, port, timeout=5.0):
|
|
||||||
deadline = time.time() + timeout
|
|
||||||
while time.time() < deadline:
|
|
||||||
try:
|
|
||||||
with socket.create_connection(("127.0.0.1", port), 0.25):
|
|
||||||
return True
|
|
||||||
except OSError:
|
|
||||||
time.sleep(0.02)
|
|
||||||
return False
|
|
||||||
|
|
||||||
def stop_all(self):
|
|
||||||
for server in self.servers:
|
|
||||||
server.stop()
|
|
||||||
self.servers = []
|
|
||||||
|
|
||||||
# ---- requests ----------------------------------------------------
|
|
||||||
|
|
||||||
def http(self, url, proxy=None, socks=None, socks4=False,
|
|
||||||
remote_dns=False, method="GET", body=None, headers=None,
|
|
||||||
auth=None, proxy_auth=None, tunnel=False, conn=None):
|
|
||||||
"""Make a request, directly or through a proxy, and read the reply.
|
|
||||||
|
|
||||||
proxy "host:port" of an HTTP proxy
|
|
||||||
socks "host:port" of a SOCKS proxy
|
|
||||||
tunnel reach the origin with CONNECT rather than an absolute URI
|
|
||||||
conn reuse a connection returned by connection()
|
|
||||||
"""
|
|
||||||
host, port, path = self._split(url)
|
|
||||||
headers = dict(headers or {})
|
|
||||||
if auth:
|
|
||||||
headers["Authorization"] = self._basic(auth)
|
|
||||||
if proxy_auth:
|
|
||||||
headers["Proxy-Authorization"] = self._basic(proxy_auth)
|
|
||||||
|
|
||||||
own = conn is None
|
|
||||||
try:
|
|
||||||
if own:
|
|
||||||
conn = self.connection(host, port, proxy=proxy, socks=socks,
|
|
||||||
socks4=socks4, remote_dns=remote_dns,
|
|
||||||
tunnel=tunnel)
|
|
||||||
target = path
|
|
||||||
if proxy and not tunnel:
|
|
||||||
target = f"http://{host}:{port}{path}"
|
|
||||||
if body is not None and not isinstance(body, bytes):
|
|
||||||
body = body.encode()
|
|
||||||
conn.request(method, target, body=body, headers=headers)
|
|
||||||
reply = conn.getresponse()
|
|
||||||
data = reply.read()
|
|
||||||
return Response(reply.status, data, dict(reply.getheaders()))
|
|
||||||
except (OSError, http.client.HTTPException) as exc:
|
|
||||||
return Response(error=f"{type(exc).__name__}: {exc}")
|
|
||||||
finally:
|
|
||||||
if own and conn is not None:
|
|
||||||
try:
|
|
||||||
conn.close()
|
|
||||||
except OSError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def connection(self, host, port, proxy=None, socks=None, socks4=False,
|
|
||||||
remote_dns=False, tunnel=False):
|
|
||||||
"""A connection to an origin, kept open for reuse."""
|
|
||||||
if socks:
|
|
||||||
shost, sport = self._hostport(socks)
|
|
||||||
sock = self._socks_connect(shost, sport, host, port,
|
|
||||||
socks4=socks4, remote_dns=remote_dns)
|
|
||||||
conn = http.client.HTTPConnection(host, port, timeout=self.timeout)
|
|
||||||
conn.sock = sock
|
|
||||||
return conn
|
|
||||||
if proxy:
|
|
||||||
phost, pport = self._hostport(proxy)
|
|
||||||
conn = http.client.HTTPConnection(phost, pport, timeout=self.timeout)
|
|
||||||
if tunnel:
|
|
||||||
conn.set_tunnel(host, port)
|
|
||||||
return conn
|
|
||||||
return http.client.HTTPConnection(host, port, timeout=self.timeout)
|
|
||||||
|
|
||||||
def raw(self, port, request, host="127.0.0.1"):
|
|
||||||
"""Send bytes as they are and return whatever comes back."""
|
|
||||||
if not isinstance(request, bytes):
|
|
||||||
request = request.encode("latin-1")
|
|
||||||
try:
|
|
||||||
with socket.create_connection((host, port), self.timeout) as sock:
|
|
||||||
sock.settimeout(self.timeout)
|
|
||||||
sock.sendall(request)
|
|
||||||
chunks = []
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
piece = sock.recv(65536)
|
|
||||||
except OSError:
|
|
||||||
# a timeout, or a reset once the server is done:
|
|
||||||
# either way keep whatever already arrived
|
|
||||||
break
|
|
||||||
if not piece:
|
|
||||||
break
|
|
||||||
chunks.append(piece)
|
|
||||||
return b"".join(chunks).decode("utf-8", "replace")
|
|
||||||
except OSError as exc:
|
|
||||||
return f"<no reply: {exc}>"
|
|
||||||
|
|
||||||
# ---- SOCKS -------------------------------------------------------
|
|
||||||
|
|
||||||
def _socks_connect(self, shost, sport, host, port, socks4=False,
|
|
||||||
remote_dns=False, auth=None):
|
|
||||||
sock = socket.create_connection((shost, sport), self.timeout)
|
|
||||||
sock.settimeout(self.timeout)
|
|
||||||
try:
|
|
||||||
if socks4:
|
|
||||||
addr = socket.inet_aton(socket.gethostbyname(host))
|
|
||||||
sock.sendall(b"\x04\x01" + struct.pack("!H", port) + addr + b"\x00")
|
|
||||||
reply = self._recvall(sock, 8)
|
|
||||||
if len(reply) < 2 or reply[1] != 0x5a:
|
|
||||||
raise OSError("SOCKS4 request refused")
|
|
||||||
return sock
|
|
||||||
|
|
||||||
if auth:
|
|
||||||
sock.sendall(b"\x05\x02\x00\x02")
|
|
||||||
else:
|
|
||||||
sock.sendall(b"\x05\x01\x00")
|
|
||||||
reply = self._recvall(sock, 2)
|
|
||||||
if len(reply) < 2 or reply[0] != 5:
|
|
||||||
raise OSError("SOCKS5 handshake failed")
|
|
||||||
if reply[1] == 0x02:
|
|
||||||
if not auth:
|
|
||||||
raise OSError("SOCKS5 server demands credentials")
|
|
||||||
user, password = auth
|
|
||||||
sock.sendall(b"\x01" + bytes([len(user)]) + user.encode() +
|
|
||||||
bytes([len(password)]) + password.encode())
|
|
||||||
status = self._recvall(sock, 2)
|
|
||||||
if len(status) < 2 or status[1] != 0:
|
|
||||||
raise OSError("SOCKS5 credentials refused")
|
|
||||||
elif reply[1] != 0x00:
|
|
||||||
raise OSError("SOCKS5 offered no acceptable method")
|
|
||||||
|
|
||||||
if remote_dns:
|
|
||||||
target = b"\x03" + bytes([len(host)]) + host.encode()
|
|
||||||
else:
|
|
||||||
target = b"\x01" + socket.inet_aton(socket.gethostbyname(host))
|
|
||||||
sock.sendall(b"\x05\x01\x00" + target + struct.pack("!H", port))
|
|
||||||
reply = self._recvall(sock, 4)
|
|
||||||
if len(reply) < 4 or reply[1] != 0:
|
|
||||||
raise OSError("SOCKS5 request refused")
|
|
||||||
self._read_socks_addr(sock, reply[3])
|
|
||||||
return sock
|
|
||||||
except Exception:
|
|
||||||
sock.close()
|
|
||||||
raise
|
|
||||||
|
|
||||||
def socks_connect(self, socks, host, port, socks4=False, remote_dns=False,
|
|
||||||
auth=None):
|
|
||||||
"""Open a SOCKS connection, reporting failure rather than raising."""
|
|
||||||
shost, sport = self._hostport(socks)
|
|
||||||
try:
|
|
||||||
sock = self._socks_connect(shost, sport, host, port, socks4=socks4,
|
|
||||||
remote_dns=remote_dns, auth=auth)
|
|
||||||
sock.close()
|
|
||||||
return None
|
|
||||||
except OSError as exc:
|
|
||||||
return str(exc)
|
|
||||||
|
|
||||||
def socks_http(self, socks, url, auth=None, **kwargs):
|
|
||||||
"""A request through SOCKS, with optional SOCKS credentials."""
|
|
||||||
host, port, path = self._split(url)
|
|
||||||
shost, sport = self._hostport(socks)
|
|
||||||
try:
|
|
||||||
sock = self._socks_connect(shost, sport, host, port, auth=auth,
|
|
||||||
**kwargs)
|
|
||||||
except OSError as exc:
|
|
||||||
return Response(error=str(exc))
|
|
||||||
conn = http.client.HTTPConnection(host, port, timeout=self.timeout)
|
|
||||||
conn.sock = sock
|
|
||||||
try:
|
|
||||||
conn.request("GET", path)
|
|
||||||
reply = conn.getresponse()
|
|
||||||
return Response(reply.status, reply.read(), dict(reply.getheaders()))
|
|
||||||
except (OSError, http.client.HTTPException) as exc:
|
|
||||||
return Response(error=str(exc))
|
|
||||||
finally:
|
|
||||||
conn.close()
|
|
||||||
|
|
||||||
def socks_udp_associate(self, port, host="127.0.0.1"):
|
|
||||||
"""Ask for a UDP association and report the port handed back.
|
|
||||||
|
|
||||||
That socket is allocated per association, which is where an intport
|
|
||||||
range has to take effect.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
with socket.create_connection((host, port), self.timeout) as sock:
|
|
||||||
sock.settimeout(self.timeout)
|
|
||||||
sock.sendall(b"\x05\x01\x00")
|
|
||||||
if self._recvall(sock, 2) != b"\x05\x00":
|
|
||||||
return None
|
|
||||||
sock.sendall(b"\x05\x03\x00\x01\x00\x00\x00\x00" +
|
|
||||||
struct.pack("!H", 0))
|
|
||||||
reply = self._recvall(sock, 4)
|
|
||||||
if len(reply) < 4 or reply[1] != 0:
|
|
||||||
return None
|
|
||||||
_, bound = self._read_socks_addr(sock, reply[3])
|
|
||||||
return bound
|
|
||||||
except OSError:
|
|
||||||
return None
|
|
||||||
|
|
||||||
def _read_socks_addr(self, sock, atyp):
|
|
||||||
if atyp == 1:
|
|
||||||
addr = socket.inet_ntoa(self._recvall(sock, 4))
|
|
||||||
elif atyp == 3:
|
|
||||||
length = self._recvall(sock, 1)[0]
|
|
||||||
addr = self._recvall(sock, length).decode()
|
|
||||||
elif atyp == 4:
|
|
||||||
addr = self._recvall(sock, 16).hex()
|
|
||||||
else:
|
|
||||||
raise OSError(f"unknown SOCKS address type {atyp}")
|
|
||||||
port = struct.unpack("!H", self._recvall(sock, 2))[0]
|
|
||||||
return addr, port
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _recvall(sock, count):
|
|
||||||
data = b""
|
|
||||||
while len(data) < count:
|
|
||||||
piece = sock.recv(count - len(data))
|
|
||||||
if not piece:
|
|
||||||
break
|
|
||||||
data += piece
|
|
||||||
return data
|
|
||||||
|
|
||||||
# ---- helpers -----------------------------------------------------
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _basic(credentials):
|
|
||||||
user, password = credentials
|
|
||||||
token = base64.b64encode(f"{user}:{password}".encode()).decode()
|
|
||||||
return "Basic " + token
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _hostport(value):
|
|
||||||
host, _, port = value.rpartition(":")
|
|
||||||
return host or "127.0.0.1", int(port)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _split(url):
|
|
||||||
prefix = "http://"
|
|
||||||
if url.startswith(prefix):
|
|
||||||
url = url[len(prefix):]
|
|
||||||
authority, _, path = url.partition("/")
|
|
||||||
host, _, port = authority.rpartition(":")
|
|
||||||
return host or "127.0.0.1", int(port), "/" + path
|
|
||||||
|
|
||||||
# ---- assertions --------------------------------------------------
|
|
||||||
|
|
||||||
def _record(self, passed, label, expected=None, actual=None):
|
|
||||||
self.checks.append((passed, label, expected, actual))
|
|
||||||
return passed
|
|
||||||
|
|
||||||
def ok(self, label):
|
|
||||||
return self._record(True, label)
|
|
||||||
|
|
||||||
def fail(self, label, expected=None, actual=None):
|
|
||||||
return self._record(False, label, expected, actual)
|
|
||||||
|
|
||||||
def eq(self, expected, actual, label):
|
|
||||||
return self._record(expected == actual, label, expected, actual)
|
|
||||||
|
|
||||||
def ne(self, unexpected, actual, label):
|
|
||||||
return self._record(unexpected != actual, label,
|
|
||||||
f"anything but {unexpected!r}", actual)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _as_text(value):
|
|
||||||
"""A reply that never arrived has no text, so report the reason."""
|
|
||||||
if isinstance(value, Response):
|
|
||||||
if value.error:
|
|
||||||
return f"<no reply: {value.error}>"
|
|
||||||
if not value.body and value.status is not None:
|
|
||||||
return f"<{value.status}, empty body>"
|
|
||||||
return value.text
|
|
||||||
return value
|
|
||||||
|
|
||||||
def contains(self, haystack, needle, label):
|
|
||||||
haystack = self._as_text(haystack)
|
|
||||||
return self._record(needle in haystack, label,
|
|
||||||
f"text containing {needle!r}", self._clip(haystack))
|
|
||||||
|
|
||||||
def not_contains(self, haystack, needle, label):
|
|
||||||
haystack = self._as_text(haystack)
|
|
||||||
return self._record(needle not in haystack, label,
|
|
||||||
f"text without {needle!r}", self._clip(haystack))
|
|
||||||
|
|
||||||
def in_range(self, value, low, high, label):
|
|
||||||
good = isinstance(value, int) and low <= value <= high
|
|
||||||
return self._record(good, label, f"between {low} and {high}", value)
|
|
||||||
|
|
||||||
def not_in_range(self, value, low, high, label):
|
|
||||||
good = isinstance(value, int) and not (low <= value <= high)
|
|
||||||
return self._record(good, label, f"outside {low}-{high}", value)
|
|
||||||
|
|
||||||
def skip(self, label):
|
|
||||||
self._skipped += 1
|
|
||||||
self.checks.append((None, label, None, None))
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _clip(text, limit=200):
|
|
||||||
text = str(text).replace("\r\n", " ").replace("\n", " ")
|
|
||||||
return text[:limit] + ("..." if len(text) > limit else "")
|
|
||||||
|
|
||||||
|
|
||||||
def field(response, name):
|
|
||||||
"""Pull one 'key=value' line out of an echo reply."""
|
|
||||||
text = response.text if isinstance(response, Response) else response
|
|
||||||
for line in text.splitlines():
|
|
||||||
key, _, value = line.partition("=")
|
|
||||||
if key == name:
|
|
||||||
return value
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def int_field(response, name):
|
|
||||||
value = field(response, name)
|
|
||||||
try:
|
|
||||||
return int(value)
|
|
||||||
except (TypeError, ValueError):
|
|
||||||
return None
|
|
||||||
141
tests/run.py
141
tests/run.py
@ -1,141 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""Run the 3proxy regression tests.
|
|
||||||
|
|
||||||
python3 tests/run.py every case
|
|
||||||
python3 tests/run.py httpsrv cases whose name matches
|
|
||||||
python3 tests/run.py --bin build/bin/3proxy
|
|
||||||
python3 tests/run.py --keep leave the temporary files behind
|
|
||||||
|
|
||||||
Each case under tests/cases/ defines the configurations it needs and the
|
|
||||||
positive and negative scenarios expected from them.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
import importlib.util
|
|
||||||
import os
|
|
||||||
import shutil
|
|
||||||
import sys
|
|
||||||
import tempfile
|
|
||||||
import traceback
|
|
||||||
|
|
||||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
||||||
|
|
||||||
from harness import Failure, Tester # noqa: E402
|
|
||||||
|
|
||||||
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
||||||
|
|
||||||
|
|
||||||
def default_binary():
|
|
||||||
"""Find a built 3proxy: the Makefiles put it in bin/, CMake in build/bin/,
|
|
||||||
and multi-configuration generators one level below that again."""
|
|
||||||
name = "3proxy.exe" if os.name == "nt" else "3proxy"
|
|
||||||
candidates = [os.path.join(ROOT, "bin", name),
|
|
||||||
os.path.join(ROOT, "build", "bin", name)]
|
|
||||||
for config in ("Release", "Debug", "RelWithDebInfo", "MinSizeRel"):
|
|
||||||
candidates.append(os.path.join(ROOT, "build", "bin", config, name))
|
|
||||||
for candidate in candidates:
|
|
||||||
if os.path.isfile(candidate):
|
|
||||||
return candidate
|
|
||||||
return candidates[0]
|
|
||||||
|
|
||||||
|
|
||||||
def load_case(path):
|
|
||||||
name = os.path.splitext(os.path.basename(path))[0]
|
|
||||||
spec = importlib.util.spec_from_file_location("case_" + name, path)
|
|
||||||
module = importlib.util.module_from_spec(spec)
|
|
||||||
spec.loader.exec_module(module)
|
|
||||||
return name, module
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
parser = argparse.ArgumentParser()
|
|
||||||
parser.add_argument("pattern", nargs="?", default="",
|
|
||||||
help="only run cases whose name contains this")
|
|
||||||
parser.add_argument("--bin", dest="binary", default=None,
|
|
||||||
help="the 3proxy binary to test")
|
|
||||||
parser.add_argument("--keep", action="store_true",
|
|
||||||
help="keep the temporary directory")
|
|
||||||
parser.add_argument("-v", "--verbose", action="store_true",
|
|
||||||
help="print every check, not just the failures")
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
binary = args.binary or os.environ.get("BIN") or default_binary()
|
|
||||||
binary = os.path.abspath(binary)
|
|
||||||
if not os.path.isfile(binary):
|
|
||||||
print(f"no 3proxy binary at {binary} (build first, or pass --bin)",
|
|
||||||
file=sys.stderr)
|
|
||||||
return 2
|
|
||||||
|
|
||||||
case_dir = os.path.join(ROOT, "tests", "cases")
|
|
||||||
paths = sorted(os.path.join(case_dir, f) for f in os.listdir(case_dir)
|
|
||||||
if f.endswith(".py") and not f.startswith("_"))
|
|
||||||
paths = [p for p in paths if args.pattern in os.path.basename(p)]
|
|
||||||
if not paths:
|
|
||||||
print(f"no cases matched {args.pattern!r}", file=sys.stderr)
|
|
||||||
return 2
|
|
||||||
|
|
||||||
tmpdir = tempfile.mkdtemp(prefix="3proxy-tests.")
|
|
||||||
print(f"3proxy tests: {binary}")
|
|
||||||
print(f"working in: {tmpdir}\n")
|
|
||||||
|
|
||||||
passed = failed = skipped = 0
|
|
||||||
failures = []
|
|
||||||
|
|
||||||
try:
|
|
||||||
for path in paths:
|
|
||||||
name, module = load_case(path)
|
|
||||||
print(f" {name}")
|
|
||||||
tester = Tester(binary, tmpdir, name)
|
|
||||||
error = None
|
|
||||||
try:
|
|
||||||
module.run(tester)
|
|
||||||
except Failure as exc:
|
|
||||||
error = str(exc)
|
|
||||||
except Exception:
|
|
||||||
error = traceback.format_exc()
|
|
||||||
finally:
|
|
||||||
tester.stop_all()
|
|
||||||
|
|
||||||
for status, label, expected, actual in tester.checks:
|
|
||||||
if status is None:
|
|
||||||
skipped += 1
|
|
||||||
print(f" skip {label}")
|
|
||||||
elif status:
|
|
||||||
passed += 1
|
|
||||||
if args.verbose:
|
|
||||||
print(f" ok {label}")
|
|
||||||
else:
|
|
||||||
failed += 1
|
|
||||||
failures.append(f"{name}: {label}")
|
|
||||||
print(f" FAIL {label}")
|
|
||||||
if expected is not None:
|
|
||||||
print(f" expected: {expected}")
|
|
||||||
if actual is not None:
|
|
||||||
print(f" actual: {actual}")
|
|
||||||
|
|
||||||
if error:
|
|
||||||
failed += 1
|
|
||||||
failures.append(f"{name}: case aborted")
|
|
||||||
print(" ERROR the case could not finish:")
|
|
||||||
for line in error.rstrip().splitlines():
|
|
||||||
print(f" {line}")
|
|
||||||
print()
|
|
||||||
finally:
|
|
||||||
if args.keep:
|
|
||||||
print(f"temporary files left in {tmpdir}")
|
|
||||||
else:
|
|
||||||
shutil.rmtree(tmpdir, ignore_errors=True)
|
|
||||||
|
|
||||||
print("-" * 41)
|
|
||||||
total = passed + failed
|
|
||||||
summary = f"cases: {len(paths)} checks: {total} passed: {passed} failed: {failed}"
|
|
||||||
if skipped:
|
|
||||||
summary += f" skipped: {skipped}"
|
|
||||||
print(summary)
|
|
||||||
for item in failures:
|
|
||||||
print(f" FAIL {item}")
|
|
||||||
return 1 if failed else 0
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
sys.exit(main())
|
|
||||||
Loading…
Reference in New Issue
Block a user