mirror of
https://github.com/3proxy/3proxy.git
synced 2026-11-07 02:05:49 +08:00
Compare commits
84 Commits
1.1.0-pre1
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
14e87dbd78 | ||
|
|
629a978c00 | ||
|
|
a75b488aaf | ||
|
|
e9714fa4df | ||
|
|
9be415b731 | ||
|
|
371e78691d | ||
|
|
a5129f85c7 | ||
|
|
33dce70177 | ||
|
|
d770c51761 | ||
|
|
aaa36f1dd1 | ||
|
|
b56e5aff2b | ||
|
|
0c80c9f526 | ||
|
|
62fd5e3a8d | ||
|
|
50bc4b4e27 | ||
|
|
b0b796823d | ||
|
|
ef70f3223f | ||
|
|
001e2874c5 | ||
|
|
72a32eecd5 | ||
|
|
9a4602872d | ||
|
|
615fa9b5fc | ||
|
|
3081a05cf7 | ||
|
|
e13c8d518f | ||
|
|
99795d8dfe | ||
|
|
94a83d126f | ||
|
|
4edb94d09e | ||
|
|
4a90c65a6a | ||
|
|
364286d6d9 | ||
|
|
07bedfa64c | ||
|
|
ef4969da19 | ||
|
|
36ac6e673b | ||
|
|
292a855f34 | ||
|
|
9651848bf1 | ||
|
|
c89a7b14c7 | ||
|
|
41edaa9757 | ||
|
|
84dd84a993 | ||
|
|
993c1ef95e | ||
|
|
5bc60d8bb3 | ||
|
|
1a66393b25 | ||
|
|
b5e9459d64 | ||
|
|
206c923d6c | ||
|
|
6a3cb4128d | ||
|
|
94fc4a7116 | ||
|
|
89d2131094 | ||
|
|
b00e3b9a9c | ||
|
|
ee5a7588f8 | ||
|
|
30ffaff182 | ||
|
|
5c78d1a342 | ||
|
|
1cea4fef4b | ||
|
|
24ac413fc5 | ||
|
|
c6e290b8e6 | ||
|
|
77f8870c5d | ||
|
|
d4eaf5acc8 | ||
|
|
f271a57238 | ||
|
|
dded867ede | ||
|
|
fad5acea9a | ||
|
|
a0ea1b3e7d | ||
|
|
5e71f7daff | ||
|
|
36ae10ad16 | ||
|
|
f52dc0f0b9 | ||
|
|
ffa1a05009 | ||
|
|
6f02ec2747 | ||
|
|
ca4667c035 | ||
|
|
41a08c11be | ||
|
|
6c4663a045 | ||
|
|
83139bc2e8 | ||
|
|
2dc4a422c5 | ||
|
|
d0ec7687db | ||
|
|
b8e5853b6b | ||
|
|
ad6e151b7c | ||
|
|
16ac797008 | ||
|
|
48d72538e1 | ||
|
|
2b8845f65a | ||
|
|
f265ea0b52 | ||
|
|
5400d53cef | ||
|
|
da2b8b3c1a | ||
|
|
ee0de3613a | ||
|
|
8971fcf991 | ||
|
|
9529a1dfcf | ||
|
|
ea4b2cc3a2 | ||
|
|
6915ea126f | ||
|
|
efa6f6560c | ||
|
|
3ff0f8b7ab | ||
|
|
382e7915f0 | ||
|
|
da66016c7f |
3
.github/workflows/build-ipk.yml
vendored
3
.github/workflows/build-ipk.yml
vendored
@ -30,7 +30,8 @@ jobs:
|
|||||||
arch: arm_cortex-a7_neon-vfpv4
|
arch: arm_cortex-a7_neon-vfpv4
|
||||||
- target: mediatek/filogic
|
- target: mediatek/filogic
|
||||||
arch: aarch64_cortex-a53
|
arch: aarch64_cortex-a53
|
||||||
runs-on: ubuntu-latest
|
# 26.04: the OpenWrt SDK refuses its non-GNU install
|
||||||
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v7
|
- uses: actions/checkout@v7
|
||||||
|
|
||||||
|
|||||||
3
.github/workflows/build-rpm-x86-64.yml
vendored
3
.github/workflows/build-rpm-x86-64.yml
vendored
@ -18,7 +18,8 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
target:
|
target:
|
||||||
- ubuntu-latest
|
# 26.04: rpm 6 runs %__gpg_sign_cmd differently, signing fails
|
||||||
|
- ubuntu-24.04
|
||||||
runs-on: ${{ matrix.target }}
|
runs-on: ${{ matrix.target }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v7
|
- uses: actions/checkout@v7
|
||||||
|
|||||||
15
.github/workflows/build-watcom.yml
vendored
15
.github/workflows/build-watcom.yml
vendored
@ -38,19 +38,10 @@ jobs:
|
|||||||
- name: make Watcom
|
- name: make Watcom
|
||||||
shell: cmd
|
shell: cmd
|
||||||
run: |
|
run: |
|
||||||
echo "volatile char VerSion[]=^"3APA3A-3proxy-Internal-Build: 3proxy-%RELEASE%-%NOW%\r\nCode certificate: https://3proxy.org/3proxy.cer\r\n^";" >>src/3proxy.c
|
echo "volatile char VerSion[]=^"3APA3A-3proxy-Internal-Build: 3proxy-%RELEASE%-%NOW%\r\n^";" >>src/3proxy.c
|
||||||
nmake /F Makefile.watcom
|
nmake /F Makefile.watcom
|
||||||
- name: Decode Certificate
|
if errorlevel 1 exit /b 1
|
||||||
shell: pwsh
|
if not exist bin\3proxy.exe (echo bin\3proxy.exe was not built & exit /b 1)
|
||||||
run: |
|
|
||||||
$pfx_cert_byte = [System.Convert]::FromBase64String("${{ secrets.WINDOWS_CERTIFICATE }}")
|
|
||||||
[System.IO.File]::WriteAllBytes("${{ github.workspace }}\cert.pfx", $pfx_cert_byte)
|
|
||||||
- name: Sign
|
|
||||||
shell: pwsh
|
|
||||||
run: |
|
|
||||||
$signtool = (Get-ChildItem "C:\Program Files (x86)\Windows Kits\10\bin\*\x64\signtool.exe" | Sort-Object { [version]$_.Directory.Parent.Name } -Descending | Select-Object -First 1).FullName
|
|
||||||
& $signtool sign /f "${{ github.workspace }}\cert.pfx" /p "${{ secrets.WINDOWS_CERTIFICATE_PASSWORD }}" /tr http://timestamp.digicert.com /td sha256 /fd sha256 "bin\3proxy.exe"
|
|
||||||
& $signtool sign /f "${{ github.workspace }}\cert.pfx" /p "${{ secrets.WINDOWS_CERTIFICATE_PASSWORD }}" /tr http://timestamp.digicert.com /td sha256 /fd sha256 "bin\3proxy_crypt.exe"
|
|
||||||
- name: make dist dir
|
- name: make dist dir
|
||||||
shell: cmd
|
shell: cmd
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
22
.github/workflows/build-win32.yml
vendored
22
.github/workflows/build-win32.yml
vendored
@ -44,27 +44,8 @@ jobs:
|
|||||||
cd "D:/a/3proxy/3proxy"
|
cd "D:/a/3proxy/3proxy"
|
||||||
set "LIB=%LIB%;c:/vcpkg/installed/x86-windows-static/lib"
|
set "LIB=%LIB%;c:/vcpkg/installed/x86-windows-static/lib"
|
||||||
set "INCLUDE=%INCLUDE%;c:/vcpkg/installed/x86-windows-static/include"
|
set "INCLUDE=%INCLUDE%;c:/vcpkg/installed/x86-windows-static/include"
|
||||||
echo "volatile char VerSion[]=^"3APA3A-3proxy-Internal-Build: 3proxy-%RELEASE%-%NOW%\r\nCode certificate: https://3proxy.org/3proxy.cer\r\n^";" >>src/3proxy.c
|
echo "volatile char VerSion[]=^"3APA3A-3proxy-Internal-Build: 3proxy-%RELEASE%-%NOW%\r\n^";" >>src/3proxy.c
|
||||||
nmake /F Makefile.msvc WOLFSSL=1
|
nmake /F Makefile.msvc WOLFSSL=1
|
||||||
- name: Decode Certificate
|
|
||||||
shell: pwsh
|
|
||||||
run: |
|
|
||||||
$pfx_cert_byte = [System.Convert]::FromBase64String("${{ secrets.WINDOWS_CERTIFICATE }}")
|
|
||||||
[System.IO.File]::WriteAllBytes("${{ github.workspace }}\cert.pfx", $pfx_cert_byte)
|
|
||||||
- name: Extract public certificate
|
|
||||||
shell: pwsh
|
|
||||||
env:
|
|
||||||
CERT_PASSWORD: ${{ secrets.WINDOWS_CERTIFICATE_PASSWORD }}
|
|
||||||
run: |
|
|
||||||
$cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2("${{ github.workspace }}\cert.pfx", "$env:CERT_PASSWORD")
|
|
||||||
[System.IO.File]::WriteAllBytes("${{ github.workspace }}\3proxy.crt", $cert.Export("Cert"))
|
|
||||||
- name: Sign
|
|
||||||
shell: pwsh
|
|
||||||
env:
|
|
||||||
CERT_PASSWORD: ${{ secrets.WINDOWS_CERTIFICATE_PASSWORD }}
|
|
||||||
run: |
|
|
||||||
$signtool = (Get-ChildItem "C:\Program Files (x86)\Windows Kits\10\bin\*\x64\signtool.exe" | Sort-Object { [version]$_.Directory.Parent.Name } -Descending | Select-Object -First 1).FullName
|
|
||||||
Get-ChildItem bin\*.exe, bin\*.dll | ForEach-Object { & $signtool sign /f "${{ github.workspace }}\cert.pfx" /p "$env:CERT_PASSWORD" /tr http://timestamp.digicert.com /td sha256 /fd sha256 $_.FullName }
|
|
||||||
- name: make dist dir
|
- name: make dist dir
|
||||||
shell: cmd
|
shell: cmd
|
||||||
run: |
|
run: |
|
||||||
@ -95,7 +76,6 @@ jobs:
|
|||||||
copy authors dist\3proxy\
|
copy authors dist\3proxy\
|
||||||
copy README.md dist\3proxy\
|
copy README.md dist\3proxy\
|
||||||
copy rus.3ps dist\3proxy\
|
copy rus.3ps dist\3proxy\
|
||||||
copy 3proxy.crt dist\3proxy\
|
|
||||||
- name: Get artifact
|
- name: Get artifact
|
||||||
uses: actions/upload-artifact@v7
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
|
|||||||
22
.github/workflows/build-win64.yml
vendored
22
.github/workflows/build-win64.yml
vendored
@ -44,28 +44,9 @@ jobs:
|
|||||||
cd "D:/a/3proxy/3proxy"
|
cd "D:/a/3proxy/3proxy"
|
||||||
set "LIB=%LIB%;c:/vcpkg/installed/x64-windows-static/lib"
|
set "LIB=%LIB%;c:/vcpkg/installed/x64-windows-static/lib"
|
||||||
set "INCLUDE=%INCLUDE%;c:/vcpkg/installed/x64-windows-static/include"
|
set "INCLUDE=%INCLUDE%;c:/vcpkg/installed/x64-windows-static/include"
|
||||||
echo "volatile char VerSion[]=^"3APA3A-3proxy-Internal-Build: 3proxy-%RELEASE%-%NOW%\r\nCode certificate: https://3proxy.org/3proxy.cer\r\n^";" >>src/3proxy.c
|
echo "volatile char VerSion[]=^"3APA3A-3proxy-Internal-Build: 3proxy-%RELEASE%-%NOW%\r\n^";" >>src/3proxy.c
|
||||||
echo %NOW% / %RELEASE% / %BUILDDATE% / %VERSION%
|
echo %NOW% / %RELEASE% / %BUILDDATE% / %VERSION%
|
||||||
nmake /F Makefile.msvc WOLFSSL=1
|
nmake /F Makefile.msvc WOLFSSL=1
|
||||||
- name: Decode Certificate
|
|
||||||
shell: pwsh
|
|
||||||
run: |
|
|
||||||
$pfx_cert_byte = [System.Convert]::FromBase64String("${{ secrets.WINDOWS_CERTIFICATE }}")
|
|
||||||
[System.IO.File]::WriteAllBytes("${{ github.workspace }}\cert.pfx", $pfx_cert_byte)
|
|
||||||
- name: Extract public certificate
|
|
||||||
shell: pwsh
|
|
||||||
env:
|
|
||||||
CERT_PASSWORD: ${{ secrets.WINDOWS_CERTIFICATE_PASSWORD }}
|
|
||||||
run: |
|
|
||||||
$cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2("${{ github.workspace }}\cert.pfx", "$env:CERT_PASSWORD")
|
|
||||||
[System.IO.File]::WriteAllBytes("${{ github.workspace }}\3proxy.crt", $cert.Export("Cert"))
|
|
||||||
- name: Sign
|
|
||||||
shell: pwsh
|
|
||||||
env:
|
|
||||||
CERT_PASSWORD: ${{ secrets.WINDOWS_CERTIFICATE_PASSWORD }}
|
|
||||||
run: |
|
|
||||||
$signtool = (Get-ChildItem "C:\Program Files (x86)\Windows Kits\10\bin\*\x64\signtool.exe" | Sort-Object { [version]$_.Directory.Parent.Name } -Descending | Select-Object -First 1).FullName
|
|
||||||
Get-ChildItem bin\*.exe, bin\*.dll | ForEach-Object { & $signtool sign /f "${{ github.workspace }}\cert.pfx" /p "$env:CERT_PASSWORD" /tr http://timestamp.digicert.com /td sha256 /fd sha256 $_.FullName }
|
|
||||||
- name: make dist dir
|
- name: make dist dir
|
||||||
shell: cmd
|
shell: cmd
|
||||||
run: |
|
run: |
|
||||||
@ -96,7 +77,6 @@ jobs:
|
|||||||
copy authors dist\3proxy\
|
copy authors dist\3proxy\
|
||||||
copy README.md dist\3proxy\
|
copy README.md dist\3proxy\
|
||||||
copy rus.3ps dist\3proxy\
|
copy rus.3ps dist\3proxy\
|
||||||
copy 3proxy.crt dist\3proxy\
|
|
||||||
- name: Get artifact
|
- name: Get artifact
|
||||||
uses: actions/upload-artifact@v7
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
|
|||||||
22
.github/workflows/build-winarm64.yml
vendored
22
.github/workflows/build-winarm64.yml
vendored
@ -44,27 +44,8 @@ jobs:
|
|||||||
cd "D:/a/3proxy/3proxy"
|
cd "D:/a/3proxy/3proxy"
|
||||||
set "LIB=%LIB%;c:/vcpkg/installed/arm64-windows-static/lib"
|
set "LIB=%LIB%;c:/vcpkg/installed/arm64-windows-static/lib"
|
||||||
set "INCLUDE=%INCLUDE%;c:/vcpkg/installed/arm64-windows-static/include"
|
set "INCLUDE=%INCLUDE%;c:/vcpkg/installed/arm64-windows-static/include"
|
||||||
echo "volatile char VerSion[]=^"3APA3A-3proxy-Internal-Build: 3proxy-%RELEASE%-%NOW%\r\nCode certificate: https://3proxy.org/3proxy.cer\r\n^";" >>src/3proxy.c
|
echo "volatile char VerSion[]=^"3APA3A-3proxy-Internal-Build: 3proxy-%RELEASE%-%NOW%\r\n^";" >>src/3proxy.c
|
||||||
nmake /F Makefile.msvc WOLFSSL=1
|
nmake /F Makefile.msvc WOLFSSL=1
|
||||||
- name: Decode Certificate
|
|
||||||
shell: pwsh
|
|
||||||
run: |
|
|
||||||
$pfx_cert_byte = [System.Convert]::FromBase64String("${{ secrets.WINDOWS_CERTIFICATE }}")
|
|
||||||
[System.IO.File]::WriteAllBytes("${{ github.workspace }}\cert.pfx", $pfx_cert_byte)
|
|
||||||
- name: Extract public certificate
|
|
||||||
shell: pwsh
|
|
||||||
env:
|
|
||||||
CERT_PASSWORD: ${{ secrets.WINDOWS_CERTIFICATE_PASSWORD }}
|
|
||||||
run: |
|
|
||||||
$cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2("${{ github.workspace }}\cert.pfx", "$env:CERT_PASSWORD")
|
|
||||||
[System.IO.File]::WriteAllBytes("${{ github.workspace }}\3proxy.crt", $cert.Export("Cert"))
|
|
||||||
- name: Sign
|
|
||||||
shell: pwsh
|
|
||||||
env:
|
|
||||||
CERT_PASSWORD: ${{ secrets.WINDOWS_CERTIFICATE_PASSWORD }}
|
|
||||||
run: |
|
|
||||||
$signtool = (Get-ChildItem "C:\Program Files (x86)\Windows Kits\10\bin\*\x64\signtool.exe" | Sort-Object { [version]$_.Directory.Parent.Name } -Descending | Select-Object -First 1).FullName
|
|
||||||
Get-ChildItem bin\*.exe, bin\*.dll | ForEach-Object { & $signtool sign /f "${{ github.workspace }}\cert.pfx" /p "$env:CERT_PASSWORD" /tr http://timestamp.digicert.com /td sha256 /fd sha256 $_.FullName }
|
|
||||||
- name: make dist dir
|
- name: make dist dir
|
||||||
shell: cmd
|
shell: cmd
|
||||||
run: |
|
run: |
|
||||||
@ -95,7 +76,6 @@ jobs:
|
|||||||
copy authors dist\3proxy\
|
copy authors dist\3proxy\
|
||||||
copy README.md dist\3proxy\
|
copy README.md dist\3proxy\
|
||||||
copy rus.3ps dist\3proxy\
|
copy rus.3ps dist\3proxy\
|
||||||
copy 3proxy.crt dist\3proxy\
|
|
||||||
- name: Get artifact
|
- name: Get artifact
|
||||||
uses: actions/upload-artifact@v7
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
|
|||||||
2
.github/workflows/c-cpp-Linux.yml
vendored
2
.github/workflows/c-cpp-Linux.yml
vendored
@ -27,7 +27,7 @@ jobs:
|
|||||||
if: ${{ startsWith(matrix.target, 'ubuntu') }}
|
if: ${{ startsWith(matrix.target, 'ubuntu') }}
|
||||||
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 MAILPROXY=true FTP=true
|
||||||
- name: regression tests
|
- name: regression tests
|
||||||
run: python3 tests/run.py
|
run: python3 tests/run.py
|
||||||
- name: mkdir
|
- name: mkdir
|
||||||
|
|||||||
85
.github/workflows/release-tarball.yml
vendored
Normal file
85
.github/workflows/release-tarball.yml
vendored
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
name: Release source tarball
|
||||||
|
|
||||||
|
on:
|
||||||
|
release:
|
||||||
|
types: [published]
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
tarball:
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
id-token: write
|
||||||
|
attestations: write
|
||||||
|
name: "source tarball"
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v7
|
||||||
|
|
||||||
|
- name: env
|
||||||
|
run: |
|
||||||
|
if [ -f RELEASE ]; then
|
||||||
|
RELEASE=$(tr -d ' \t\r\n' < RELEASE)
|
||||||
|
else
|
||||||
|
RELEASE=$(tr -d ' \t\r\n' < DEVEL)
|
||||||
|
fi
|
||||||
|
echo "RELEASE=$RELEASE" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Create tarball
|
||||||
|
run: |
|
||||||
|
# git archive is reproducible from the tag: anyone can regenerate the
|
||||||
|
# tarball and compare it against the published checksum.
|
||||||
|
git archive --format=tar.gz -9 \
|
||||||
|
--prefix="3proxy-${{ env.RELEASE }}/" \
|
||||||
|
-o "3proxy-${{ env.RELEASE }}.tar.gz" HEAD
|
||||||
|
tar tzf "3proxy-${{ env.RELEASE }}.tar.gz" >/dev/null
|
||||||
|
ls -l *.tar.gz
|
||||||
|
|
||||||
|
- name: Get artifact
|
||||||
|
uses: actions/upload-artifact@v7
|
||||||
|
with:
|
||||||
|
name: "3proxy-${{ env.RELEASE }}-src"
|
||||||
|
path: "*.tar.gz"
|
||||||
|
|
||||||
|
- name: Import signing key
|
||||||
|
if: github.event_name == 'release'
|
||||||
|
env:
|
||||||
|
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
|
||||||
|
run: |
|
||||||
|
if [ -z "$GPG_PRIVATE_KEY" ]; then echo "GPG_PRIVATE_KEY is not set"; exit 1; fi
|
||||||
|
mkdir -p ~/.gnupg && chmod 700 ~/.gnupg
|
||||||
|
printf 'allow-loopback-pinentry\n' > ~/.gnupg/gpg-agent.conf
|
||||||
|
gpgconf --kill gpg-agent || true
|
||||||
|
printf '%s' "$GPG_PRIVATE_KEY" | gpg --batch --import
|
||||||
|
KEYID=$(gpg --list-secret-keys --with-colons | awk -F: '/^sec:/{print $5; exit}')
|
||||||
|
echo "GPG_KEYID=$KEYID" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Checksums and detached signatures
|
||||||
|
if: github.event_name == 'release'
|
||||||
|
env:
|
||||||
|
GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
|
||||||
|
run: |
|
||||||
|
sha256sum *.tar.gz > SHA256SUMS-src
|
||||||
|
for f in *.tar.gz SHA256SUMS-src; do
|
||||||
|
gpg --batch --yes --pinentry-mode loopback --passphrase "$GPG_PASSPHRASE" \
|
||||||
|
-u "$GPG_KEYID" --armor --detach-sign "$f"
|
||||||
|
done
|
||||||
|
sha256sum -c SHA256SUMS-src
|
||||||
|
gpg --verify SHA256SUMS-src.asc SHA256SUMS-src
|
||||||
|
|
||||||
|
- name: Attest build provenance
|
||||||
|
if: github.event_name == 'release'
|
||||||
|
uses: actions/attest-build-provenance@v4
|
||||||
|
with:
|
||||||
|
subject-path: |
|
||||||
|
*.tar.gz
|
||||||
|
|
||||||
|
- name: Upload to release
|
||||||
|
if: github.event_name == 'release'
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ github.token }}
|
||||||
|
TAG: ${{ github.event.release.tag_name }}
|
||||||
|
run: gh release upload "$TAG" *.tar.gz *.tar.gz.asc SHA256SUMS-src SHA256SUMS-src.asc
|
||||||
1
.github/workflows/update-docs.yml
vendored
1
.github/workflows/update-docs.yml
vendored
@ -2,6 +2,7 @@ name: Update HTML documentation
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
|
branches: [master]
|
||||||
paths:
|
paths:
|
||||||
- 'man/**'
|
- 'man/**'
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|||||||
33
.github/workflows/update-version.yml
vendored
33
.github/workflows/update-version.yml
vendored
@ -4,6 +4,7 @@ on:
|
|||||||
push:
|
push:
|
||||||
paths:
|
paths:
|
||||||
- RELEASE
|
- RELEASE
|
||||||
|
- DEVEL
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
@ -19,12 +20,23 @@ jobs:
|
|||||||
|
|
||||||
- name: Regenerate version files
|
- name: Regenerate version files
|
||||||
run: |
|
run: |
|
||||||
RELEASE=$(tr -d ' \t\r\n' < RELEASE)
|
# A release branch carries RELEASE, a development branch DEVEL, whose
|
||||||
if [ -z "$RELEASE" ]; then echo "RELEASE is empty"; exit 1; fi
|
# version may have a suffix such as 2.0.0-devel.
|
||||||
MAJOR=$(echo "$RELEASE" | cut -d . -f 1)
|
if [ -f RELEASE ]; then VERFILE=RELEASE
|
||||||
SUBMAJOR=$(echo "$RELEASE" | cut -d . -f 2)
|
elif [ -f DEVEL ]; then VERFILE=DEVEL
|
||||||
MINOR=$(echo "$RELEASE" | cut -d . -f 3)
|
else echo "neither RELEASE nor DEVEL is present"; exit 1
|
||||||
SUBMINOR=$(echo "$RELEASE" | cut -d . -f 4)
|
fi
|
||||||
|
RELEASE=$(tr -d ' \t\r\n' < "$VERFILE")
|
||||||
|
if [ -z "$RELEASE" ]; then echo "$VERFILE is empty"; exit 1; fi
|
||||||
|
|
||||||
|
# the numbered fields take the numeric part, the strings keep all of it
|
||||||
|
NUMBERS=${RELEASE%%[!0-9.]*}
|
||||||
|
NUMBERS=${NUMBERS%.}
|
||||||
|
if [ -z "$NUMBERS" ]; then echo "no version number in '$RELEASE'"; exit 1; fi
|
||||||
|
MAJOR=$(echo "$NUMBERS" | cut -d . -f 1)
|
||||||
|
SUBMAJOR=$(echo "$NUMBERS" | cut -d . -f 2)
|
||||||
|
MINOR=$(echo "$NUMBERS" | cut -d . -f 3)
|
||||||
|
SUBMINOR=$(echo "$NUMBERS" | cut -d . -f 4)
|
||||||
[ -n "$SUBMAJOR" ] || SUBMAJOR=0
|
[ -n "$SUBMAJOR" ] || SUBMAJOR=0
|
||||||
[ -n "$MINOR" ] || MINOR=0
|
[ -n "$MINOR" ] || MINOR=0
|
||||||
[ -n "$SUBMINOR" ] || SUBMINOR=0
|
[ -n "$SUBMINOR" ] || SUBMINOR=0
|
||||||
@ -39,7 +51,10 @@ jobs:
|
|||||||
else
|
else
|
||||||
BUILDDATE=$(date -u +%y%m%d%H%M%S)
|
BUILDDATE=$(date -u +%y%m%d%H%M%S)
|
||||||
fi
|
fi
|
||||||
echo "release $RELEASE -> $MAJOR $SUBMAJOR $MINOR $SUBMINOR, build date $BUILDDATE"
|
# rpm refuses a hyphen in Version, so a suffix becomes a tilde, which
|
||||||
|
# also sorts before the release of the same number
|
||||||
|
RPMVERSION=$(echo "$RELEASE" | tr '-' '~')
|
||||||
|
echo "$VERFILE $RELEASE -> $MAJOR $SUBMAJOR $MINOR $SUBMINOR, rpm $RPMVERSION, build date $BUILDDATE"
|
||||||
SOURCE_DATE_EPOCH=$(date -u -d "20${BUILDDATE:0:2}-${BUILDDATE:2:2}-${BUILDDATE:4:2} ${BUILDDATE:6:2}:${BUILDDATE:8:2}:${BUILDDATE:10:2}" +%s)
|
SOURCE_DATE_EPOCH=$(date -u -d "20${BUILDDATE:0:2}-${BUILDDATE:2:2}-${BUILDDATE:4:2} ${BUILDDATE:6:2}:${BUILDDATE:8:2}:${BUILDDATE:10:2}" +%s)
|
||||||
YEAR=$(date -u -d @$SOURCE_DATE_EPOCH +%Y)
|
YEAR=$(date -u -d @$SOURCE_DATE_EPOCH +%Y)
|
||||||
|
|
||||||
@ -59,7 +74,7 @@ jobs:
|
|||||||
|
|
||||||
mv scripts/rh/3proxy.spec scripts/rh/3proxy.spec.old
|
mv scripts/rh/3proxy.spec scripts/rh/3proxy.spec.old
|
||||||
{ echo "Name: 3proxy"
|
{ echo "Name: 3proxy"
|
||||||
echo "Version: $RELEASE"
|
echo "Version: $RPMVERSION"
|
||||||
echo "Release: 1%{?dist}"
|
echo "Release: 1%{?dist}"
|
||||||
tail --lines=+4 scripts/rh/3proxy.spec.old
|
tail --lines=+4 scripts/rh/3proxy.spec.old
|
||||||
} > scripts/rh/3proxy.spec
|
} > scripts/rh/3proxy.spec
|
||||||
@ -90,5 +105,5 @@ jobs:
|
|||||||
git config user.name "github-actions[bot]"
|
git config user.name "github-actions[bot]"
|
||||||
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||||
git add debian/changelog scripts/rh/3proxy.spec src/version.h
|
git add debian/changelog scripts/rh/3proxy.spec src/version.h
|
||||||
git commit -m "Update version files for $(tr -d ' \t\r\n' < RELEASE)"
|
git commit -m "Update version files for $(tr -d ' \t\r\n' < $(test -f RELEASE && echo RELEASE || echo DEVEL))"
|
||||||
git push
|
git push
|
||||||
|
|||||||
3
.github/workflows/update-wiki.yml
vendored
3
.github/workflows/update-wiki.yml
vendored
@ -2,6 +2,7 @@ name: Update wiki
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
|
branches: [master]
|
||||||
paths:
|
paths:
|
||||||
- 'doc/html/**'
|
- 'doc/html/**'
|
||||||
workflow_run:
|
workflow_run:
|
||||||
@ -18,6 +19,8 @@ concurrency:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
wiki:
|
wiki:
|
||||||
|
# the wiki mirrors master, so a run started by anything else is not for it
|
||||||
|
if: github.event_name != 'workflow_run' || github.event.workflow_run.head_branch == 'master'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v7
|
- uses: actions/checkout@v7
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@ -273,3 +273,4 @@ bin/3proxy_udppm
|
|||||||
build*/*
|
build*/*
|
||||||
testcerts/
|
testcerts/
|
||||||
/.claude
|
/.claude
|
||||||
|
bench/mix/mix-linux-*
|
||||||
|
|||||||
36
CHANGELOG
36
CHANGELOG
@ -1,13 +1,25 @@
|
|||||||
3proxy-1.0.0 Released August, 22 2026
|
3proxy-1.0.2 Released October, 24 2026
|
||||||
|
|
||||||
No changes in 3proxy code, this release only changes how packages are built, signed and published.
|
! Fix: dnspr relayed to the client whatever came first to the socket it asked the name server from, from any address and under any id, so a forged answer could be passed on; the query now goes out under a random id, and only an answer from the name server asked, with that id and the same question, is relayed
|
||||||
|
! Fix: a client sending a part of a TLS record and then nothing kept a thread taking all of a CPU, and the read never timed out
|
||||||
! Fix: TLS, PCRE2 and PAM support was silently left out when 3proxy was built on a system whose /bin/sh is not dash, which is every RPM based distribution and macOS; the library checks built their test program with echo and escape sequences, which only dash expands, so every check failed and the features were dropped without a diagnostic
|
! Fix: without -Ne, the reply to SOCKS CONNECT and BIND gave the address of the client's connection to the proxy instead of the socket towards the destination: a BIND client was told the proxy's own port and the peer could never reach it (since 0.9.6)
|
||||||
! Fix: rpm packages were built on Ubuntu and required glibc 2.38, libssl.so.3 and libpcre2-8, which no RPM based distribution provides, so they could not be installed anywhere; they are built against AlmaLinux 8, 9 and 10 now and carry an el8, el9 or el10 tag, covering RHEL, AlmaLinux, Rocky and CentOS Stream of the same version
|
! Fix: a client which shut down its sending side (half-close) could get the answer cut short, megabytes short with large answers; likewise an upload after the server half-closed
|
||||||
! Fix: the rpm package depended on its own interpreter, /bin/3proxy, taken from the first line of the installed configuration file, and could not be installed on el9 or el10 because rpm resolves that dependency to /usr/bin/3proxy
|
! Fix: with chroot every local time was UTC and every syslog message was lost: the time zone is loaded and syslog connected before chroot; the chroot entry of 3proxy.cfg(5) lists what PAM, LDAP and ODBC still need inside the chroot
|
||||||
! Fix: deb packages required glibc 2.38 and libssl3t64 and installed on Ubuntu 24.04 and newer only; they are built against Ubuntu 22.04 now and install on Ubuntu 22.04 and later as well as Debian 12 and later
|
! Fix: the HTTP proxy closed the server connection of a kept-alive client and opened another one when only the request method changed (a POST after a GET to the same host)
|
||||||
+ Signed apt and dnf repositories are published at https://3proxy.org/repo/ in two channels: current, built from the master branch, and lts, built from the 0.9 branch; packages and repository metadata are both signed
|
! Fix: the last ssl_client_mode in the configuration applied to every service, and a reload changed it for the services still running
|
||||||
+ Release binaries are published with SHA256 checksums, an OpenPGP signature and a GitHub build provenance attestation; docker images are signed and attested as well
|
! Fix: memory leaks: on every refused configuration reload, on every failed TLS handshake, the server CA paths of every TLS service, compiled pcre: / regex: host names of access rules and patterns of httpsrv rules
|
||||||
+ The release signing key is an RSA-4096 key published as 3proxy-release-key.asc in the repository; rpm 4.14 and earlier can not import an Ed25519 key at all, which would leave RHEL 8 and its derivatives unable to verify anything
|
! Fix: Unix sockets: on Linux a proxy listening on a Unix socket relayed only the first bytes of an answer (result 95); an abstract socket (unix:@NAME) was bound under a name padded with zeros, which clients did not reach; tcppm refused port 0 for a unix: target, as given in the manual's example; on a reload the socket file of a listening service was removed
|
||||||
+ Packages are reproducible: rebuilding a release produces byte identical deb and rpm files, every timestamp is derived from the build date recorded for the release
|
! Fix: SIGPIPE could kill 3proxy when a client was gone while services were starting, and standalone services (proxy, socks, ...) at any time; sends use MSG_NOSIGNAL where the system has it
|
||||||
- 32-bit ARM (armhf) is published as a deb package only, Enterprise Linux has no 32-bit ARM build
|
! Fix: TLS MitM failed for every connection with wolfSSL built with WOLFSSL_QT (wolfSSL 5.9.1 of Ubuntu 26.04): the source certificate's serial number could not be copied; a serial number made from the hash of the source certificate is used then
|
||||||
|
! Fix: Windows: closing a connection could leave its socket open (SO_LINGER on a non-blocking socket) or reset it while the peer still sent, so the end of an answer could be lost
|
||||||
|
! Fix: httpsrv: the body of a request answered without being read (for example when authentication is required) was read as the next request; a client still sending when a connection without keep-alive closed could get a reset instead of the answer
|
||||||
|
! Fix: httpsrv on Windows: UNC paths (\\server\share) did not work
|
||||||
|
! Fix: httpsrv and admin services and their clients were named ZOMBIE on the admin services page, in %N of logs and in RADIUS
|
||||||
|
! Fix: the Watcom (lite) build did not compile, 1.0.1 had no binaries for it
|
||||||
|
+ -s (splice) per direction: -s1 splices what the server sends (downloads), -s2 what the client sends (uploads), -s3 both; -s alone splices only where it pays (large bodies, long streams); pipes are kept with the connection for its next requests. splice() is built by default on Linux again and is used only by services given -s
|
||||||
|
+ Socket options (-oc, -os, -ol, -or, -oR) take NAME=VALUE; NAME=0 turns an option off, also one set by default; an unknown name or a value which is not a number is an error
|
||||||
|
+ The HTTP proxy frees its 64 KB request buffer while a CONNECT tunnel relays data and while a kept-alive connection waits for its next request: 2000 tunnels take 213 MB instead of 339 MB
|
||||||
|
+ TLS records are read ahead with OpenSSL 1.1.0 and later: decrypting takes 9-14% less CPU
|
||||||
|
+ Faster service stop and configuration reload, no fixed delays; a connection waiting for a free maxconn slot gets it at once
|
||||||
|
- TCP_NODELAY is set by default on proxy-to-client and proxy-to-server sockets; -ocTCP_NODELAY=0 / -osTCP_NODELAY=0 turns it off
|
||||||
|
- The HTTP proxy fetches ftp:// URLs only when the service is given the new -Xftp option (in a build with FTP=true)
|
||||||
|
|||||||
@ -1,13 +1,25 @@
|
|||||||
3proxy-1.0.0 Вышел 22 Августа 2026
|
3proxy-1.0.2 Вышел 24 Октября 2026
|
||||||
|
|
||||||
Изменений в коде 3proxy нет, этот выпуск меняет только сборку, подписывание и публикацию пакетов.
|
! Исправление: dnspr передавал клиенту то, что первым пришло на сокет, с которого он обращался к серверу имён, с любого адреса и с любым id, так что поддельный ответ мог быть передан клиенту; теперь запрос отправляется со случайным id, и передаётся только ответ от того сервера имён, к которому обращались, с этим id и тем же вопросом
|
||||||
|
! Исправление: клиент, отправивший часть записи TLS и затем ничего, занимал поток на всё процессорное время, и чтение не завершалось по таймауту
|
||||||
! Исправление: поддержка TLS, PCRE2 и PAM молча не включалась при сборке в системах, где /bin/sh не dash, то есть во всех дистрибутивах на основе RPM и в macOS; проверки наличия библиотек формировали тестовую программу через echo с escape-последовательностями, которые раскрывает только dash, поэтому все проверки завершались неудачно и возможности отключались без каких-либо сообщений
|
! Исправление: без -Ne ответ на SOCKS CONNECT и BIND содержал адрес соединения клиента с прокси вместо адреса сокета в сторону назначения: клиенту BIND сообщался собственный порт прокси, и удалённая сторона не могла к нему подключиться (с 0.9.6)
|
||||||
! Исправление: пакеты rpm собирались в Ubuntu и требовали glibc 2.38, libssl.so.3 и libpcre2-8, которых нет ни в одном дистрибутиве на основе RPM, поэтому установить их было невозможно нигде; теперь они собираются в AlmaLinux 8, 9 и 10 и содержат метку el8, el9 или el10, что покрывает RHEL, AlmaLinux, Rocky и CentOS Stream соответствующей версии
|
! Исправление: клиент, закрывший свою сторону передачи (half-close), мог получить ответ не полностью, на больших ответах без нескольких мегабайт; так же обрезалась загрузка на сервер после того, как сервер закрыл свою сторону
|
||||||
! Исправление: пакет rpm зависел от собственного интерпретатора /bin/3proxy, взятого из первой строки устанавливаемого файла конфигурации, и не устанавливался в el9 и el10, поскольку rpm приводит эту зависимость к /usr/bin/3proxy
|
! Исправление: при chroot всё местное время становилось UTC и все сообщения syslog терялись: часовой пояс теперь загружается, а syslog подключается до chroot; в описании chroot в 3proxy.cfg(5) перечислено, что ещё нужно внутри chroot для PAM, LDAP и ODBC
|
||||||
! Исправление: пакеты deb требовали glibc 2.38 и libssl3t64 и устанавливались только в Ubuntu 24.04 и новее; теперь они собираются в Ubuntu 22.04 и устанавливаются в Ubuntu 22.04 и новее, а также в Debian 12 и новее
|
! Исправление: HTTP прокси закрывал соединение с сервером для клиента с keep-alive и открывал новое, когда менялся только метод запроса (POST после GET к тому же хосту)
|
||||||
+ Подписанные репозитории apt и dnf публикуются на https://3proxy.org/repo/ в двух каналах: current, собираемый из ветки master, и lts, собираемый из ветки 0.9; подписываются и пакеты, и метаданные репозитория
|
! Исправление: последний ssl_client_mode в конфигурации действовал для всех служб, а перезагрузка конфигурации меняла его для уже работающих служб
|
||||||
+ Двоичные файлы релиза публикуются с контрольными суммами SHA256, подписью OpenPGP и подтверждением происхождения сборки GitHub (build provenance attestation); образы docker также подписываются и снабжаются подтверждением
|
! Исправление: утечки памяти: при каждой отвергнутой перезагрузке конфигурации, при каждом неудачном согласовании TLS, пути CA сервера каждой службы с TLS, скомпилированные имена хостов pcre: / regex: в правилах доступа и шаблоны правил httpsrv
|
||||||
+ Ключ подписи релизов — RSA-4096, опубликован в репозитории как 3proxy-release-key.asc; rpm версии 4.14 и более ранние вообще не могут импортировать ключ Ed25519, что лишило бы RHEL 8 и производные от него возможности проверки
|
! Исправление: сокеты Unix: в Linux прокси, слушающий на сокете Unix, передавал только первые байты ответа (результат 95); абстрактный сокет (unix:@NAME) привязывался к имени, дополненному нулями, до которого клиенты не доходили; tcppm отвергал порт 0 для цели unix:, как в примере из документации; при перезагрузке конфигурации файл сокета слушающей службы удалялся
|
||||||
+ Пакеты воспроизводимы: повторная сборка релиза даёт побайтово идентичные файлы deb и rpm, все отметки времени берутся из даты сборки, записанной для релиза
|
! Исправление: SIGPIPE мог завершить 3proxy, если клиент отключался во время запуска служб, а отдельные службы (proxy, socks, ...) — в любой момент; отправка данных использует MSG_NOSIGNAL там, где система его поддерживает
|
||||||
- Для 32-битной ARM (armhf) публикуется только пакет deb, в Enterprise Linux нет сборки для 32-битной ARM
|
! Исправление: TLS MitM не работал ни для одного соединения с wolfSSL, собранным с WOLFSSL_QT (wolfSSL 5.9.1 в Ubuntu 26.04): не удавалось скопировать серийный номер исходного сертификата; в этом случае используется серийный номер, полученный из хеша исходного сертификата
|
||||||
|
! Исправление: Windows: при закрытии соединения сокет мог остаться открытым (SO_LINGER на неблокирующем сокете) или соединение сбрасывалось, пока другая сторона ещё передавала данные, так что конец ответа мог потеряться
|
||||||
|
! Исправление: httpsrv: тело запроса, на который ответ дан без его чтения (например, когда требуется аутентификация), читалось как следующий запрос; клиент, ещё передающий данные при закрытии соединения без keep-alive, мог получить сброс соединения вместо ответа
|
||||||
|
! Исправление: httpsrv в Windows: не работали пути UNC (\\server\share)
|
||||||
|
! Исправление: службы httpsrv и admin и их клиенты назывались ZOMBIE на странице служб admin, в %N журнала и в RADIUS
|
||||||
|
! Исправление: сборка Watcom (lite) не компилировалась, в 1.0.1 для неё не было двоичных файлов
|
||||||
|
+ -s (splice) по направлениям: -s1 — то, что передаёт сервер (скачивание), -s2 — то, что передаёт клиент (загрузка), -s3 — оба; -s без числа использует splice только там, где это выгодно (большие тела, длинные потоки); каналы (pipe) сохраняются с соединением для следующих запросов. splice() снова собирается по умолчанию в Linux и используется только службами с -s
|
||||||
|
+ Опции сокетов (-oc, -os, -ol, -or, -oR) принимают ИМЯ=ЗНАЧЕНИЕ; ИМЯ=0 выключает опцию, в том числе установленную по умолчанию; неизвестное имя или значение, не являющееся числом, — ошибка
|
||||||
|
+ HTTP прокси освобождает буфер запроса размером 64 КБ, пока туннель CONNECT передаёт данные и пока соединение с keep-alive ждёт следующего запроса: 2000 туннелей занимают 213 МБ вместо 339 МБ
|
||||||
|
+ Записи TLS читаются с упреждением (read-ahead) с OpenSSL 1.1.0 и новее: расшифровка занимает на 9-14% меньше процессорного времени
|
||||||
|
+ Более быстрая остановка служб и перезагрузка конфигурации, без фиксированных задержек; соединение, ожидающее свободного места в maxconn, получает его сразу
|
||||||
|
- TCP_NODELAY устанавливается по умолчанию на сокетах прокси-клиент и прокси-сервер; -ocTCP_NODELAY=0 / -osTCP_NODELAY=0 выключает его
|
||||||
|
- HTTP прокси обрабатывает адреса ftp:// только если службе задана новая опция -Xftp (в сборке с FTP=true)
|
||||||
|
|||||||
100
CMakeLists.txt
100
CMakeLists.txt
@ -4,8 +4,20 @@
|
|||||||
|
|
||||||
cmake_minimum_required(VERSION 3.16)
|
cmake_minimum_required(VERSION 3.16)
|
||||||
|
|
||||||
# Read version from RELEASE file
|
# Read the version. A release branch carries RELEASE, a development branch
|
||||||
file(STRINGS "${CMAKE_CURRENT_SOURCE_DIR}/RELEASE" PROJECT_VERSION LIMIT_COUNT 1)
|
# DEVEL, whose version may have a suffix that project() will not take.
|
||||||
|
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/RELEASE")
|
||||||
|
file(STRINGS "${CMAKE_CURRENT_SOURCE_DIR}/RELEASE" PROJECT_VERSION_FULL LIMIT_COUNT 1)
|
||||||
|
elseif(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/DEVEL")
|
||||||
|
file(STRINGS "${CMAKE_CURRENT_SOURCE_DIR}/DEVEL" PROJECT_VERSION_FULL LIMIT_COUNT 1)
|
||||||
|
else()
|
||||||
|
message(FATAL_ERROR "Neither RELEASE nor DEVEL found: cannot tell the version")
|
||||||
|
endif()
|
||||||
|
string(STRIP "${PROJECT_VERSION_FULL}" PROJECT_VERSION_FULL)
|
||||||
|
string(REGEX MATCH "^[0-9]+(\\.[0-9]+)*" PROJECT_VERSION "${PROJECT_VERSION_FULL}")
|
||||||
|
if(NOT PROJECT_VERSION)
|
||||||
|
message(FATAL_ERROR "No version number in '${PROJECT_VERSION_FULL}'")
|
||||||
|
endif()
|
||||||
|
|
||||||
project(3proxy
|
project(3proxy
|
||||||
VERSION ${PROJECT_VERSION}
|
VERSION ${PROJECT_VERSION}
|
||||||
@ -51,12 +63,15 @@ option(3PROXY_USE_OPENSSL "Enable TLS/SSL support via OpenSSL (fallback when wol
|
|||||||
option(3PROXY_USE_PCRE2 "Enable PCRE2 regex filtering" ON)
|
option(3PROXY_USE_PCRE2 "Enable PCRE2 regex filtering" ON)
|
||||||
option(3PROXY_USE_PAM "Enable PAM/PamAuth" ON)
|
option(3PROXY_USE_PAM "Enable PAM/PamAuth" ON)
|
||||||
option(3PROXY_USE_ODBC "Enable ODBC support (Unix only, always ON on Windows)" OFF)
|
option(3PROXY_USE_ODBC "Enable ODBC support (Unix only, always ON on Windows)" OFF)
|
||||||
option(3PROXY_USE_SPLICE "Build Linux splice() support, slower than read/write for most traffic (Linux only)" OFF)
|
option(3PROXY_USE_SPLICE "Build Linux splice() support, used by services given -s (Linux only)" ON)
|
||||||
option(3PROXY_USE_POLL "Use poll() instead of select() (Unix only)" ON)
|
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_TRANSPARENT "Build transparent proxying support (Linux and BSD 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)
|
option(3PROXY_USE_HTTPSRV "Build the HTTP server and the admin interface on top of it" ON)
|
||||||
|
option(3PROXY_USE_MAILPROXY "Build the pop3p, imapp and smtpp proxies" OFF)
|
||||||
|
option(3PROXY_USE_FTP "Build FTP support: the ftppr service and ftp:// in the HTTP proxy" OFF)
|
||||||
|
|
||||||
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)
|
||||||
@ -70,10 +85,13 @@ set(3PROXY_BINARY_PREFIX "3proxy_" CACHE STRING "Prefix for standalone module an
|
|||||||
option(3PROXY_BUILD_NONE "Do not build standalone binaries" OFF)
|
option(3PROXY_BUILD_NONE "Do not build standalone binaries" OFF)
|
||||||
option(3PROXY_BUILD_PROXY "Build standalone proxy binary" ON)
|
option(3PROXY_BUILD_PROXY "Build standalone proxy binary" ON)
|
||||||
option(3PROXY_BUILD_SOCKS "Build standalone socks binary" ON)
|
option(3PROXY_BUILD_SOCKS "Build standalone socks binary" ON)
|
||||||
option(3PROXY_BUILD_POP3P "Build standalone pop3p binary" ON)
|
# The mail proxies and FTP are asked for rather than assumed: without them
|
||||||
option(3PROXY_BUILD_IMAPP "Build standalone imapp binary" ON)
|
# pop3p, imapp and smtpp are the STARTTLS proxy under those names, and ftp
|
||||||
option(3PROXY_BUILD_SMTPP "Build standalone smtpp binary" ON)
|
# is not spoken at all. A standalone binary needs the support it is made of.
|
||||||
option(3PROXY_BUILD_FTPPR "Build standalone ftppr binary" ON)
|
option(3PROXY_BUILD_POP3P "Build standalone pop3p binary" OFF)
|
||||||
|
option(3PROXY_BUILD_IMAPP "Build standalone imapp binary" OFF)
|
||||||
|
option(3PROXY_BUILD_SMTPP "Build standalone smtpp binary" OFF)
|
||||||
|
option(3PROXY_BUILD_FTPPR "Build standalone ftppr binary" OFF)
|
||||||
option(3PROXY_BUILD_TCPPM "Build standalone tcppm binary" ON)
|
option(3PROXY_BUILD_TCPPM "Build standalone tcppm binary" ON)
|
||||||
option(3PROXY_BUILD_UDPPM "Build standalone udppm binary" ON)
|
option(3PROXY_BUILD_UDPPM "Build standalone udppm binary" ON)
|
||||||
option(3PROXY_BUILD_TLSPR "Build standalone tlspr binary" ON)
|
option(3PROXY_BUILD_TLSPR "Build standalone tlspr binary" ON)
|
||||||
@ -102,6 +120,9 @@ set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
|||||||
if(WIN32)
|
if(WIN32)
|
||||||
# Windows-specific configuration
|
# Windows-specific configuration
|
||||||
add_compile_definitions(
|
add_compile_definitions(
|
||||||
|
# Windows does not take a recv and a send on one socket from two
|
||||||
|
# threads, so a UDP service answers from a socket of its own
|
||||||
|
NO_SHARE_UDP_SOCKET
|
||||||
WIN32
|
WIN32
|
||||||
_WIN32
|
_WIN32
|
||||||
_MBCS
|
_MBCS
|
||||||
@ -153,7 +174,7 @@ if(WIN32)
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
# Windows libraries
|
# Windows libraries
|
||||||
set(WINDOWS_LIBS ws2_32 advapi32 user32 kernel32 gdi32 crypt32)
|
set(WINDOWS_LIBS ws2_32 mswsock advapi32 user32 kernel32 gdi32 crypt32)
|
||||||
|
|
||||||
# Windows plugins (always built)
|
# Windows plugins (always built)
|
||||||
set(DEFAULT_PLUGINS
|
set(DEFAULT_PLUGINS
|
||||||
@ -193,7 +214,6 @@ elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
|||||||
set(DEFAULT_PLUGINS
|
set(DEFAULT_PLUGINS
|
||||||
StringsPlugin
|
StringsPlugin
|
||||||
TrafficPlugin
|
TrafficPlugin
|
||||||
TransparentPlugin
|
|
||||||
FilePlugin
|
FilePlugin
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -215,7 +235,6 @@ elseif(CMAKE_SYSTEM_NAME MATCHES "FreeBSD|Darwin|OpenBSD|NetBSD")
|
|||||||
set(DEFAULT_PLUGINS
|
set(DEFAULT_PLUGINS
|
||||||
StringsPlugin
|
StringsPlugin
|
||||||
TrafficPlugin
|
TrafficPlugin
|
||||||
TransparentPlugin
|
|
||||||
FilePlugin
|
FilePlugin
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -232,15 +251,41 @@ else()
|
|||||||
set(DEFAULT_PLUGINS
|
set(DEFAULT_PLUGINS
|
||||||
StringsPlugin
|
StringsPlugin
|
||||||
TrafficPlugin
|
TrafficPlugin
|
||||||
TransparentPlugin
|
|
||||||
FilePlugin
|
FilePlugin
|
||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if(3PROXY_USE_MAILPROXY)
|
||||||
|
add_compile_definitions(WITH_POP3P WITH_IMAPP WITH_SMTPP)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(3PROXY_USE_FTP)
|
||||||
|
add_compile_definitions(WITH_FTP)
|
||||||
|
endif()
|
||||||
|
|
||||||
if(3PROXY_USE_HTTPSRV)
|
if(3PROXY_USE_HTTPSRV)
|
||||||
add_compile_definitions(WITH_HTTPSRV)
|
add_compile_definitions(WITH_HTTPSRV)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
# Transparent proxying needs a redirection that leaves the original
|
||||||
|
# destination where 3proxy reads it: the kernel on Linux, the socket on the
|
||||||
|
# BSDs. That means netfilter, OpenBSD divert-to or FreeBSD ipfw fwd. NetBSD
|
||||||
|
# and macOS rewrite the destination instead and are left out.
|
||||||
|
if(3PROXY_USE_TRANSPARENT AND (CMAKE_SYSTEM_NAME STREQUAL "Linux"
|
||||||
|
OR CMAKE_SYSTEM_NAME MATCHES "FreeBSD|OpenBSD|NetBSD"))
|
||||||
|
add_compile_definitions(WITH_TRANSPARENT)
|
||||||
|
set(3PROXY_TRANSPARENT_BUILT ON)
|
||||||
|
# pf keeps the original destination in its state table, which is read
|
||||||
|
# through /dev/pf. macOS has the device but ships no header for it.
|
||||||
|
include(CheckIncludeFiles)
|
||||||
|
check_include_files("sys/types.h;sys/socket.h;net/if.h;net/pfvar.h" HAVE_PFVAR_H)
|
||||||
|
if(HAVE_PFVAR_H)
|
||||||
|
add_compile_definitions(WITH_PF)
|
||||||
|
endif()
|
||||||
|
else()
|
||||||
|
set(3PROXY_TRANSPARENT_BUILT OFF)
|
||||||
|
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)
|
||||||
@ -370,11 +415,12 @@ set(3PROXY_CORE_SOURCES
|
|||||||
# Object libraries for common sources (shared between executables)
|
# Object libraries for common sources (shared between executables)
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
||||||
# Common object library (sockmap, sockgetchar, common, log)
|
# Common object library (sockmap, sockgetchar, common, sem, log)
|
||||||
add_library(common_obj OBJECT
|
add_library(common_obj OBJECT
|
||||||
src/sockmap.c
|
src/sockmap.c
|
||||||
src/sockgetchar.c
|
src/sockgetchar.c
|
||||||
src/common.c
|
src/common.c
|
||||||
|
src/sem.c
|
||||||
src/log.c
|
src/log.c
|
||||||
)
|
)
|
||||||
target_include_directories(common_obj PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
|
target_include_directories(common_obj PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
|
||||||
@ -471,6 +517,10 @@ if(PCRE2_FOUND)
|
|||||||
target_sources(3proxy PRIVATE src/pcre.c)
|
target_sources(3proxy PRIVATE src/pcre.c)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if(3PROXY_TRANSPARENT_BUILT)
|
||||||
|
target_sources(3proxy PRIVATE src/transparent.c)
|
||||||
|
endif()
|
||||||
|
|
||||||
target_include_directories(3proxy PRIVATE
|
target_include_directories(3proxy PRIVATE
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src
|
${CMAKE_CURRENT_SOURCE_DIR}/src
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/libs
|
${CMAKE_CURRENT_SOURCE_DIR}/src/libs
|
||||||
@ -648,6 +698,15 @@ foreach(PROXY_NAME proxy socks pop3p imapp smtpp ftppr tcppm udppm tlspr)
|
|||||||
continue()
|
continue()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
# A binary of nothing: without the support built, these files hold the
|
||||||
|
# stand-in the main binary uses and no service of their own.
|
||||||
|
if(NOT 3PROXY_USE_MAILPROXY AND PROXY_NAME MATCHES "^(pop3p|imapp|smtpp)$")
|
||||||
|
continue()
|
||||||
|
endif()
|
||||||
|
if(NOT 3PROXY_USE_FTP AND PROXY_NAME STREQUAL "ftppr")
|
||||||
|
continue()
|
||||||
|
endif()
|
||||||
|
|
||||||
if(PROXY_NAME STREQUAL "ftppr" OR PROXY_NAME STREQUAL "proxy")
|
if(PROXY_NAME STREQUAL "ftppr" OR PROXY_NAME STREQUAL "proxy")
|
||||||
# ftppr and proxy use ftp_obj
|
# ftppr and proxy use ftp_obj
|
||||||
add_executable(${PROXY_NAME}
|
add_executable(${PROXY_NAME}
|
||||||
@ -670,12 +729,12 @@ foreach(PROXY_NAME proxy socks pop3p imapp smtpp ftppr tcppm udppm tlspr)
|
|||||||
${CMAKE_CURRENT_SOURCE_DIR}/src
|
${CMAKE_CURRENT_SOURCE_DIR}/src
|
||||||
)
|
)
|
||||||
|
|
||||||
target_compile_definitions(${PROXY_NAME} PRIVATE
|
target_compile_definitions(${PROXY_NAME} PRIVATE WITHMAIN)
|
||||||
WITHMAIN
|
|
||||||
NOPORTMAP
|
|
||||||
)
|
|
||||||
|
|
||||||
if(NOT PROXY_NAME STREQUAL "udppm")
|
# each takes the main loop of its kind only: udppm UDP, the others TCP
|
||||||
|
if(PROXY_NAME STREQUAL "udppm")
|
||||||
|
target_compile_definitions(${PROXY_NAME} PRIVATE NOTCPMAIN)
|
||||||
|
else()
|
||||||
target_compile_definitions(${PROXY_NAME} PRIVATE NOUDPMAIN)
|
target_compile_definitions(${PROXY_NAME} PRIVATE NOUDPMAIN)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
@ -685,8 +744,12 @@ foreach(PROXY_NAME proxy socks pop3p imapp smtpp ftppr tcppm udppm tlspr)
|
|||||||
target_compile_definitions(${PROXY_NAME} PRIVATE ANONYMOUS)
|
target_compile_definitions(${PROXY_NAME} PRIVATE ANONYMOUS)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
# port mappers take port, host and port as arguments, the others options
|
||||||
|
# only; one of the two is defined, as in src/Makefile.inc
|
||||||
if(PROXY_NAME STREQUAL "tcppm" OR PROXY_NAME STREQUAL "udppm" OR PROXY_NAME STREQUAL "tlspr")
|
if(PROXY_NAME STREQUAL "tcppm" OR PROXY_NAME STREQUAL "udppm" OR PROXY_NAME STREQUAL "tlspr")
|
||||||
target_compile_definitions(${PROXY_NAME} PRIVATE PORTMAP)
|
target_compile_definitions(${PROXY_NAME} PRIVATE PORTMAP)
|
||||||
|
else()
|
||||||
|
target_compile_definitions(${PROXY_NAME} PRIVATE NOPORTMAP)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
@ -917,7 +980,7 @@ endif()
|
|||||||
# Summary
|
# Summary
|
||||||
message(STATUS "")
|
message(STATUS "")
|
||||||
message(STATUS "3proxy configuration summary:")
|
message(STATUS "3proxy configuration summary:")
|
||||||
message(STATUS " Version: ${PROJECT_VERSION}")
|
message(STATUS " Version: ${PROJECT_VERSION_FULL}")
|
||||||
message(STATUS " Platform: ${CMAKE_SYSTEM_NAME}")
|
message(STATUS " Platform: ${CMAKE_SYSTEM_NAME}")
|
||||||
message(STATUS " Compiler: ${CMAKE_C_COMPILER_ID} ${CMAKE_C_COMPILER_VERSION}")
|
message(STATUS " Compiler: ${CMAKE_C_COMPILER_ID} ${CMAKE_C_COMPILER_VERSION}")
|
||||||
message(STATUS " Build type: ${CMAKE_BUILD_TYPE}")
|
message(STATUS " Build type: ${CMAKE_BUILD_TYPE}")
|
||||||
@ -927,6 +990,7 @@ message(STATUS " BUILD_SHARED: ${3PROXY_BUILD_SHARED}")
|
|||||||
message(STATUS " USE_WOLFSSL: ${3PROXY_USE_WOLFSSL}")
|
message(STATUS " USE_WOLFSSL: ${3PROXY_USE_WOLFSSL}")
|
||||||
message(STATUS " USE_OPENSSL: ${3PROXY_USE_OPENSSL}")
|
message(STATUS " USE_OPENSSL: ${3PROXY_USE_OPENSSL}")
|
||||||
message(STATUS " USE_PCRE2: ${3PROXY_USE_PCRE2}")
|
message(STATUS " USE_PCRE2: ${3PROXY_USE_PCRE2}")
|
||||||
|
message(STATUS " TRANSPARENT: ${3PROXY_TRANSPARENT_BUILT}")
|
||||||
message(STATUS " USE_PAM: ${3PROXY_USE_PAM}")
|
message(STATUS " USE_PAM: ${3PROXY_USE_PAM}")
|
||||||
message(STATUS " USE_ODBC: ${3PROXY_USE_ODBC}")
|
message(STATUS " USE_ODBC: ${3PROXY_USE_ODBC}")
|
||||||
message(STATUS " USE_POLL: ${3PROXY_USE_POLL}")
|
message(STATUS " USE_POLL: ${3PROXY_USE_POLL}")
|
||||||
|
|||||||
@ -35,7 +35,7 @@ RUN TAG=$(curl -s https://api.github.com/repos/wolfSSL/wolfssl/releases/latest \
|
|||||||
make -j$(nproc) && make install
|
make -j$(nproc) && make install
|
||||||
|
|
||||||
FROM docker.io/alpine:latest AS buildenv
|
FROM docker.io/alpine:latest AS buildenv
|
||||||
RUN apk add --no-cache gcc make musl-dev pcre2-dev
|
RUN apk add --no-cache gcc make musl-dev linux-headers pcre2-dev
|
||||||
COPY --from=wolfssl /usr/local /usr/local
|
COPY --from=wolfssl /usr/local /usr/local
|
||||||
COPY . 3proxy
|
COPY . 3proxy
|
||||||
RUN cd 3proxy && mkdir -p bin &&\
|
RUN cd 3proxy && mkdir -p bin &&\
|
||||||
|
|||||||
@ -41,7 +41,7 @@ RUN TAG=$(curl -s https://api.github.com/repos/wolfSSL/wolfssl/releases/latest \
|
|||||||
make -j$(nproc) && make install
|
make -j$(nproc) && make install
|
||||||
|
|
||||||
FROM docker.io/alpine:latest AS buildenv
|
FROM docker.io/alpine:latest AS buildenv
|
||||||
RUN apk add --no-cache gcc make musl-dev
|
RUN apk add --no-cache gcc make musl-dev linux-headers
|
||||||
COPY --from=wolfssl /usr/local /usr/local
|
COPY --from=wolfssl /usr/local /usr/local
|
||||||
COPY . 3proxy
|
COPY . 3proxy
|
||||||
RUN cd 3proxy && mkdir -p bin &&\
|
RUN cd 3proxy && mkdir -p bin &&\
|
||||||
|
|||||||
@ -24,6 +24,16 @@ 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
|
||||||
|
MAILPROXY ?= false
|
||||||
|
ifeq ($(MAILPROXY),true)
|
||||||
|
CFLAGS += -DWITH_POP3P -DWITH_IMAPP -DWITH_SMTPP
|
||||||
|
MAIL_EXES = $(BUILDDIR)$(PREFIX)pop3p$(EXESUFFICS) $(BUILDDIR)$(PREFIX)imapp$(EXESUFFICS) $(BUILDDIR)$(PREFIX)smtpp$(EXESUFFICS)
|
||||||
|
endif
|
||||||
|
FTP ?= false
|
||||||
|
ifeq ($(FTP),true)
|
||||||
|
CFLAGS += -DWITH_FTP
|
||||||
|
FTP_EXES = $(BUILDDIR)$(PREFIX)ftppr$(EXESUFFICS)
|
||||||
|
endif
|
||||||
HTTPSRV ?= true
|
HTTPSRV ?= true
|
||||||
ifeq ($(HTTPSRV),true)
|
ifeq ($(HTTPSRV),true)
|
||||||
CFLAGS += -DWITH_HTTPSRV
|
CFLAGS += -DWITH_HTTPSRV
|
||||||
@ -44,7 +54,20 @@ AFTERCLEAN = (find . -type f -name "*.o" -delete && find src/ -type f -name "Mak
|
|||||||
TYPECOMMAND = cat
|
TYPECOMMAND = cat
|
||||||
COMPATLIBS =
|
COMPATLIBS =
|
||||||
MAKEFILE = Makefile.FreeBSD
|
MAKEFILE = Makefile.FreeBSD
|
||||||
PLUGINS ?= StringsPlugin TrafficPlugin TransparentPlugin FilePlugin
|
PLUGINS ?= StringsPlugin TrafficPlugin FilePlugin
|
||||||
|
|
||||||
|
# Transparent proxying, built in. The destination of a redirected connection
|
||||||
|
# comes from pf where its headers are available, and from the socket where a
|
||||||
|
# redirection leaves it there (OpenBSD divert-to, FreeBSD ipfw fwd). macOS
|
||||||
|
# has /dev/pf but ships no pfvar.h, so only the socket route is built there
|
||||||
|
# and no macOS redirection leaves the address on the socket.
|
||||||
|
CFLAGS += -DWITH_TRANSPARENT
|
||||||
|
TRANSPARENT_OBJS = transparent$(OBJSUFFICS)
|
||||||
|
|
||||||
|
PF_CHECK ?= $(shell printf "\#include <sys/types.h>\\n\#include <sys/socket.h>\\n\#include <net/if.h>\\n\#include <net/pfvar.h>\\n int main(){return 0;}" | tr -d \\\\ | $(CC) -x c $(CFLAGS) -o testpf.o -c - 2>/dev/null && rm testpf.o && echo true||echo false)
|
||||||
|
ifeq ($(PF_CHECK), true)
|
||||||
|
CFLAGS += -DWITH_PF
|
||||||
|
endif
|
||||||
ifeq ($(STATIC), true)
|
ifeq ($(STATIC), true)
|
||||||
LDFLAGS += -static
|
LDFLAGS += -static
|
||||||
CFLAGS += -DNOPLUGINS -DNOSTDRESOLVE -DNOCRYPT
|
CFLAGS += -DNOPLUGINS -DNOSTDRESOLVE -DNOCRYPT
|
||||||
@ -112,7 +135,7 @@ install: all
|
|||||||
install -d $(DESTDIR)$(MANDIR)/man8
|
install -d $(DESTDIR)$(MANDIR)/man8
|
||||||
install -m 644 man/3proxy.8 $(DESTDIR)$(MANDIR)/man8/3proxy.8
|
install -m 644 man/3proxy.8 $(DESTDIR)$(MANDIR)/man8/3proxy.8
|
||||||
for f in proxy socks pop3p imapp smtpp ftppr tcppm udppm tlspr; do \
|
for f in proxy socks pop3p imapp smtpp ftppr tcppm udppm tlspr; do \
|
||||||
if [ -f man/$$f.8 ]; then install -m 644 man/$$f.8 $(DESTDIR)$(MANDIR)/man8/$(PREFIX)$$f.8; fi; \
|
if [ -f man/$$f.8 ] && [ -f bin/$(PREFIX)$$f ]; then install -m 644 man/$$f.8 $(DESTDIR)$(MANDIR)/man8/$(PREFIX)$$f.8; fi; \
|
||||||
done
|
done
|
||||||
install -m 644 man/3proxy_crypt.8 $(DESTDIR)$(MANDIR)/man8/$(CRYPT_PREFIX)crypt.8
|
install -m 644 man/3proxy_crypt.8 $(DESTDIR)$(MANDIR)/man8/$(CRYPT_PREFIX)crypt.8
|
||||||
install -d $(DESTDIR)$(MANDIR)/man5
|
install -d $(DESTDIR)$(MANDIR)/man5
|
||||||
|
|||||||
@ -4,9 +4,9 @@
|
|||||||
# add -DWITH_ODBC to CFLAGS and -lodbc to LIBS to compile with ODBC
|
# add -DWITH_ODBC to CFLAGS and -lodbc to LIBS to compile with ODBC
|
||||||
# library support. Add -DSAFESQL for poorely written ODBC library / drivers.
|
# library support. Add -DSAFESQL for poorely written ODBC library / drivers.
|
||||||
#
|
#
|
||||||
# Linux splice() support is not built by default, it is slower than the
|
# Linux splice() support is built, but used only by services given the -s
|
||||||
# read/write path for most traffic. Add -DWITHSPLICE to CFLAGS to build it,
|
# option: it is faster for large transfers and slower for small requests.
|
||||||
# it must additionally be enabled per service with the -s option.
|
# Remove -DWITHSPLICE from CFLAGS to build without it.
|
||||||
|
|
||||||
BUILDDIR = ../bin/
|
BUILDDIR = ../bin/
|
||||||
PREFIX ?= 3proxy_
|
PREFIX ?= 3proxy_
|
||||||
@ -14,7 +14,7 @@ CRYPT_PREFIX ?= $(PREFIX)
|
|||||||
CC ?= gcc
|
CC ?= gcc
|
||||||
|
|
||||||
CFLAGS ?= -O3 -flto
|
CFLAGS ?= -O3 -flto
|
||||||
CFLAGS += -fno-strict-aliasing -c -pthread -D_GNU_SOURCE -DGETHOSTBYNAME_R -D_THREAD_SAFE -D_REENTRANT -DFD_SETSIZE=4096 -DWITH_POLL -DWITH_NETFILTER -D WITH_UN
|
CFLAGS += -fno-strict-aliasing -c -pthread -DWITHSPLICE -D_GNU_SOURCE -DGETHOSTBYNAME_R -D_THREAD_SAFE -D_REENTRANT -DFD_SETSIZE=4096 -DWITH_POLL -DWITH_NETFILTER -D WITH_UN
|
||||||
COUT = -o
|
COUT = -o
|
||||||
LN ?= ${CC}
|
LN ?= ${CC}
|
||||||
DCFLAGS ?= -fPIC
|
DCFLAGS ?= -fPIC
|
||||||
@ -27,6 +27,16 @@ CFLAGS += $(EXTRA_CFLAGS)
|
|||||||
LDFLAGS += $(EXTRA_LDFLAGS)
|
LDFLAGS += $(EXTRA_LDFLAGS)
|
||||||
# The HTTP server serves the endpoints declared by http lines. The admin
|
# 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.
|
# interface is built on top of it, so turning it off removes both.
|
||||||
|
MAILPROXY ?= false
|
||||||
|
ifeq ($(MAILPROXY),true)
|
||||||
|
CFLAGS += -DWITH_POP3P -DWITH_IMAPP -DWITH_SMTPP
|
||||||
|
MAIL_EXES = $(BUILDDIR)$(PREFIX)pop3p$(EXESUFFICS) $(BUILDDIR)$(PREFIX)imapp$(EXESUFFICS) $(BUILDDIR)$(PREFIX)smtpp$(EXESUFFICS)
|
||||||
|
endif
|
||||||
|
FTP ?= false
|
||||||
|
ifeq ($(FTP),true)
|
||||||
|
CFLAGS += -DWITH_FTP
|
||||||
|
FTP_EXES = $(BUILDDIR)$(PREFIX)ftppr$(EXESUFFICS)
|
||||||
|
endif
|
||||||
HTTPSRV ?= true
|
HTTPSRV ?= true
|
||||||
ifeq ($(HTTPSRV),true)
|
ifeq ($(HTTPSRV),true)
|
||||||
CFLAGS += -DWITH_HTTPSRV
|
CFLAGS += -DWITH_HTTPSRV
|
||||||
@ -50,8 +60,12 @@ MAKEFILE = Makefile.Linux
|
|||||||
# PamAuth requires libpam, you may require pam-devel package to be installed
|
# PamAuth requires libpam, you may require pam-devel package to be installed
|
||||||
# SSLPlugin requires -lcrypto -lssl
|
# SSLPlugin requires -lcrypto -lssl
|
||||||
#LIBS = -lcrypto -lssl -ldl
|
#LIBS = -lcrypto -lssl -ldl
|
||||||
#PLUGINS = SSLPlugin StringsPlugin TrafficPlugin PCREPlugin TransparentPlugin PamAuth
|
#PLUGINS = StringsPlugin TrafficPlugin PamAuth LdapPlugin
|
||||||
PLUGINS ?= StringsPlugin TrafficPlugin TransparentPlugin FilePlugin
|
PLUGINS ?= StringsPlugin TrafficPlugin FilePlugin
|
||||||
|
|
||||||
|
# Transparent proxying, built in: it needs the packet filter of the platform
|
||||||
|
CFLAGS += -DWITH_TRANSPARENT
|
||||||
|
TRANSPARENT_OBJS = transparent$(OBJSUFFICS)
|
||||||
ifeq ($(STATIC), true)
|
ifeq ($(STATIC), true)
|
||||||
LDFLAGS += -static
|
LDFLAGS += -static
|
||||||
CFLAGS += -DNOPLUGINS -DNOSTDRESOLVE -DNOCRYPT
|
CFLAGS += -DNOPLUGINS -DNOSTDRESOLVE -DNOCRYPT
|
||||||
@ -109,15 +123,18 @@ INSTALL_BIN = $(INSTALL) -m 755
|
|||||||
INSTALL_DATA = $(INSTALL) -m 644
|
INSTALL_DATA = $(INSTALL) -m 644
|
||||||
INSTALL_OBJS = bin/3proxy \
|
INSTALL_OBJS = bin/3proxy \
|
||||||
bin/$(CRYPT_PREFIX)crypt \
|
bin/$(CRYPT_PREFIX)crypt \
|
||||||
bin/$(PREFIX)ftppr \
|
|
||||||
bin/$(PREFIX)imapp \
|
|
||||||
bin/$(PREFIX)pop3p \
|
|
||||||
bin/$(PREFIX)proxy \
|
bin/$(PREFIX)proxy \
|
||||||
bin/$(PREFIX)smtpp \
|
|
||||||
bin/$(PREFIX)socks \
|
bin/$(PREFIX)socks \
|
||||||
bin/$(PREFIX)tcppm \
|
bin/$(PREFIX)tcppm \
|
||||||
bin/$(PREFIX)tlspr \
|
bin/$(PREFIX)tlspr \
|
||||||
bin/$(PREFIX)udppm
|
bin/$(PREFIX)udppm
|
||||||
|
# the mail proxies and ftppr are only there when they were built
|
||||||
|
ifeq ($(MAILPROXY),true)
|
||||||
|
INSTALL_OBJS += bin/$(PREFIX)imapp bin/$(PREFIX)pop3p bin/$(PREFIX)smtpp
|
||||||
|
endif
|
||||||
|
ifeq ($(FTP),true)
|
||||||
|
INSTALL_OBJS += bin/$(PREFIX)ftppr
|
||||||
|
endif
|
||||||
|
|
||||||
|
|
||||||
INSTALL_CFG = scripts/3proxy.cfg.chroot
|
INSTALL_CFG = scripts/3proxy.cfg.chroot
|
||||||
@ -175,7 +192,7 @@ install-man:
|
|||||||
$(INSTALL_DATA) man/3proxy.cfg.5 $(MANDIR5)
|
$(INSTALL_DATA) man/3proxy.cfg.5 $(MANDIR5)
|
||||||
$(INSTALL_DATA) man/3proxy.8 $(MANDIR8)
|
$(INSTALL_DATA) man/3proxy.8 $(MANDIR8)
|
||||||
for f in proxy socks pop3p imapp smtpp ftppr tcppm udppm tlspr; do \
|
for f in proxy socks pop3p imapp smtpp ftppr tcppm udppm tlspr; do \
|
||||||
if [ -f man/$$f.8 ]; then $(INSTALL_DATA) man/$$f.8 $(MANDIR8)/$(PREFIX)$$f.8; fi; \
|
if [ -f man/$$f.8 ] && [ -f bin/$(PREFIX)$$f ]; then $(INSTALL_DATA) man/$$f.8 $(MANDIR8)/$(PREFIX)$$f.8; fi; \
|
||||||
done
|
done
|
||||||
$(INSTALL_DATA) man/3proxy_crypt.8 $(MANDIR8)/$(CRYPT_PREFIX)crypt.8
|
$(INSTALL_DATA) man/3proxy_crypt.8 $(MANDIR8)/$(CRYPT_PREFIX)crypt.8
|
||||||
|
|
||||||
|
|||||||
@ -14,6 +14,16 @@ COUT = -o ./
|
|||||||
LN = $(CC)
|
LN = $(CC)
|
||||||
LDFLAGS = -xO3
|
LDFLAGS = -xO3
|
||||||
DCFLAGS = -fPIC
|
DCFLAGS = -fPIC
|
||||||
|
MAILPROXY ?= false
|
||||||
|
ifeq ($(MAILPROXY),true)
|
||||||
|
CFLAGS += -DWITH_POP3P -DWITH_IMAPP -DWITH_SMTPP
|
||||||
|
MAIL_EXES = $(BUILDDIR)$(PREFIX)pop3p$(EXESUFFICS) $(BUILDDIR)$(PREFIX)imapp$(EXESUFFICS) $(BUILDDIR)$(PREFIX)smtpp$(EXESUFFICS)
|
||||||
|
endif
|
||||||
|
FTP ?= false
|
||||||
|
ifeq ($(FTP),true)
|
||||||
|
CFLAGS += -DWITH_FTP
|
||||||
|
FTP_EXES = $(BUILDDIR)$(PREFIX)ftppr$(EXESUFFICS)
|
||||||
|
endif
|
||||||
HTTPSRV ?= true
|
HTTPSRV ?= true
|
||||||
ifeq ($(HTTPSRV),true)
|
ifeq ($(HTTPSRV),true)
|
||||||
CFLAGS += -DWITH_HTTPSRV
|
CFLAGS += -DWITH_HTTPSRV
|
||||||
@ -34,7 +44,7 @@ AFTERCLEAN = (find . -type f -name "*.o" -delete && find src/ -type f -name "Mak
|
|||||||
TYPECOMMAND = cat
|
TYPECOMMAND = cat
|
||||||
COMPATLIBS =
|
COMPATLIBS =
|
||||||
MAKEFILE = Makefile.Solaris
|
MAKEFILE = Makefile.Solaris
|
||||||
PLUGINS = StringsPlugin TrafficPlugin TransparentPlugin FilePlugin
|
PLUGINS = StringsPlugin TrafficPlugin FilePlugin
|
||||||
|
|
||||||
WOLFSSL_CHECK = $(shell printf "\#include <wolfssl/options.h>\\n\#include <wolfssl/openssl/ssl.h>\\n int main(){return 0;}" | tr -d \\\\ | $(CC) -x c $(CFLAGS) -o testwssl.o - 2>/dev/null && $(CC) $(LDFLAGS) -o testwssl testwssl.o -lwolfssl 2>/dev/null && rm testwssl testwssl.o && echo true||echo false)
|
WOLFSSL_CHECK = $(shell printf "\#include <wolfssl/options.h>\\n\#include <wolfssl/openssl/ssl.h>\\n int main(){return 0;}" | tr -d \\\\ | $(CC) -x c $(CFLAGS) -o testwssl.o - 2>/dev/null && $(CC) $(LDFLAGS) -o testwssl testwssl.o -lwolfssl 2>/dev/null && rm testwssl testwssl.o && echo true||echo false)
|
||||||
ifeq ($(WOLFSSL_CHECK), true)
|
ifeq ($(WOLFSSL_CHECK), true)
|
||||||
|
|||||||
@ -18,13 +18,13 @@ 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 = /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" /D "NO_SHARE_UDP_SOCKET" /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
|
||||||
DLFLAGS = /DLL
|
DLFLAGS = /DLL
|
||||||
DLSUFFICS = .dll
|
DLSUFFICS = .dll
|
||||||
LIBS = ws2_32.lib advapi32.lib odbc32.lib user32.lib kernel32.lib Gdi32.lib Crypt32.lib $(SSL_LIBS) pcre2-8.lib
|
LIBS = ws2_32.lib mswsock.lib advapi32.lib odbc32.lib user32.lib kernel32.lib Gdi32.lib Crypt32.lib $(SSL_LIBS) pcre2-8.lib
|
||||||
LIBSPREFIX =
|
LIBSPREFIX =
|
||||||
LIBSSUFFIX = .lib
|
LIBSSUFFIX = .lib
|
||||||
LIBEXT = .lib
|
LIBEXT = .lib
|
||||||
|
|||||||
@ -26,6 +26,16 @@ 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
|
||||||
|
MAILPROXY ?= false
|
||||||
|
ifeq ($(MAILPROXY),true)
|
||||||
|
CFLAGS += -DWITH_POP3P -DWITH_IMAPP -DWITH_SMTPP
|
||||||
|
MAIL_EXES = $(BUILDDIR)$(PREFIX)pop3p$(EXESUFFICS) $(BUILDDIR)$(PREFIX)imapp$(EXESUFFICS) $(BUILDDIR)$(PREFIX)smtpp$(EXESUFFICS)
|
||||||
|
endif
|
||||||
|
FTP ?= false
|
||||||
|
ifeq ($(FTP),true)
|
||||||
|
CFLAGS += -DWITH_FTP
|
||||||
|
FTP_EXES = $(BUILDDIR)$(PREFIX)ftppr$(EXESUFFICS)
|
||||||
|
endif
|
||||||
HTTPSRV ?= true
|
HTTPSRV ?= true
|
||||||
ifeq ($(HTTPSRV),true)
|
ifeq ($(HTTPSRV),true)
|
||||||
CFLAGS += -DWITH_HTTPSRV
|
CFLAGS += -DWITH_HTTPSRV
|
||||||
@ -46,7 +56,13 @@ AFTERCLEAN = (find . -type f -name "*.o" -delete && find src/ -type f -name "Mak
|
|||||||
TYPECOMMAND = cat
|
TYPECOMMAND = cat
|
||||||
COMPATLIBS =
|
COMPATLIBS =
|
||||||
MAKEFILE = Makefile.unix
|
MAKEFILE = Makefile.unix
|
||||||
PLUGINS ?= StringsPlugin TrafficPlugin TransparentPlugin FilePlugin
|
PLUGINS ?= StringsPlugin TrafficPlugin FilePlugin
|
||||||
|
|
||||||
|
# Transparent proxying is not built here: this makefile is for the systems
|
||||||
|
# without a redirection 3proxy can read the original destination from. Where
|
||||||
|
# the platform has one - OpenBSD divert-to is the case that fits - uncomment:
|
||||||
|
#CFLAGS += -DWITH_TRANSPARENT
|
||||||
|
#TRANSPARENT_OBJS = transparent$(OBJSUFFICS)
|
||||||
ifeq ($(STATIC), true)
|
ifeq ($(STATIC), true)
|
||||||
LDFLAGS += -static
|
LDFLAGS += -static
|
||||||
CFLAGS += -DNOPLUGINS -DNOSTDRESOLVE -DNOCRYPT
|
CFLAGS += -DNOPLUGINS -DNOSTDRESOLVE -DNOCRYPT
|
||||||
@ -114,7 +130,7 @@ install: all
|
|||||||
install -d $(DESTDIR)$(MANDIR)/man8
|
install -d $(DESTDIR)$(MANDIR)/man8
|
||||||
install -m 644 man/3proxy.8 $(DESTDIR)$(MANDIR)/man8/3proxy.8
|
install -m 644 man/3proxy.8 $(DESTDIR)$(MANDIR)/man8/3proxy.8
|
||||||
for f in proxy socks pop3p imapp smtpp ftppr tcppm udppm tlspr; do \
|
for f in proxy socks pop3p imapp smtpp ftppr tcppm udppm tlspr; do \
|
||||||
if [ -f man/$$f.8 ]; then install -m 644 man/$$f.8 $(DESTDIR)$(MANDIR)/man8/$(PREFIX)$$f.8; fi; \
|
if [ -f man/$$f.8 ] && [ -f bin/$(PREFIX)$$f ]; then install -m 644 man/$$f.8 $(DESTDIR)$(MANDIR)/man8/$(PREFIX)$$f.8; fi; \
|
||||||
done
|
done
|
||||||
install -m 644 man/3proxy_crypt.8 $(DESTDIR)$(MANDIR)/man8/$(CRYPT_PREFIX)crypt.8
|
install -m 644 man/3proxy_crypt.8 $(DESTDIR)$(MANDIR)/man8/$(CRYPT_PREFIX)crypt.8
|
||||||
install -d $(DESTDIR)$(MANDIR)/man5
|
install -d $(DESTDIR)$(MANDIR)/man5
|
||||||
|
|||||||
@ -8,13 +8,13 @@ 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 = /D "WITH_HTTPSRV" /nologo /Ox /MT /D "NOIPV6" /D "NO_UN" /D "NODEBUG" /D "NORADIUS" /D"WATCOM" /D "NO_SHARE_UDP_SOCKET" /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
|
||||||
DLFLAGS = /DLL
|
DLFLAGS = /DLL
|
||||||
DLSUFFICS = .dll
|
DLSUFFICS = .dll
|
||||||
LIBS = ws2_32.lib advapi32.lib user32.lib kernel32.lib
|
LIBS = ws2_32.lib mswsock.lib advapi32.lib user32.lib kernel32.lib
|
||||||
LIBSPREFIX =
|
LIBSPREFIX =
|
||||||
LIBSSUFFIX = .lib
|
LIBSSUFFIX = .lib
|
||||||
LIBEXT = .lib
|
LIBEXT = .lib
|
||||||
|
|||||||
15
Makefile.win
15
Makefile.win
@ -11,6 +11,9 @@ CRYPT_PREFIX ?= $(PREFIX)
|
|||||||
CC ?= gcc
|
CC ?= gcc
|
||||||
CFLAGS ?= -O3 -flto
|
CFLAGS ?= -O3 -flto
|
||||||
CFLAGS += -fno-strict-aliasing -c -mthreads -DWITH_WSAPOLL -DWITH_ODBC
|
CFLAGS += -fno-strict-aliasing -c -mthreads -DWITH_WSAPOLL -DWITH_ODBC
|
||||||
|
# Windows does not take a recv and a send on one socket from two threads,
|
||||||
|
# so a UDP service answers from a socket of its own
|
||||||
|
CFLAGS += -DNO_SHARE_UDP_SOCKET
|
||||||
COUT = -o
|
COUT = -o
|
||||||
LN ?= $(CC)
|
LN ?= $(CC)
|
||||||
LDFLAGS ?= -O3 -flto
|
LDFLAGS ?= -O3 -flto
|
||||||
@ -20,6 +23,16 @@ 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)
|
||||||
|
MAILPROXY ?= false
|
||||||
|
ifeq ($(MAILPROXY),true)
|
||||||
|
CFLAGS += -DWITH_POP3P -DWITH_IMAPP -DWITH_SMTPP
|
||||||
|
MAIL_EXES = $(BUILDDIR)$(PREFIX)pop3p$(EXESUFFICS) $(BUILDDIR)$(PREFIX)imapp$(EXESUFFICS) $(BUILDDIR)$(PREFIX)smtpp$(EXESUFFICS)
|
||||||
|
endif
|
||||||
|
FTP ?= false
|
||||||
|
ifeq ($(FTP),true)
|
||||||
|
CFLAGS += -DWITH_FTP
|
||||||
|
FTP_EXES = $(BUILDDIR)$(PREFIX)ftppr$(EXESUFFICS)
|
||||||
|
endif
|
||||||
HTTPSRV ?= true
|
HTTPSRV ?= true
|
||||||
ifeq ($(HTTPSRV),true)
|
ifeq ($(HTTPSRV),true)
|
||||||
CFLAGS += -DWITH_HTTPSRV
|
CFLAGS += -DWITH_HTTPSRV
|
||||||
@ -27,7 +40,7 @@ ifeq ($(HTTPSRV),true)
|
|||||||
endif
|
endif
|
||||||
DLFLAGS ?= -shared
|
DLFLAGS ?= -shared
|
||||||
DLSUFFICS = .dll
|
DLSUFFICS = .dll
|
||||||
LIBS += -lws2_32 -lodbc32 -ladvapi32 -luser32 -lbcrypt
|
LIBS += -lws2_32 -lmswsock -lodbc32 -ladvapi32 -luser32 -lbcrypt
|
||||||
LIBSPREFIX = -l
|
LIBSPREFIX = -l
|
||||||
LIBSSUFFIX =
|
LIBSSUFFIX =
|
||||||
LNOUT = -o
|
LNOUT = -o
|
||||||
|
|||||||
@ -8,7 +8,8 @@
|
|||||||
|
|
||||||
- **Master** (current/latest) branch - 3proxy 1.0
|
- **Master** (current/latest) branch - 3proxy 1.0
|
||||||
- **0.9** (LTS) branch - 3proxy 0.9.9.x
|
- **0.9** (LTS) branch - 3proxy 0.9.9.x
|
||||||
- **Devel** branch - 3proxy 2.0-devel (don't use it)
|
- **Devel** branch - 3proxy 1.1 (development, don't use it in production)
|
||||||
|
- **Experimental** branch - 3proxy 2.0 (experimental, don't use it)
|
||||||
|
|
||||||
### Binaries (deb / rpm / Windows zip)
|
### Binaries (deb / rpm / Windows zip)
|
||||||
|
|
||||||
|
|||||||
23
SECURITY.md
23
SECURITY.md
@ -4,8 +4,9 @@
|
|||||||
|
|
||||||
| Version | Supported |
|
| Version | Supported |
|
||||||
| ------- | ------------------ |
|
| ------- | ------------------ |
|
||||||
| 0.9.8 | :white_check_mark: |
|
| 0.9.9.x (LTS) | :white_check_mark: |
|
||||||
| < 0.9.8 | :x: |
|
| 1.0.x (Main) | :white_check_mark: |
|
||||||
|
| <= 0.9.8 | :x: |
|
||||||
|
|
||||||
## Hardening a deployment
|
## Hardening a deployment
|
||||||
|
|
||||||
@ -34,8 +35,8 @@ For High/Critical patched version is released within 2 weeks
|
|||||||
|
|
||||||
## Verifying downloads
|
## Verifying downloads
|
||||||
|
|
||||||
Release binaries are published with SHA256 checksums, an OpenPGP signature and
|
Release binaries and the source tarball are published with SHA256 checksums, an
|
||||||
a GitHub build provenance attestation.
|
OpenPGP signature and a GitHub build provenance attestation.
|
||||||
|
|
||||||
The release signing key is `3proxy-release-key.asc` in the root of this
|
The release signing key is `3proxy-release-key.asc` in the root of this
|
||||||
repository, an RSA-4096 key:
|
repository, an RSA-4096 key:
|
||||||
@ -59,6 +60,18 @@ gpg --verify SHA256SUMS-x86_64.asc SHA256SUMS-x86_64
|
|||||||
sha256sum -c SHA256SUMS-x86_64
|
sha256sum -c SHA256SUMS-x86_64
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The source tarball published with each release is signed as well:
|
||||||
|
|
||||||
|
```
|
||||||
|
gpg --verify SHA256SUMS-src.asc SHA256SUMS-src
|
||||||
|
sha256sum -c SHA256SUMS-src
|
||||||
|
gpg --verify 3proxy-0.9.9.tar.gz.asc 3proxy-0.9.9.tar.gz
|
||||||
|
```
|
||||||
|
|
||||||
|
Prefer it over the `Source code (tar.gz)` link GitHub generates automatically:
|
||||||
|
only the published tarball is signed. It is produced with `git archive` from
|
||||||
|
the release tag, so it can be regenerated and compared byte for byte.
|
||||||
|
|
||||||
RPM packages are signed, the signature is checked by rpm itself:
|
RPM packages are signed, the signature is checked by rpm itself:
|
||||||
|
|
||||||
```
|
```
|
||||||
@ -79,5 +92,3 @@ verified with the GitHub CLI:
|
|||||||
gh attestation verify 3proxy-0.9.9.x86_64.rpm --owner 3proxy
|
gh attestation verify 3proxy-0.9.9.x86_64.rpm --owner 3proxy
|
||||||
gh attestation verify oci://docker.io/3proxy/3proxy:latest --owner 3proxy
|
gh attestation verify oci://docker.io/3proxy/3proxy:latest --owner 3proxy
|
||||||
```
|
```
|
||||||
|
|
||||||
Windows binaries are Authenticode signed in addition to the above.
|
|
||||||
|
|||||||
12
debian/changelog
vendored
12
debian/changelog
vendored
@ -1,3 +1,15 @@
|
|||||||
|
3proxy (1.0.2-1) buster; urgency=medium
|
||||||
|
|
||||||
|
*3proxy 1.0.2 initial build
|
||||||
|
|
||||||
|
-- z3APA3A <3apa3a@3proxy.org> Sat, 24 Oct 2026 11:21:40 +0000
|
||||||
|
|
||||||
|
3proxy (1.0.1-1) buster; urgency=medium
|
||||||
|
|
||||||
|
*3proxy 1.0.1 initial build
|
||||||
|
|
||||||
|
-- z3APA3A <3apa3a@3proxy.org> Sat, 10 Oct 2026 16:21:01 +0000
|
||||||
|
|
||||||
3proxy (1.0.0-1) buster; urgency=medium
|
3proxy (1.0.0-1) buster; urgency=medium
|
||||||
|
|
||||||
*3proxy 1.0.0 initial build
|
*3proxy 1.0.0 initial build
|
||||||
|
|||||||
2
debian/control
vendored
2
debian/control
vendored
@ -14,5 +14,5 @@ Depends: ${shlibs:Depends}, ${misc:Depends}
|
|||||||
Description: tiny free proxy server
|
Description: tiny free proxy server
|
||||||
3Proxy tiny free proxy server is really tiny freeware proxy servers set.
|
3Proxy tiny free proxy server is really tiny freeware proxy servers set.
|
||||||
It includes HTTP proxy with HTTPS and FTP support, SOCKSv4/SOCKSv4.5/SOCKSv5 proxy (socks/socks.exe), POP3 proxy, SMTP proxy, FTP proxy, caching DNS proxy, TCP and UDP portmappers.
|
It includes HTTP proxy with HTTPS and FTP support, SOCKSv4/SOCKSv4.5/SOCKSv5 proxy (socks/socks.exe), POP3 proxy, SMTP proxy, FTP proxy, caching DNS proxy, TCP and UDP portmappers.
|
||||||
You can use every proxy as a standalone program (socks, proxy, tcppm, udppm, pop3p) or use combined program (3proxy). Combined proxy additionally supports features like access control, bandwidth limiting, limiting daily/weekly/monthly traffic amount, proxy chaining, log rotation, syslog and ODBC logging, etc.
|
You can use every proxy as a standalone program (socks, proxy, tcppm, udppm, tlspr) or use combined program (3proxy). Combined proxy additionally supports features like access control, bandwidth limiting, limiting daily/weekly/monthly traffic amount, proxy chaining, log rotation, syslog and ODBC logging, etc.
|
||||||
It's created to be small, simple and yet very functional.
|
It's created to be small, simple and yet very functional.
|
||||||
23
doc/changelog/1/0/1
Normal file
23
doc/changelog/1/0/1
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
! Fix: crash (segmentation fault) on configuration reload (SIGUSR1) on a busy server: a closing session could still use its service after the reload had freed it; the race existed since 0.9.7, the 5 second linger added in 0.9.9 made it much more likely (#1278)
|
||||||
|
! Fix: -Di and -De (bind to interface) were not applied to SOCKSv5 UDP ASSOCIATE and BIND sockets, the FTP proxy data connection and connect back (#1277)
|
||||||
|
! Fix: SOCKSv5 UDP ASSOCIATE over IPv6: the source address of a datagram was truncated and the reply was sent to the wrong address (#1279)
|
||||||
|
! Fix: SOCKSv5 UDP ASSOCIATE: an access rule with host names matched any datagram sent to a literal address; the address is used as the host name now, as it is for TCP requests
|
||||||
|
! Fix: a request rewritten with pcre_rewrite was only logged and was sent unchanged to the server on a direct connection; the rewritten path and query are what the server receives now
|
||||||
|
! Fix: buffer could be insufficient for the headers the HTTP proxy adds after a pcre_rewrite had grown the request
|
||||||
|
! Fix: heap buffer overflow in pcre_rewrite: a $N reference to a group which did not match was written out as its digits, which the space allocated for the result did not count
|
||||||
|
! Fix: pcre_extend did nothing, the rules it appended were never consulted; the manual described an argument the command does not take
|
||||||
|
! Fix: certificates generated for TLS MitM (openssl) carried no subject and authority key identifiers and were refused by strict verifiers, including macOS clients, OpenSSL with x509_strict and Python 3.13
|
||||||
|
! Fix: null pointer dereference in ftppr on PASS without USER and in smtpp on a user name without a host
|
||||||
|
! Fix: no service started with wolfSSL on glibc: the default thread stack was too small for wolfSSL thread-local storage, pthread_create() failed and the failure was not reported
|
||||||
|
! Fix: dnspr did not work on Windows; Windows builds answer UDP from a socket of their own again
|
||||||
|
+ Built-in HTTP server: httpsrv service with http rules to serve files (cached or with sendfile / TransmitFile), redirects, rewrites, fixed replies and proxypass to forward requests as the proxy does, so one port can serve both a site and a proxy; admin is httpsrv with the administration pages
|
||||||
|
+ Host names in access rules may be regular expressions with a pcre: (or regex:) prefix
|
||||||
|
+ transparent / notransparent commands: transparent proxying is built in (netfilter on Linux, pf on BSD, socket for divert-to and ipfw fwd), TransparentPlugin is removed and a configuration loading it keeps working
|
||||||
|
+ parent: weight may be written as a fraction (.5) or a percentage (50%); weight 0 is a fallback parent used only when every weighted parent of its group has failed; a failed parent is left out of the next attempt of the same connection
|
||||||
|
+ parent extport / intport: range of local ports for outgoing connections and for sockets facing the client (UDP ASSOCIATE, FTP data)
|
||||||
|
+ A parent given by host name is resolved for each connection when nserver is used, instead of once on configuration load
|
||||||
|
+ $$ in the configuration stands for a single dollar sign, a dollar inside quotes is ordinary text
|
||||||
|
+ OpenWrt package (procd init script, UCI configuration) and OpenWrt builds for mipsel_24kc, mips_24kc, arm_cortex-a7_neon-vfpv4 and aarch64_cortex-a53
|
||||||
|
- pop3p, imapp, smtpp and ftppr are not in a default build: build with MAILPROXY=true and FTP=true (CMake: 3PROXY_USE_MAILPROXY, 3PROXY_USE_FTP); without them the mail proxies run as tlspr negotiating STARTTLS, and ftppr refuses clients
|
||||||
|
- FTP over HTTP proxy (ftp:// URLs) is not in a default build either and has to be enabled explicitly: build with FTP=true (CMake: 3PROXY_USE_FTP), without it the HTTP proxy does not fetch ftp:// URLs
|
||||||
|
- Windows binaries are no longer signed with a self-signed Authenticode certificate, it gave no trust and triggered antivirus false positives
|
||||||
23
doc/changelog/1/0/2
Normal file
23
doc/changelog/1/0/2
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
! Fix: dnspr relayed to the client whatever came first to the socket it asked the name server from, from any address and under any id, so a forged answer could be passed on; the query now goes out under a random id, and only an answer from the name server asked, with that id and the same question, is relayed
|
||||||
|
! Fix: a client sending a part of a TLS record and then nothing kept a thread taking all of a CPU, and the read never timed out
|
||||||
|
! Fix: without -Ne, the reply to SOCKS CONNECT and BIND gave the address of the client's connection to the proxy instead of the socket towards the destination: a BIND client was told the proxy's own port and the peer could never reach it (since 0.9.6)
|
||||||
|
! Fix: a client which shut down its sending side (half-close) could get the answer cut short, megabytes short with large answers; likewise an upload after the server half-closed
|
||||||
|
! Fix: with chroot every local time was UTC and every syslog message was lost: the time zone is loaded and syslog connected before chroot; the chroot entry of 3proxy.cfg(5) lists what PAM, LDAP and ODBC still need inside the chroot
|
||||||
|
! Fix: the HTTP proxy closed the server connection of a kept-alive client and opened another one when only the request method changed (a POST after a GET to the same host)
|
||||||
|
! Fix: the last ssl_client_mode in the configuration applied to every service, and a reload changed it for the services still running
|
||||||
|
! Fix: memory leaks: on every refused configuration reload, on every failed TLS handshake, the server CA paths of every TLS service, compiled pcre: / regex: host names of access rules and patterns of httpsrv rules
|
||||||
|
! Fix: Unix sockets: on Linux a proxy listening on a Unix socket relayed only the first bytes of an answer (result 95); an abstract socket (unix:@NAME) was bound under a name padded with zeros, which clients did not reach; tcppm refused port 0 for a unix: target, as given in the manual's example; on a reload the socket file of a listening service was removed
|
||||||
|
! Fix: SIGPIPE could kill 3proxy when a client was gone while services were starting, and standalone services (proxy, socks, ...) at any time; sends use MSG_NOSIGNAL where the system has it
|
||||||
|
! Fix: TLS MitM failed for every connection with wolfSSL built with WOLFSSL_QT (wolfSSL 5.9.1 of Ubuntu 26.04): the source certificate's serial number could not be copied; a serial number made from the hash of the source certificate is used then
|
||||||
|
! Fix: Windows: closing a connection could leave its socket open (SO_LINGER on a non-blocking socket) or reset it while the peer still sent, so the end of an answer could be lost
|
||||||
|
! Fix: httpsrv: the body of a request answered without being read (for example when authentication is required) was read as the next request; a client still sending when a connection without keep-alive closed could get a reset instead of the answer
|
||||||
|
! Fix: httpsrv on Windows: UNC paths (\\server\share) did not work
|
||||||
|
! Fix: httpsrv and admin services and their clients were named ZOMBIE on the admin services page, in %N of logs and in RADIUS
|
||||||
|
! Fix: the Watcom (lite) build did not compile, 1.0.1 had no binaries for it
|
||||||
|
+ -s (splice) per direction: -s1 splices what the server sends (downloads), -s2 what the client sends (uploads), -s3 both; -s alone splices only where it pays (large bodies, long streams); pipes are kept with the connection for its next requests. splice() is built by default on Linux again and is used only by services given -s
|
||||||
|
+ Socket options (-oc, -os, -ol, -or, -oR) take NAME=VALUE; NAME=0 turns an option off, also one set by default; an unknown name or a value which is not a number is an error
|
||||||
|
+ The HTTP proxy frees its 64 KB request buffer while a CONNECT tunnel relays data and while a kept-alive connection waits for its next request: 2000 tunnels take 213 MB instead of 339 MB
|
||||||
|
+ TLS records are read ahead with OpenSSL 1.1.0 and later: decrypting takes 9-14% less CPU
|
||||||
|
+ Faster service stop and configuration reload, no fixed delays; a connection waiting for a free maxconn slot gets it at once
|
||||||
|
- TCP_NODELAY is set by default on proxy-to-client and proxy-to-server sockets; -ocTCP_NODELAY=0 / -osTCP_NODELAY=0 turns it off
|
||||||
|
- The HTTP proxy fetches ftp:// URLs only when the service is given the new -Xftp option (in a build with FTP=true)
|
||||||
@ -34,7 +34,8 @@ Each connected client requires:
|
|||||||
<ul>
|
<ul>
|
||||||
<li>1 thread (process)
|
<li>1 thread (process)
|
||||||
<li>2 sockets (file descriptors). For FTP, 4 sockets are required.
|
<li>2 sockets (file descriptors). For FTP, 4 sockets are required.
|
||||||
<br>Up to 128K of kernel buffer memory. This is the theoretical maximum; actual numbers depend on connection quality and traffic amount.
|
<br>With splice() (Linux, -s), a pipe (2 file descriptors) for each spliced direction: up to 4 more file descriptors, 6 for FTP.
|
||||||
|
<br>Up to 128K (up to 256K with splice()) of kernel buffer memory. This is the theoretical maximum; actual numbers depend on connection quality and traffic amount.
|
||||||
<br>1 additional socket (file descriptor) during name resolution for non-cached names
|
<br>1 additional socket (file descriptor) during name resolution for non-cached names
|
||||||
<br>1 additional socket during authentication or logging for RADIUS authentication or logging.
|
<br>1 additional socket during authentication or logging for RADIUS authentication or logging.
|
||||||
<li>1 ephemeral port (3 ephemeral ports for FTP connections).
|
<li>1 ephemeral port (3 ephemeral ports for FTP connections).
|
||||||
@ -279,6 +280,8 @@ tuning anything else.
|
|||||||
|
|
||||||
If 3proxy is used in a VPS environment, there can be additional limitations.
|
If 3proxy is used in a VPS environment, there can be additional limitations.
|
||||||
For example, kernel resources, system CPU usage, and IOCTLs can be limited differently, and this can become a bottleneck.
|
For example, kernel resources, system CPU usage, and IOCTLs can be limited differently, and this can become a bottleneck.
|
||||||
|
splice() (see below) moves work into the kernel, so where the hosting limits kernel resources
|
||||||
|
(seen as nearly 100% CPU usage without any real activity of the application) do not enable it.
|
||||||
|
|
||||||
<h4>Extending the Ephemeral Port Range</h4>
|
<h4>Extending the Ephemeral Port Range</h4>
|
||||||
|
|
||||||
@ -641,17 +644,42 @@ monitor "/some/path/3proxy/3proxy.ver"
|
|||||||
e.g., with "touch /some/path/3proxy/3proxy.ver".
|
e.g., with "touch /some/path/3proxy/3proxy.ver".
|
||||||
</ol>
|
</ol>
|
||||||
|
|
||||||
<h4>Use TCP_NODELAY to Speed Up Connections with Small Amounts of Data</h4>
|
<h4>TCP_NODELAY for Connections with Small Amounts of Data</h4>
|
||||||
|
|
||||||
If most requests require an exchange with a small amount of data in both directions
|
TCP_NODELAY is set by default on client (oc) and server (os)
|
||||||
without the need for bandwidth, e.g., messengers or small web requests,
|
connections: without it, Nagle's algorithm holds back a short write until the
|
||||||
you can eliminate Nagle's algorithm delay with the TCP_NODELAY flag. Usage example:
|
previous one is acknowledged, and a peer delays its acknowledgement, so a reply
|
||||||
|
written in more than one part (e.g. HTTP headers, then a short body) waits tens
|
||||||
|
of milliseconds. On slow connections with high delays, where the bandwidth is the
|
||||||
|
bottleneck and fewer packets matter more than latency, it can be turned off:
|
||||||
<pre>
|
<pre>
|
||||||
proxy -osTCP_NODELAY -ocTCP_NODELAY
|
proxy -osTCP_NODELAY=0 -ocTCP_NODELAY=0
|
||||||
</pre>
|
</pre>
|
||||||
sets TCP_NODELAY for client (oc) and server (os) connections.
|
|
||||||
<p>Do not use TCP_NODELAY on slow connections with high delays when
|
<h4>Use Splice to Speed Up Large Data Transfers</h4>
|
||||||
connection bandwidth is a bottleneck.
|
|
||||||
|
On Linux, splice() passes data between the client and the server connections
|
||||||
|
through a kernel pipe, without copying it into the 3proxy process. Linux builds
|
||||||
|
include it, but a service uses it only when given the -s option:
|
||||||
|
<pre>
|
||||||
|
proxy -s
|
||||||
|
socks -s
|
||||||
|
tcppm -s3 8080 192.168.1.1 80
|
||||||
|
</pre>
|
||||||
|
-s alone splices both directions where it pays: an HTTP body of known length if at
|
||||||
|
least 16K of it is left to read, a stream (CONNECT, SOCKS, tcppm) once it carries
|
||||||
|
large reads, going back to copying when they turn small. -s1 always splices what the
|
||||||
|
server sends to the client (downloads), -s2 what the client sends to the server
|
||||||
|
(uploads), -s3 both; -s5, -s6 and -s7 (the same as -s) do it for those directions
|
||||||
|
only where it pays. -s0, the default, turns it off. In builds without it (CMake option 3PROXY_USE_SPLICE=OFF,
|
||||||
|
or -DWITHSPLICE removed from the Makefile) -s is accepted and ignored.
|
||||||
|
<p>splice() pays off when most of the traffic is large transfers: in the
|
||||||
|
<a href="https://github.com/3proxy/3proxy/blob/devel/bench/RESULTS.md">benchmarks</a> it moved
|
||||||
|
1.3 to 2.1 times more data than the read/write path at 40-55% less CPU per gigabyte.
|
||||||
|
It does not help small requests and short connections, which go slower with it, and
|
||||||
|
every spliced direction needs a pipe (two file descriptors) and more kernel
|
||||||
|
buffer memory. Data filters are not applied to spliced data.
|
||||||
|
<p>TCP_NODELAY and splice() do not conflict and can be combined on high-speed connections.
|
||||||
|
|
||||||
<h4>Add Grace Delay to Reduce System Calls</h4>
|
<h4>Add Grace Delay to Reduce System Calls</h4>
|
||||||
|
|
||||||
@ -661,7 +689,7 @@ the minimal number of packets in the same direction to apply the algorithm,
|
|||||||
and the last value is the delay added after polling and prior to reading data.
|
and the last value is the delay added after polling and prior to reading data.
|
||||||
The example above adds a 10-millisecond delay before reading data if the average
|
The example above adds a 10-millisecond delay before reading data if the average
|
||||||
polling size is below 8000 bytes and 3 read operations have been made in the same
|
polling size is below 8000 bytes and 3 read operations have been made in the same
|
||||||
direction. <pre>logdump 1 1</pre> is useful
|
direction. It's especially useful with splice. <pre>logdump 1 1</pre> is useful
|
||||||
to see how grace delays work; choose a delay value to avoid filling the read
|
to see how grace delays work; choose a delay value to avoid filling the read
|
||||||
buffer (typically 64K) but keep the request sizes close to the chosen average
|
buffer (typically 64K) but keep the request sizes close to the chosen average
|
||||||
on large file uploads/downloads.
|
on large file uploads/downloads.
|
||||||
|
|||||||
@ -34,9 +34,11 @@
|
|||||||
<li><a href="#ISFTP">How to set up an FTP proxy</a></li>
|
<li><a href="#ISFTP">How to set up an FTP proxy</a></li>
|
||||||
<li><a href="#TLSPR">How to set up an SNI proxy (tlspr)</a></li>
|
<li><a href="#TLSPR">How to set up an SNI proxy (tlspr)</a></li>
|
||||||
<li><a href="#DNSPR">How to set up a DNS proxy (dnspr)</a></li>
|
<li><a href="#DNSPR">How to set up a DNS proxy (dnspr)</a></li>
|
||||||
|
<li><a href="#HTTPSRV">How to serve pages with the built-in HTTP server (httpsrv)</a></li>
|
||||||
<li><a href="#SSLPLUGIN">How to set up TLS/SSL (https proxy, mTLS)</a></li>
|
<li><a href="#SSLPLUGIN">How to set up TLS/SSL (https proxy, mTLS)</a></li>
|
||||||
<li><a href="#CERTIFICATES">How to create CA and certificates for SSL</a></li>
|
<li><a href="#CERTIFICATES">How to create CA and certificates for SSL</a></li>
|
||||||
<li><a href="#PCRE">How to use PCRE filtering (regular expressions)</a></li>
|
<li><a href="#PCRE">How to use PCRE filtering (regular expressions)</a></li>
|
||||||
|
<li><a href="#TRANSPARENT">How to proxy transparently</a></li>
|
||||||
<li><A HREF="#AUTH">How to limit service access</a>
|
<li><A HREF="#AUTH">How to limit service access</a>
|
||||||
<li><A HREF="#USERS">How to create a user list</a>
|
<li><A HREF="#USERS">How to create a user list</a>
|
||||||
<li><A HREF="#ACL">How to limit user access to resources</a>
|
<li><A HREF="#ACL">How to limit user access to resources</a>
|
||||||
@ -646,16 +648,16 @@ see <a href="#DNSPR">How to set up a DNS proxy (dnspr)</a>.
|
|||||||
<br>tlspr can be used as a parent with the "tls" type to implement SNI splitting for DPI bypass (similar to NoDPI/GoodByeDPI).
|
<br>tlspr can be used as a parent with the "tls" type to implement SNI splitting for DPI bypass (similar to NoDPI/GoodByeDPI).
|
||||||
The client sends the first part of the TLS ClientHello, tlspr splits it at the SNI extension and sends it in two TCP packets,
|
The client sends the first part of the TLS ClientHello, tlspr splits it at the SNI extension and sends it in two TCP packets,
|
||||||
which can bypass some DPI systems that look for blocked hostnames in TLS handshakes.
|
which can bypass some DPI systems that look for blocked hostnames in TLS handshakes.
|
||||||
<br>To enable SNI break, use <code>parent ... tls 0.0.0.0 0</code> and the <code>-s</code> option on the listening service with TCP_NODELAY:
|
<br>To enable SNI break, use <code>parent ... tls 0.0.0.0 0</code> and the <code>-B</code> option on the listening service:
|
||||||
</p><pre>
|
</p><pre>
|
||||||
auth iponly
|
auth iponly
|
||||||
allow *
|
allow *
|
||||||
parent 1000 tls 0.0.0.0 0
|
parent 1000 tls 0.0.0.0 0
|
||||||
allow *
|
allow *
|
||||||
proxy -s -i127.0.0.1 -ocTCP_NODELAY -osTCP_NODELAY -p1443
|
proxy -B -i127.0.0.1 -p1443
|
||||||
</pre>
|
</pre>
|
||||||
<p>
|
<p>
|
||||||
TCP_NODELAY is required to prevent the kernel from merging the split packets.
|
TCP_NODELAY, set by default, is required to prevent the kernel from merging the split packets: do not turn it off with -ocTCP_NODELAY=0 or -osTCP_NODELAY=0.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<b>Configuration examples:</b>
|
<b>Configuration examples:</b>
|
||||||
@ -726,6 +728,218 @@ nscache 65536
|
|||||||
nscache6 65536
|
nscache6 65536
|
||||||
dnspr -p53 -F10.0.0.1
|
dnspr -p53 -F10.0.0.1
|
||||||
</pre>
|
</pre>
|
||||||
|
</p>
|
||||||
|
<li><a name="HTTPSRV"><i>How to serve pages with the built-in HTTP server (httpsrv)</i></a>
|
||||||
|
<p>
|
||||||
|
httpsrv answers requests itself instead of forwarding them. What it does with a
|
||||||
|
request is decided by <code>http</code> rules written before the service, the way
|
||||||
|
access rules are: the first rule whose host and URL both match handles the
|
||||||
|
request. It is useful for a status page, a small static site, a block page for
|
||||||
|
requests an ACL rejects, or a health check an upstream balancer can poll.
|
||||||
|
</p><pre>
|
||||||
|
http OPERATION HOST URL [PARAMETERS]
|
||||||
|
</pre>
|
||||||
|
<p>
|
||||||
|
HOST is matched against the Host header, URL against the path with the query
|
||||||
|
string removed. A minimal static site:
|
||||||
|
</p><pre>
|
||||||
|
auth iponly
|
||||||
|
allow *
|
||||||
|
|
||||||
|
http file * / /usr/local/web/index.html
|
||||||
|
http file * /*.html "/usr/local/web/$1.html"
|
||||||
|
http file * /css/*.css "/usr/local/web/css/$1.css"
|
||||||
|
http cache * /img/** "/usr/local/web/img/$1" * 3600
|
||||||
|
httpsrv -p80 -i127.0.0.1
|
||||||
|
</pre>
|
||||||
|
<p>
|
||||||
|
<b>Patterns.</b> <code>*</code> stands for any run of characters within one
|
||||||
|
element of the path and does not cross a <code>/</code>, so a rule cannot reach
|
||||||
|
into a directory it did not name. <code>**</code> crosses them. Each star, and
|
||||||
|
each group of a regular expression, is remembered in order: <code>$1</code>
|
||||||
|
upwards stand for them in the path or location the rule builds, and
|
||||||
|
<code>$0</code> for the whole request path. A <code>rewrite_host</code> rule
|
||||||
|
uses the stars of its own host pattern instead, since that is what it is
|
||||||
|
rewriting. With a PCRE build a pattern may be
|
||||||
|
written as a regular expression with a <code>pcre:</code> prefix, for the URL and
|
||||||
|
for the host alike:
|
||||||
|
</p><pre>
|
||||||
|
http file * /d/*.txt "/usr/local/web/$1.txt"
|
||||||
|
http cache * "pcre:^/(.*)/pic/(.*)\.(gif|jpeg)$" "/usr/local/web/picts/$1/$2.$3"
|
||||||
|
http file status.example.com /** "/usr/local/web/status/$1"
|
||||||
|
http file "pcre:^(www|web)\.example\.com$" /** "/usr/local/web/$1"
|
||||||
|
</pre>
|
||||||
|
<p>
|
||||||
|
Outside quotes a dollar begins the name of a file to include, so an argument
|
||||||
|
holding one - a path built with <code>$1</code>, a regular expression anchored
|
||||||
|
with <code>$</code> - is written in quotes, as above. <code>$$</code> stands for
|
||||||
|
a single dollar and is not read as an include either.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<b>Operations.</b>
|
||||||
|
</p><pre>
|
||||||
|
# file - send the file, using sendfile/TransmitFile where the system can
|
||||||
|
http file * /dl/** "/usr/local/web/dl/$1"
|
||||||
|
|
||||||
|
# cache - read it into memory on the first request and answer from there
|
||||||
|
http cache * /css/*.css "/usr/local/web/css/$1.css"
|
||||||
|
|
||||||
|
# redir - answer with a redirect, 302 unless a status is given
|
||||||
|
http redir * /old/** 301 "https://example.org/$1"
|
||||||
|
|
||||||
|
# rewrite - change the path and hand the request to the rules after this one
|
||||||
|
http rewrite * /alias/** "/w/$1"
|
||||||
|
|
||||||
|
# rewrite_host - the same for the host, which decides which rules match next
|
||||||
|
http rewrite_host *.old.example ** "$1.new.example"
|
||||||
|
|
||||||
|
# reply - a status and nothing else
|
||||||
|
http reply * /health** 200 "X-Health: ok"
|
||||||
|
http reply * /down** 503 "Retry-After: 30"
|
||||||
|
|
||||||
|
# echo, data - describe the request, or generate content of a given size
|
||||||
|
http echo * /echo**
|
||||||
|
http data * /gen** size=1048576
|
||||||
|
</pre>
|
||||||
|
<p>
|
||||||
|
<b>What a rule adds to the answer.</b> <code>file</code> and <code>cache</code>
|
||||||
|
take, after the path, a content type, a max-age, headers to add and a status to
|
||||||
|
answer with. Each may be left out or written as <code>*</code>:
|
||||||
|
</p><pre>
|
||||||
|
http OPERATION HOST URL PATH [TYPE [MAX-AGE [HEADERS [CODE]]]]
|
||||||
|
|
||||||
|
# type worked out from the name, cached by clients for an hour
|
||||||
|
http file * /img/*.png "/usr/local/web/img/$1.png" * 3600
|
||||||
|
|
||||||
|
# a type of its own, and a header
|
||||||
|
http file * /api/*.json "/usr/local/web/api/$1.json" application/json * "X-Api: 1"
|
||||||
|
|
||||||
|
# a file serving as the body of an error page
|
||||||
|
http file * /err/** /usr/local/web/404.html text/html * * 404
|
||||||
|
</pre>
|
||||||
|
<p>
|
||||||
|
HEADERS is one argument holding whole header lines, separated by a backslash and
|
||||||
|
an n - the two characters, since a configuration line cannot carry a line
|
||||||
|
ending. Quote it, headers contain spaces. A rule's headers and max-age go with
|
||||||
|
whatever status that rule asked for, but not with a refusal the server itself
|
||||||
|
decided on: a request for a file which is not there is answered 404 by the
|
||||||
|
server, not by the rule.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Types not known to the server are registered with
|
||||||
|
<code>http_content_type</code>, and a type named by a rule is used whatever the
|
||||||
|
name of the file says:
|
||||||
|
</p><pre>
|
||||||
|
http_content_type .webp image/webp
|
||||||
|
http_content_type wasm application/wasm
|
||||||
|
</pre>
|
||||||
|
<p>
|
||||||
|
<b>Files and dates.</b> Only a full path is taken - a relative one would be read
|
||||||
|
against whatever directory the service happens to be in - and a path holding
|
||||||
|
<code>.</code> or <code>..</code> as an element, a line ending or a star is
|
||||||
|
refused. On Windows a path must name a drive or a share (<code>"C:\web\$1"</code>
|
||||||
|
or <code>"\\host\share\$1"</code>). A request which decodes to a path leaving the
|
||||||
|
tree is refused before any of this. Every answer carries Last-Modified, and a
|
||||||
|
request carrying If-Modified-Since is answered 304 with no body when the file
|
||||||
|
has not changed.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<b>Caching.</b> <code>cache</code> reads the file once and answers from memory
|
||||||
|
afterwards; a file which has changed on disk is read again, and one larger than
|
||||||
|
a megabyte is sent as <code>file</code> would. With a MAX-AGE the file is not
|
||||||
|
looked at again for that long - the rule has already told clients the file may
|
||||||
|
be treated as unchanged for that time - so a request costs nothing but the copy
|
||||||
|
out. Without one every request stats the file and a change is picked up at once.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<b>A block page.</b> A service which rejects a request with a redirect can send
|
||||||
|
the client to an httpsrv running beside it:
|
||||||
|
</p><pre>
|
||||||
|
auth iponly
|
||||||
|
deny * * "pcre:^(ads|track)[0-9]*\.example\.(com|net)$"
|
||||||
|
allow *
|
||||||
|
proxy -p3128 -i192.168.1.1
|
||||||
|
|
||||||
|
flush
|
||||||
|
auth iponly
|
||||||
|
allow *
|
||||||
|
http file * /** /usr/local/web/blocked.html text/html * * 403
|
||||||
|
httpsrv -p8080 -i127.0.0.1
|
||||||
|
</pre>
|
||||||
|
<p>
|
||||||
|
<b>Both a site and a proxy.</b> A request may arrive the way it arrives at a
|
||||||
|
site - a path, with the name in the Host header - or the way it arrives at a
|
||||||
|
proxy, naming the whole URL, or the host alone with CONNECT. Both are read. A
|
||||||
|
proxy-form request authenticates with Proxy-Authorization and is refused with
|
||||||
|
407, the way a proxy refuses one; a site-form request uses Authorization and
|
||||||
|
401. What answers it is decided by the rules either way.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<code>proxypass</code> is the rule which answers by fetching, so one service can
|
||||||
|
serve what it has and proxy the rest:
|
||||||
|
</p><pre>
|
||||||
|
auth iponly
|
||||||
|
allow *
|
||||||
|
http file * /local/** "/usr/local/web/$1"
|
||||||
|
http proxypass * /**
|
||||||
|
httpsrv -p8080
|
||||||
|
</pre>
|
||||||
|
<p>
|
||||||
|
An access rule redirecting to the local proxy does the same without a rule for
|
||||||
|
it. The chain with no address is what "the local proxy" is written as, and the
|
||||||
|
second <code>allow</code> is what the proxy matches on the pass it makes itself,
|
||||||
|
since a rule carrying the chain is not taken twice:
|
||||||
|
</p><pre>
|
||||||
|
auth iponly
|
||||||
|
allow *
|
||||||
|
parent 1000 http 0.0.0.0 0
|
||||||
|
allow *
|
||||||
|
http file * /local/** "/usr/local/web/$1"
|
||||||
|
httpsrv -p8080
|
||||||
|
</pre>
|
||||||
|
<p>
|
||||||
|
Authentication happens twice for the same reason - once for the service, once
|
||||||
|
for the proxy - so a configuration which asks for credentials asks for them the
|
||||||
|
way a proxy does.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
The access rules are read from the top on both passes, and the second one is
|
||||||
|
where the request's destination is known. On the first pass the service is
|
||||||
|
answering for itself, so an address or a port in a rule is matched against the
|
||||||
|
address the client connected to; the name from the request is matched on both
|
||||||
|
passes. On the second the destination is the one the request names, so rules
|
||||||
|
written with an address, a port or a name decide what the proxy may fetch, and
|
||||||
|
they decide it before it connects:
|
||||||
|
</p><pre>
|
||||||
|
auth iponly
|
||||||
|
allow *
|
||||||
|
parent 1000 http 0.0.0.0 0
|
||||||
|
allow * * * 80,443
|
||||||
|
deny *
|
||||||
|
httpsrv -p8080
|
||||||
|
</pre>
|
||||||
|
<p>
|
||||||
|
Everything reaches the rules, only ports 80 and 443 are fetched, and a
|
||||||
|
<code>deny</code> written before the rule carrying the chain applies on both
|
||||||
|
passes just the same. The connection to the server is kept for the next request and
|
||||||
|
closed when that request goes elsewhere, or when the server has closed it in the
|
||||||
|
meantime.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<b>Connections.</b> A client asking in HTTP/1.1 gets a 1.1 answer and the
|
||||||
|
connection is kept for the next request, unless it sent
|
||||||
|
<code>Connection: close</code>; a 1.0 client has to ask for keep-alive. The
|
||||||
|
connection is only kept when the length of the answer is known exactly, which is
|
||||||
|
true of every operation except the administration pages, so those are always the
|
||||||
|
last thing on a connection. A request body the server cannot read to its end -
|
||||||
|
one sent chunked, or one larger than a megabyte - ends the connection too.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<b>Administration.</b> The <code>admin</code> service is httpsrv with the pages
|
||||||
|
of the administration interface already declared, see
|
||||||
|
<a href="#ADMIN">Administering and information analysis</a>. Rules may be added
|
||||||
|
before it in the same way, and are taken first.
|
||||||
|
</p>
|
||||||
</p>
|
</p>
|
||||||
<li><a name="SSLPLUGIN"><i>How to set up TLS/SSL (https proxy, mTLS)</i></a>
|
<li><a name="SSLPLUGIN"><i>How to set up TLS/SSL (https proxy, mTLS)</i></a>
|
||||||
<p>
|
<p>
|
||||||
@ -981,6 +1195,189 @@ openssl pkcs12 -export -out client.p12 -passout pass: \
|
|||||||
openssl verify -x509_strict -CAfile ca.crt server.crt
|
openssl verify -x509_strict -CAfile ca.crt server.crt
|
||||||
openssl verify -x509_strict -CAfile ca.crt client.crt
|
openssl verify -x509_strict -CAfile ca.crt client.crt
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
|
<li><a name="TRANSPARENT"><i>How to proxy transparently</i></a>
|
||||||
|
<p>
|
||||||
|
A transparent proxy serves clients that were never configured to use one. A
|
||||||
|
packet filter redirects their connections to 3proxy, and the
|
||||||
|
<b>transparent</b> command tells the service to take the destination from the
|
||||||
|
filter instead of from the request. Every other feature applies as usual:
|
||||||
|
access rules, parent proxies, limits and logging all see the real destination.
|
||||||
|
It works on Linux and on the BSDs. Since 1.0.1 it is part of the binary; before
|
||||||
|
that it was the separate TransparentPlugin, and the <b>plugin</b> line it
|
||||||
|
needed is no longer required.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
The command supplies both the address and the port the client was trying to
|
||||||
|
reach, so a service can serve whatever was redirected to it rather than one
|
||||||
|
port with one target.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Without it a service has to get a destination from somewhere else: an HTTP
|
||||||
|
proxy falls back to the <b>Host</b> header, and a port mapper uses the address
|
||||||
|
it was configured with. Taking the destination from the filter is what makes
|
||||||
|
the other protocols work, and what makes the address authoritative rather than
|
||||||
|
something the client claimed.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<b>tlspr</b> is the clearest case. Nothing reaches it at all unless traffic is
|
||||||
|
redirected to it, or the clients resolve the names to it through DNS. With a
|
||||||
|
redirection it gets the address as well as the name from the handshake, and
|
||||||
|
that is what lets access rules be written with host names: the name from the
|
||||||
|
handshake is matched, and the connection still goes to the address the client
|
||||||
|
was going to. Without the address it would have to resolve the name itself,
|
||||||
|
which is a second lookup and a second answer.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
A configuration for web and TLS traffic:
|
||||||
|
</p><pre>
|
||||||
|
log /var/log/3proxy.log D
|
||||||
|
auth iponly
|
||||||
|
allow *
|
||||||
|
|
||||||
|
# the destination comes from the redirection for the services below
|
||||||
|
transparent
|
||||||
|
|
||||||
|
# ordinary web traffic, redirected here from port 80
|
||||||
|
proxy -p3129 -e192.0.2.10
|
||||||
|
|
||||||
|
# TLS, redirected here from port 443: tlspr would otherwise have only the
|
||||||
|
# name in the handshake, and this gives it the address as well
|
||||||
|
tlspr -p3143 -e192.0.2.10
|
||||||
|
|
||||||
|
notransparent
|
||||||
|
</pre>
|
||||||
|
<p>
|
||||||
|
<b>-e</b> gives those services an address of their own to connect from. That
|
||||||
|
address is what the redirection rules exclude, and excluding it is what stops
|
||||||
|
the proxy's own connections from being redirected back into it. Without an
|
||||||
|
exclusion the connection 3proxy makes to the origin matches the same rule,
|
||||||
|
returns to 3proxy, and the traffic goes round until something gives out.
|
||||||
|
Running 3proxy as its own account and excluding that account works too, and is
|
||||||
|
the better choice when the machine has one address.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p><b>Linux, iptables</b>. For traffic the machine forwards for others:
|
||||||
|
</p><pre>
|
||||||
|
iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 80 -j REDIRECT --to-ports 3129
|
||||||
|
iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 443 -j REDIRECT --to-ports 3143
|
||||||
|
</pre>
|
||||||
|
<p>
|
||||||
|
Traffic the machine generates itself passes through OUTPUT instead, where the
|
||||||
|
proxy's own connections have to be excluded:
|
||||||
|
</p><pre>
|
||||||
|
# by the address the services connect from
|
||||||
|
iptables -t nat -A OUTPUT -p tcp --dport 80 ! -s 192.0.2.10 -j REDIRECT --to-ports 3129
|
||||||
|
|
||||||
|
# or by the account 3proxy runs as
|
||||||
|
iptables -t nat -A OUTPUT -p tcp --dport 80 -m owner ! --uid-owner proxy3 \
|
||||||
|
-j REDIRECT --to-ports 3129
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p><b>Linux, nftables</b> (Debian 11+, Ubuntu 22.04+, RHEL 8+, Fedora, Arch,
|
||||||
|
where nftables is what iptables is a front end for):
|
||||||
|
</p><pre>
|
||||||
|
table ip proxy3 {
|
||||||
|
chain prerouting {
|
||||||
|
type nat hook prerouting priority dstnat; policy accept;
|
||||||
|
iif "eth0" tcp dport 80 redirect to :3129
|
||||||
|
iif "eth0" tcp dport 443 redirect to :3143
|
||||||
|
}
|
||||||
|
|
||||||
|
chain output {
|
||||||
|
type nat hook output priority dstnat; policy accept;
|
||||||
|
meta skuid != "proxy3" tcp dport 80 redirect to :3129
|
||||||
|
meta skuid != "proxy3" tcp dport 443 redirect to :3143
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</pre>
|
||||||
|
<p>
|
||||||
|
Load it with <b>nft -f</b>, and keep it across reboots in
|
||||||
|
<b>/etc/nftables.conf</b> (Debian, Ubuntu) or
|
||||||
|
<b>/etc/sysconfig/nftables.conf</b> (RHEL, Fedora). A table name cannot begin
|
||||||
|
with a digit, which is why the table above is not called 3proxy.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p><b>Linux, firewalld</b> (RHEL, CentOS Stream, Fedora, openSUSE). Redirect an
|
||||||
|
incoming port on a zone:
|
||||||
|
</p><pre>
|
||||||
|
firewall-cmd --permanent --zone=internal --add-forward-port=port=80:proto=tcp:toport=3129
|
||||||
|
firewall-cmd --permanent --zone=internal --add-forward-port=port=443:proto=tcp:toport=3143
|
||||||
|
firewall-cmd --reload
|
||||||
|
</pre>
|
||||||
|
<p>
|
||||||
|
firewalld has no exclusion for the proxy's own traffic in that form, so put
|
||||||
|
that part in a direct rule:
|
||||||
|
</p><pre>
|
||||||
|
firewall-cmd --permanent --direct --add-rule ipv4 nat OUTPUT 0 \
|
||||||
|
-p tcp --dport 80 -m owner ! --uid-owner proxy3 -j REDIRECT --to-ports 3129
|
||||||
|
firewall-cmd --reload
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p><b>Linux, ufw</b> (Ubuntu, Debian). ufw has no command for redirection;
|
||||||
|
add the rules to <b>/etc/ufw/before.rules</b>, above the <b>*filter</b> block:
|
||||||
|
</p><pre>
|
||||||
|
*nat
|
||||||
|
:PREROUTING ACCEPT [0:0]
|
||||||
|
-A PREROUTING -i eth0 -p tcp --dport 80 -j REDIRECT --to-ports 3129
|
||||||
|
-A PREROUTING -i eth0 -p tcp --dport 443 -j REDIRECT --to-ports 3143
|
||||||
|
COMMIT
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
On Linux 3proxy asks the kernel where the connection was going. On the BSDs it
|
||||||
|
asks pf, which keeps the original destination in its state table, through
|
||||||
|
<b>/dev/pf</b> - so <b>rdr</b> rules work, and 3proxy has to be able to read
|
||||||
|
that device. Where a redirection leaves the destination on the socket instead,
|
||||||
|
that is used: OpenBSD <b>divert-to</b> and FreeBSD <b>ipfw fwd</b> both do.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
The mechanism is chosen automatically, and <b>transparent</b> takes an
|
||||||
|
argument for the installations that need to pin it: <b>auto</b> (the default),
|
||||||
|
<b>netfilter</b>, <b>pf</b>, or <b>socket</b> for reading it off the socket. A
|
||||||
|
mode the build has no code for is refused, so a configuration written for
|
||||||
|
another platform fails where it is wrong instead of quietly doing something
|
||||||
|
else.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p><b>FreeBSD, NetBSD and OpenBSD, pf</b>. Redirect in <b>/etc/pf.conf</b>,
|
||||||
|
excluding the address the proxy connects from:
|
||||||
|
</p><pre>
|
||||||
|
rdr pass on em0 inet proto tcp from ! 192.0.2.10 to any port 80 -> 127.0.0.1 port 3129
|
||||||
|
rdr pass on em0 inet proto tcp from ! 192.0.2.10 to any port 443 -> 127.0.0.1 port 3143
|
||||||
|
</pre>
|
||||||
|
<p>
|
||||||
|
Load with <b>pfctl -f /etc/pf.conf</b>. 3proxy looks the destination up in pf's
|
||||||
|
state table, so it needs to read <b>/dev/pf</b>: either run it as root, or give
|
||||||
|
its account access to that device.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p><b>OpenBSD, divert-to</b> is an alternative which leaves the destination on
|
||||||
|
the socket, and needs no access to <b>/dev/pf</b>:
|
||||||
|
</p><pre>
|
||||||
|
pass in on em0 inet proto tcp to any port 80 divert-to 127.0.0.1 port 3129
|
||||||
|
pass in on em0 inet proto tcp to any port 443 divert-to 127.0.0.1 port 3143
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p><b>FreeBSD, ipfw</b>. <b>fwd</b> delivers the connection locally without
|
||||||
|
rewriting it, which also leaves the destination on the socket:
|
||||||
|
</p><pre>
|
||||||
|
ipfw add fwd 127.0.0.1,3129 tcp from any to any 80 in recv em0
|
||||||
|
ipfw add fwd 127.0.0.1,3143 tcp from any to any 443 in recv em0
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p><b>macOS</b> has <b>/dev/pf</b> but ships no header for it, so a macOS build
|
||||||
|
has no pf lookup, and macOS has neither <b>divert-to</b> nor ipfw to leave the
|
||||||
|
address on the socket. Transparent proxying is not usable there, even though
|
||||||
|
the commands exist in a macOS build.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Check the result by asking for a host through a redirected port and reading
|
||||||
|
the log: the request should appear with the address the client asked for,
|
||||||
|
which is what it would look like through a configured proxy.
|
||||||
|
</p>
|
||||||
|
|
||||||
<li><a name="PCRE"><i>How to use PCRE filtering (regular expressions)</i></a>
|
<li><a name="PCRE"><i>How to use PCRE filtering (regular expressions)</i></a>
|
||||||
<p>
|
<p>
|
||||||
Since version 0.9.7, PCRE (Perl Compatible Regular Expressions) filtering is built into
|
Since version 0.9.7, PCRE (Perl Compatible Regular Expressions) filtering is built into
|
||||||
@ -1039,6 +1436,34 @@ pcre_extend deny * 192.168.0.1/16
|
|||||||
<p>
|
<p>
|
||||||
<b>Note:</b> Regular expressions don't require authentication and cannot replace
|
<b>Note:</b> Regular expressions don't require authentication and cannot replace
|
||||||
authentication and/or allow/deny ACLs.
|
authentication and/or allow/deny ACLs.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<b>Regular expressions in host names:</b> a host name in the target list of an
|
||||||
|
access rule may be written as a regular expression instead of a wildmask, by
|
||||||
|
giving it a <code>pcre:</code> prefix (<code>regex:</code> means the same). This
|
||||||
|
needs a build with PCRE support, the same as the <code>pcre</code> commands
|
||||||
|
above.
|
||||||
|
</p><pre>
|
||||||
|
# Wildmask: a name may only be matched at its beginning and its end
|
||||||
|
deny * * *ads.example.com
|
||||||
|
|
||||||
|
# Regular expression: anything PCRE can express
|
||||||
|
deny * * "pcre:^(ads|track)[0-9]*\.example\.(com|net)$"
|
||||||
|
allow * * "pcre:^(www|api)\.example\.com$"
|
||||||
|
</pre>
|
||||||
|
<p>
|
||||||
|
The name is lowercased and trailing dots are removed before the pattern is
|
||||||
|
matched, so write patterns in lower case. Quote a pattern which ends in
|
||||||
|
<code>$</code>, or write it as <code>$$</code>: outside quotes a lone dollar
|
||||||
|
begins the name of a file to include. Only the target list takes names - the
|
||||||
|
source list is addresses - and the name is only checked when the request
|
||||||
|
carries one. A wildmask is cheaper and is enough for most rules; a regular
|
||||||
|
expression is matched per request.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
The same prefix and the same patterns are used by the <code>http</code> command
|
||||||
|
of the built-in HTTP server, for the host a rule answers for and for the URL it
|
||||||
|
matches.
|
||||||
</p>
|
</p>
|
||||||
<li><A NAME="AUTH">How to limit service access</a>
|
<li><A NAME="AUTH">How to limit service access</a>
|
||||||
<p>
|
<p>
|
||||||
@ -1517,10 +1942,12 @@ allowed traffic in megabytes (MB). nocountin allows you to set exclusions.
|
|||||||
For name resolution and caching, use the commands nserver, nscache / nscache6, and nsrecord.
|
For name resolution and caching, use the commands nserver, nscache / nscache6, and nsrecord.
|
||||||
<pre>
|
<pre>
|
||||||
nserver 192.168.1.2
|
nserver 192.168.1.2
|
||||||
nserver 192.168.1.3:5353/tcp</pre>
|
nserver 192.168.1.3:5353/tcp
|
||||||
|
nserver [2001:4860:4860::8844]</pre>
|
||||||
sets DNS resolvers. 192.168.1.3 will be used via TCP/5353 (instead of default UDP/53)
|
sets DNS resolvers. 192.168.1.3 will be used via TCP/5353 (instead of default UDP/53)
|
||||||
only if 192.168.1.2 fails. Up to 5 nservers may be specified.
|
only if 192.168.1.2 fails. Up to 5 nservers may be specified.
|
||||||
If no nserver is configured, default system name resolution functions are used.
|
If no nserver is configured, default system name resolution functions are used.
|
||||||
|
An IPv6 address has to be written in square brackets.
|
||||||
<pre>
|
<pre>
|
||||||
nscache 65535
|
nscache 65535
|
||||||
nscache6 65535</pre>
|
nscache6 65535</pre>
|
||||||
|
|||||||
@ -34,9 +34,11 @@
|
|||||||
<li><a href="#ISFTP">Как настроить FTP прокси?</a></li>
|
<li><a href="#ISFTP">Как настроить FTP прокси?</a></li>
|
||||||
<li><a href="#TLSPR">Как настроить SNI proxy (tlspr)</a></li>
|
<li><a href="#TLSPR">Как настроить SNI proxy (tlspr)</a></li>
|
||||||
<li><a href="#DNSPR">Как настроить DNS proxy (dnspr)</a></li>
|
<li><a href="#DNSPR">Как настроить DNS proxy (dnspr)</a></li>
|
||||||
|
<li><a href="#HTTPSRV">Как отдавать страницы встроенным HTTP-сервером (httpsrv)</a></li>
|
||||||
<li><a href="#SSLPLUGIN">Как настроить TLS/SSL (https прокси, mTLS)</a></li>
|
<li><a href="#SSLPLUGIN">Как настроить TLS/SSL (https прокси, mTLS)</a></li>
|
||||||
<li><a href="#CERTIFICATES">Как создать CA и сертификаты для SSL</a></li>
|
<li><a href="#CERTIFICATES">Как создать CA и сертификаты для SSL</a></li>
|
||||||
<li><a href="#PCRE">Как использовать PCRE-фильтрацию (регулярные выражения)</a></li>
|
<li><a href="#PCRE">Как использовать PCRE-фильтрацию (регулярные выражения)</a></li>
|
||||||
|
<li><a href="#TRANSPARENT">Как сделать транспарентный прокси</a></li>
|
||||||
<li><a href="#AUTH">Как ограничить доступ к службе</a>
|
<li><a href="#AUTH">Как ограничить доступ к службе</a>
|
||||||
<li><a href="#USERS">Как создать список пользователей</a>
|
<li><a href="#USERS">Как создать список пользователей</a>
|
||||||
<li><a href="#ACL">Как ограничить доступ пользователей к ресурсам</a>
|
<li><a href="#ACL">Как ограничить доступ пользователей к ресурсам</a>
|
||||||
@ -654,16 +656,16 @@ iptables -t nat -A PREROUTING -p tcp -m multiport --dports 443,993,465 -j REDIRE
|
|||||||
<br>tlspr может использоваться как родительский прокси типа "tls" для реализации SNI-фрагментации (аналог NoDPI/GoodByeDPI).
|
<br>tlspr может использоваться как родительский прокси типа "tls" для реализации SNI-фрагментации (аналог NoDPI/GoodByeDPI).
|
||||||
Клиент отправляет первую часть TLS ClientHello, tlspr разбивает его на расширении SNI и отправляет двумя TCP-пакетами,
|
Клиент отправляет первую часть TLS ClientHello, tlspr разбивает его на расширении SNI и отправляет двумя TCP-пакетами,
|
||||||
что позволяет обойти некоторые DPI-системы, ищущие заблокированные имена хостов в TLS-рукопожатиях.
|
что позволяет обойти некоторые DPI-системы, ищущие заблокированные имена хостов в TLS-рукопожатиях.
|
||||||
<br>Для включения SNI break используйте <code>parent ... tls 0.0.0.0 0</code> и опцию <code>-s</code> на слушающем сервисе с TCP_NODELAY:
|
<br>Для включения SNI break используйте <code>parent ... tls 0.0.0.0 0</code> и опцию <code>-B</code> на слушающем сервисе:
|
||||||
</p><pre>
|
</p><pre>
|
||||||
auth iponly
|
auth iponly
|
||||||
allow *
|
allow *
|
||||||
parent 1000 tls 0.0.0.0 0
|
parent 1000 tls 0.0.0.0 0
|
||||||
allow *
|
allow *
|
||||||
proxy -s -i127.0.0.1 -ocTCP_NODELAY -osTCP_NODELAY -p1443
|
proxy -B -i127.0.0.1 -p1443
|
||||||
</pre>
|
</pre>
|
||||||
<p>
|
<p>
|
||||||
TCP_NODELAY необходим, чтобы ядро не объединяло разделенные пакеты.
|
TCP_NODELAY, установленный по умолчанию, необходим, чтобы ядро не объединяло разделенные пакеты: не отключайте его опциями -ocTCP_NODELAY=0 или -osTCP_NODELAY=0.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<b>Примеры конфигурации:</b>
|
<b>Примеры конфигурации:</b>
|
||||||
@ -737,6 +739,217 @@ dnspr -p53 -F10.0.0.1
|
|||||||
</pre>
|
</pre>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
<li><a name="HTTPSRV"><i>Как отдавать страницы встроенным HTTP-сервером (httpsrv)</i></a>
|
||||||
|
<p>
|
||||||
|
httpsrv отвечает на запросы сам, а не пересылает их. Что делать с запросом,
|
||||||
|
определяют правила <code>http</code>, записанные перед сервисом, как и правила
|
||||||
|
доступа: запрос обрабатывает первое правило, у которого совпали и хост, и URL.
|
||||||
|
Это удобно для страницы состояния, небольшого статического сайта, страницы
|
||||||
|
блокировки для запросов, отклонённых ACL, или health check, который опрашивает
|
||||||
|
вышестоящий балансировщик.
|
||||||
|
</p><pre>
|
||||||
|
http ОПЕРАЦИЯ ХОСТ URL [ПАРАМЕТРЫ]
|
||||||
|
</pre>
|
||||||
|
<p>
|
||||||
|
ХОСТ сопоставляется с заголовком Host, URL - с путём без строки запроса.
|
||||||
|
Минимальный статический сайт:
|
||||||
|
</p><pre>
|
||||||
|
auth iponly
|
||||||
|
allow *
|
||||||
|
|
||||||
|
http file * / /usr/local/web/index.html
|
||||||
|
http file * /*.html "/usr/local/web/$1.html"
|
||||||
|
http file * /css/*.css "/usr/local/web/css/$1.css"
|
||||||
|
http cache * /img/** "/usr/local/web/img/$1" * 3600
|
||||||
|
httpsrv -p80 -i127.0.0.1
|
||||||
|
</pre>
|
||||||
|
<p>
|
||||||
|
<b>Шаблоны.</b> <code>*</code> означает любую последовательность символов внутри
|
||||||
|
одного элемента пути и не пересекает <code>/</code>, поэтому правило не может
|
||||||
|
попасть в каталог, который не назван в нём. <code>**</code> пересекает.
|
||||||
|
Каждая звёздочка и каждая группа регулярного выражения запоминаются по порядку:
|
||||||
|
<code>$1</code> и далее подставляют их в путь или адрес, который строит правило,
|
||||||
|
<code>$0</code> - весь путь запроса. Правило <code>rewrite_host</code>
|
||||||
|
использует звёздочки собственного шаблона хоста, поскольку переписывает именно
|
||||||
|
его. В сборке с PCRE шаблон можно записать
|
||||||
|
регулярным выражением с префиксом <code>pcre:</code> - и для URL, и для хоста:
|
||||||
|
</p><pre>
|
||||||
|
http file * /d/*.txt "/usr/local/web/$1.txt"
|
||||||
|
http cache * "pcre:^/(.*)/pic/(.*)\.(gif|jpeg)$" "/usr/local/web/picts/$1/$2.$3"
|
||||||
|
http file status.example.com /** "/usr/local/web/status/$1"
|
||||||
|
http file "pcre:^(www|web)\.example\.com$" /** "/usr/local/web/$1"
|
||||||
|
</pre>
|
||||||
|
<p>
|
||||||
|
Вне кавычек доллар начинает имя включаемого файла, поэтому аргумент, содержащий
|
||||||
|
доллар - путь с <code>$1</code>, регулярное выражение с якорем <code>$</code> -
|
||||||
|
записывается в кавычках, как выше. <code>$$</code> означает один доллар и тоже
|
||||||
|
не читается как включение файла.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<b>Операции.</b>
|
||||||
|
</p><pre>
|
||||||
|
# file - отдать файл, через sendfile/TransmitFile там, где система это умеет
|
||||||
|
http file * /dl/** "/usr/local/web/dl/$1"
|
||||||
|
|
||||||
|
# cache - прочитать в память при первом запросе и отвечать из неё
|
||||||
|
http cache * /css/*.css "/usr/local/web/css/$1.css"
|
||||||
|
|
||||||
|
# redir - ответить редиректом, 302, если код не задан
|
||||||
|
http redir * /old/** 301 "https://example.org/$1"
|
||||||
|
|
||||||
|
# rewrite - изменить путь и передать запрос следующим правилам
|
||||||
|
http rewrite * /alias/** "/w/$1"
|
||||||
|
|
||||||
|
# rewrite_host - то же для хоста, от которого зависит выбор следующих правил
|
||||||
|
http rewrite_host *.old.example ** "$1.new.example"
|
||||||
|
|
||||||
|
# reply - только код ответа, без тела
|
||||||
|
http reply * /health** 200 "X-Health: ok"
|
||||||
|
http reply * /down** 503 "Retry-After: 30"
|
||||||
|
|
||||||
|
# echo, data - описание запроса или генерация содержимого заданного размера
|
||||||
|
http echo * /echo**
|
||||||
|
http data * /gen** size=1048576
|
||||||
|
</pre>
|
||||||
|
<p>
|
||||||
|
<b>Что правило добавляет в ответ.</b> <code>file</code> и <code>cache</code>
|
||||||
|
принимают после пути тип содержимого, max-age, добавляемые заголовки и код
|
||||||
|
ответа. Любой из них можно опустить или записать как <code>*</code>:
|
||||||
|
</p><pre>
|
||||||
|
http ОПЕРАЦИЯ ХОСТ URL ПУТЬ [ТИП [MAX-AGE [ЗАГОЛОВКИ [КОД]]]]
|
||||||
|
|
||||||
|
# тип определяется по имени файла, клиенты кэшируют час
|
||||||
|
http file * /img/*.png "/usr/local/web/img/$1.png" * 3600
|
||||||
|
|
||||||
|
# собственный тип и заголовок
|
||||||
|
http file * /api/*.json "/usr/local/web/api/$1.json" application/json * "X-Api: 1"
|
||||||
|
|
||||||
|
# файл как тело страницы ошибки
|
||||||
|
http file * /err/** /usr/local/web/404.html text/html * * 404
|
||||||
|
</pre>
|
||||||
|
<p>
|
||||||
|
ЗАГОЛОВКИ - один аргумент, содержащий целые строки заголовков, разделённые
|
||||||
|
обратной косой чертой и n - двумя символами, поскольку строка конфигурации не
|
||||||
|
может содержать конец строки. Аргумент нужно брать в кавычки, в заголовках есть
|
||||||
|
пробелы. Заголовки и max-age правила отправляются с тем кодом, который правило
|
||||||
|
задало, но не с отказом, который решил вернуть сам сервер: на запрос
|
||||||
|
отсутствующего файла 404 отвечает сервер, а не правило.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Неизвестные серверу типы регистрируются командой
|
||||||
|
<code>http_content_type</code>, а тип, названный в правиле, используется
|
||||||
|
независимо от имени файла:
|
||||||
|
</p><pre>
|
||||||
|
http_content_type .webp image/webp
|
||||||
|
http_content_type wasm application/wasm
|
||||||
|
</pre>
|
||||||
|
<p>
|
||||||
|
<b>Файлы и даты.</b> Принимается только полный путь - относительный отсчитывался
|
||||||
|
бы от того каталога, в котором оказался сервис, - а путь с элементом
|
||||||
|
<code>.</code> или <code>..</code>, концом строки или звёздочкой отвергается. В
|
||||||
|
Windows путь должен указывать диск или сетевой ресурс (<code>"C:\web\$1"</code>
|
||||||
|
или <code>"\\host\share\$1"</code>). Запрос, который декодируется в путь за
|
||||||
|
пределами дерева, отвергается раньше всего этого. Каждый ответ содержит
|
||||||
|
Last-Modified, а запрос с If-Modified-Since получает 304 без тела, если файл не
|
||||||
|
изменился.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<b>Кэширование.</b> <code>cache</code> читает файл один раз и дальше отвечает из
|
||||||
|
памяти; изменившийся на диске файл читается заново, а файл больше мегабайта
|
||||||
|
отдаётся так же, как это сделал бы <code>file</code>. При заданном MAX-AGE файл
|
||||||
|
не проверяется в течение этого времени - правило уже сообщило клиентам, что
|
||||||
|
столько файл можно считать неизменным, - и запрос стоит только копирования
|
||||||
|
наружу. Без MAX-AGE каждый запрос делает stat, и изменение подхватывается сразу.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<b>Страница блокировки.</b> Сервис, отклоняющий запрос редиректом, может
|
||||||
|
отправить клиента на httpsrv, работающий рядом:
|
||||||
|
</p><pre>
|
||||||
|
auth iponly
|
||||||
|
deny * * "pcre:^(ads|track)[0-9]*\.example\.(com|net)$"
|
||||||
|
allow *
|
||||||
|
proxy -p3128 -i192.168.1.1
|
||||||
|
|
||||||
|
flush
|
||||||
|
auth iponly
|
||||||
|
allow *
|
||||||
|
http file * /** /usr/local/web/blocked.html text/html * * 403
|
||||||
|
httpsrv -p8080 -i127.0.0.1
|
||||||
|
</pre>
|
||||||
|
<p>
|
||||||
|
<b>И сайт, и прокси.</b> Запрос может прийти так, как приходит на сайт - путь,
|
||||||
|
имя в заголовке Host, - или так, как приходит на прокси: с полным URL, либо, для
|
||||||
|
туннеля, с одним именем хоста в CONNECT. Читается и то, и другое. Запрос в форме
|
||||||
|
для прокси аутентифицируется через Proxy-Authorization и отклоняется кодом 407,
|
||||||
|
как это делает прокси; запрос в форме для сайта - через Authorization и 401. Чем
|
||||||
|
он будет обработан, в обоих случаях решают правила.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<code>proxypass</code> - правило, которое отвечает, забирая ресурс, поэтому один
|
||||||
|
сервис может отдавать своё и проксировать остальное:
|
||||||
|
</p><pre>
|
||||||
|
auth iponly
|
||||||
|
allow *
|
||||||
|
http file * /local/** "/usr/local/web/$1"
|
||||||
|
http proxypass * /**
|
||||||
|
httpsrv -p8080
|
||||||
|
</pre>
|
||||||
|
<p>
|
||||||
|
Правило доступа с перенаправлением на локальный прокси делает то же самое без
|
||||||
|
отдельного правила. Цепочка без адреса и означает "локальный прокси", а второй
|
||||||
|
<code>allow</code> - то, с чем совпадает сам прокси на своём проходе, так как
|
||||||
|
правило с цепочкой второй раз не берётся:
|
||||||
|
</p><pre>
|
||||||
|
auth iponly
|
||||||
|
allow *
|
||||||
|
parent 1000 http 0.0.0.0 0
|
||||||
|
allow *
|
||||||
|
http file * /local/** "/usr/local/web/$1"
|
||||||
|
httpsrv -p8080
|
||||||
|
</pre>
|
||||||
|
<p>
|
||||||
|
Аутентификация по той же причине происходит дважды - для сервиса и для прокси, -
|
||||||
|
поэтому конфигурация, требующая учётных данных, запрашивает их так, как это
|
||||||
|
делает прокси.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Правила доступа просматриваются с начала на обоих проходах, и назначение запроса
|
||||||
|
известно на втором. На первом сервис отвечает сам за себя, поэтому адрес или порт
|
||||||
|
в правиле сопоставляется с адресом, на который подключился клиент; имя из запроса
|
||||||
|
сопоставляется на обоих проходах. На втором назначение - то, которое названо в
|
||||||
|
запросе, поэтому правила с адресом, портом или именем определяют, что прокси
|
||||||
|
разрешено забрать, и определяют это до установления соединения:
|
||||||
|
</p><pre>
|
||||||
|
auth iponly
|
||||||
|
allow *
|
||||||
|
parent 1000 http 0.0.0.0 0
|
||||||
|
allow * * * 80,443
|
||||||
|
deny *
|
||||||
|
httpsrv -p8080
|
||||||
|
</pre>
|
||||||
|
<p>
|
||||||
|
До правил доходит всё, забираются только порты 80 и 443, а <code>deny</code>,
|
||||||
|
записанный до правила с цепочкой, действует на обоих проходах точно так же. Соединение с сервером сохраняется для следующего запроса и
|
||||||
|
закрывается, если следующий запрос идёт в другое место или если сервер за это
|
||||||
|
время его закрыл.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<b>Соединения.</b> Клиент, обратившийся по HTTP/1.1, получает ответ 1.1, и
|
||||||
|
соединение сохраняется для следующего запроса, если он не прислал
|
||||||
|
<code>Connection: close</code>; клиенту 1.0 нужно запросить keep-alive явно.
|
||||||
|
Соединение сохраняется только тогда, когда длина ответа известна точно - это
|
||||||
|
верно для всех операций, кроме страниц администрирования, поэтому они всегда
|
||||||
|
последнее, что отдаётся в соединении. Тело запроса, которое сервер не может
|
||||||
|
дочитать до конца - присланное chunked или размером больше мегабайта, - тоже
|
||||||
|
завершает соединение.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<b>Администрирование.</b> Сервис <code>admin</code> - это httpsrv с уже
|
||||||
|
объявленными страницами интерфейса администрирования, см.
|
||||||
|
<a href="#ADMIN">Администрирование и анализ информации</a>. Правила можно
|
||||||
|
добавлять перед ним так же, и они проверяются первыми.
|
||||||
|
</p>
|
||||||
|
</p>
|
||||||
<li><a name="SSLPLUGIN"><i>Как настроить TLS/SSL (https прокси, mTLS)</i></a>
|
<li><a name="SSLPLUGIN"><i>Как настроить TLS/SSL (https прокси, mTLS)</i></a>
|
||||||
<p>
|
<p>
|
||||||
Начиная с версии 0.9.7 поддержка TLS/SSL встроена в 3proxy при компиляции с OpenSSL
|
Начиная с версии 0.9.7 поддержка TLS/SSL встроена в 3proxy при компиляции с OpenSSL
|
||||||
@ -992,6 +1205,176 @@ openssl verify -x509_strict -CAfile ca.crt server.crt
|
|||||||
openssl verify -x509_strict -CAfile ca.crt client.crt
|
openssl verify -x509_strict -CAfile ca.crt client.crt
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
|
|
||||||
|
<li><a name="TRANSPARENT"><i>Как сделать транспарентный прокси</i></a>
|
||||||
|
<p>
|
||||||
|
Транспарентный прокси обслуживает клиентов, которые не настроены на работу
|
||||||
|
через прокси. Пакетный фильтр перенаправляет их соединения на 3proxy, а команда
|
||||||
|
<b>transparent</b> указывает сервису брать адрес назначения у фильтра, а не из
|
||||||
|
запроса. Всё остальное работает как обычно: правила доступа, родительские
|
||||||
|
прокси, ограничения и логирование видят настоящий адрес назначения. Работает в
|
||||||
|
Linux и BSD. С версии 1.0.1 встроено в бинарник, раньше это был отдельный
|
||||||
|
TransparentPlugin, и строка <b>plugin</b> больше не нужна.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Команда даёт и адрес, и порт назначения, поэтому сервис обслуживает всё, что
|
||||||
|
на него перенаправлено, а не один порт с одним адресом назначения.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Без неё сервис берёт адрес откуда-то ещё: HTTP-прокси - из заголовка
|
||||||
|
<b>Host</b>, порт-маппер - из своей конфигурации. Именно получение адреса от
|
||||||
|
фильтра позволяет работать с остальными протоколами и делает адрес
|
||||||
|
достоверным, а не заявленным клиентом.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Нагляднее всего это с <b>tlspr</b>. Без перенаправления трафика (или без
|
||||||
|
резолва имён на него через DNS) на него вообще ничего не попадёт. С
|
||||||
|
перенаправлением он получает и имя из TLS handshake, и адрес назначения -
|
||||||
|
именно это позволяет писать правила доступа по именам хостов: имя из handshake
|
||||||
|
проверяется в ACL, а соединение идёт на тот адрес, куда шёл клиент. Без адреса
|
||||||
|
пришлось бы резолвить имя самостоятельно, то есть делать ещё один запрос и
|
||||||
|
получать ещё один ответ.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Конфигурация для веб- и TLS-трафика:
|
||||||
|
</p><pre>
|
||||||
|
log /var/log/3proxy.log D
|
||||||
|
auth iponly
|
||||||
|
allow *
|
||||||
|
|
||||||
|
# для сервисов ниже адрес назначения берётся из перенаправления
|
||||||
|
transparent
|
||||||
|
|
||||||
|
# обычный веб-трафик, перенаправленный сюда с порта 80
|
||||||
|
proxy -p3129 -e192.0.2.10
|
||||||
|
|
||||||
|
# TLS, перенаправленный сюда с порта 443: без этого у tlspr было бы только
|
||||||
|
# имя из handshake, а так есть и адрес
|
||||||
|
tlspr -p3143 -e192.0.2.10
|
||||||
|
|
||||||
|
notransparent
|
||||||
|
</pre>
|
||||||
|
<p>
|
||||||
|
<b>-e</b> задаёт сервисам собственный адрес для исходящих соединений. Именно
|
||||||
|
этот адрес исключается в правилах перенаправления, и это исключение не даёт
|
||||||
|
соединениям самого прокси попасть обратно в него. Без исключения соединение,
|
||||||
|
которое 3proxy устанавливает к серверу назначения, попадает под то же правило,
|
||||||
|
возвращается в 3proxy, и трафик зацикливается. Можно вместо этого запускать
|
||||||
|
3proxy под отдельной учётной записью и исключать её - так лучше, если у машины
|
||||||
|
один адрес.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p><b>Linux, iptables</b>. Для транзитного трафика:
|
||||||
|
</p><pre>
|
||||||
|
iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 80 -j REDIRECT --to-ports 3129
|
||||||
|
iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 443 -j REDIRECT --to-ports 3143
|
||||||
|
</pre>
|
||||||
|
<p>
|
||||||
|
Трафик самой машины проходит через цепочку OUTPUT, где соединения прокси нужно
|
||||||
|
исключить:
|
||||||
|
</p><pre>
|
||||||
|
# по адресу, с которого сервисы устанавливают соединения
|
||||||
|
iptables -t nat -A OUTPUT -p tcp --dport 80 ! -s 192.0.2.10 -j REDIRECT --to-ports 3129
|
||||||
|
|
||||||
|
# либо по учётной записи, под которой работает 3proxy
|
||||||
|
iptables -t nat -A OUTPUT -p tcp --dport 80 -m owner ! --uid-owner proxy3 \
|
||||||
|
-j REDIRECT --to-ports 3129
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p><b>Linux, nftables</b> (Debian 11+, Ubuntu 22.04+, RHEL 8+, Fedora, Arch):
|
||||||
|
</p><pre>
|
||||||
|
table ip proxy3 {
|
||||||
|
chain prerouting {
|
||||||
|
type nat hook prerouting priority dstnat; policy accept;
|
||||||
|
iif "eth0" tcp dport 80 redirect to :3129
|
||||||
|
iif "eth0" tcp dport 443 redirect to :3143
|
||||||
|
}
|
||||||
|
|
||||||
|
chain output {
|
||||||
|
type nat hook output priority dstnat; policy accept;
|
||||||
|
meta skuid != "proxy3" tcp dport 80 redirect to :3129
|
||||||
|
meta skuid != "proxy3" tcp dport 443 redirect to :3143
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</pre>
|
||||||
|
<p>
|
||||||
|
Загружается через <b>nft -f</b>, сохраняется в <b>/etc/nftables.conf</b>
|
||||||
|
(Debian, Ubuntu) или <b>/etc/sysconfig/nftables.conf</b> (RHEL, Fedora). Имя
|
||||||
|
таблицы не может начинаться с цифры, поэтому таблица называется не 3proxy.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p><b>Linux, firewalld</b> (RHEL, CentOS Stream, Fedora, openSUSE):
|
||||||
|
</p><pre>
|
||||||
|
firewall-cmd --permanent --zone=internal --add-forward-port=port=80:proto=tcp:toport=3129
|
||||||
|
firewall-cmd --permanent --zone=internal --add-forward-port=port=443:proto=tcp:toport=3143
|
||||||
|
firewall-cmd --reload
|
||||||
|
</pre>
|
||||||
|
<p>
|
||||||
|
Исключение для трафика самого прокси в таком виде не задаётся, для него нужно
|
||||||
|
прямое правило:
|
||||||
|
</p><pre>
|
||||||
|
firewall-cmd --permanent --direct --add-rule ipv4 nat OUTPUT 0 \
|
||||||
|
-p tcp --dport 80 -m owner ! --uid-owner proxy3 -j REDIRECT --to-ports 3129
|
||||||
|
firewall-cmd --reload
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p><b>Linux, ufw</b> (Ubuntu, Debian). В ufw нет команды для перенаправления,
|
||||||
|
правила добавляются в <b>/etc/ufw/before.rules</b> перед блоком <b>*filter</b>:
|
||||||
|
</p><pre>
|
||||||
|
*nat
|
||||||
|
:PREROUTING ACCEPT [0:0]
|
||||||
|
-A PREROUTING -i eth0 -p tcp --dport 80 -j REDIRECT --to-ports 3129
|
||||||
|
-A PREROUTING -i eth0 -p tcp --dport 443 -j REDIRECT --to-ports 3143
|
||||||
|
COMMIT
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
В Linux 3proxy спрашивает у ядра, куда шло соединение. В BSD он спрашивает у pf,
|
||||||
|
который хранит исходный адрес назначения в таблице состояний, через
|
||||||
|
<b>/dev/pf</b> - поэтому работают правила <b>rdr</b>, и 3proxy должен иметь
|
||||||
|
доступ к этому устройству. Если перенаправление оставляет адрес на самом сокете,
|
||||||
|
используется он: так делают OpenBSD <b>divert-to</b> и FreeBSD <b>ipfw fwd</b>.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Механизм выбирается автоматически, а команда <b>transparent</b> принимает
|
||||||
|
аргумент для случаев, когда его надо зафиксировать: <b>auto</b> (по умолчанию),
|
||||||
|
<b>netfilter</b>, <b>pf</b> или <b>socket</b> для чтения адреса с сокета. Режим,
|
||||||
|
которого нет в сборке, отвергается, поэтому конфигурация, написанная для другой
|
||||||
|
платформы, не запустится вместо того, чтобы молча делать что-то другое.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p><b>FreeBSD, NetBSD, OpenBSD, pf</b>. Перенаправление в <b>/etc/pf.conf</b> с
|
||||||
|
исключением адреса, с которого соединяется прокси:
|
||||||
|
</p><pre>
|
||||||
|
rdr pass on em0 inet proto tcp from ! 192.0.2.10 to any port 80 -> 127.0.0.1 port 3129
|
||||||
|
rdr pass on em0 inet proto tcp from ! 192.0.2.10 to any port 443 -> 127.0.0.1 port 3143
|
||||||
|
</pre>
|
||||||
|
<p>
|
||||||
|
Загружается через <b>pfctl -f /etc/pf.conf</b>. 3proxy ищет адрес назначения в
|
||||||
|
таблице состояний pf, поэтому ему нужен доступ на чтение к <b>/dev/pf</b>: либо
|
||||||
|
запуск от root, либо права на устройство для его учётной записи.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p><b>OpenBSD, divert-to</b> - альтернатива, оставляющая адрес на сокете, доступ
|
||||||
|
к <b>/dev/pf</b> при этом не нужен:
|
||||||
|
</p><pre>
|
||||||
|
pass in on em0 inet proto tcp to any port 80 divert-to 127.0.0.1 port 3129
|
||||||
|
pass in on em0 inet proto tcp to any port 443 divert-to 127.0.0.1 port 3143
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p><b>FreeBSD, ipfw</b>. <b>fwd</b> доставляет соединение локально, не переписывая
|
||||||
|
его, и адрес тоже остаётся на сокете:
|
||||||
|
</p><pre>
|
||||||
|
ipfw add fwd 127.0.0.1,3129 tcp from any to any 80 in recv em0
|
||||||
|
ipfw add fwd 127.0.0.1,3143 tcp from any to any 443 in recv em0
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p><b>macOS</b>: <b>/dev/pf</b> есть, но заголовочных файлов для него нет,
|
||||||
|
поэтому в сборке под macOS нет обращения к pf, а ни <b>divert-to</b>, ни ipfw в
|
||||||
|
macOS нет. Транспарентное проксирование там неприменимо, хотя команды в сборке
|
||||||
|
присутствуют.
|
||||||
|
</p>
|
||||||
|
|
||||||
<li><a name="PCRE"><i>Как использовать PCRE-фильтрацию (регулярные выражения)</i></a>
|
<li><a name="PCRE"><i>Как использовать PCRE-фильтрацию (регулярные выражения)</i></a>
|
||||||
<p>
|
<p>
|
||||||
Начиная с версии 0.9.7 фильтрация PCRE встроена в 3proxy при компиляции с поддержкой
|
Начиная с версии 0.9.7 фильтрация PCRE встроена в 3proxy при компиляции с поддержкой
|
||||||
@ -1050,6 +1433,34 @@ pcre_extend deny * 192.168.0.1/16
|
|||||||
<p>
|
<p>
|
||||||
<b>Примечание:</b> Регулярные выражения не требуют авторизации и не могут заменить
|
<b>Примечание:</b> Регулярные выражения не требуют авторизации и не могут заменить
|
||||||
авторизацию и/или ACL allow/deny.
|
авторизацию и/или ACL allow/deny.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<b>Регулярные выражения в именах хостов:</b> имя хоста в списке назначения
|
||||||
|
правила доступа может быть записано регулярным выражением вместо маски, для
|
||||||
|
этого используется префикс <code>pcre:</code> (<code>regex:</code> означает то
|
||||||
|
же самое). Требуется сборка с поддержкой PCRE, как и для команд
|
||||||
|
<code>pcre</code> выше.
|
||||||
|
</p><pre>
|
||||||
|
# Маска: имя сопоставляется только с начала и с конца
|
||||||
|
deny * * *ads.example.com
|
||||||
|
|
||||||
|
# Регулярное выражение: всё, что выразимо средствами PCRE
|
||||||
|
deny * * "pcre:^(ads|track)[0-9]*\.example\.(com|net)$"
|
||||||
|
allow * * "pcre:^(www|api)\.example\.com$"
|
||||||
|
</pre>
|
||||||
|
<p>
|
||||||
|
Перед сопоставлением имя приводится к нижнему регистру, завершающие точки
|
||||||
|
удаляются, поэтому шаблоны пишутся в нижнем регистре. Шаблон, оканчивающийся на
|
||||||
|
<code>$</code>, нужно взять в кавычки или записать как <code>$$</code>: вне
|
||||||
|
кавычек одиночный доллар начинает имя включаемого файла. Имена допустимы только
|
||||||
|
в списке назначения (список источника - адреса), и имя проверяется лишь тогда,
|
||||||
|
когда оно присутствует в запросе. Маска обходится дешевле и достаточна для
|
||||||
|
большинства правил, регулярное выражение сопоставляется на каждый запрос.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Тот же префикс и те же шаблоны использует команда <code>http</code> встроенного
|
||||||
|
HTTP-сервера - для хоста, на который отвечает правило, и для URL, который оно
|
||||||
|
сопоставляет.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<li><a name="AUTH"><i>Как ограничить доступ к службе</i></a>
|
<li><a name="AUTH"><i>Как ограничить доступ к службе</i></a>
|
||||||
@ -1582,11 +1993,13 @@ socks -p1080
|
|||||||
nscache и nsrecord.
|
nscache и nsrecord.
|
||||||
<pre>
|
<pre>
|
||||||
nserver 192.168.1.2
|
nserver 192.168.1.2
|
||||||
nserver 192.168.1.3:5353/tcp</pre>
|
nserver 192.168.1.3:5353/tcp
|
||||||
|
nserver [2001:4860:4860::8844]</pre>
|
||||||
указывает 3proxy какие машины следует использвоать в качестве серверов
|
указывает 3proxy какие машины следует использвоать в качестве серверов
|
||||||
DNS. Сервер 192.168.1.3 будет использоваться по порту TCP/5353 (вместо дефолтного UDP/53) только при недостижимости
|
DNS. Сервер 192.168.1.3 будет использоваться по порту TCP/5353 (вместо дефолтного UDP/53) только при недостижимости
|
||||||
192.168.1.2. Можно указать до 5 серверов. Если nserver не указан, будут
|
192.168.1.2. Можно указать до 5 серверов. Если nserver не указан, будут
|
||||||
использованы системные функции разрешения имен.
|
использованы системные функции разрешения имен.
|
||||||
|
Адрес IPv6 необходимо записывать в квадратных скобках.
|
||||||
<pre>
|
<pre>
|
||||||
nscache 65535
|
nscache 65535
|
||||||
nscache6 65535</pre>
|
nscache6 65535</pre>
|
||||||
|
|||||||
@ -18,6 +18,15 @@
|
|||||||
<a href="#PCRE FILTERING">PCRE FILTERING</a><br>
|
<a href="#PCRE FILTERING">PCRE FILTERING</a><br>
|
||||||
<a href="#PCRE Commands">PCRE Commands</a><br>
|
<a href="#PCRE Commands">PCRE Commands</a><br>
|
||||||
<a href="#PCRE Parameters">PCRE Parameters</a><br>
|
<a href="#PCRE Parameters">PCRE Parameters</a><br>
|
||||||
|
<a href="#OPTIONAL SERVICES">OPTIONAL SERVICES</a><br>
|
||||||
|
<a href="#BUILT IN HTTP SERVER">BUILT IN HTTP SERVER</a><br>
|
||||||
|
<a href="#Operations">Operations</a><br>
|
||||||
|
<a href="#What a rule adds to the answer">What a rule adds to the answer</a><br>
|
||||||
|
<a href="#Patterns">Patterns</a><br>
|
||||||
|
<a href="#Both a site and a proxy">Both a site and a proxy</a><br>
|
||||||
|
<a href="#Connections">Connections</a><br>
|
||||||
|
<a href="#Paths a rule builds">Paths a rule builds</a><br>
|
||||||
|
<a href="#Examples">Examples</a><br>
|
||||||
<a href="#BUGS">BUGS</a><br>
|
<a href="#BUGS">BUGS</a><br>
|
||||||
<a href="#SEE ALSO">SEE ALSO</a><br>
|
<a href="#SEE ALSO">SEE ALSO</a><br>
|
||||||
<a href="#AUTHORS">AUTHORS</a><br>
|
<a href="#AUTHORS">AUTHORS</a><br>
|
||||||
@ -77,8 +86,10 @@ characters) is treated as space character (arguments
|
|||||||
delimiter instead of end of command delimiter). Thus,
|
delimiter instead of end of command delimiter). Thus,
|
||||||
include files are only useful to store long single-line
|
include files are only useful to store long single-line
|
||||||
commands (like userlist, network lists, etc). To use dollar
|
commands (like userlist, network lists, etc). To use dollar
|
||||||
sign somewhere in argument it must be quoted. Recursion is
|
sign somewhere in argument it must be quoted or doubled:
|
||||||
not allowed.</p>
|
inside quotes a dollar is ordinary text, and <b>$$</b>
|
||||||
|
stands for a single dollar and is not read as an include.
|
||||||
|
Recursion is not allowed.</p>
|
||||||
|
|
||||||
<p style="margin-left:9%; margin-top: 1em">Next commands
|
<p style="margin-left:9%; margin-top: 1em">Next commands
|
||||||
start gateway services:</p>
|
start gateway services:</p>
|
||||||
@ -104,10 +115,17 @@ handshake), may be used to redirect any TLS-based traffic
|
|||||||
<b><br>
|
<b><br>
|
||||||
auto</b> Proxy with protocol autoselection between proxy /
|
auto</b> Proxy with protocol autoselection between proxy /
|
||||||
socks / tlspr <b><br>
|
socks / tlspr <b><br>
|
||||||
pop3p</b> POP3 proxy (default port 110) <b><br>
|
pop3p</b> POP3 proxy (default port 110), in a build which
|
||||||
imapp</b> IMAPv4 proxy (default port 143) <b><br>
|
has one: otherwise the service is <b>tlspr</b> speaking POP3
|
||||||
smtpp</b> SMTP proxy (default port 25) <b><br>
|
to negotiate STARTTLS, under this name. <b><br>
|
||||||
ftppr</b> FTP proxy (default port 21) <b><br>
|
imapp</b> IMAPv4 proxy (default port 143), or <b>tlspr</b>
|
||||||
|
speaking IMAP, as above. <b><br>
|
||||||
|
smtpp</b> SMTP proxy (default port 25), or <b>tlspr</b>
|
||||||
|
speaking SMTP, as above. <b><br>
|
||||||
|
ftppr</b> FTP proxy (default port 21), in a build with FTP
|
||||||
|
support. Without it the service is known but answers
|
||||||
|
nothing, and <b>ftp://</b> is not a URL the HTTP proxy
|
||||||
|
fetches. <b><br>
|
||||||
admin</b> Web interface (default port 80) <b><br>
|
admin</b> Web interface (default port 80) <b><br>
|
||||||
dnspr</b> caching DNS proxy (default port 53) <b><br>
|
dnspr</b> caching DNS proxy (default port 53) <b><br>
|
||||||
tcppm</b> TCP portmapper. Destination address (DSTADDR) can
|
tcppm</b> TCP portmapper. Destination address (DSTADDR) can
|
||||||
@ -192,18 +210,30 @@ or hostname, useful in case of dynamic DNS. <b><br>
|
|||||||
-oR</b><i>OPTIONS</i> options for proxy-to-client
|
-oR</b><i>OPTIONS</i> options for proxy-to-client
|
||||||
(<b>-oc</b>), proxy-to-server (<b>-os</b>), proxy listening
|
(<b>-oc</b>), proxy-to-server (<b>-os</b>), proxy listening
|
||||||
(<b>-ol</b>), connect back client (<b>-or</b>), connect back
|
(<b>-ol</b>), connect back client (<b>-or</b>), connect back
|
||||||
listening (<b>-oR</b>) sockets. Options like TCP_CORK,
|
listening (<b>-oR</b>) sockets. <i>OPTIONS</i> is a comma
|
||||||
TCP_NODELAY, TCP_DEFER_ACCEPT, TCP_QUICKACK, TCP_TIMESTAMPS,
|
separated list of <i>NAME</i> or <i>NAME</i>=<i>VALUE</i>; a
|
||||||
|
name alone sets the option to 1 (TCP_MAXSEG to the value of
|
||||||
|
<b>maxseg</b>), and <i>NAME</i>=0 turns it off. Options add
|
||||||
|
to the defaults and to the options given earlier for the
|
||||||
|
same socket. An unknown name or a value which is not a
|
||||||
|
number is an error. TCP_NODELAY is set by default on
|
||||||
|
proxy-to-client and proxy-to-server sockets, so a reply
|
||||||
|
written in several parts is not delayed by Nagle’s
|
||||||
|
algorithm; use <b>-ocTCP_NODELAY=0</b> or
|
||||||
|
<b>-osTCP_NODELAY=0</b> to turn it off, e.g. to send fewer
|
||||||
|
packets on slow links. Options like TCP_CORK, TCP_NODELAY,
|
||||||
|
TCP_DEFER_ACCEPT, TCP_QUICKACK, TCP_TIMESTAMPS,
|
||||||
TCP_FASTOPEN, SO_REUSEADDR, SO_REUSEPORT,
|
TCP_FASTOPEN, SO_REUSEADDR, SO_REUSEPORT,
|
||||||
SO_EXCLUSIVEADDRUSE, SO_PORT_SCALABILITY,
|
SO_EXCLUSIVEADDRUSE, SO_PORT_SCALABILITY,
|
||||||
SO_REUSE_UNICASTPORT, SO_KEEPALIVE, SO_DONTROUTE may be
|
SO_REUSE_UNICASTPORT, SO_KEEPALIVE, SO_DONTROUTE may be
|
||||||
supported depending on OS. SO_REUSEADDR and SO_REUSEPORT are
|
supported depending on OS. SO_REUSEADDR and SO_REUSEPORT are
|
||||||
set on the listening socket by default on Unix. On Windows
|
set by default on the listening sockets (<b>-ol</b>,
|
||||||
SO_REUSEADDR is not set: it is not required to rebind a
|
<b>-oR</b>) on Unix; <b>-olSO_REUSEPORT=0</b> turns one off.
|
||||||
listening port and it only lets another local process bind
|
On Windows SO_REUSEADDR is not set: it is not required to
|
||||||
the same address and port. Use SO_EXCLUSIVEADDRUSE (Windows)
|
rebind a listening port and it only lets another local
|
||||||
on the listening socket (<b>-ol</b>) to prevent that.
|
process bind the same address and port. Use
|
||||||
<b><br>
|
SO_EXCLUSIVEADDRUSE (Windows) on the listening socket
|
||||||
|
(<b>-ol</b>) to prevent that. <b><br>
|
||||||
-H</b> (for all services) Expect HAProxy PROXY protocol v1
|
-H</b> (for all services) Expect HAProxy PROXY protocol v1
|
||||||
header on incoming connection. This allows the proxy to
|
header on incoming connection. This allows the proxy to
|
||||||
receive real client IP address from HAProxy or other load
|
receive real client IP address from HAProxy or other load
|
||||||
@ -215,15 +245,34 @@ delay GRACE_DELAY milliseconds before polling if average
|
|||||||
polling size is below GRACE_TRAFF bytes and GRACE_NUM read
|
polling size is below GRACE_TRAFF bytes and GRACE_NUM read
|
||||||
operations in a single direction are detected within 1
|
operations in a single direction are detected within 1
|
||||||
second. Useful to minimize polling <b><br>
|
second. Useful to minimize polling <b><br>
|
||||||
|
-B</b> (for TCP services) split the TLS ClientHello across
|
||||||
|
two TCP segments in the middle of the server name (SNI) when
|
||||||
|
it is passed through tlspr, as with <b>parent ... tls
|
||||||
|
0.0.0.0 0</b>: some DPI systems looking for blocked host
|
||||||
|
names do not reassemble it. TCP_NODELAY, set by default,
|
||||||
|
must not be turned off, or the kernel may join the segments
|
||||||
|
again. In builds without splice() <b>-s</b> does the same.
|
||||||
|
<b><br>
|
||||||
-s</b> <br>
|
-s</b> <br>
|
||||||
(for admin) secure, allow only secure operations, currently
|
(for admin) secure, allow only secure operations, currently
|
||||||
only traffic counters view without ability to reset. <br>
|
only traffic counters view without ability to reset. <br>
|
||||||
(for TCP services, Linux) enable splice(). splice() is not
|
(for TCP services, Linux) use splice() to pass data between
|
||||||
built by default and is disabled even when built, because
|
the client and the server without copying it through 3proxy.
|
||||||
current Linux does not implement SPLICE_F_MOVE, so no real
|
<b>-s</b> alone splices both directions where it pays: a
|
||||||
zero-copy takes place and the read/write path is faster for
|
body of known length if at least 16K of it is left to read,
|
||||||
most traffic. Rebuild with -DWITHSPLICE to make -s
|
a stream once it carries large reads, back to copying when
|
||||||
available, -s0 disables it explicitly. <b><br>
|
they turn small. <b>-s1</b> always splices what the server
|
||||||
|
sends to the client (downloads), <b>-s2</b> what the client
|
||||||
|
sends (uploads), <b>-s3</b> both; adding 4 (<b>-s5</b>,
|
||||||
|
<b>-s6</b>, <b>-s7</b>, the last the same as <b>-s</b>)
|
||||||
|
splices those directions only where it pays. <b>-s0</b>, the
|
||||||
|
default, turns it off. splice() suits large transfers: they
|
||||||
|
go faster and use less CPU; small requests go slower with
|
||||||
|
it. A spliced direction needs a pipe (two file descriptors),
|
||||||
|
made when first needed and kept for the connection, and more
|
||||||
|
kernel memory; data filters are not applied to spliced data.
|
||||||
|
Available in builds with WITHSPLICE, the default on Linux.
|
||||||
|
<b><br>
|
||||||
-C</b> <br>
|
-C</b> <br>
|
||||||
(for TCP services) a connection closed by any of the sides
|
(for TCP services) a connection closed by any of the sides
|
||||||
terminates the session, buffered data is delivered before
|
terminates the session, buffered data is delivered before
|
||||||
@ -259,7 +308,12 @@ client reported) <b><br>
|
|||||||
-a1</b> (for proxy) anonymous proxy (random client
|
-a1</b> (for proxy) anonymous proxy (random client
|
||||||
information reported) <b><br>
|
information reported) <b><br>
|
||||||
-a2</b> (for proxy) generate Via: and X-Forwarded-For:
|
-a2</b> (for proxy) generate Via: and X-Forwarded-For:
|
||||||
instead of Forwarded: <br>
|
instead of Forwarded: <b><br>
|
||||||
|
-Xftp</b> (for proxy) fetch <b>ftp://</b> URLs, in a build
|
||||||
|
with FTP support. Without this the scheme is not one the
|
||||||
|
service knows, whatever the build: fetching it opens a
|
||||||
|
second connection and builds a listing, which a service
|
||||||
|
serving http has no use for. <br>
|
||||||
Also, all options mentioned for <b>proxy</b>(8)
|
Also, all options mentioned for <b>proxy</b>(8)
|
||||||
<b>socks</b>(8) <b>pop3p</b>(8) <b>imapp</b>(8)
|
<b>socks</b>(8) <b>pop3p</b>(8) <b>imapp</b>(8)
|
||||||
<b>tcppm</b>(8) <b>udppm</b>(8) <b>ftppr</b>(8) <br>
|
<b>tcppm</b>(8) <b>udppm</b>(8) <b>ftppr</b>(8) <br>
|
||||||
@ -482,7 +536,11 @@ experimental.</p>
|
|||||||
Nameserver to use for name resolutions. If none specified
|
Nameserver to use for name resolutions. If none specified
|
||||||
system routines for name resolution is used. Optional port
|
system routines for name resolution is used. Optional port
|
||||||
number may be specified. If optional /tcp is added to IP
|
number may be specified. If optional /tcp is added to IP
|
||||||
address, name resolution is performed over TCP.</p>
|
address, name resolution is performed over TCP. An IPv6
|
||||||
|
address has to be enclosed in square brackets: <b><br>
|
||||||
|
nserver 1.1.1.1 <br>
|
||||||
|
nserver [2001:4860:4860::8844] <br>
|
||||||
|
nserver [2001:4860:4860::8844]:5353/tcp</b></p>
|
||||||
|
|
||||||
|
|
||||||
<p style="margin-left:9%; margin-top: 1em"><b>authnserver</b>
|
<p style="margin-left:9%; margin-top: 1em"><b>authnserver</b>
|
||||||
@ -706,9 +764,24 @@ A.B.C.D - W.X.Y.Z (since 0.8) or CIDRs (W.X.Y.Z/L). Since
|
|||||||
addresses. It´s possible to use a wildmask in the
|
addresses. It´s possible to use a wildmask in the
|
||||||
beginning and at the end of the hostname, e.g. *badsite.com
|
beginning and at the end of the hostname, e.g. *badsite.com
|
||||||
or *badcontent*. The hostname is only checked if a hostname
|
or *badcontent*. The hostname is only checked if a hostname
|
||||||
is present in the request. Targetportlist may contain ports
|
is present in the request. A name written with a
|
||||||
(X) or port ranges lists (X-Y). For any field * sign means
|
<b>pcre:</b> prefix (<b>regex:</b> is the same thing) is a
|
||||||
ANY. If access list is empty it´s assumed to be <br>
|
regular expression instead of a wildmask, in a build with
|
||||||
|
PCRE support: <br>
|
||||||
|
deny * *
|
||||||
|
"pcre:ˆ(ads|track)[0-9]*\.example\.(com|net)$"
|
||||||
|
<br>
|
||||||
|
The name is lowercased and any trailing dots are removed
|
||||||
|
before it is matched, so patterns are written in lower case.
|
||||||
|
A pattern ending in <b>$</b> has to be quoted or written
|
||||||
|
<b>$$</b>, since a lone dollar outside quotes begins the
|
||||||
|
name of a file to include. The same patterns, and the same
|
||||||
|
prefix, are used by the <b>http</b> command, see BUILT IN
|
||||||
|
HTTP SERVER. Regular expressions are matched per request and
|
||||||
|
cost more than a wildmask, which is enough for most rules.
|
||||||
|
Targetportlist may contain ports (X) or port ranges lists
|
||||||
|
(X-Y). For any field * sign means ANY. If access list is
|
||||||
|
empty it´s assumed to be <br>
|
||||||
allow * <br>
|
allow * <br>
|
||||||
If access list is not empty last item in access list is
|
If access list is not empty last item in access list is
|
||||||
assumed to be <br>
|
assumed to be <br>
|
||||||
@ -799,15 +872,40 @@ grouped. Proxy inside the group is selected randomly. If few
|
|||||||
groups are specified one proxy is randomly picked from each
|
groups are specified one proxy is randomly picked from each
|
||||||
group and chain of proxies is created (that is second proxy
|
group and chain of proxies is created (that is second proxy
|
||||||
connected through first one and so on). Weight is used to
|
connected through first one and so on). Weight is used to
|
||||||
group proxies. Weight is a number between 1 and 1000.
|
group proxies. A weight is a share of the whole, written
|
||||||
|
either as a fraction of one, anything beginning with 0 or
|
||||||
|
with a point, or the old way, in thousandths: <b><br>
|
||||||
|
.5</b> and <b>0.5</b> and <b>500</b> are all a half <b><br>
|
||||||
|
.333</b> and <b>333</b> are both 333 thousandths <b><br>
|
||||||
|
1000</b> and <b>1.0</b> and <b>100%</b> are all the whole
|
||||||
|
share <b><br>
|
||||||
|
50.5%</b> and <b>.505</b> and <b>505</b> are all the same
|
||||||
|
share <b><br>
|
||||||
|
0</b> is a fallback, described below <br>
|
||||||
|
A fraction takes up to nine digits after the point,
|
||||||
|
<b>.333333333</b> being the finest share there is, and the
|
||||||
|
old notation may now carry further digits after a point of
|
||||||
|
its own, so <b>123.456</b> means the same as <b>.123456</b>.
|
||||||
|
Weights are scanned as integers, nothing is read as a
|
||||||
|
floating point number. <b>1.0</b>, with as many zeroes after
|
||||||
|
the point as you care to write, is the one weight read as it
|
||||||
|
looks rather than in thousandths, so that the whole share
|
||||||
|
can be written as a fraction too: a bare <b>1</b> is still a
|
||||||
|
thousandth of it, and <b>1.5</b> still one and a half of
|
||||||
|
them. A weight ending in <b>%</b> is a percentage, and takes
|
||||||
|
up to seven digits after the point. <br>
|
||||||
Weights are summed and proxies are grouped together until
|
Weights are summed and proxies are grouped together until
|
||||||
the weight of the group is 1000. That is: <br>
|
the weight of the group is the whole share. A group which
|
||||||
|
falls short of it by no more than a thousandth, which three
|
||||||
|
weights of <b>333</b> do, is taken for a whole one rather
|
||||||
|
than for a group with a remainder, so a share which cannot
|
||||||
|
be divided evenly needs no adjusting by hand. That is: <br>
|
||||||
allow * <br>
|
allow * <br>
|
||||||
parent 500 socks5 192.168.10.1 1080 <br>
|
parent 500 socks5 192.168.10.1 1080 <br>
|
||||||
parent 500 connect 192.168.10.1 3128 <br>
|
parent 500 connect 192.168.10.1 3128 <br>
|
||||||
makes 3proxy to randomly choose between 2 proxies for all
|
makes 3proxy to randomly choose between 2 proxies for all
|
||||||
outgoing connections. These 2 proxies form 1 group
|
outgoing connections. These 2 proxies form 1 group (their
|
||||||
(summarized weight is 1000). <br>
|
weights are the whole share between them). <br>
|
||||||
allow * * * 80 <br>
|
allow * * * 80 <br>
|
||||||
parent 1000 socks5 192.168.10.1 1080 <br>
|
parent 1000 socks5 192.168.10.1 1080 <br>
|
||||||
parent 1000 connect 192.168.20.1 3128 <br>
|
parent 1000 connect 192.168.20.1 3128 <br>
|
||||||
@ -835,13 +933,18 @@ argument, with 0.0.0.0 as the address, for example <b>parent
|
|||||||
be asked to pick the port itself (Linux
|
be asked to pick the port itself (Linux
|
||||||
<b>IP_LOCAL_PORT_RANGE</b>) it does, otherwise a port is
|
<b>IP_LOCAL_PORT_RANGE</b>) it does, otherwise a port is
|
||||||
picked at random from the range and retried if it is already
|
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
|
in use, up to ten times. A port which binds but whose pair
|
||||||
within <i>net.ipv4.ip_local_port_range</i>, commonly
|
with the destination is still closing from an earlier
|
||||||
32768-60999: the kernel ignores a range outside it and picks
|
connection, which Windows refuses at the connect, is retried
|
||||||
an ordinary ephemeral port instead. If no port in the range
|
in the same way and out of the same ten tries, so a repeated
|
||||||
can be bound, an ephemeral port is used rather than failing
|
connection to one destination does not fail for a port it
|
||||||
the connection. It can be chained with another parent type,
|
has just used. On Linux the range has to lie within
|
||||||
and the access rule it belongs to decides which requests it
|
<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
|
applies to, so <b>allow * * * * UDPASSOC</b> followed by
|
||||||
<b>parent 1000 extport 0.0.0.0 40000-40100</b> limits it to
|
<b>parent 1000 extport 0.0.0.0 40000-40100</b> limits it to
|
||||||
UDP associations. The range is applied when the outgoing
|
UDP associations. The range is applied when the outgoing
|
||||||
@ -947,13 +1050,36 @@ Changes the external address for a given connection to
|
|||||||
1.2.3.4 (equivalent to <b>-e1.2.3.4</b>) <br>
|
1.2.3.4 (equivalent to <b>-e1.2.3.4</b>) <br>
|
||||||
Optional username and password are used to authenticate on
|
Optional username and password are used to authenticate on
|
||||||
parent proxy. Username of ´*´ means username
|
parent proxy. Username of ´*´ means username
|
||||||
must be supplied by user.</p>
|
must be supplied by user. <br>
|
||||||
|
A parent which fails is taken out of the choice for the rest
|
||||||
|
of that connection, so the next attempt, see
|
||||||
|
<b>parentretries</b>, goes to another member of the group
|
||||||
|
instead of the same parent again. Its share is spread over
|
||||||
|
the parents of the group which are left, in proportion to
|
||||||
|
their weights. <br>
|
||||||
|
Weight 0 marks a fallback parent. Such a parent takes no
|
||||||
|
share of the random choice, and none of the share left over
|
||||||
|
by a parent which failed, and is only used once every
|
||||||
|
weighted parent of its group has failed, which is how a
|
||||||
|
parent used only when another one is down is configured:
|
||||||
|
<br>
|
||||||
|
allow * <br>
|
||||||
|
parent 1000 socks5 192.168.10.1 1080 <br>
|
||||||
|
parent 0 socks5 192.168.20.1 1080 <br>
|
||||||
|
Several fallbacks are tried in the order they are written.
|
||||||
|
Reaching a fallback costs an attempt, so
|
||||||
|
<b>parentretries</b> has to be at least as large as the
|
||||||
|
number of parents to try. <br>
|
||||||
|
When every parent of a group has failed the request fails as
|
||||||
|
well, rather than being sent without a parent.</p>
|
||||||
|
|
||||||
|
|
||||||
<p style="margin-left:9%; margin-top: 1em"><b>parentretries</b>
|
<p style="margin-left:9%; margin-top: 1em"><b>parentretries</b>
|
||||||
<i><number></i> <br>
|
<i><number></i> <br>
|
||||||
Number of retries to connect to parent proxy. Default is
|
Number of attempts to reach a parent proxy. Default is 2.
|
||||||
1.</p>
|
Each attempt picks a parent again, leaving out the ones
|
||||||
|
which already failed, so this is also the number of
|
||||||
|
different parents a request may be tried through.</p>
|
||||||
|
|
||||||
<p style="margin-left:9%; margin-top: 1em"><b>nolog</b>
|
<p style="margin-left:9%; margin-top: 1em"><b>nolog</b>
|
||||||
<i><n></i> <br>
|
<i><n></i> <br>
|
||||||
@ -1115,6 +1241,57 @@ users "test4:CR:$3$salt$G47yV9w...." <br>
|
|||||||
Note: double quotes are required because the password
|
Note: double quotes are required because the password
|
||||||
contains a $ sign.</p>
|
contains a $ sign.</p>
|
||||||
|
|
||||||
|
|
||||||
|
<p style="margin-left:9%; margin-top: 1em"><b>transparent</b>
|
||||||
|
<i>[auto|netfilter|pf|socket]</i> <br>
|
||||||
|
Take the destination of a connection, both address and port,
|
||||||
|
from the packet filter that redirected it, instead of from
|
||||||
|
the request. It applies to services declared after it, and
|
||||||
|
<b>notransparent</b> turns it off again for the services
|
||||||
|
after that. Built into the binary since 1.0.1, and
|
||||||
|
previously the separate TransparentPlugin. <br>
|
||||||
|
On Linux the kernel is asked, so <b>iptables</b> or
|
||||||
|
<b>nftables</b> redirection is enough. On the BSDs pf is
|
||||||
|
asked through <b>/dev/pf</b>, which 3proxy must be able to
|
||||||
|
read, so <b>rdr</b> rules work; a redirection that leaves
|
||||||
|
the destination on the socket is used where there is one, as
|
||||||
|
<b>divert-to</b> on OpenBSD and <b>ipfw fwd</b> on FreeBSD
|
||||||
|
do. macOS ships no header for pf and has neither of those,
|
||||||
|
so the commands exist in a macOS build but cannot be used.
|
||||||
|
<br>
|
||||||
|
The mechanism is chosen automatically. The optional argument
|
||||||
|
pins it for an installation that has more than one:
|
||||||
|
<b>auto</b> is the default, <b>netfilter</b> asks the Linux
|
||||||
|
kernel, <b>pf</b> looks the connection up in the packet
|
||||||
|
filter, and <b>socket</b> reads the address off the socket.
|
||||||
|
A mode the build has no code for is refused rather than
|
||||||
|
ignored. <br>
|
||||||
|
A connection that reaches a <b>socket</b> mode service
|
||||||
|
without having been redirected is refused: its destination
|
||||||
|
is the address the service listens on, and using that would
|
||||||
|
send the service to itself. <br>
|
||||||
|
A redirected connection carries no destination of its own,
|
||||||
|
so without this the service uses whatever it would use
|
||||||
|
otherwise: the <b>Host</b> header for an HTTP request, or
|
||||||
|
the address a port mapper was configured with. <b>tlspr</b>
|
||||||
|
receives nothing at all unless traffic is redirected to it
|
||||||
|
or the clients resolve names to it, and with a redirection
|
||||||
|
it has the address as well as the name from the handshake,
|
||||||
|
which is what allows access rules to be written with host
|
||||||
|
names. With it, every service reaches the address the client
|
||||||
|
was trying to reach, and access rules, parents, limits and
|
||||||
|
logging apply to it as usual. <br>
|
||||||
|
The redirection rules must not match the connections the
|
||||||
|
proxy itself makes to those destinations, or the traffic
|
||||||
|
returns to the proxy and loops. Give the service an outgoing
|
||||||
|
address with <b>-e</b> and exclude that address in the
|
||||||
|
rules, or run 3proxy as its own user and exclude that user.
|
||||||
|
See the <b>TRANSPARENT PROXYING</b> section of the
|
||||||
|
documentation for rules per platform. <b><br>
|
||||||
|
notransparent</b> <br>
|
||||||
|
Stop taking the destination from the packet filter for the
|
||||||
|
services declared after it.</p>
|
||||||
|
|
||||||
<p style="margin-left:9%; margin-top: 1em"><b>flush</b>
|
<p style="margin-left:9%; margin-top: 1em"><b>flush</b>
|
||||||
<br>
|
<br>
|
||||||
empty the active access list. The access list must be
|
empty the active access list. The access list must be
|
||||||
@ -1159,8 +1336,15 @@ groupname. Unix only.</p>
|
|||||||
<i><path></i> [<i><uid></i>]
|
<i><path></i> [<i><uid></i>]
|
||||||
[<i><gid></i>] <br>
|
[<i><gid></i>] <br>
|
||||||
calls chroot(path) and sets gid/uid. Unix only. uid/gid
|
calls chroot(path) and sets gid/uid. Unix only. uid/gid
|
||||||
supported since 0.9, can be numeric or
|
supported since 0.9, can be numeric or username/groupname.
|
||||||
username/groupname</p>
|
The time zone and the syslog connection are set up before
|
||||||
|
the call. Plugins which read their own configuration or load
|
||||||
|
modules while running need them inside the chroot: PAM
|
||||||
|
(<i>/etc/pam.d</i> and the modules it names, read for every
|
||||||
|
authentication), LDAP (<i>ldap.conf</i> and, for ldaps, the
|
||||||
|
CA certificates), ODBC (<i>odbc.ini</i>, <i>odbcinst.ini</i>
|
||||||
|
and the driver libraries, loaded again whenever the
|
||||||
|
connection is reopened).</p>
|
||||||
|
|
||||||
|
|
||||||
<p style="margin-left:9%; margin-top: 1em"><b>stacksize</b>
|
<p style="margin-left:9%; margin-top: 1em"><b>stacksize</b>
|
||||||
@ -1377,8 +1561,17 @@ PCRE_NO_AUTO_CAPTURE, PCRE_NO_UTF8_CHECK, PCRE_AUTO_CALLOUT,
|
|||||||
PCRE_PARTIAL, PCRE_DFA_SHORTEST, PCRE_DFA_RESTART,
|
PCRE_PARTIAL, PCRE_DFA_SHORTEST, PCRE_DFA_RESTART,
|
||||||
PCRE_FIRSTLINE, PCRE_DUPNAMES, PCRE_NEWLINE_CR,
|
PCRE_FIRSTLINE, PCRE_DUPNAMES, PCRE_NEWLINE_CR,
|
||||||
PCRE_NEWLINE_LF, PCRE_NEWLINE_CRLF, PCRE_NEWLINE_ANY,
|
PCRE_NEWLINE_LF, PCRE_NEWLINE_CRLF, PCRE_NEWLINE_ANY,
|
||||||
PCRE_NEWLINE_ANYCRLF, PCRE_BSR_ANYCRLF,
|
PCRE_NEWLINE_ANYCRLF, PCRE_BSR_ANYCRLF, PCRE_BSR_UNICODE.
|
||||||
PCRE_BSR_UNICODE.</p>
|
<br>
|
||||||
|
These options apply to every pattern the configuration
|
||||||
|
compiles, the host patterns of access rules and <b>http</b>
|
||||||
|
rules included, so set them before the rules which are to
|
||||||
|
use them. <br>
|
||||||
|
Regular expressions are not only for these commands: a host
|
||||||
|
name in the target list of an access rule, and the host and
|
||||||
|
URL of an <b>http</b> rule, take one when it is written with
|
||||||
|
a <b>pcre:</b> prefix. See <b>allow</b> and BUILT IN HTTP
|
||||||
|
SERVER.</p>
|
||||||
|
|
||||||
<h3>PCRE Parameters
|
<h3>PCRE Parameters
|
||||||
<a name="PCRE Parameters"></a>
|
<a name="PCRE Parameters"></a>
|
||||||
@ -1431,6 +1624,289 @@ the connection data. Warning: Regular expressions
|
|||||||
don’t require authentication and cannot replace
|
don’t require authentication and cannot replace
|
||||||
authentication and/or allow/deny ACLs.</p>
|
authentication and/or allow/deny ACLs.</p>
|
||||||
|
|
||||||
|
<h2>OPTIONAL SERVICES
|
||||||
|
<a name="OPTIONAL SERVICES"></a>
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
|
||||||
|
<p style="margin-left:9%; margin-top: 1em">The mail proxies
|
||||||
|
and FTP are built when they are asked for, and are not in a
|
||||||
|
default build. <b>MAILPROXY=true</b> builds <b>pop3p</b>,
|
||||||
|
<b>imapp</b> and <b>smtpp</b>, and <b>FTP=true</b> builds
|
||||||
|
<b>ftppr</b> and the <b>ftp://</b> scheme of the HTTP proxy,
|
||||||
|
which a <b>proxy</b> service then still has to ask for with
|
||||||
|
<b>-Xftp</b>; with CMake the switches are
|
||||||
|
<b>-D3PROXY_USE_MAILPROXY=ON</b> and
|
||||||
|
<b>-D3PROXY_USE_FTP=ON</b>. The standalone binaries of those
|
||||||
|
services are built with them and not without. <br>
|
||||||
|
A configuration naming a service which was not built is
|
||||||
|
still read. The three mail proxies become <b>tlspr</b>
|
||||||
|
negotiating STARTTLS in that protocol, which is what most of
|
||||||
|
their use amounts to now that the mail protocols are used
|
||||||
|
over TLS, and a <b>parent</b> chain naming <b>pop3</b>,
|
||||||
|
<b>imap</b> or <b>smtp</b> does the same. <b>ftppr</b> is
|
||||||
|
answered by nothing: a client reaching it is turned away and
|
||||||
|
the refusal logged, since there is no protocol to fall back
|
||||||
|
on.</p>
|
||||||
|
|
||||||
|
<h2>BUILT IN HTTP SERVER
|
||||||
|
<a name="BUILT IN HTTP SERVER"></a>
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
|
||||||
|
<p style="margin-left:9%; margin-top: 1em">The
|
||||||
|
<b>httpsrv</b> service answers requests itself instead of
|
||||||
|
forwarding them. What it does with a request is decided by
|
||||||
|
<b>http</b> rules, which are taken in the order they are
|
||||||
|
written: the first whose host and URL both match handles the
|
||||||
|
request. Rules belong to the service that follows them, the
|
||||||
|
way access rules do, and <b>admin</b> is <b>httpsrv</b> with
|
||||||
|
a set of rules already in place.</p>
|
||||||
|
|
||||||
|
<p style="margin-left:9%; margin-top: 1em"><b>http</b>
|
||||||
|
<i>OPERATION HOST URL [PARAMETERS]</i> <br>
|
||||||
|
Handle a request for <i>URL</i> on <i>HOST</i> with
|
||||||
|
<i>OPERATION</i>. HOST is matched against the Host header,
|
||||||
|
URL against the path, with the query string removed.</p>
|
||||||
|
|
||||||
|
<h3>Operations
|
||||||
|
<a name="Operations"></a>
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
|
||||||
|
<p style="margin-left:9%; margin-top: 1em"><b>file</b>
|
||||||
|
<i>PATH [TYPE [MAX-AGE [HEADERS [CODE]]]]</i> - send the
|
||||||
|
file at PATH. The file is handed to the socket by the system
|
||||||
|
where it can do that (sendfile, TransmitFile) and read here
|
||||||
|
where it cannot, as when the connection carries TLS. The
|
||||||
|
arguments after PATH are described below, and each of them
|
||||||
|
may be written as <b>*</b> to leave it out. <b><br>
|
||||||
|
cache</b> <i>PATH [TYPE [MAX-AGE [HEADERS [CODE]]]]</i> -
|
||||||
|
the same, but the file is read into memory on the first
|
||||||
|
request and answered from there afterwards. A file that has
|
||||||
|
changed on disk is read again, and one larger than a
|
||||||
|
megabyte is sent as <b>file</b> would. With a MAX-AGE the
|
||||||
|
file is not looked at again for that long: the rule has
|
||||||
|
already told clients the file may be treated as unchanged
|
||||||
|
for that time, so the server treats its own copy the same
|
||||||
|
way and a request costs nothing but the copy out. Without
|
||||||
|
one every request stats the file, so a change is picked up
|
||||||
|
at once. <b><br>
|
||||||
|
reply</b> <i>[CODE [HEADERS]]</i> - answer with a status and
|
||||||
|
nothing else. CODE is the status to send, 200 without one. A
|
||||||
|
status which carries no body of its own (1xx, 204, 304) is
|
||||||
|
sent without a length; anything else is sent with a length
|
||||||
|
of zero. <b><br>
|
||||||
|
redir</b> <i>[CODE] LOCATION</i> - answer with a redirect.
|
||||||
|
CODE is 301 or 302, or any status from 300 to 399; without
|
||||||
|
one, 302 is used. <b><br>
|
||||||
|
rewrite</b> <i>PATH</i> - change the path of the request and
|
||||||
|
hand it to the rules that follow this one. <b><br>
|
||||||
|
rewrite_host</b> <i>HOST</i> - the same for the host, which
|
||||||
|
decides which of the rules after it match. <b>$1</b> upwards
|
||||||
|
stand for what the stars, or the groups, of this
|
||||||
|
rule´s host pattern matched, the way they stand for
|
||||||
|
those of the URL in a <b>rewrite</b>. What is built has to
|
||||||
|
be a host name; the name the client sent is what access
|
||||||
|
rules matched and what the log records. <b><br>
|
||||||
|
echo</b> - answer with a description of the request: the
|
||||||
|
method, path, query, host, and the address and port it came
|
||||||
|
from. For testing. <b><br>
|
||||||
|
data</b> <i>[size=N] [block=N] [status=N] [chunked=1]
|
||||||
|
[delay=N]</i> - answer with generated content of the size
|
||||||
|
asked for. For testing. <b><br>
|
||||||
|
proxypass</b> - hand the request to the proxy code, which
|
||||||
|
fetches it the way <b>proxy</b> would, see BOTH A SITE AND A
|
||||||
|
PROXY. <b><br>
|
||||||
|
admin</b>, <b>admin_counters</b>, <b>admin_reload</b>,
|
||||||
|
<b>admin_services</b> - the pages of the administration
|
||||||
|
interface.</p>
|
||||||
|
|
||||||
|
<h3>What a rule adds to the answer
|
||||||
|
<a name="What a rule adds to the answer"></a>
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
|
||||||
|
<p style="margin-left:9%; margin-top: 1em"><b>TYPE</b> is
|
||||||
|
the content type to answer with. Without it, or with
|
||||||
|
<b>*</b>, the type is worked out from the name of the file,
|
||||||
|
see <b>http_content_type</b>. <b><br>
|
||||||
|
MAX-AGE</b> is a number of seconds, and is sent as
|
||||||
|
Cache-Control: max-age. Without it, or with <b>*</b>,
|
||||||
|
nothing is said about caching. <b><br>
|
||||||
|
HEADERS</b> is one argument holding whole header lines,
|
||||||
|
separated by a backslash and an n - the two characters,
|
||||||
|
since a configuration line cannot carry a line ending. Each
|
||||||
|
becomes a real line ending in the answer. Quote the argument
|
||||||
|
if any header holds a space, which they usually do. <b><br>
|
||||||
|
CODE</b> is the status to answer with instead of 200, which
|
||||||
|
is how a file serves as the body of an error page. <br>
|
||||||
|
A rule’s headers and MAX-AGE go with whatever status
|
||||||
|
that rule asked for. They are not sent with a refusal the
|
||||||
|
server itself decided on: a request for a file which is not
|
||||||
|
there is answered 404 by the server, not by the rule.
|
||||||
|
<b><br>
|
||||||
|
file</b> and <b>cache</b> send Last-Modified, and answer a
|
||||||
|
request carrying If-Modified-Since with 304 and no body when
|
||||||
|
the file has not changed since the time it names. All three
|
||||||
|
date formats HTTP allows are read; one which cannot be read
|
||||||
|
is treated as no date at all. A rule answering with a CODE
|
||||||
|
of its own is answering something other than the file, so it
|
||||||
|
is never turned into a 304. <br>
|
||||||
|
http file * /err/** "/usr/local/web/404.html"
|
||||||
|
text/html * "X-Served: static" 404 <br>
|
||||||
|
http reply * /health** 200 "X-Health: ok" <br>
|
||||||
|
http reply * /down** 503 "Retry-After: 30"</p>
|
||||||
|
|
||||||
|
|
||||||
|
<p style="margin-left:9%; margin-top: 1em"><b>http_content_type</b>
|
||||||
|
<i>EXTENSION TYPE</i> <br>
|
||||||
|
Answer for a file with that extension with that content
|
||||||
|
type, in addition to the types already known. The extension
|
||||||
|
may be written with or without its dot. A type named by a
|
||||||
|
rule is used whatever this says, and a name the server knows
|
||||||
|
nothing about is answered as application/octet-stream. <br>
|
||||||
|
http_content_type .webp image/webp</p>
|
||||||
|
|
||||||
|
<h3>Patterns
|
||||||
|
<a name="Patterns"></a>
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
|
||||||
|
<p style="margin-left:9%; margin-top: 1em">A URL is matched
|
||||||
|
with stars, or with a regular expression when it carries a
|
||||||
|
<b>pcre:</b> prefix (<b>regex:</b> is taken as well). A host
|
||||||
|
is matched the way an access list matches one, and takes the
|
||||||
|
same prefix. <b><br>
|
||||||
|
*</b> stands for any run of characters within one element of
|
||||||
|
the path: it does not cross a <b>/</b>, so a rule cannot
|
||||||
|
reach into a directory it did not name. <b><br>
|
||||||
|
**</b> crosses them, and is what a rule which should match
|
||||||
|
everything below a point, or everything at all, is written
|
||||||
|
with. <br>
|
||||||
|
Each star, and each group of a regular expression, is
|
||||||
|
remembered in the order it appears. <b>$1</b> upwards stand
|
||||||
|
for them in the path or location a rule builds, and
|
||||||
|
<b>$0</b> for the whole request path. <br>
|
||||||
|
Outside quotes a dollar begins the name of a file to
|
||||||
|
include, so an argument holding one - a path or location
|
||||||
|
built with <b>$1</b>, a regular expression anchored with
|
||||||
|
<b>$</b> - is written in quotes. <b>$$</b> stands for a
|
||||||
|
single dollar and is not read as an include either, which is
|
||||||
|
how a dollar reaches a rule as text.</p>
|
||||||
|
|
||||||
|
<h3>Both a site and a proxy
|
||||||
|
<a name="Both a site and a proxy"></a>
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
|
||||||
|
<p style="margin-left:9%; margin-top: 1em">A request may
|
||||||
|
arrive the way it arrives at a site, naming a path and a
|
||||||
|
host in the Host header, or the way it arrives at a proxy,
|
||||||
|
naming the whole URL, or, for a tunnel, the host alone with
|
||||||
|
CONNECT. Both are read. A request in the proxy form
|
||||||
|
authenticates with Proxy-Authorization and is refused with
|
||||||
|
407, as a proxy refuses one; a request in the site form uses
|
||||||
|
Authorization and 401. <br>
|
||||||
|
What answers a request is still decided by the rules.
|
||||||
|
<b>proxypass</b> is the rule which answers by fetching, so a
|
||||||
|
service can serve what it has and proxy the rest: <br>
|
||||||
|
http file * /local/** "/usr/local/web/$1" <br>
|
||||||
|
http proxypass * /** <br>
|
||||||
|
httpsrv -p8080 <br>
|
||||||
|
The same happens without a rule for it where an access rule
|
||||||
|
redirects to the local proxy, which is written as a chain of
|
||||||
|
no address: the rules are asked first, and a request none of
|
||||||
|
them answers is fetched. <br>
|
||||||
|
allow * <br>
|
||||||
|
parent 1000 http 0.0.0.0 0 <br>
|
||||||
|
allow * <br>
|
||||||
|
The second <b>allow</b> is what the proxy matches on the
|
||||||
|
pass it makes itself: a rule carrying the chain is not taken
|
||||||
|
twice. Authentication happens twice for the same reason,
|
||||||
|
once for the service and once for the proxy, so a
|
||||||
|
configuration asking for credentials asks for them as a
|
||||||
|
proxy does. <br>
|
||||||
|
The access rules are read from the top on both passes, and
|
||||||
|
it is the second pass which describes where the request is
|
||||||
|
going. On the first one the service is answering for itself,
|
||||||
|
so the destination an address or a port is matched against
|
||||||
|
is the address the client connected to; the name from the
|
||||||
|
request is matched on both. On the second the destination is
|
||||||
|
the one the request names, so rules written with an address,
|
||||||
|
a port or a name decide what the proxy is allowed to fetch,
|
||||||
|
and they decide it before the connection is made: <br>
|
||||||
|
allow * <br>
|
||||||
|
parent 1000 http 0.0.0.0 0 <br>
|
||||||
|
allow * * * 80,443 <br>
|
||||||
|
deny * <br>
|
||||||
|
Everything reaches the rules, and only ports 80 and 443 are
|
||||||
|
fetched. A rule before the one carrying the chain applies on
|
||||||
|
both passes just the same, so a <b>deny</b> written there
|
||||||
|
stops the request as well. <br>
|
||||||
|
The connection to the server is kept for the request after
|
||||||
|
it, and closed when the request after it goes somewhere
|
||||||
|
else, or when the server has closed it in the meantime. A
|
||||||
|
tunnel is fetched by the proxy code as well, which means the
|
||||||
|
connection carrying it belongs to that request alone.</p>
|
||||||
|
|
||||||
|
<h3>Connections
|
||||||
|
<a name="Connections"></a>
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
|
||||||
|
<p style="margin-left:9%; margin-top: 1em">An answer is
|
||||||
|
sent as HTTP/1.1 to a client which asked in HTTP/1.1, and
|
||||||
|
the connection is kept for the next request unless the
|
||||||
|
client sent <b>Connection: close</b>. A 1.0 client gets a
|
||||||
|
1.0 answer, and the connection is kept only when it asked
|
||||||
|
with <b>Connection: keep-alive</b>. <br>
|
||||||
|
The connection is kept only when what was sent is framed
|
||||||
|
exactly: every operation but the administration pages states
|
||||||
|
a length, or sends a chunked body a 1.1 client can read, so
|
||||||
|
the pages of <b>admin</b> are always the last thing on a
|
||||||
|
connection. A request body which cannot be read to its end
|
||||||
|
ends the connection as well: one sent with
|
||||||
|
<b>Transfer-Encoding</b>, which this server does not read,
|
||||||
|
and one longer than a megabyte, which it will not.</p>
|
||||||
|
|
||||||
|
<h3>Paths a rule builds
|
||||||
|
<a name="Paths a rule builds"></a>
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
|
||||||
|
<p style="margin-left:9%; margin-top: 1em">The path a rule
|
||||||
|
builds is used as it is, so it is refused rather than
|
||||||
|
corrected when it is not a plain full path. A relative path
|
||||||
|
is refused: it would be read against whatever directory the
|
||||||
|
service happens to be in. So is one holding <b>.</b> or
|
||||||
|
<b>..</b> as an element, a carriage return, a newline or a
|
||||||
|
star. On Windows a path must name a drive or a share, and is
|
||||||
|
converted to the extended \\?\ form and opened through the
|
||||||
|
wide interface, so a long path works. <br>
|
||||||
|
A request is checked before any of this: a path which
|
||||||
|
decodes to one leaving the tree is refused outright.</p>
|
||||||
|
|
||||||
|
<h3>Examples
|
||||||
|
<a name="Examples"></a>
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
|
||||||
|
<p style="margin-left:9%; margin-top: 1em">http file
|
||||||
|
example.com /my/webpath/*.html
|
||||||
|
"/usr/local/web/$1.html" <br>
|
||||||
|
http cache example.com
|
||||||
|
"pcre:ˆ/(.*)/pic/(.*).(gif|jpeg)$"
|
||||||
|
"/usr/local/web/picts/$1/$2.$3" <br>
|
||||||
|
http redir * /old/** 301 "https://example.org/$1"
|
||||||
|
<br>
|
||||||
|
http rewrite * /alias/** "/w/$1" <br>
|
||||||
|
http rewrite_host *.old.example **
|
||||||
|
"$1.new.example" <br>
|
||||||
|
http file * /static/** "/usr/local/web/static/$1"
|
||||||
|
<br>
|
||||||
|
httpsrv -p8080</p>
|
||||||
|
|
||||||
<h2>BUGS
|
<h2>BUGS
|
||||||
<a name="BUGS"></a>
|
<a name="BUGS"></a>
|
||||||
</h2>
|
</h2>
|
||||||
|
|||||||
@ -230,15 +230,16 @@ a certificate (not compatible with TLS 1.3)</p></td></tr>
|
|||||||
<td width="4%">
|
<td width="4%">
|
||||||
|
|
||||||
|
|
||||||
<p><b>-s</b></p></td>
|
<p><b>-B</b></p></td>
|
||||||
<td width="5%"></td>
|
<td width="5%"></td>
|
||||||
<td width="82%">
|
<td width="82%">
|
||||||
|
|
||||||
|
|
||||||
<p>Split the TLS Client HELLO packet across multiple TCP
|
<p>Split the TLS Client HELLO packet across two TCP
|
||||||
segments to make SNI-based DPI detection harder. An optional
|
segments, in the middle of the SNI, to make SNI-based DPI
|
||||||
numeric value can follow (e.g. <b>-s1</b>) to control the
|
detection harder. In builds without splice() <b>-s</b> does
|
||||||
splitting behaviour.</p></td></tr>
|
the same; with splice() (Linux) <b>-s</b> is splice, see
|
||||||
|
<b>3proxy.cfg</b>(5).</p> </td></tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<p style="margin-left:9%;"><b>-Ximap | -Xpop3 |
|
<p style="margin-left:9%;"><b>-Ximap | -Xpop3 |
|
||||||
|
|||||||
@ -1,31 +1,56 @@
|
|||||||
<h3>3proxy TransparentPlugin (Linux/BSD only)</h3>
|
<h3>3proxy transparent proxying (Linux/BSD only)</h3>
|
||||||
|
|
||||||
This plugin can turn 3proxy into a transparent proxy for virtually any TCP-based protocol
|
Transparent proxying is part of 3proxy itself since 1.0.1. It was the separate
|
||||||
and use all 3proxy features - redirections, parent proxies, ACLs, traffic limitations,
|
TransparentPlugin before that, and the <b>plugin</b> line that used to load it
|
||||||
etc. The TransparentPlugin takes the destination IP:port from Linux and uses this
|
is no longer needed: the <b>transparent</b> and <b>notransparent</b> commands
|
||||||
information as the target IP in the proxy. An example usage:
|
are always available on the platforms that can redirect a connection.
|
||||||
|
|
||||||
|
<p>
|
||||||
|
It turns 3proxy into a transparent proxy for virtually any TCP-based protocol,
|
||||||
|
with the rest of 3proxy applying as usual - redirections, parent proxies, ACLs,
|
||||||
|
traffic limitations and logging. The destination IP and port come from the
|
||||||
|
packet filter that redirected the connection, and are used as the target of the
|
||||||
|
proxied connection.
|
||||||
|
</p>
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
plugin /path/to/TransparentPlugin.ld.so transparent_plugin
|
|
||||||
log /path/to/log
|
log /path/to/log
|
||||||
auth iponly
|
auth iponly
|
||||||
allow * * * 80
|
allow * * * 80
|
||||||
parent 1000 http 0.0.0.0 0
|
parent 1000 http 0.0.0.0 0
|
||||||
allow *
|
allow *
|
||||||
parent 1000 socks5 SOCKS5_IP SOCKS5_PORT USER PASSWORD
|
parent 1000 socks5 SOCKS5_IP SOCKS5_PORT USER PASSWORD
|
||||||
|
|
||||||
transparent
|
transparent
|
||||||
tcppm -iLOCAL_IP 12345 127.0.0.1 11111
|
tcppm -eLOCAL_IP 12345 127.0.0.1 11111
|
||||||
notransparent
|
notransparent
|
||||||
proxy
|
proxy
|
||||||
</pre>
|
</pre>
|
||||||
Now, any TCP traffic transparently redirected to port 12345 will be routed via
|
|
||||||
the parent SOCKSv5 proxy and logged; all URLs for web requests are visible in logs.
|
|
||||||
The parameters '127.0.0.1 11111' in this case are not used and are overwritten by
|
|
||||||
the destination IP:port for each transparent connection.
|
|
||||||
|
|
||||||
<h4>Download:</h4>
|
<p>
|
||||||
<ul>
|
Any TCP traffic redirected to port 12345 is routed through the parent SOCKSv5
|
||||||
<li>Plugin is included in 3proxy 0.8
|
proxy and logged, with the URLs of web requests visible in the log. The
|
||||||
</li></ul>
|
'127.0.0.1 11111' arguments are not used in that case: they are replaced by the
|
||||||
|
destination the client was trying to reach.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
The destination is looked up in pf on the BSDs, through <b>/dev/pf</b>, and
|
||||||
|
asked of the kernel on Linux; a redirection that leaves the address on the
|
||||||
|
socket is used where there is one. <b>transparent</b> takes an optional
|
||||||
|
<b>auto</b>, <b>netfilter</b>, <b>pf</b> or <b>socket</b> to pin that choice.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
The redirection rules must not match the connections 3proxy itself makes, or
|
||||||
|
the traffic returns to the proxy and loops. Give the service an address to
|
||||||
|
connect from with <b>-e</b> and exclude it in the rules, or run 3proxy as its
|
||||||
|
own account and exclude that account.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Redirection rules for iptables, nftables, firewalld, ufw and pf are in
|
||||||
|
<a href="../howtoe.html#TRANSPARENT">How to proxy transparently</a>, and the
|
||||||
|
commands are described in 3proxy.cfg(5).
|
||||||
|
</p>
|
||||||
|
|
||||||
© Vladimir Dubrovin, License: BSD style
|
© Vladimir Dubrovin, License: BSD style
|
||||||
|
|||||||
@ -1,33 +1,56 @@
|
|||||||
<h3>Плагин TransparentPlugin 3proxy (только для Linux/BSD)</h3>
|
<h3>Транспарентное проксирование 3proxy (только для Linux/BSD)</h3>
|
||||||
|
|
||||||
Плагин превращает 3proxy в транспарентный прокси для практически любых TCP-соединений
|
Начиная с 1.0.1 транспарентное проксирование встроено в 3proxy. Раньше это был
|
||||||
и позволяет прозрачно для клиентов использовать весь фунционал прокси - редиректоры,
|
отдельный TransparentPlugin, и строка <b>plugin</b>, которой он загружался,
|
||||||
родительские прокси, ACLи, ограничения трафика. TransparentPlugin получает IP:port
|
больше не нужна: команды <b>transparent</b> и <b>notransparent</b> доступны
|
||||||
назначения от Linux и использует эту информацию в качестве конечного адреса назначения.
|
всегда на тех платформах, где соединение можно перенаправить.
|
||||||
<br>
|
|
||||||
Пример использования:
|
<p>
|
||||||
|
3proxy становится транспарентным прокси практически для любых TCP-соединений,
|
||||||
|
причём весь остальной функционал работает как обычно - редиректоры,
|
||||||
|
родительские прокси, ACLи, ограничения трафика и логирование. IP и порт
|
||||||
|
назначения берутся у пакетного фильтра, перенаправившего соединение, и
|
||||||
|
используются как адрес назначения проксируемого соединения.
|
||||||
|
</p>
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
plugin /path/to/TransparentPlugin.ld.so transparent_plugin
|
|
||||||
log /path/to/log
|
log /path/to/log
|
||||||
auth iponly
|
auth iponly
|
||||||
allow * * * 80
|
allow * * * 80
|
||||||
parent 1000 http 0.0.0.0 0
|
parent 1000 http 0.0.0.0 0
|
||||||
allow *
|
allow *
|
||||||
parent 1000 socks5 SOCKS5_IP SOCKS5_PORT USER PASSWORD
|
parent 1000 socks5 SOCKS5_IP SOCKS5_PORT USER PASSWORD
|
||||||
|
|
||||||
transparent
|
transparent
|
||||||
tcppm -iLOCAL_IP 12345 127.0.0.1 11111
|
tcppm -eLOCAL_IP 12345 127.0.0.1 11111
|
||||||
notransparent
|
notransparent
|
||||||
proxy
|
proxy
|
||||||
</pre>
|
</pre>
|
||||||
Теперь любые TCP-соединения транспарентно перенаправленные в локальный порт 12345
|
|
||||||
будут прологгированы и перенаправлены в родительский SOCKSv5 proxy, при этом для
|
<p>
|
||||||
HTTP-запросов по порту TCP/80 будут видны параметры HTTP-запроса.
|
Любой TCP-трафик, перенаправленный на порт 12345, пойдёт через родительский
|
||||||
Параметры '127.0.0.1 11111' в данном случае не оказывают влияния, т.к.
|
SOCKSv5 прокси и будет залогирован, URL веб-запросов видны в логе. Аргументы
|
||||||
будут перезаписываться IP и портом назначения для каждого TCP-соединения соответственно.
|
'127.0.0.1 11111' в этом случае не используются: они заменяются адресом, к
|
||||||
<h4>Загрузить:</h4>
|
которому обращался клиент.
|
||||||
<ul>
|
</p>
|
||||||
<li>Плагин включен в дистрибутив 3proxy 0.8
|
|
||||||
</li></ul>
|
<p>
|
||||||
|
В BSD адрес назначения ищется в pf через <b>/dev/pf</b>, в Linux запрашивается у
|
||||||
|
ядра; если перенаправление оставляет адрес на сокете, используется он. Команда
|
||||||
|
<b>transparent</b> принимает необязательный аргумент <b>auto</b>,
|
||||||
|
<b>netfilter</b>, <b>pf</b> или <b>socket</b>, чтобы зафиксировать выбор.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Правила перенаправления не должны попадать на соединения, которые устанавливает
|
||||||
|
сам 3proxy, иначе трафик возвращается в прокси и зацикливается. Задайте сервису
|
||||||
|
адрес для исходящих соединений через <b>-e</b> и исключите его в правилах, либо
|
||||||
|
запускайте 3proxy под отдельной учётной записью и исключайте её.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Правила перенаправления для iptables, nftables, firewalld, ufw и pf приведены в
|
||||||
|
<a href="../howtor.html#TRANSPARENT">описании транспарентного проксирования</a>,
|
||||||
|
команды описаны в 3proxy.cfg(5).
|
||||||
|
</p>
|
||||||
|
|
||||||
© Vladimir Dubrovin, License: BSD style
|
© Vladimir Dubrovin, License: BSD style
|
||||||
|
|||||||
431
man/3proxy.cfg.5
431
man/3proxy.cfg.5
@ -39,7 +39,9 @@ For included file <CR> (end of line characters) is treated as space character
|
|||||||
(arguments delimiter instead of end of command delimiter).
|
(arguments delimiter instead of end of command delimiter).
|
||||||
Thus, include files are only useful to store long single-line commands
|
Thus, include files are only useful to store long single-line commands
|
||||||
(like userlist, network lists, etc).
|
(like userlist, network lists, etc).
|
||||||
To use dollar sign somewhere in argument it must be quoted.
|
To use dollar sign somewhere in argument it must be quoted or doubled: inside
|
||||||
|
quotes a dollar is ordinary text, and \fB$$\fR stands for a single dollar and is
|
||||||
|
not read as an include.
|
||||||
Recursion is not allowed.
|
Recursion is not allowed.
|
||||||
|
|
||||||
.br
|
.br
|
||||||
@ -93,16 +95,19 @@ SNI proxy (destination address is taken from TLS handshake), may be used to redi
|
|||||||
Proxy with protocol autoselection between proxy / socks / tlspr
|
Proxy with protocol autoselection between proxy / socks / tlspr
|
||||||
.br
|
.br
|
||||||
.B pop3p
|
.B pop3p
|
||||||
POP3 proxy (default port 110)
|
POP3 proxy (default port 110), in a build which has one: otherwise the
|
||||||
|
service is \fBtlspr\fR speaking POP3 to negotiate STARTTLS, under this name.
|
||||||
.br
|
.br
|
||||||
.B imapp
|
.B imapp
|
||||||
IMAPv4 proxy (default port 143)
|
IMAPv4 proxy (default port 143), or \fBtlspr\fR speaking IMAP, as above.
|
||||||
.br
|
.br
|
||||||
.B smtpp
|
.B smtpp
|
||||||
SMTP proxy (default port 25)
|
SMTP proxy (default port 25), or \fBtlspr\fR speaking SMTP, as above.
|
||||||
.br
|
.br
|
||||||
.B ftppr
|
.B ftppr
|
||||||
FTP proxy (default port 21)
|
FTP proxy (default port 21), in a build with FTP support. Without it the
|
||||||
|
service is known but answers nothing, and \fBftp://\fR is not a URL the HTTP
|
||||||
|
proxy fetches.
|
||||||
.br
|
.br
|
||||||
.B admin
|
.B admin
|
||||||
Web interface (default port 80)
|
Web interface (default port 80)
|
||||||
@ -179,8 +184,10 @@ connect to given remote HOST:port instead of listening local connection on -p or
|
|||||||
.br
|
.br
|
||||||
.B -oc\fIOPTIONS\fB, -os\fIOPTIONS\fB, -ol\fIOPTIONS\fB, -or\fIOPTIONS\fB, -oR\fIOPTIONS\fR
|
.B -oc\fIOPTIONS\fB, -os\fIOPTIONS\fB, -ol\fIOPTIONS\fB, -or\fIOPTIONS\fB, -oR\fIOPTIONS\fR
|
||||||
options for proxy-to-client (\fB-oc\fR), proxy-to-server (\fB-os\fR), proxy listening (\fB-ol\fR), connect back client (\fB-or\fR), connect back listening (\fB-oR\fR) sockets.
|
options for proxy-to-client (\fB-oc\fR), proxy-to-server (\fB-os\fR), proxy listening (\fB-ol\fR), connect back client (\fB-or\fR), connect back listening (\fB-oR\fR) sockets.
|
||||||
|
\fIOPTIONS\fR is a comma separated list of \fINAME\fR or \fINAME\fR=\fIVALUE\fR; a name alone sets the option to 1 (TCP_MAXSEG to the value of \fBmaxseg\fR), and \fINAME\fR=0 turns it off. Options add to the defaults and to the options given earlier for the same socket. An unknown name or a value which is not a number is an error.
|
||||||
|
TCP_NODELAY is set by default on proxy-to-client and proxy-to-server sockets, so a reply written in several parts is not delayed by Nagle's algorithm; use \fB-ocTCP_NODELAY=0\fR or \fB-osTCP_NODELAY=0\fR to turn it off, e.g. to send fewer packets on slow links.
|
||||||
Options like TCP_CORK, TCP_NODELAY, TCP_DEFER_ACCEPT, TCP_QUICKACK, TCP_TIMESTAMPS, TCP_FASTOPEN, SO_REUSEADDR, SO_REUSEPORT, SO_EXCLUSIVEADDRUSE, SO_PORT_SCALABILITY, SO_REUSE_UNICASTPORT, SO_KEEPALIVE, SO_DONTROUTE may be supported depending on OS.
|
Options like TCP_CORK, TCP_NODELAY, TCP_DEFER_ACCEPT, TCP_QUICKACK, TCP_TIMESTAMPS, TCP_FASTOPEN, SO_REUSEADDR, SO_REUSEPORT, SO_EXCLUSIVEADDRUSE, SO_PORT_SCALABILITY, SO_REUSE_UNICASTPORT, SO_KEEPALIVE, SO_DONTROUTE may be supported depending on OS.
|
||||||
SO_REUSEADDR and SO_REUSEPORT are set on the listening socket by default on Unix. On Windows SO_REUSEADDR is not set: it is not required to rebind a listening port and it only lets another local process bind the same address and port. Use SO_EXCLUSIVEADDRUSE (Windows) on the listening socket (\fB-ol\fR) to prevent that.
|
SO_REUSEADDR and SO_REUSEPORT are set by default on the listening sockets (\fB-ol\fR, \fB-oR\fR) on Unix; \fB-olSO_REUSEPORT=0\fR turns one off. On Windows SO_REUSEADDR is not set: it is not required to rebind a listening port and it only lets another local process bind the same address and port. Use SO_EXCLUSIVEADDRUSE (Windows) on the listening socket (\fB-ol\fR) to prevent that.
|
||||||
.br
|
.br
|
||||||
.B -H
|
.B -H
|
||||||
(for all services) Expect HAProxy PROXY protocol v1 header on incoming connection.
|
(for all services) Expect HAProxy PROXY protocol v1 header on incoming connection.
|
||||||
@ -191,15 +198,29 @@ any protocol-specific data.
|
|||||||
.B -g(\fIGRACE_TRAFF\fB,\fIGRACE_NUM\fB,\fIGRACE_DELAY\fR)
|
.B -g(\fIGRACE_TRAFF\fB,\fIGRACE_NUM\fB,\fIGRACE_DELAY\fR)
|
||||||
delay GRACE_DELAY milliseconds before polling if average polling size is below GRACE_TRAFF bytes and GRACE_NUM read operations in a single direction are detected within 1 second. Useful to minimize polling
|
delay GRACE_DELAY milliseconds before polling if average polling size is below GRACE_TRAFF bytes and GRACE_NUM read operations in a single direction are detected within 1 second. Useful to minimize polling
|
||||||
.br
|
.br
|
||||||
|
.B -B
|
||||||
|
(for TCP services) split the TLS ClientHello across two TCP segments in the
|
||||||
|
middle of the server name (SNI) when it is passed through tlspr, as with
|
||||||
|
\fBparent ... tls 0.0.0.0 0\fR: some DPI systems looking for blocked host names
|
||||||
|
do not reassemble it. TCP_NODELAY, set by default, must not be turned off, or the
|
||||||
|
kernel may join the segments again. In builds without splice() \fB-s\fR does the same.
|
||||||
|
.br
|
||||||
.B -s
|
.B -s
|
||||||
(for admin) secure, allow only secure operations, currently only traffic counters
|
(for admin) secure, allow only secure operations, currently only traffic counters
|
||||||
view without ability to reset.
|
view without ability to reset.
|
||||||
.br
|
.br
|
||||||
(for TCP services, Linux) enable splice(). splice() is not built by default and
|
(for TCP services, Linux) use splice() to pass data between the client and the
|
||||||
is disabled even when built, because current Linux does not implement
|
server without copying it through 3proxy. \fB-s\fR alone splices both directions where
|
||||||
SPLICE_F_MOVE, so no real zero-copy takes place and the read/write path is faster
|
it pays: a body of known length if at least 16K of it is left to read, a stream
|
||||||
for most traffic. Rebuild with -DWITHSPLICE to make -s available, -s0 disables it
|
once it carries large reads, back to copying when they turn small. \fB-s1\fR always
|
||||||
explicitly.
|
splices what the server sends to the client (downloads), \fB-s2\fR what the client
|
||||||
|
sends (uploads), \fB-s3\fR both; adding 4 (\fB-s5\fR, \fB-s6\fR, \fB-s7\fR, the last the same as
|
||||||
|
\fB-s\fR) splices those directions only where it pays. \fB-s0\fR, the default, turns it
|
||||||
|
off. splice() suits large transfers: they go faster and use less CPU; small
|
||||||
|
requests go slower with it. A spliced direction needs a pipe (two file
|
||||||
|
descriptors), made when first needed and kept for the connection, and more
|
||||||
|
kernel memory; data filters are not applied to spliced data. Available in builds
|
||||||
|
with WITHSPLICE, the default on Linux.
|
||||||
.br
|
.br
|
||||||
.B -C
|
.B -C
|
||||||
(for TCP services) a connection closed by any of the sides terminates the
|
(for TCP services) a connection closed by any of the sides terminates the
|
||||||
@ -239,6 +260,12 @@ Never ask for username/password
|
|||||||
.br
|
.br
|
||||||
.B -a2
|
.B -a2
|
||||||
(for proxy) generate Via: and X-Forwarded-For: instead of Forwarded:
|
(for proxy) generate Via: and X-Forwarded-For: instead of Forwarded:
|
||||||
|
.br
|
||||||
|
.B -Xftp
|
||||||
|
(for proxy) fetch \fBftp://\fR URLs, in a build with FTP support. Without
|
||||||
|
this the scheme is not one the service knows, whatever the build: fetching it
|
||||||
|
opens a second connection and builds a listing, which a service serving http
|
||||||
|
has no use for.
|
||||||
.br
|
.br
|
||||||
Also, all options mentioned for
|
Also, all options mentioned for
|
||||||
.BR proxy (8)
|
.BR proxy (8)
|
||||||
@ -506,6 +533,13 @@ system routines for name resolution is
|
|||||||
used. Optional port number may be specified.
|
used. Optional port number may be specified.
|
||||||
If optional /tcp is added to IP address, name resolution is
|
If optional /tcp is added to IP address, name resolution is
|
||||||
performed over TCP.
|
performed over TCP.
|
||||||
|
An IPv6 address has to be enclosed in square brackets:
|
||||||
|
.br
|
||||||
|
\fBnserver 1.1.1.1\fR
|
||||||
|
.br
|
||||||
|
\fBnserver [2001:4860:4860::8844]\fR
|
||||||
|
.br
|
||||||
|
\fBnserver [2001:4860:4860::8844]:5353/tcp\fR
|
||||||
|
|
||||||
.br
|
.br
|
||||||
.BR authnserver
|
.BR authnserver
|
||||||
@ -743,6 +777,17 @@ Since 0.6, the targetlist may also contain host names,
|
|||||||
instead of addresses. It\'s possible to use a wildmask in
|
instead of addresses. It\'s possible to use a wildmask in
|
||||||
the beginning and at the end of the hostname, e.g. *badsite.com or *badcontent*.
|
the beginning and at the end of the hostname, e.g. *badsite.com or *badcontent*.
|
||||||
The hostname is only checked if a hostname is present in the request.
|
The hostname is only checked if a hostname is present in the request.
|
||||||
|
A name written with a \fBpcre:\fR prefix (\fBregex:\fR is the same thing) is a
|
||||||
|
regular expression instead of a wildmask, in a build with PCRE support:
|
||||||
|
.br
|
||||||
|
deny * * "pcre:^(ads|track)[0-9]*\\.example\\.(com|net)$"
|
||||||
|
.br
|
||||||
|
The name is lowercased and any trailing dots are removed before it is matched,
|
||||||
|
so patterns are written in lower case. A pattern ending in \fB$\fR has to be
|
||||||
|
quoted or written \fB$$\fR, since a lone dollar outside quotes begins the name
|
||||||
|
of a file to include. The same patterns, and the same prefix, are used by the
|
||||||
|
\fBhttp\fR command, see BUILT IN HTTP SERVER. Regular expressions are matched
|
||||||
|
per request and cost more than a wildmask, which is enough for most rules.
|
||||||
Targetportlist may contain ports (X) or port ranges lists (X-Y). For any field *
|
Targetportlist may contain ports (X) or port ranges lists (X-Y). For any field *
|
||||||
sign means ANY. If access list is empty it\'s assumed to be
|
sign means ANY. If access list is empty it\'s assumed to be
|
||||||
.br
|
.br
|
||||||
@ -847,9 +892,35 @@ build proxy chain. Proxies may be grouped. Proxy inside the
|
|||||||
group is selected randomly. If few groups are specified one proxy
|
group is selected randomly. If few groups are specified one proxy
|
||||||
is randomly picked from each group and chain of proxies is created
|
is randomly picked from each group and chain of proxies is created
|
||||||
(that is second proxy connected through first one and so on).
|
(that is second proxy connected through first one and so on).
|
||||||
Weight is used to group proxies. Weight is a number between 1 and 1000.
|
Weight is used to group proxies. A weight is a share of the whole, written
|
||||||
Weights are summed and proxies are grouped together until the weight of
|
either as a fraction of one, anything beginning with 0 or with a point, or the
|
||||||
the group is 1000. That is:
|
old way, in thousandths:
|
||||||
|
.br
|
||||||
|
\fB.5\fR and \fB0.5\fR and \fB500\fR are all a half
|
||||||
|
.br
|
||||||
|
\fB.333\fR and \fB333\fR are both 333 thousandths
|
||||||
|
.br
|
||||||
|
\fB1000\fR and \fB1.0\fR and \fB100%\fR are all the whole share
|
||||||
|
.br
|
||||||
|
\fB50.5%\fR and \fB.505\fR and \fB505\fR are all the same share
|
||||||
|
.br
|
||||||
|
\fB0\fR is a fallback, described below
|
||||||
|
.br
|
||||||
|
A fraction takes up to nine digits after the point, \fB.333333333\fR being
|
||||||
|
the finest share there is, and the old notation may now carry further digits
|
||||||
|
after a point of its own, so \fB123.456\fR means the same as \fB.123456\fR.
|
||||||
|
Weights are scanned as integers, nothing is read as a floating point number.
|
||||||
|
\fB1.0\fR, with as many zeroes after the point as you care to write, is the
|
||||||
|
one weight read as it looks rather than in thousandths, so that the whole
|
||||||
|
share can be written as a fraction too: a bare \fB1\fR is still a thousandth
|
||||||
|
of it, and \fB1.5\fR still one and a half of them. A weight ending in
|
||||||
|
\fB%\fR is a percentage, and takes up to seven digits after the point.
|
||||||
|
.br
|
||||||
|
Weights are summed and proxies are grouped together until the weight of
|
||||||
|
the group is the whole share. A group which falls short of it by no more than
|
||||||
|
a thousandth, which three weights of \fB333\fR do, is taken for a whole one
|
||||||
|
rather than for a group with a remainder, so a share which cannot be divided
|
||||||
|
evenly needs no adjusting by hand. That is:
|
||||||
.br
|
.br
|
||||||
allow *
|
allow *
|
||||||
.br
|
.br
|
||||||
@ -858,7 +929,8 @@ the group is 1000. That is:
|
|||||||
parent 500 connect 192.168.10.1 3128
|
parent 500 connect 192.168.10.1 3128
|
||||||
.br
|
.br
|
||||||
makes 3proxy to randomly choose between 2 proxies for all outgoing
|
makes 3proxy to randomly choose between 2 proxies for all outgoing
|
||||||
connections. These 2 proxies form 1 group (summarized weight is 1000).
|
connections. These 2 proxies form 1 group (their weights are the whole share
|
||||||
|
between them).
|
||||||
.br
|
.br
|
||||||
allow * * * 80
|
allow * * * 80
|
||||||
.br
|
.br
|
||||||
@ -879,7 +951,7 @@ with probability of 0.7) for outgoing web connections. Chains are only applied t
|
|||||||
.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
|
.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.
|
\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. A port which binds but whose pair with the destination is still closing from an earlier connection, which Windows refuses at the connect, is retried in the same way and out of the same ten tries, so a repeated connection to one destination does not fail for a port it has just used. 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
|
.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.
|
\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
|
||||||
@ -991,12 +1063,37 @@ local HTTP proxy parses requests and allows only GET and POST requests.
|
|||||||
.br
|
.br
|
||||||
Optional username and password are used to authenticate on parent
|
Optional username and password are used to authenticate on parent
|
||||||
proxy. Username of \'*\' means username must be supplied by user.
|
proxy. Username of \'*\' means username must be supplied by user.
|
||||||
|
.br
|
||||||
|
A parent which fails is taken out of the choice for the rest of that
|
||||||
|
connection, so the next attempt, see \fBparentretries\fR, goes to another
|
||||||
|
member of the group instead of the same parent again. Its share is spread over
|
||||||
|
the parents of the group which are left, in proportion to their weights.
|
||||||
|
.br
|
||||||
|
Weight 0 marks a fallback parent. Such a parent takes no share of the random
|
||||||
|
choice, and none of the share left over by a parent which failed, and is only
|
||||||
|
used once every weighted parent of its group has failed,
|
||||||
|
which is how a parent used only when another one is down is configured:
|
||||||
|
.br
|
||||||
|
allow *
|
||||||
|
.br
|
||||||
|
parent 1000 socks5 192.168.10.1 1080
|
||||||
|
.br
|
||||||
|
parent 0 socks5 192.168.20.1 1080
|
||||||
|
.br
|
||||||
|
Several fallbacks are tried in the order they are written. Reaching a fallback
|
||||||
|
costs an attempt, so \fBparentretries\fR has to be at least as large as the
|
||||||
|
number of parents to try.
|
||||||
|
.br
|
||||||
|
When every parent of a group has failed the request fails as well, rather
|
||||||
|
than being sent without a parent.
|
||||||
|
|
||||||
.br
|
.br
|
||||||
.BR parentretries
|
.BR parentretries
|
||||||
\fI<number>\fR
|
\fI<number>\fR
|
||||||
.br
|
.br
|
||||||
Number of retries to connect to parent proxy. Default is 1.
|
Number of attempts to reach a parent proxy. Default is 2. Each attempt
|
||||||
|
picks a parent again, leaving out the ones which already failed, so this is
|
||||||
|
also the number of different parents a request may be tried through.
|
||||||
|
|
||||||
|
|
||||||
.br
|
.br
|
||||||
@ -1192,6 +1289,54 @@ the format:
|
|||||||
Note: double quotes are required because the password contains a $ sign.
|
Note: double quotes are required because the password contains a $ sign.
|
||||||
|
|
||||||
.br
|
.br
|
||||||
|
.BR transparent
|
||||||
|
\fI[auto|netfilter|pf|socket]\fR
|
||||||
|
.br
|
||||||
|
Take the destination of a connection, both address and port, from the packet
|
||||||
|
filter that redirected it, instead of from the request. It applies to services declared after it, and
|
||||||
|
\fBnotransparent\fR turns it off again for the services after that. Built into the
|
||||||
|
binary since 1.0.1, and previously the separate TransparentPlugin.
|
||||||
|
.br
|
||||||
|
On Linux the kernel is asked, so \fBiptables\fR or \fBnftables\fR
|
||||||
|
redirection is enough. On the BSDs pf is asked through \fB/dev/pf\fR, which
|
||||||
|
3proxy must be able to read, so \fBrdr\fR rules work; a redirection that
|
||||||
|
leaves the destination on the socket is used where there is one, as
|
||||||
|
\fBdivert-to\fR on OpenBSD and \fBipfw fwd\fR on FreeBSD do. macOS ships no
|
||||||
|
header for pf and has neither of those, so the commands exist in a macOS build
|
||||||
|
but cannot be used.
|
||||||
|
.br
|
||||||
|
The mechanism is chosen automatically. The optional argument pins it for an
|
||||||
|
installation that has more than one: \fBauto\fR is the default,
|
||||||
|
\fBnetfilter\fR asks the Linux kernel, \fBpf\fR looks the connection up in
|
||||||
|
the packet filter, and \fBsocket\fR reads the address off the socket. A mode
|
||||||
|
the build has no code for is refused rather than ignored.
|
||||||
|
.br
|
||||||
|
A connection that reaches a \fBsocket\fR mode service without having been
|
||||||
|
redirected is refused: its destination is the address the service listens on,
|
||||||
|
and using that would send the service to itself.
|
||||||
|
.br
|
||||||
|
A redirected connection carries no destination of its own, so without this the
|
||||||
|
service uses whatever it would use otherwise: the \fBHost\fR header for an
|
||||||
|
HTTP request, or the address a port mapper was configured with. \fBtlspr\fR
|
||||||
|
receives nothing at all unless traffic is redirected to it or the clients
|
||||||
|
resolve names to it, and with a redirection it has the address as well as the
|
||||||
|
name from the handshake, which is what allows access rules to be written with
|
||||||
|
host names. With it, every service reaches the address the
|
||||||
|
client was trying to reach, and access rules, parents, limits and logging apply
|
||||||
|
to it as usual.
|
||||||
|
.br
|
||||||
|
The redirection rules must not match the connections the proxy itself makes to
|
||||||
|
those destinations, or the traffic returns to the proxy and loops. Give the
|
||||||
|
service an outgoing address with \fB-e\fR and exclude that address in the rules,
|
||||||
|
or run 3proxy as its own user and exclude that user. See the
|
||||||
|
.B TRANSPARENT PROXYING
|
||||||
|
section of the documentation for rules per platform.
|
||||||
|
.br
|
||||||
|
.BR notransparent
|
||||||
|
.br
|
||||||
|
Stop taking the destination from the packet filter for the services declared
|
||||||
|
after it.
|
||||||
|
|
||||||
.B flush
|
.B flush
|
||||||
.br
|
.br
|
||||||
empty the active access list. The access list must be flushed every time you create a
|
empty the active access list. The access list must be flushed every time you create a
|
||||||
@ -1250,7 +1395,12 @@ for all threads.
|
|||||||
.BR chroot
|
.BR chroot
|
||||||
\fI<path>\fR [\fI<uid>\fR] [\fI<gid>\fR]
|
\fI<path>\fR [\fI<uid>\fR] [\fI<gid>\fR]
|
||||||
.br
|
.br
|
||||||
calls chroot(path) and sets gid/uid. Unix only. uid/gid supported since 0.9, can be numeric or username/groupname
|
calls chroot(path) and sets gid/uid. Unix only. uid/gid supported since 0.9, can be numeric or username/groupname.
|
||||||
|
The time zone and the syslog connection are set up before the call.
|
||||||
|
Plugins which read their own configuration or load modules while running need them inside the chroot:
|
||||||
|
PAM (\fI/etc/pam.d\fR and the modules it names, read for every authentication),
|
||||||
|
LDAP (\fIldap.conf\fR and, for ldaps, the CA certificates),
|
||||||
|
ODBC (\fIodbc.ini\fR, \fIodbcinst.ini\fR and the driver libraries, loaded again whenever the connection is reopened).
|
||||||
|
|
||||||
.br
|
.br
|
||||||
.BR stacksize
|
.BR stacksize
|
||||||
@ -1463,6 +1613,15 @@ PCRE_NOTEMPTY, PCRE_UTF8, PCRE_NO_AUTO_CAPTURE, PCRE_NO_UTF8_CHECK, PCRE_AUTO_CA
|
|||||||
PCRE_PARTIAL, PCRE_DFA_SHORTEST, PCRE_DFA_RESTART, PCRE_FIRSTLINE, PCRE_DUPNAMES,
|
PCRE_PARTIAL, PCRE_DFA_SHORTEST, PCRE_DFA_RESTART, PCRE_FIRSTLINE, PCRE_DUPNAMES,
|
||||||
PCRE_NEWLINE_CR, PCRE_NEWLINE_LF, PCRE_NEWLINE_CRLF, PCRE_NEWLINE_ANY, PCRE_NEWLINE_ANYCRLF,
|
PCRE_NEWLINE_CR, PCRE_NEWLINE_LF, PCRE_NEWLINE_CRLF, PCRE_NEWLINE_ANY, PCRE_NEWLINE_ANYCRLF,
|
||||||
PCRE_BSR_ANYCRLF, PCRE_BSR_UNICODE.
|
PCRE_BSR_ANYCRLF, PCRE_BSR_UNICODE.
|
||||||
|
.br
|
||||||
|
These options apply to every pattern the configuration compiles, the host
|
||||||
|
patterns of access rules and \fBhttp\fR rules included, so set them before the
|
||||||
|
rules which are to use them.
|
||||||
|
.br
|
||||||
|
Regular expressions are not only for these commands: a host name in the target
|
||||||
|
list of an access rule, and the host and URL of an \fBhttp\fR rule, take one
|
||||||
|
when it is written with a \fBpcre:\fR prefix. See \fBallow\fR and BUILT IN
|
||||||
|
HTTP SERVER.
|
||||||
|
|
||||||
.SS PCRE Parameters
|
.SS PCRE Parameters
|
||||||
TYPE - type of filtered data (comma-delimited list):
|
TYPE - type of filtered data (comma-delimited list):
|
||||||
@ -1503,6 +1662,242 @@ matched if the ACL matches the connection data.
|
|||||||
Warning: Regular expressions don't require authentication and cannot replace
|
Warning: Regular expressions don't require authentication and cannot replace
|
||||||
authentication and/or allow/deny ACLs.
|
authentication and/or allow/deny ACLs.
|
||||||
|
|
||||||
|
.SH OPTIONAL SERVICES
|
||||||
|
The mail proxies and FTP are built when they are asked for, and are not in a
|
||||||
|
default build. \fBMAILPROXY=true\fR builds \fBpop3p\fR, \fBimapp\fR and
|
||||||
|
\fBsmtpp\fR, and \fBFTP=true\fR builds \fBftppr\fR and the \fBftp://\fR
|
||||||
|
scheme of the HTTP proxy, which a \fBproxy\fR service then still has to ask
|
||||||
|
for with \fB-Xftp\fR; with CMake the switches are
|
||||||
|
\fB-D3PROXY_USE_MAILPROXY=ON\fR and \fB-D3PROXY_USE_FTP=ON\fR. The standalone
|
||||||
|
binaries of those services are built with them and not without.
|
||||||
|
.br
|
||||||
|
A configuration naming a service which was not built is still read. The three
|
||||||
|
mail proxies become \fBtlspr\fR negotiating STARTTLS in that protocol, which
|
||||||
|
is what most of their use amounts to now that the mail protocols are used over
|
||||||
|
TLS, and a \fBparent\fR chain naming \fBpop3\fR, \fBimap\fR or \fBsmtp\fR
|
||||||
|
does the same. \fBftppr\fR is answered by nothing: a client reaching it is
|
||||||
|
turned away and the refusal logged, since there is no protocol to fall back on.
|
||||||
|
|
||||||
|
.SH BUILT IN HTTP SERVER
|
||||||
|
The \fBhttpsrv\fR service answers requests itself instead of forwarding them.
|
||||||
|
What it does with a request is decided by \fBhttp\fR rules, which are taken in
|
||||||
|
the order they are written: the first whose host and URL both match handles the
|
||||||
|
request. Rules belong to the service that follows them, the way access rules do,
|
||||||
|
and \fBadmin\fR is \fBhttpsrv\fR with a set of rules already in place.
|
||||||
|
|
||||||
|
.BR http
|
||||||
|
\fIOPERATION HOST URL [PARAMETERS]\fR
|
||||||
|
.br
|
||||||
|
Handle a request for \fIURL\fR on \fIHOST\fR with \fIOPERATION\fR. HOST is
|
||||||
|
matched against the Host header, URL against the path, with the query string
|
||||||
|
removed.
|
||||||
|
|
||||||
|
.SS Operations
|
||||||
|
.br
|
||||||
|
\fBfile\fR \fIPATH [TYPE [MAX-AGE [HEADERS [CODE]]]]\fR - send the file at PATH.
|
||||||
|
The file is handed to the socket by the system where it can do that (sendfile,
|
||||||
|
TransmitFile) and read here where it cannot, as when the connection carries TLS.
|
||||||
|
The arguments after PATH are described below, and each of them may be written as
|
||||||
|
\fB*\fR to leave it out.
|
||||||
|
.br
|
||||||
|
\fBcache\fR \fIPATH [TYPE [MAX-AGE [HEADERS [CODE]]]]\fR - the same, but the
|
||||||
|
file is read into memory on the first request and answered from there afterwards.
|
||||||
|
A file that has changed on disk is read again, and one larger than a megabyte is
|
||||||
|
sent as \fBfile\fR would. With a MAX-AGE the file is not looked at again for
|
||||||
|
that long: the rule has already told clients the file may be treated as
|
||||||
|
unchanged for that time, so the server treats its own copy the same way and a
|
||||||
|
request costs nothing but the copy out. Without one every request stats the
|
||||||
|
file, so a change is picked up at once.
|
||||||
|
.br
|
||||||
|
\fBreply\fR \fI[CODE [HEADERS]]\fR - answer with a status and nothing else.
|
||||||
|
CODE is the status to send, 200 without one. A status which carries no body of
|
||||||
|
its own (1xx, 204, 304) is sent without a length; anything else is sent with a
|
||||||
|
length of zero.
|
||||||
|
.br
|
||||||
|
\fBredir\fR \fI[CODE] LOCATION\fR - answer with a redirect. CODE is 301 or 302,
|
||||||
|
or any status from 300 to 399; without one, 302 is used.
|
||||||
|
.br
|
||||||
|
\fBrewrite\fR \fIPATH\fR - change the path of the request and hand it to the
|
||||||
|
rules that follow this one.
|
||||||
|
.br
|
||||||
|
\fBrewrite_host\fR \fIHOST\fR - the same for the host, which decides which of
|
||||||
|
the rules after it match. \fB$1\fR upwards stand for what the stars, or the
|
||||||
|
groups, of this rule\'s host pattern matched, the way they stand for those of
|
||||||
|
the URL in a \fBrewrite\fR. What is built has to be a host name; the name the
|
||||||
|
client sent is what access rules matched and what the log records.
|
||||||
|
.br
|
||||||
|
\fBecho\fR - answer with a description of the request: the method, path, query,
|
||||||
|
host, and the address and port it came from. For testing.
|
||||||
|
.br
|
||||||
|
\fBdata\fR \fI[size=N] [block=N] [status=N] [chunked=1] [delay=N]\fR - answer
|
||||||
|
with generated content of the size asked for. For testing.
|
||||||
|
.br
|
||||||
|
\fBproxypass\fR - hand the request to the proxy code, which fetches it the
|
||||||
|
way \fBproxy\fR would, see BOTH A SITE AND A PROXY.
|
||||||
|
.br
|
||||||
|
\fBadmin\fR, \fBadmin_counters\fR, \fBadmin_reload\fR, \fBadmin_services\fR -
|
||||||
|
the pages of the administration interface.
|
||||||
|
|
||||||
|
.SS What a rule adds to the answer
|
||||||
|
\fBTYPE\fR is the content type to answer with. Without it, or with \fB*\fR, the
|
||||||
|
type is worked out from the name of the file, see \fBhttp_content_type\fR.
|
||||||
|
.br
|
||||||
|
\fBMAX-AGE\fR is a number of seconds, and is sent as Cache-Control: max-age.
|
||||||
|
Without it, or with \fB*\fR, nothing is said about caching.
|
||||||
|
.br
|
||||||
|
\fBHEADERS\fR is one argument holding whole header lines, separated by a
|
||||||
|
backslash and an n \- the two characters, since a configuration line cannot
|
||||||
|
carry a line ending. Each becomes a real line ending in the answer. Quote the
|
||||||
|
argument if any header holds a space, which they usually do.
|
||||||
|
.br
|
||||||
|
\fBCODE\fR is the status to answer with instead of 200, which is how a file
|
||||||
|
serves as the body of an error page.
|
||||||
|
.br
|
||||||
|
A rule's headers and MAX-AGE go with whatever status that rule asked for. They
|
||||||
|
are not sent with a refusal the server itself decided on: a request for a file
|
||||||
|
which is not there is answered 404 by the server, not by the rule.
|
||||||
|
.br
|
||||||
|
\fBfile\fR and \fBcache\fR send Last-Modified, and answer a request carrying
|
||||||
|
If-Modified-Since with 304 and no body when the file has not changed since the
|
||||||
|
time it names. All three date formats HTTP allows are read; one which cannot be
|
||||||
|
read is treated as no date at all. A rule answering with a CODE of its own is
|
||||||
|
answering something other than the file, so it is never turned into a 304.
|
||||||
|
.br
|
||||||
|
http file * /err/** "/usr/local/web/404.html" text/html * "X-Served: static" 404
|
||||||
|
.br
|
||||||
|
http reply * /health** 200 "X-Health: ok"
|
||||||
|
.br
|
||||||
|
http reply * /down** 503 "Retry-After: 30"
|
||||||
|
|
||||||
|
.BR http_content_type
|
||||||
|
\fIEXTENSION TYPE\fR
|
||||||
|
.br
|
||||||
|
Answer for a file with that extension with that content type, in addition to
|
||||||
|
the types already known. The extension may be written with or without its dot.
|
||||||
|
A type named by a rule is used whatever this says, and a name the server knows
|
||||||
|
nothing about is answered as application/octet-stream.
|
||||||
|
.br
|
||||||
|
http_content_type .webp image/webp
|
||||||
|
|
||||||
|
.SS Patterns
|
||||||
|
A URL is matched with stars, or with a regular expression when it carries a
|
||||||
|
\fBpcre:\fR prefix (\fBregex:\fR is taken as well). A host is matched the way an
|
||||||
|
access list matches one, and takes the same prefix.
|
||||||
|
.br
|
||||||
|
\fB*\fR stands for any run of characters within one element of the path: it
|
||||||
|
does not cross a \fB/\fR, so a rule cannot reach into a directory it did not
|
||||||
|
name.
|
||||||
|
.br
|
||||||
|
\fB**\fR crosses them, and is what a rule which should match everything below a
|
||||||
|
point, or everything at all, is written with.
|
||||||
|
.br
|
||||||
|
Each star, and each group of a regular expression, is remembered in the order
|
||||||
|
it appears. \fB$1\fR upwards stand for them in the path or location a rule
|
||||||
|
builds, and \fB$0\fR for the whole request path.
|
||||||
|
.br
|
||||||
|
Outside quotes a dollar begins the name of a file to include, so an argument
|
||||||
|
holding one \- a path or location built with \fB$1\fR, a regular expression
|
||||||
|
anchored with \fB$\fR \- is written in quotes. \fB$$\fR stands for a single
|
||||||
|
dollar and is not read as an include either, which is how a dollar reaches a
|
||||||
|
rule as text.
|
||||||
|
|
||||||
|
.SS Both a site and a proxy
|
||||||
|
A request may arrive the way it arrives at a site, naming a path and a host in
|
||||||
|
the Host header, or the way it arrives at a proxy, naming the whole URL, or, for
|
||||||
|
a tunnel, the host alone with CONNECT. Both are read. A request in the proxy
|
||||||
|
form authenticates with Proxy-Authorization and is refused with 407, as a proxy
|
||||||
|
refuses one; a request in the site form uses Authorization and 401.
|
||||||
|
.br
|
||||||
|
What answers a request is still decided by the rules. \fBproxypass\fR is the
|
||||||
|
rule which answers by fetching, so a service can serve what it has and proxy the
|
||||||
|
rest:
|
||||||
|
.br
|
||||||
|
http file * /local/** "/usr/local/web/$1"
|
||||||
|
.br
|
||||||
|
http proxypass * /**
|
||||||
|
.br
|
||||||
|
httpsrv -p8080
|
||||||
|
.br
|
||||||
|
The same happens without a rule for it where an access rule redirects to the
|
||||||
|
local proxy, which is written as a chain of no address: the rules are asked
|
||||||
|
first, and a request none of them answers is fetched.
|
||||||
|
.br
|
||||||
|
allow *
|
||||||
|
.br
|
||||||
|
parent 1000 http 0.0.0.0 0
|
||||||
|
.br
|
||||||
|
allow *
|
||||||
|
.br
|
||||||
|
The second \fBallow\fR is what the proxy matches on the pass it makes itself:
|
||||||
|
a rule carrying the chain is not taken twice. Authentication happens twice for
|
||||||
|
the same reason, once for the service and once for the proxy, so a configuration
|
||||||
|
asking for credentials asks for them as a proxy does.
|
||||||
|
.br
|
||||||
|
The access rules are read from the top on both passes, and it is the second
|
||||||
|
pass which describes where the request is going. On the first one the service is
|
||||||
|
answering for itself, so the destination an address or a port is matched against
|
||||||
|
is the address the client connected to; the name from the request is matched on
|
||||||
|
both. On the second the destination is the one the request names, so rules
|
||||||
|
written with an address, a port or a name decide what the proxy is allowed to
|
||||||
|
fetch, and they decide it before the connection is made:
|
||||||
|
.br
|
||||||
|
allow *
|
||||||
|
.br
|
||||||
|
parent 1000 http 0.0.0.0 0
|
||||||
|
.br
|
||||||
|
allow * * * 80,443
|
||||||
|
.br
|
||||||
|
deny *
|
||||||
|
.br
|
||||||
|
Everything reaches the rules, and only ports 80 and 443 are fetched. A rule
|
||||||
|
before the one carrying the chain applies on both passes just the same, so a
|
||||||
|
\fBdeny\fR written there stops the request as well.
|
||||||
|
.br
|
||||||
|
The connection to the server is kept for the request after it, and closed when
|
||||||
|
the request after it goes somewhere else, or when the server has closed it in
|
||||||
|
the meantime. A tunnel is fetched by the proxy code as well, which means the
|
||||||
|
connection carrying it belongs to that request alone.
|
||||||
|
|
||||||
|
.SS Connections
|
||||||
|
An answer is sent as HTTP/1.1 to a client which asked in HTTP/1.1, and the
|
||||||
|
connection is kept for the next request unless the client sent
|
||||||
|
\fBConnection: close\fR. A 1.0 client gets a 1.0 answer, and the connection is
|
||||||
|
kept only when it asked with \fBConnection: keep-alive\fR.
|
||||||
|
.br
|
||||||
|
The connection is kept only when what was sent is framed exactly: every
|
||||||
|
operation but the administration pages states a length, or sends a chunked body
|
||||||
|
a 1.1 client can read, so the pages of \fBadmin\fR are always the last thing on
|
||||||
|
a connection. A request body which cannot be read to its end ends the connection
|
||||||
|
as well: one sent with \fBTransfer-Encoding\fR, which this server does not read,
|
||||||
|
and one longer than a megabyte, which it will not.
|
||||||
|
|
||||||
|
.SS Paths a rule builds
|
||||||
|
The path a rule builds is used as it is, so it is refused rather than corrected
|
||||||
|
when it is not a plain full path. A relative path is refused: it would be read
|
||||||
|
against whatever directory the service happens to be in. So is one holding
|
||||||
|
\fB.\fR or \fB..\fR as an element, a carriage return, a newline or a star. On
|
||||||
|
Windows a path must name a drive or a share, and is converted to the extended
|
||||||
|
\\\\?\\ form and opened through the wide interface, so a long path works.
|
||||||
|
.br
|
||||||
|
A request is checked before any of this: a path which decodes to one leaving
|
||||||
|
the tree is refused outright.
|
||||||
|
|
||||||
|
.SS Examples
|
||||||
|
.br
|
||||||
|
http file example.com /my/webpath/*.html "/usr/local/web/$1.html"
|
||||||
|
.br
|
||||||
|
http cache example.com "pcre:^/(.*)/pic/(.*)\.(gif|jpeg)$" "/usr/local/web/picts/$1/$2.$3"
|
||||||
|
.br
|
||||||
|
http redir * /old/** 301 "https://example.org/$1"
|
||||||
|
.br
|
||||||
|
http rewrite * /alias/** "/w/$1"
|
||||||
|
.br
|
||||||
|
http rewrite_host *.old.example ** "$1.new.example"
|
||||||
|
.br
|
||||||
|
http file * /static/** "/usr/local/web/static/$1"
|
||||||
|
.br
|
||||||
|
httpsrv -p8080
|
||||||
|
|
||||||
.SH BUGS
|
.SH BUGS
|
||||||
Report all bugs to
|
Report all bugs to
|
||||||
.BR 3proxy@3proxy.org
|
.BR 3proxy@3proxy.org
|
||||||
|
|||||||
@ -76,8 +76,8 @@ destination_port. Port to establish outgoing connections. Required unless the Tr
|
|||||||
.B -c
|
.B -c
|
||||||
TLS_CHECK_LEVEL. 0 (default) - allow non-TLS traffic to pass, 1 - require TLS, only check client HELLO packet, 2 - require TLS, check both client and server HELLO, 3 - require TLS, check that the server sends a certificate (not compatible with TLS 1.3), 4 - require mutual TLS, check that the server sends a certificate request and the client sends a certificate (not compatible with TLS 1.3)
|
TLS_CHECK_LEVEL. 0 (default) - allow non-TLS traffic to pass, 1 - require TLS, only check client HELLO packet, 2 - require TLS, check both client and server HELLO, 3 - require TLS, check that the server sends a certificate (not compatible with TLS 1.3), 4 - require mutual TLS, check that the server sends a certificate request and the client sends a certificate (not compatible with TLS 1.3)
|
||||||
.TP
|
.TP
|
||||||
.B -s
|
.B -B
|
||||||
Split the TLS Client HELLO packet across multiple TCP segments to make SNI-based DPI detection harder. An optional numeric value can follow (e.g. \fB-s1\fR) to control the splitting behaviour.
|
Split the TLS Client HELLO packet across two TCP segments, in the middle of the SNI, to make SNI-based DPI detection harder. In builds without splice() \fB-s\fR does the same; with splice() (Linux) \fB-s\fR is splice, see \fB3proxy.cfg\fR(5).
|
||||||
.TP
|
.TP
|
||||||
.B -Ximap | -Xpop3 | -Xsmtp
|
.B -Ximap | -Xpop3 | -Xsmtp
|
||||||
STARTTLS mode for the given protocol. The proxy speaks the plaintext protocol
|
STARTTLS mode for the given protocol. The proxy speaks the plaintext protocol
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
Name: 3proxy
|
Name: 3proxy
|
||||||
Version: 1.0.0
|
Version: 1.0.2
|
||||||
Release: 1%{?dist}
|
Release: 1%{?dist}
|
||||||
Summary: 3proxy tiny proxy server
|
Summary: 3proxy tiny proxy server
|
||||||
License: GPL/LGPL/Apache/BSD
|
License: GPL/LGPL/Apache/BSD
|
||||||
@ -36,11 +36,7 @@ make clean
|
|||||||
%files
|
%files
|
||||||
/bin/3proxy
|
/bin/3proxy
|
||||||
/bin/3proxy_crypt
|
/bin/3proxy_crypt
|
||||||
/bin/3proxy_ftppr
|
|
||||||
/bin/3proxy_imapp
|
|
||||||
/bin/3proxy_pop3p
|
|
||||||
/bin/3proxy_proxy
|
/bin/3proxy_proxy
|
||||||
/bin/3proxy_smtpp
|
|
||||||
/bin/3proxy_socks
|
/bin/3proxy_socks
|
||||||
/bin/3proxy_tcppm
|
/bin/3proxy_tcppm
|
||||||
/bin/3proxy_tlspr
|
/bin/3proxy_tlspr
|
||||||
|
|||||||
112
src/3proxy.c
112
src/3proxy.c
@ -13,6 +13,12 @@ void ssl_install(void);
|
|||||||
#ifdef WITH_PCRE
|
#ifdef WITH_PCRE
|
||||||
void pcre_install(void);
|
void pcre_install(void);
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef WITH_TRANSPARENT
|
||||||
|
void transparent_install(void);
|
||||||
|
#endif
|
||||||
|
#ifdef WITH_HTTPSRV
|
||||||
|
void httpsrv_init(void);
|
||||||
|
#endif
|
||||||
#ifndef _WIN32
|
#ifndef _WIN32
|
||||||
#include <sys/resource.h>
|
#include <sys/resource.h>
|
||||||
#ifndef NOPLUGINS
|
#ifndef NOPLUGINS
|
||||||
@ -68,13 +74,8 @@ void __stdcall CommandHandler( DWORD dwCommand )
|
|||||||
SetStatus( SERVICE_STOP_PENDING, 0, 1 );
|
SetStatus( SERVICE_STOP_PENDING, 0, 1 );
|
||||||
conf.timetoexit = 1;
|
conf.timetoexit = 1;
|
||||||
conf.paused++;
|
conf.paused++;
|
||||||
Sleep(2000);
|
/* ServiceMain() reports the service stopped once cyclestep() has
|
||||||
SetStatus( SERVICE_STOPPED, 0, 0 );
|
seen this and the services are done */
|
||||||
#ifdef WITH_ODBC
|
|
||||||
_3proxy_mutex_lock(&log_mutex);
|
|
||||||
close_sql();
|
|
||||||
_3proxy_mutex_unlock(&log_mutex);
|
|
||||||
#endif
|
|
||||||
break;
|
break;
|
||||||
case SERVICE_CONTROL_PAUSE:
|
case SERVICE_CONTROL_PAUSE:
|
||||||
SetStatus( SERVICE_PAUSE_PENDING, 0, 1 );
|
SetStatus( SERVICE_PAUSE_PENDING, 0, 1 );
|
||||||
@ -100,6 +101,13 @@ void __stdcall ServiceMain(int argc, unsigned char* argv[] )
|
|||||||
SetStatus( SERVICE_START_PENDING, 0, 1 );
|
SetStatus( SERVICE_START_PENDING, 0, 1 );
|
||||||
SetStatus( SERVICE_RUNNING, 0, 0 );
|
SetStatus( SERVICE_RUNNING, 0, 0 );
|
||||||
cyclestep();
|
cyclestep();
|
||||||
|
#ifdef WITH_ODBC
|
||||||
|
_3proxy_mutex_lock(&log_mutex);
|
||||||
|
close_sql();
|
||||||
|
_3proxy_mutex_unlock(&log_mutex);
|
||||||
|
#endif
|
||||||
|
/* the last thing: the process may be ended once this is reported */
|
||||||
|
SetStatus( SERVICE_STOPPED, 0, 0 );
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@ -234,6 +242,39 @@ void dumpcounters(struct trafcount *tlin, int counterd){
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Lets the services finish before the process exits, for EXITWAIT ms at
|
||||||
|
most: each one waits for its connections, and a connection may hang on
|
||||||
|
its peer. Posts left over from reloads are consumed on the way, the
|
||||||
|
count decides. */
|
||||||
|
#define EXITWAIT 3000
|
||||||
|
static unsigned long nowms(void){
|
||||||
|
#ifdef _WIN32
|
||||||
|
return (unsigned long)GetTickCount();
|
||||||
|
#else
|
||||||
|
struct timeval tv;
|
||||||
|
|
||||||
|
gettimeofday(&tv, NULL);
|
||||||
|
return (unsigned long)tv.tv_sec * 1000 + tv.tv_usec / 1000;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static void waitservices(void){
|
||||||
|
unsigned long start = nowms(), passed;
|
||||||
|
|
||||||
|
for(;;){
|
||||||
|
int n;
|
||||||
|
|
||||||
|
_3proxy_mutex_lock(&runningservices_mutex);
|
||||||
|
n = runningservices;
|
||||||
|
_3proxy_mutex_unlock(&runningservices_mutex);
|
||||||
|
if(!n) return;
|
||||||
|
/* a clock set back makes this huge, which ends the wait as well */
|
||||||
|
passed = nowms() - start;
|
||||||
|
if(passed >= EXITWAIT) return;
|
||||||
|
if(_3proxy_sem_timedlock(serviceexited, (unsigned)(EXITWAIT - passed))) return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void cyclestep(void){
|
void cyclestep(void){
|
||||||
struct tm *tm;
|
struct tm *tm;
|
||||||
time_t minutecounter;
|
time_t minutecounter;
|
||||||
@ -330,9 +371,7 @@ void cyclestep(void){
|
|||||||
if(conf.timetoexit){
|
if(conf.timetoexit){
|
||||||
conf.paused++;
|
conf.paused++;
|
||||||
doschedule();
|
doschedule();
|
||||||
usleep(SLEEPTIME*999);
|
waitservices();
|
||||||
usleep(SLEEPTIME*999);
|
|
||||||
usleep(SLEEPTIME*999);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -343,6 +382,21 @@ void cyclestep(void){
|
|||||||
#define RETURN(x) {res = x; goto CLEARRETURN;}
|
#define RETURN(x) {res = x; goto CLEARRETURN;}
|
||||||
|
|
||||||
|
|
||||||
|
static void printusage(struct extparam *cconf, int argc, char * argv[]){
|
||||||
|
|
||||||
|
fprintf(stderr, "Usage: %s [conffile]\n", argv[0]);
|
||||||
|
#ifdef _WIN32
|
||||||
|
fprintf(stderr, "\n\t%s --install [conffile]\n\tto install as service\n"
|
||||||
|
"\n\t%s --remove\n\tto remove service\n", argv[0], argv[0]);
|
||||||
|
#else
|
||||||
|
fprintf(stderr, "\n if conffile is missing, configuration is expected from stdin\n");
|
||||||
|
#endif
|
||||||
|
fprintf(stderr, "\n\t%s --help\n\tthis message\n", argv[0]);
|
||||||
|
fprintf(stderr, "available socket options:\n\t%s\n", printopts("\n\t"));
|
||||||
|
fprintf(stderr, "built with:\n\t%s\n", printfeatures("\n\t"));
|
||||||
|
fprintf(stderr, "\n%s %s\n%s\n", cconf->stringtable[2], cconf->stringtable[3], copyright);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#ifndef _WINCE
|
#ifndef _WINCE
|
||||||
int main(int argc, char * argv[]) {
|
int main(int argc, char * argv[]) {
|
||||||
@ -385,6 +439,10 @@ int WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
conf.stringtable = strings;
|
conf.stringtable = strings;
|
||||||
|
if(argc == 2 && (!strcmp((char *)argv[1], "--help") || !strcmp((char *)argv[1], "-h"))) {
|
||||||
|
printusage(&conf, argc, argv);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
#ifndef _WINCE
|
#ifndef _WINCE
|
||||||
if((argc == 2 || argc == 3)&& !strcmp((char *)argv[1], "--install")) {
|
if((argc == 2 || argc == 3)&& !strcmp((char *)argv[1], "--install")) {
|
||||||
@ -497,20 +555,17 @@ int WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int
|
|||||||
}
|
}
|
||||||
if(argc > 2 || !(fp)) {
|
if(argc > 2 || !(fp)) {
|
||||||
|
|
||||||
fprintf(stderr, "Usage: %s [conffile]\n", argv[0]);
|
printusage(&conf, argc, argv);
|
||||||
#ifdef _WIN32
|
|
||||||
fprintf(stderr, "\n\t%s --install [conffile]\n\tto install as service\n"
|
|
||||||
"\n\t%s --remove\n\tto remove service\n", argv[0], argv[0]);
|
|
||||||
#else
|
|
||||||
fprintf(stderr, "\n if conffile is missing, configuration is expected from stdin\n");
|
|
||||||
#endif
|
|
||||||
fprintf(stderr, "available socket options:\n\t%s\n", printopts("\n\t"));
|
|
||||||
fprintf(stderr, "\n%s %s\n%s\n", conf.stringtable[2], conf.stringtable[3], copyright);
|
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
|
/* before readconfig() starts the services: a client gone while it is
|
||||||
|
written to is an error, not the end */
|
||||||
|
signal(SIGPIPE, SIG_IGN);
|
||||||
|
#endif
|
||||||
_3proxy_mutex_init(&config_mutex);
|
_3proxy_mutex_init(&config_mutex);
|
||||||
|
_3proxy_mutex_init(&runningservices_mutex);
|
||||||
_3proxy_mutex_init(&bandlim_mutex);
|
_3proxy_mutex_init(&bandlim_mutex);
|
||||||
_3proxy_mutex_init(&connlim_mutex);
|
_3proxy_mutex_init(&connlim_mutex);
|
||||||
_3proxy_mutex_init(&tc_mutex);
|
_3proxy_mutex_init(&tc_mutex);
|
||||||
@ -518,7 +573,7 @@ int WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int
|
|||||||
#ifndef NORADIUS
|
#ifndef NORADIUS
|
||||||
_3proxy_mutex_init(&rad_mutex);
|
_3proxy_mutex_init(&rad_mutex);
|
||||||
#endif
|
#endif
|
||||||
if(_3proxy_sem_init(conf.threadinit, 1, 1)){
|
if(_3proxy_sem_init_timed(serviceexited, 0, 0x7fffffff)){
|
||||||
fprintf(stderr, "semaphore init failed\n");
|
fprintf(stderr, "semaphore init failed\n");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@ -529,13 +584,21 @@ int WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int
|
|||||||
#ifdef WITH_PCRE
|
#ifdef WITH_PCRE
|
||||||
pcre_install();
|
pcre_install();
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef WITH_TRANSPARENT
|
||||||
|
transparent_install();
|
||||||
|
#endif
|
||||||
|
#ifdef WITH_HTTPSRV
|
||||||
|
httpsrv_init();
|
||||||
|
#endif
|
||||||
|
|
||||||
freeconf(&conf);
|
freeconf(&conf);
|
||||||
initcommands();
|
initcommands();
|
||||||
res = readconfig(fp);
|
res = readconfig(fp);
|
||||||
conf.version++;
|
conf.version++;
|
||||||
|
|
||||||
if(res) RETURN(res);
|
/* readconfig() names the failing line, which an exit status would cut
|
||||||
|
to 8 bits: a failed configuration exits with 1 */
|
||||||
|
if(res) RETURN(1);
|
||||||
fclose(fp); fp = NULL;
|
fclose(fp); fp = NULL;
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
@ -560,7 +623,6 @@ int WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int
|
|||||||
signal(SIGCONT, mysigpause);
|
signal(SIGCONT, mysigpause);
|
||||||
signal(SIGTERM, mysigterm);
|
signal(SIGTERM, mysigterm);
|
||||||
signal(SIGUSR1, mysigusr1);
|
signal(SIGUSR1, mysigusr1);
|
||||||
signal(SIGPIPE, SIG_IGN);
|
|
||||||
cyclestep();
|
cyclestep();
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
@ -568,6 +630,6 @@ int WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int
|
|||||||
CLEARRETURN:
|
CLEARRETURN:
|
||||||
|
|
||||||
if(fp && fp != stdin) {fclose(fp); fp = NULL;}
|
if(fp && fp != stdin) {fclose(fp); fp = NULL;}
|
||||||
return 0;
|
return res;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
# 3 proxy common Makefile
|
# 3 proxy common Makefile
|
||||||
#
|
#
|
||||||
|
|
||||||
all: $(BUILDDIR)3proxy$(EXESUFFICS) $(BUILDDIR)$(CRYPT_PREFIX)crypt$(EXESUFFICS) $(BUILDDIR)$(PREFIX)pop3p$(EXESUFFICS) $(BUILDDIR)$(PREFIX)imapp$(EXESUFFICS) $(BUILDDIR)$(PREFIX)smtpp$(EXESUFFICS) $(BUILDDIR)$(PREFIX)ftppr$(EXESUFFICS) $(BUILDDIR)$(PREFIX)tcppm$(EXESUFFICS) $(BUILDDIR)$(PREFIX)udppm$(EXESUFFICS) $(BUILDDIR)$(PREFIX)tlspr$(EXESUFFICS) $(BUILDDIR)$(PREFIX)socks$(EXESUFFICS) $(BUILDDIR)$(PREFIX)proxy$(EXESUFFICS) allplugins
|
all: $(BUILDDIR)3proxy$(EXESUFFICS) $(BUILDDIR)$(CRYPT_PREFIX)crypt$(EXESUFFICS) $(MAIL_EXES) $(FTP_EXES) $(BUILDDIR)$(PREFIX)tcppm$(EXESUFFICS) $(BUILDDIR)$(PREFIX)udppm$(EXESUFFICS) $(BUILDDIR)$(PREFIX)tlspr$(EXESUFFICS) $(BUILDDIR)$(PREFIX)socks$(EXESUFFICS) $(BUILDDIR)$(PREFIX)proxy$(EXESUFFICS) allplugins
|
||||||
|
|
||||||
sockmap$(OBJSUFFICS): sockmap.c proxy.h structures.h
|
sockmap$(OBJSUFFICS): sockmap.c proxy.h structures.h
|
||||||
$(CC) $(CFLAGS) sockmap.c
|
$(CC) $(CFLAGS) sockmap.c
|
||||||
@ -13,6 +13,9 @@ udpsockmap$(OBJSUFFICS): udpsockmap.c proxy.h structures.h
|
|||||||
common$(OBJSUFFICS): common.c proxy.h structures.h
|
common$(OBJSUFFICS): common.c proxy.h structures.h
|
||||||
$(CC) $(CFLAGS) common.c
|
$(CC) $(CFLAGS) common.c
|
||||||
|
|
||||||
|
sem$(OBJSUFFICS): sem.c proxy.h structures.h
|
||||||
|
$(CC) $(CFLAGS) sem.c
|
||||||
|
|
||||||
plugins$(OBJSUFFICS): plugins.c proxy.h structures.h
|
plugins$(OBJSUFFICS): plugins.c proxy.h structures.h
|
||||||
$(CC) $(CFLAGS) plugins.c
|
$(CC) $(CFLAGS) plugins.c
|
||||||
|
|
||||||
@ -44,7 +47,7 @@ tcppm$(OBJSUFFICS): tcppm.c proxy.h structures.h proxymain.c
|
|||||||
$(CC) $(CFLAGS) $(DEFINEOPTION)WITHMAIN $(DEFINEOPTION)PORTMAP $(DEFINEOPTION)NOUDPMAIN tcppm.c
|
$(CC) $(CFLAGS) $(DEFINEOPTION)WITHMAIN $(DEFINEOPTION)PORTMAP $(DEFINEOPTION)NOUDPMAIN tcppm.c
|
||||||
|
|
||||||
udppm$(OBJSUFFICS): udppm.c proxy.h structures.h proxymain.c
|
udppm$(OBJSUFFICS): udppm.c proxy.h structures.h proxymain.c
|
||||||
$(CC) $(CFLAGS) $(DEFINEOPTION)WITHMAIN $(DEFINEOPTION)PORTMAP udppm.c
|
$(CC) $(CFLAGS) $(DEFINEOPTION)WITHMAIN $(DEFINEOPTION)PORTMAP $(DEFINEOPTION)NOTCPMAIN udppm.c
|
||||||
|
|
||||||
tlspr$(OBJSUFFICS): tlspr.c proxy.h structures.h proxymain.c
|
tlspr$(OBJSUFFICS): tlspr.c proxy.h structures.h proxymain.c
|
||||||
$(CC) $(CFLAGS) $(DEFINEOPTION)WITHMAIN $(DEFINEOPTION)PORTMAP $(DEFINEOPTION)NOUDPMAIN tlspr.c
|
$(CC) $(CFLAGS) $(DEFINEOPTION)WITHMAIN $(DEFINEOPTION)PORTMAP $(DEFINEOPTION)NOUDPMAIN tlspr.c
|
||||||
@ -56,32 +59,32 @@ socks$(OBJSUFFICS): socks.c proxy.h structures.h proxymain.c
|
|||||||
3proxy$(OBJSUFFICS): 3proxy.c proxy.h structures.h
|
3proxy$(OBJSUFFICS): 3proxy.c proxy.h structures.h
|
||||||
$(CC) $(CFLAGS) 3proxy.c
|
$(CC) $(CFLAGS) 3proxy.c
|
||||||
|
|
||||||
$(BUILDDIR)$(PREFIX)proxy$(EXESUFFICS): sockmap$(OBJSUFFICS) proxy$(OBJSUFFICS) sockgetchar$(OBJSUFFICS) common$(OBJSUFFICS) log$(OBJSUFFICS) base64$(OBJSUFFICS) ftp$(OBJSUFFICS) $(COMPATLIBS)
|
$(BUILDDIR)$(PREFIX)proxy$(EXESUFFICS): sockmap$(OBJSUFFICS) proxy$(OBJSUFFICS) sockgetchar$(OBJSUFFICS) common$(OBJSUFFICS) sem$(OBJSUFFICS) log$(OBJSUFFICS) base64$(OBJSUFFICS) ftp$(OBJSUFFICS) $(COMPATLIBS)
|
||||||
$(LN) $(LNOUT)$(BUILDDIR)$(PREFIX)proxy$(EXESUFFICS) $(LDFLAGS) sockmap$(OBJSUFFICS) proxy$(OBJSUFFICS) sockgetchar$(OBJSUFFICS) log$(OBJSUFFICS) common$(OBJSUFFICS) base64$(OBJSUFFICS) ftp$(OBJSUFFICS) $(COMPATLIBS) $(LIBS)
|
$(LN) $(LNOUT)$(BUILDDIR)$(PREFIX)proxy$(EXESUFFICS) $(LDFLAGS) sockmap$(OBJSUFFICS) proxy$(OBJSUFFICS) sockgetchar$(OBJSUFFICS) log$(OBJSUFFICS) common$(OBJSUFFICS) sem$(OBJSUFFICS) base64$(OBJSUFFICS) ftp$(OBJSUFFICS) $(COMPATLIBS) $(LIBS)
|
||||||
|
|
||||||
$(BUILDDIR)$(PREFIX)pop3p$(EXESUFFICS): sockmap$(OBJSUFFICS) pop3p$(OBJSUFFICS) sockgetchar$(OBJSUFFICS) common$(OBJSUFFICS) log$(OBJSUFFICS) $(COMPATLIBS)
|
$(BUILDDIR)$(PREFIX)pop3p$(EXESUFFICS): sockmap$(OBJSUFFICS) pop3p$(OBJSUFFICS) sockgetchar$(OBJSUFFICS) common$(OBJSUFFICS) sem$(OBJSUFFICS) log$(OBJSUFFICS) $(COMPATLIBS)
|
||||||
$(LN) $(LNOUT)$(BUILDDIR)$(PREFIX)pop3p$(EXESUFFICS) $(LDFLAGS) sockmap$(OBJSUFFICS) pop3p$(OBJSUFFICS) sockgetchar$(OBJSUFFICS) log$(OBJSUFFICS) common$(OBJSUFFICS) $(COMPATLIBS) $(LIBS)
|
$(LN) $(LNOUT)$(BUILDDIR)$(PREFIX)pop3p$(EXESUFFICS) $(LDFLAGS) sockmap$(OBJSUFFICS) pop3p$(OBJSUFFICS) sockgetchar$(OBJSUFFICS) log$(OBJSUFFICS) common$(OBJSUFFICS) sem$(OBJSUFFICS) $(COMPATLIBS) $(LIBS)
|
||||||
|
|
||||||
$(BUILDDIR)$(PREFIX)imapp$(EXESUFFICS): sockmap$(OBJSUFFICS) imapp$(OBJSUFFICS) sockgetchar$(OBJSUFFICS) common$(OBJSUFFICS) log$(OBJSUFFICS) base64$(OBJSUFFICS) $(COMPATLIBS)
|
$(BUILDDIR)$(PREFIX)imapp$(EXESUFFICS): sockmap$(OBJSUFFICS) imapp$(OBJSUFFICS) sockgetchar$(OBJSUFFICS) common$(OBJSUFFICS) sem$(OBJSUFFICS) log$(OBJSUFFICS) base64$(OBJSUFFICS) $(COMPATLIBS)
|
||||||
$(LN) $(LNOUT)$(BUILDDIR)$(PREFIX)imapp$(EXESUFFICS) $(LDFLAGS) sockmap$(OBJSUFFICS) imapp$(OBJSUFFICS) sockgetchar$(OBJSUFFICS) log$(OBJSUFFICS) common$(OBJSUFFICS) base64$(OBJSUFFICS) $(COMPATLIBS) $(LIBS)
|
$(LN) $(LNOUT)$(BUILDDIR)$(PREFIX)imapp$(EXESUFFICS) $(LDFLAGS) sockmap$(OBJSUFFICS) imapp$(OBJSUFFICS) sockgetchar$(OBJSUFFICS) log$(OBJSUFFICS) common$(OBJSUFFICS) sem$(OBJSUFFICS) base64$(OBJSUFFICS) $(COMPATLIBS) $(LIBS)
|
||||||
|
|
||||||
$(BUILDDIR)$(PREFIX)smtpp$(EXESUFFICS): sockmap$(OBJSUFFICS) smtpp$(OBJSUFFICS) sockgetchar$(OBJSUFFICS) common$(OBJSUFFICS) log$(OBJSUFFICS) base64$(OBJSUFFICS) $(COMPATLIBS)
|
$(BUILDDIR)$(PREFIX)smtpp$(EXESUFFICS): sockmap$(OBJSUFFICS) smtpp$(OBJSUFFICS) sockgetchar$(OBJSUFFICS) common$(OBJSUFFICS) sem$(OBJSUFFICS) log$(OBJSUFFICS) base64$(OBJSUFFICS) $(COMPATLIBS)
|
||||||
$(LN) $(LNOUT)$(BUILDDIR)$(PREFIX)smtpp$(EXESUFFICS) $(LDFLAGS) sockmap$(OBJSUFFICS) smtpp$(OBJSUFFICS) sockgetchar$(OBJSUFFICS) base64$(OBJSUFFICS) log$(OBJSUFFICS) common$(OBJSUFFICS) $(COMPATLIBS) $(LIBS)
|
$(LN) $(LNOUT)$(BUILDDIR)$(PREFIX)smtpp$(EXESUFFICS) $(LDFLAGS) sockmap$(OBJSUFFICS) smtpp$(OBJSUFFICS) sockgetchar$(OBJSUFFICS) base64$(OBJSUFFICS) log$(OBJSUFFICS) common$(OBJSUFFICS) sem$(OBJSUFFICS) $(COMPATLIBS) $(LIBS)
|
||||||
|
|
||||||
$(BUILDDIR)$(PREFIX)ftppr$(EXESUFFICS): sockmap$(OBJSUFFICS) ftppr$(OBJSUFFICS) ftp$(OBJSUFFICS) sockgetchar$(OBJSUFFICS) common$(OBJSUFFICS) log$(OBJSUFFICS) $(COMPATLIBS)
|
$(BUILDDIR)$(PREFIX)ftppr$(EXESUFFICS): sockmap$(OBJSUFFICS) ftppr$(OBJSUFFICS) ftp$(OBJSUFFICS) sockgetchar$(OBJSUFFICS) common$(OBJSUFFICS) sem$(OBJSUFFICS) log$(OBJSUFFICS) $(COMPATLIBS)
|
||||||
$(LN) $(LNOUT)$(BUILDDIR)$(PREFIX)ftppr$(EXESUFFICS) $(LDFLAGS) sockmap$(OBJSUFFICS) ftppr$(OBJSUFFICS) sockgetchar$(OBJSUFFICS) common$(OBJSUFFICS) log$(OBJSUFFICS) ftp$(OBJSUFFICS) $(COMPATLIBS) $(LIBS)
|
$(LN) $(LNOUT)$(BUILDDIR)$(PREFIX)ftppr$(EXESUFFICS) $(LDFLAGS) sockmap$(OBJSUFFICS) ftppr$(OBJSUFFICS) sockgetchar$(OBJSUFFICS) common$(OBJSUFFICS) sem$(OBJSUFFICS) log$(OBJSUFFICS) ftp$(OBJSUFFICS) $(COMPATLIBS) $(LIBS)
|
||||||
|
|
||||||
$(BUILDDIR)$(PREFIX)socks$(EXESUFFICS): sockmap$(OBJSUFFICS) udpsockmap$(OBJSUFFICS) socks$(OBJSUFFICS) sockgetchar$(OBJSUFFICS) common$(OBJSUFFICS) log$(OBJSUFFICS)
|
$(BUILDDIR)$(PREFIX)socks$(EXESUFFICS): sockmap$(OBJSUFFICS) udpsockmap$(OBJSUFFICS) socks$(OBJSUFFICS) sockgetchar$(OBJSUFFICS) common$(OBJSUFFICS) sem$(OBJSUFFICS) log$(OBJSUFFICS)
|
||||||
$(LN) $(LNOUT)$(BUILDDIR)$(PREFIX)socks$(EXESUFFICS) $(LDFLAGS) sockmap$(OBJSUFFICS) udpsockmap$(OBJSUFFICS) socks$(OBJSUFFICS) sockgetchar$(OBJSUFFICS) log$(OBJSUFFICS) common$(OBJSUFFICS) $(LIBS)
|
$(LN) $(LNOUT)$(BUILDDIR)$(PREFIX)socks$(EXESUFFICS) $(LDFLAGS) sockmap$(OBJSUFFICS) udpsockmap$(OBJSUFFICS) socks$(OBJSUFFICS) sockgetchar$(OBJSUFFICS) log$(OBJSUFFICS) common$(OBJSUFFICS) sem$(OBJSUFFICS) $(LIBS)
|
||||||
|
|
||||||
$(BUILDDIR)$(PREFIX)tcppm$(EXESUFFICS): sockmap$(OBJSUFFICS) sockgetchar$(OBJSUFFICS) tcppm$(OBJSUFFICS) common$(OBJSUFFICS) log$(OBJSUFFICS)
|
$(BUILDDIR)$(PREFIX)tcppm$(EXESUFFICS): sockmap$(OBJSUFFICS) sockgetchar$(OBJSUFFICS) tcppm$(OBJSUFFICS) common$(OBJSUFFICS) sem$(OBJSUFFICS) log$(OBJSUFFICS)
|
||||||
$(LN) $(LNOUT)$(BUILDDIR)$(PREFIX)tcppm$(EXESUFFICS) $(LDFLAGS) sockmap$(OBJSUFFICS) sockgetchar$(OBJSUFFICS) tcppm$(OBJSUFFICS) log$(OBJSUFFICS) common$(OBJSUFFICS) $(LIBS)
|
$(LN) $(LNOUT)$(BUILDDIR)$(PREFIX)tcppm$(EXESUFFICS) $(LDFLAGS) sockmap$(OBJSUFFICS) sockgetchar$(OBJSUFFICS) tcppm$(OBJSUFFICS) log$(OBJSUFFICS) common$(OBJSUFFICS) sem$(OBJSUFFICS) $(LIBS)
|
||||||
|
|
||||||
$(BUILDDIR)$(PREFIX)udppm$(EXESUFFICS): sockmap$(OBJSUFFICS) udpsockmap$(OBJSUFFICS) sockgetchar$(OBJSUFFICS) udppm$(OBJSUFFICS) common$(OBJSUFFICS) log$(OBJSUFFICS) hash$(OBJSUFFICS)
|
$(BUILDDIR)$(PREFIX)udppm$(EXESUFFICS): sockmap$(OBJSUFFICS) udpsockmap$(OBJSUFFICS) sockgetchar$(OBJSUFFICS) udppm$(OBJSUFFICS) common$(OBJSUFFICS) sem$(OBJSUFFICS) log$(OBJSUFFICS) hash$(OBJSUFFICS)
|
||||||
$(LN) $(LNOUT)$(BUILDDIR)$(PREFIX)udppm$(EXESUFFICS) $(LDFLAGS) sockmap$(OBJSUFFICS) udpsockmap$(OBJSUFFICS) sockgetchar$(OBJSUFFICS) udppm$(OBJSUFFICS) log$(OBJSUFFICS) common$(OBJSUFFICS) hash$(OBJSUFFICS) $(LIBS)
|
$(LN) $(LNOUT)$(BUILDDIR)$(PREFIX)udppm$(EXESUFFICS) $(LDFLAGS) sockmap$(OBJSUFFICS) udpsockmap$(OBJSUFFICS) sockgetchar$(OBJSUFFICS) udppm$(OBJSUFFICS) log$(OBJSUFFICS) common$(OBJSUFFICS) sem$(OBJSUFFICS) hash$(OBJSUFFICS) $(LIBS)
|
||||||
|
|
||||||
$(BUILDDIR)$(PREFIX)tlspr$(EXESUFFICS): sockmap$(OBJSUFFICS) sockgetchar$(OBJSUFFICS) tlspr$(OBJSUFFICS) common$(OBJSUFFICS) log$(OBJSUFFICS)
|
$(BUILDDIR)$(PREFIX)tlspr$(EXESUFFICS): sockmap$(OBJSUFFICS) sockgetchar$(OBJSUFFICS) tlspr$(OBJSUFFICS) common$(OBJSUFFICS) sem$(OBJSUFFICS) log$(OBJSUFFICS)
|
||||||
$(LN) $(LNOUT)$(BUILDDIR)$(PREFIX)tlspr$(EXESUFFICS) $(LDFLAGS) sockmap$(OBJSUFFICS) sockgetchar$(OBJSUFFICS) tlspr$(OBJSUFFICS) log$(OBJSUFFICS) common$(OBJSUFFICS) $(LIBS)
|
$(LN) $(LNOUT)$(BUILDDIR)$(PREFIX)tlspr$(EXESUFFICS) $(LDFLAGS) sockmap$(OBJSUFFICS) sockgetchar$(OBJSUFFICS) tlspr$(OBJSUFFICS) log$(OBJSUFFICS) common$(OBJSUFFICS) sem$(OBJSUFFICS) $(LIBS)
|
||||||
|
|
||||||
mainfunc$(OBJSUFFICS): proxy.h structures.h proxymain.c
|
mainfunc$(OBJSUFFICS): proxy.h structures.h proxymain.c
|
||||||
$(CC) $(COUT)mainfunc$(OBJSUFFICS) $(CFLAGS) $(DEFINEOPTION)MODULEMAINFUNC=mainfunc proxymain.c
|
$(CC) $(COUT)mainfunc$(OBJSUFFICS) $(CFLAGS) $(DEFINEOPTION)MODULEMAINFUNC=mainfunc proxymain.c
|
||||||
@ -116,6 +119,9 @@ srvsocks$(OBJSUFFICS): socks.c proxy.h structures.h
|
|||||||
srvwebadmin$(OBJSUFFICS): webadmin.c proxy.h structures.h
|
srvwebadmin$(OBJSUFFICS): webadmin.c proxy.h structures.h
|
||||||
$(CC) $(COUT)srvwebadmin$(OBJSUFFICS) $(CFLAGS) webadmin.c
|
$(CC) $(COUT)srvwebadmin$(OBJSUFFICS) $(CFLAGS) webadmin.c
|
||||||
|
|
||||||
|
transparent$(OBJSUFFICS): transparent.c proxy.h structures.h
|
||||||
|
$(CC) $(COUT)transparent$(OBJSUFFICS) $(CFLAGS) transparent.c
|
||||||
|
|
||||||
srvhttpsrv$(OBJSUFFICS): httpsrv.c proxy.h structures.h
|
srvhttpsrv$(OBJSUFFICS): httpsrv.c proxy.h structures.h
|
||||||
$(CC) $(COUT)srvhttpsrv$(OBJSUFFICS) $(CFLAGS) httpsrv.c
|
$(CC) $(COUT)srvhttpsrv$(OBJSUFFICS) $(CFLAGS) httpsrv.c
|
||||||
|
|
||||||
@ -191,6 +197,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) sem$(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) $(TRANSPARENT_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) sem$(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) $(TRANSPARENT_OBJS) $(COMPATLIBS) $(LIBS) $(PCRE_LIBS)
|
||||||
|
|
||||||
|
|||||||
241
src/acl.c
241
src/acl.c
@ -8,6 +8,247 @@
|
|||||||
|
|
||||||
#include "proxy.h"
|
#include "proxy.h"
|
||||||
|
|
||||||
|
/* The pattern engine lives here rather than in common.c: common.c is linked
|
||||||
|
into the standalone binaries as well, and those carry neither the regular
|
||||||
|
expression code this calls nor a use for a host pattern. */
|
||||||
|
/* 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.
|
||||||
|
*/
|
||||||
|
/* A pattern written as a regular expression carries a prefix. Both spellings
|
||||||
|
are taken so a configuration reads the way its author thinks of it. */
|
||||||
|
static unsigned char * regexprefix(unsigned char *arg)
|
||||||
|
{
|
||||||
|
if(!strncmp((char *)arg, "pcre:", 5)) return arg + 5;
|
||||||
|
if(!strncmp((char *)arg, "regex:", 6)) return arg + 6;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Shared by every pattern the configuration can carry. Returns 0 on success. */
|
||||||
|
static int compileregex(struct hostname *h, unsigned char *pattern)
|
||||||
|
{
|
||||||
|
#ifdef WITH_PCRE
|
||||||
|
char err[256];
|
||||||
|
|
||||||
|
h->re = pcre_pattern_compile(pattern, err, sizeof(err));
|
||||||
|
if(!h->re){
|
||||||
|
fprintf(stderr, "Bad regular expression '%s': %s\n", pattern, err);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
h->matchtype = MATCHREGEX;
|
||||||
|
h->name = (unsigned char *)strdup((char *)pattern);
|
||||||
|
return h->name? 0 : 1;
|
||||||
|
#else
|
||||||
|
fprintf(stderr, "Regular expression '%s' needs a build with PCRE\n", pattern);
|
||||||
|
return 1;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
int parsepattern(struct hostname *h, unsigned char *arg)
|
||||||
|
{
|
||||||
|
int arglen;
|
||||||
|
unsigned char *pattern;
|
||||||
|
|
||||||
|
h->re = NULL;
|
||||||
|
if((pattern = regexprefix(arg))) return compileregex(h, 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;
|
||||||
|
if(h->matchtype == MATCHREGEX || h->matchtype == MATCHGLOB){
|
||||||
|
struct capture caps[MAXCAPTURES];
|
||||||
|
|
||||||
|
if(!patternmatchcaps(h, str, caps, NULL)) return 0;
|
||||||
|
if(start) *start = caps[1].start;
|
||||||
|
if(len) *len = caps[1].len;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 patternmatchcaps(h, str, NULL, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Match a glob, recording what each star stood for.
|
||||||
|
|
||||||
|
A single star stands for any run of characters within one element of the
|
||||||
|
path, so it stops at a slash; a double star crosses them. Stars are
|
||||||
|
numbered in the order they appear, which is how a template refers to them.
|
||||||
|
*/
|
||||||
|
static int globmatch(const unsigned char *pat, const unsigned char *str,
|
||||||
|
const unsigned char *subject, struct capture *caps, int maxcaps, int star)
|
||||||
|
{
|
||||||
|
while(*pat){
|
||||||
|
if(*pat == '*'){
|
||||||
|
int crosses = (pat[1] == '*');
|
||||||
|
const unsigned char *rest = pat + (crosses? 2 : 1);
|
||||||
|
int len;
|
||||||
|
|
||||||
|
for(len = 0; ; len++){
|
||||||
|
if(star < maxcaps && caps){
|
||||||
|
caps[star].start = (int)(str - subject);
|
||||||
|
caps[star].len = len;
|
||||||
|
}
|
||||||
|
if(globmatch(rest, str + len, subject, caps, maxcaps, star + 1)) return 1;
|
||||||
|
if(!str[len]) return 0;
|
||||||
|
if(!crosses && str[len] == '/') return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(*pat != *str) return 0;
|
||||||
|
pat++;
|
||||||
|
str++;
|
||||||
|
}
|
||||||
|
return *str == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Match a pattern of any kind and report what its stars or groups stood for.
|
||||||
|
caps may be NULL when only the yes or no answer is wanted. */
|
||||||
|
int patternmatchcaps(const struct hostname *h, const unsigned char *str,
|
||||||
|
struct capture *caps, int *ncaps)
|
||||||
|
{
|
||||||
|
int n = 0;
|
||||||
|
|
||||||
|
if(ncaps) *ncaps = 0;
|
||||||
|
if(!h || !str) return 0;
|
||||||
|
|
||||||
|
if(h->matchtype == MATCHREGEX){
|
||||||
|
#ifdef WITH_PCRE
|
||||||
|
struct capture local[MAXCAPTURES];
|
||||||
|
|
||||||
|
n = pcre_pattern_match(h->re, str, caps? caps : local, MAXCAPTURES);
|
||||||
|
if(ncaps) *ncaps = n;
|
||||||
|
return n > 0;
|
||||||
|
#else
|
||||||
|
return 0;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
if(h->matchtype == MATCHGLOB){
|
||||||
|
struct capture local[MAXCAPTURES];
|
||||||
|
struct capture *use = caps? caps : local;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for(i = 0; i < MAXCAPTURES; i++){
|
||||||
|
use[i].start = 0;
|
||||||
|
use[i].len = 0;
|
||||||
|
}
|
||||||
|
use[0].start = 0;
|
||||||
|
use[0].len = (int)strlen((char *)str);
|
||||||
|
if(!h->name) return 0;
|
||||||
|
if(!globmatch(h->name, str, str, use, MAXCAPTURES, 1)) return 0;
|
||||||
|
if(ncaps){
|
||||||
|
for(n = MAXCAPTURES - 1; n > 0 && !use[n].len && !use[n].start; n--);
|
||||||
|
*ncaps = n + 1;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* the star at one end or both, as an access rule has always written it */
|
||||||
|
if(caps){
|
||||||
|
int start = 0, len = 0;
|
||||||
|
|
||||||
|
if(!patternmatchpos(h, str, &start, &len)) return 0;
|
||||||
|
caps[0].start = 0;
|
||||||
|
caps[0].len = (int)strlen((char *)str);
|
||||||
|
caps[1].start = start;
|
||||||
|
caps[1].len = len;
|
||||||
|
if(ncaps) *ncaps = 2;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return patternmatchpos(h, str, NULL, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* A URL in an http rule: stars anywhere, or a regular expression. */
|
||||||
|
int parsepathpattern(struct hostname *h, unsigned char *arg)
|
||||||
|
{
|
||||||
|
unsigned char *pattern;
|
||||||
|
|
||||||
|
h->re = NULL;
|
||||||
|
if((pattern = regexprefix(arg))) return compileregex(h, pattern);
|
||||||
|
|
||||||
|
h->matchtype = MATCHGLOB;
|
||||||
|
h->name = (unsigned char *)strdup((char *)arg);
|
||||||
|
return h->name? 0 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
int IPInentry(struct sockaddr *sa, struct iplist *ipentry){
|
int IPInentry(struct sockaddr *sa, struct iplist *ipentry){
|
||||||
int addrlen;
|
int addrlen;
|
||||||
unsigned char *ip, *ipf, *ipt;
|
unsigned char *ip, *ipf, *ipt;
|
||||||
|
|||||||
@ -20,8 +20,9 @@ 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
|
#ifdef WITH_HTTPSRV
|
||||||
/* The http server answers the request itself, so authorization must not
|
/* The http server answers the request itself, so authorization must not
|
||||||
try to reach a destination that does not exist. */
|
try to reach a destination that does not exist. A request it has handed
|
||||||
res = (param->srv->service == S_HTTPSRV)? 0 : doconnect(param);
|
to another child does have one, and that child needs it opened. */
|
||||||
|
res = (param->srv->service == S_HTTPSRV && !param->onerequest)? 0 : doconnect(param);
|
||||||
#else
|
#else
|
||||||
res = doconnect(param);
|
res = doconnect(param);
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -198,12 +198,20 @@ static int calc_replydigest(char *packet, char *original, const char *secret, in
|
|||||||
int secretlen;
|
int secretlen;
|
||||||
uint8_t calc_digest[AUTH_VECTOR_LEN];
|
uint8_t calc_digest[AUTH_VECTOR_LEN];
|
||||||
uint8_t calc_vector[AUTH_VECTOR_LEN];
|
uint8_t calc_vector[AUTH_VECTOR_LEN];
|
||||||
|
unsigned int dlen = AUTH_VECTOR_LEN;
|
||||||
|
mdh_ctx *ctx;
|
||||||
|
|
||||||
memcpy(calc_vector, packet + 4, AUTH_VECTOR_LEN);
|
memcpy(calc_vector, packet + 4, AUTH_VECTOR_LEN);
|
||||||
memcpy(packet + 4, original, AUTH_VECTOR_LEN);
|
memcpy(packet + 4, original, AUTH_VECTOR_LEN);
|
||||||
secretlen = strlen(secret);
|
secretlen = strlen(secret);
|
||||||
memcpy(packet + len, secret, secretlen);
|
/* hash the reply, then the secret: nothing is written past the reply */
|
||||||
md5_calc(calc_digest, (u_char *)packet, len + secretlen);
|
if(!(ctx = mdh_init(MDH_MD5, 16))) return 2;
|
||||||
|
if(!mdh_update(ctx, packet, len) || !mdh_update(ctx, secret, secretlen) ||
|
||||||
|
!mdh_final(ctx, calc_digest, &dlen)) {
|
||||||
|
mdh_free(ctx);
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
mdh_free(ctx);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Return 0 if OK, 2 if not OK.
|
* Return 0 if OK, 2 if not OK.
|
||||||
@ -298,6 +306,7 @@ int radsend(struct clientparam * param, int auth, int stop){
|
|||||||
int total_length;
|
int total_length;
|
||||||
int len;
|
int len;
|
||||||
PROXYSOCKADDRTYPE saremote;
|
PROXYSOCKADDRTYPE saremote;
|
||||||
|
PROXYSOCKADDRTYPE safrom;
|
||||||
struct pollfd fds[1];
|
struct pollfd fds[1];
|
||||||
char vector[AUTH_VECTOR_LEN];
|
char vector[AUTH_VECTOR_LEN];
|
||||||
radius_packet_t packet, rpacket;
|
radius_packet_t packet, rpacket;
|
||||||
@ -568,15 +577,25 @@ int radsend(struct clientparam * param, int auth, int stop){
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
salen = sizeof(saremote);
|
salen = sizeof(safrom);
|
||||||
|
|
||||||
data_len = so._recvfrom(so.state, remsock, (char *)&rpacket, sizeof(packet)-16,
|
data_len = so._recvfrom(so.state, remsock, (char *)&rpacket, sizeof(packet)-16,
|
||||||
0, (struct sockaddr *)&saremote, &salen);
|
0, (struct sockaddr *)&safrom, &salen);
|
||||||
|
|
||||||
|
|
||||||
if (data_len < 20) {
|
if (data_len < 20) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
/* the reply must come from the server we asked */
|
||||||
|
if(*SAFAMILY(&safrom) != *SAFAMILY(&saremote) ||
|
||||||
|
memcmp(SAADDR(&safrom), SAADDR(&saremote), SAADDRLEN(&saremote)) ||
|
||||||
|
*SAPORT(&safrom) != *SAPORT(&saremote)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
/* and answer this request, and be of its own length */
|
||||||
|
if(data_len != ntohs(rpacket.length) || rpacket.id != packet.id) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if( auth && rpacket.code != PW_AUTHENTICATION_ACK &&
|
if( auth && rpacket.code != PW_AUTHENTICATION_ACK &&
|
||||||
rpacket.code != PW_AUTHENTICATION_REJECT ){
|
rpacket.code != PW_AUTHENTICATION_REJECT ){
|
||||||
|
|||||||
277
src/common.c
277
src/common.c
@ -187,33 +187,6 @@ size_t threadstacksize(int 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
int _3proxy_sem_init_f(_3proxy_sem_t *sem, unsigned count, unsigned maxcount){
|
|
||||||
sem->count = count;
|
|
||||||
sem->maxcount = maxcount;
|
|
||||||
if(pthread_mutex_init(&sem->mutex, NULL)) return 1;
|
|
||||||
if(pthread_cond_init(&sem->cond, NULL)){
|
|
||||||
pthread_mutex_destroy(&sem->mutex);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void _3proxy_sem_lock_f(_3proxy_sem_t *sem){
|
|
||||||
pthread_mutex_lock(&sem->mutex);
|
|
||||||
while(!sem->count) pthread_cond_wait(&sem->cond, &sem->mutex);
|
|
||||||
sem->count--;
|
|
||||||
pthread_mutex_unlock(&sem->mutex);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _3proxy_sem_unlock_f(_3proxy_sem_t *sem){
|
|
||||||
pthread_mutex_lock(&sem->mutex);
|
|
||||||
if(sem->count < sem->maxcount){
|
|
||||||
sem->count++;
|
|
||||||
pthread_cond_signal(&sem->cond);
|
|
||||||
}
|
|
||||||
pthread_mutex_unlock(&sem->mutex);
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
struct extparam conf = {
|
struct extparam conf = {
|
||||||
@ -414,7 +387,6 @@ int
|
|||||||
return shutdown(s, how);
|
return shutdown(s, how);
|
||||||
}
|
}
|
||||||
int WINAPI def_closesocket(void* state, SOCKET s){
|
int WINAPI def_closesocket(void* state, SOCKET s){
|
||||||
usleep(SLEEPTIME);
|
|
||||||
return closesocket(s);
|
return closesocket(s);
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
@ -454,11 +426,18 @@ int
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* a peer gone is an error of the send, not SIGPIPE, where the system can
|
||||||
|
say so for a call: SIGPIPE is ignored too, for TLS and splice() */
|
||||||
|
#ifdef MSG_NOSIGNAL
|
||||||
|
#define SENDFLAGS MSG_NOSIGNAL
|
||||||
|
#else
|
||||||
|
#define SENDFLAGS 0
|
||||||
|
#endif
|
||||||
ssize_t def_send(void* state, SOCKET s, const void *msg, size_t len, int flags){
|
ssize_t def_send(void* state, SOCKET s, const void *msg, size_t len, int flags){
|
||||||
return send(s, msg, len, flags);
|
return send(s, msg, len, flags | SENDFLAGS);
|
||||||
}
|
}
|
||||||
ssize_t def_sendto(void* state, SOCKET s, const void *msg, size_t len, int flags, const struct sockaddr *to, socklen_t tolen){
|
ssize_t def_sendto(void* state, SOCKET s, const void *msg, size_t len, int flags, const struct sockaddr *to, socklen_t tolen){
|
||||||
return sendto(s, msg, len, flags, to, tolen);
|
return sendto(s, msg, len, flags | SENDFLAGS, to, tolen);
|
||||||
}
|
}
|
||||||
ssize_t def_recv(void* state, SOCKET s, void *buf, size_t len, int flags){
|
ssize_t def_recv(void* state, SOCKET s, void *buf, size_t len, int flags){
|
||||||
return recv(s, buf, len, flags);
|
return recv(s, buf, len, flags);
|
||||||
@ -655,6 +634,26 @@ int parseconnusername(char *username, struct clientparam *param, int extpasswd,
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* What waits for a socket waits in poll(): a call on it never does. */
|
||||||
|
void nonblock(SOCKET sock){
|
||||||
|
#ifdef _WIN32
|
||||||
|
unsigned long ul = 1;
|
||||||
|
ioctlsocket(sock, FIONBIO, &ul);
|
||||||
|
#else
|
||||||
|
fcntl(sock, F_SETFL, O_NONBLOCK | fcntl(sock, F_GETFL));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/* accept() with nothing to accept: another took it, or it was gone */
|
||||||
|
int acceptagain(void){
|
||||||
|
#ifdef _WIN32
|
||||||
|
int err = WSAGetLastError();
|
||||||
|
return err == WSAEWOULDBLOCK || err == WSAECONNRESET || err == WSAEINTR;
|
||||||
|
#else
|
||||||
|
return errno == EAGAIN || errno == EWOULDBLOCK || errno == ECONNABORTED || errno == EINTR;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
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){
|
||||||
struct pollfd fds[1];
|
struct pollfd fds[1];
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
@ -677,6 +676,55 @@ int connectwithpoll(struct clientparam *param, SOCKET sock, struct sockaddr *sa,
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* 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
|
||||||
|
|
||||||
|
static int bindrange(struct clientparam *param, SOCKET sock, PROXYSOCKADDRTYPE *sa, uint32_t range, int *tries);
|
||||||
|
|
||||||
|
/* -Di / -De: ties a socket to the interface asked for, for TCP and UDP alike.
|
||||||
|
Where the option is set per protocol, family is the one of the address the
|
||||||
|
socket will be bound to. */
|
||||||
|
int bindtodevice(struct sockfuncs *sf, void *state, SOCKET sock, const char *device, int family){
|
||||||
|
if(!device) return 0;
|
||||||
|
#if defined SO_BINDTODEVICE
|
||||||
|
if(sf->_setsockopt(state, sock, SOL_SOCKET, SO_BINDTODEVICE, device, strlen(device) + 1))
|
||||||
|
return 1;
|
||||||
|
#elif defined IP_BOUND_IF
|
||||||
|
{
|
||||||
|
int idx;
|
||||||
|
idx = if_nametoindex(device);
|
||||||
|
if(!idx || (family == AF_INET && sf->_setsockopt(state, sock, IPPROTO_IP, IP_BOUND_IF, &idx, sizeof(idx))))
|
||||||
|
return 1;
|
||||||
|
#ifndef NOIPV6
|
||||||
|
#ifndef IPV6_BOUND_IF
|
||||||
|
#define IPV6_BOUND_IF 125
|
||||||
|
#endif
|
||||||
|
if(family == AF_INET6 && sf->_setsockopt(state, sock, IPPROTO_IPV6, IPV6_BOUND_IF, &idx, sizeof(idx))) return 1;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* the external side, a socket to be bound to sinsl */
|
||||||
|
int extbindtodevice(struct clientparam *param, SOCKET sock){
|
||||||
|
#if defined SO_BINDTODEVICE || defined IP_BOUND_IF
|
||||||
|
return bindtodevice(¶m->srv->so, param->sostate, sock, param->srv->obindtodevice, *SAFAMILY(¶m->sinsl));
|
||||||
|
#else
|
||||||
|
return 0;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/* the internal side, a socket to be bound to sincl */
|
||||||
|
int intbindtodevice(struct clientparam *param, SOCKET sock){
|
||||||
|
#if defined SO_BINDTODEVICE || defined IP_BOUND_IF
|
||||||
|
return bindtodevice(¶m->srv->so, param->sostate, sock, param->srv->ibindtodevice, *SAFAMILY(¶m->sincl));
|
||||||
|
#else
|
||||||
|
return 0;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
int doconnect(struct clientparam * param){
|
int doconnect(struct clientparam * param){
|
||||||
SASIZETYPE size;
|
SASIZETYPE size;
|
||||||
|
|
||||||
@ -693,7 +741,10 @@ int doconnect(struct clientparam * param){
|
|||||||
if(param->srv->so._getpeername(param->sostate, param->remsock, (struct sockaddr *)¶m->sinsr, &size)==-1) {return (14);}
|
if(param->srv->so._getpeername(param->sostate, param->remsock, (struct sockaddr *)¶m->sinsr, &size)==-1) {return (14);}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
#ifndef _WIN32
|
||||||
struct linger lg = {1,conf.timeouts[LINGER_TO]};
|
struct linger lg = {1,conf.timeouts[LINGER_TO]};
|
||||||
|
#endif
|
||||||
|
int tries = RANGETRIES;
|
||||||
|
|
||||||
if(SAISNULL(¶m->sinsr)){
|
if(SAISNULL(¶m->sinsr)){
|
||||||
if(SAISNULL(¶m->req)) {
|
if(SAISNULL(¶m->req)) {
|
||||||
@ -703,6 +754,7 @@ int doconnect(struct clientparam * param){
|
|||||||
memcpy(SAADDR(¶m->sinsr), SAADDR(¶m->req), SAADDRLEN(¶m->req));
|
memcpy(SAADDR(¶m->sinsr), SAADDR(¶m->req), SAADDRLEN(¶m->req));
|
||||||
}
|
}
|
||||||
if(!*SAPORT(¶m->sinsr))*SAPORT(¶m->sinsr) = *SAPORT(¶m->req);
|
if(!*SAPORT(¶m->sinsr))*SAPORT(¶m->sinsr) = *SAPORT(¶m->req);
|
||||||
|
for(;;){
|
||||||
if ((param->remsock=param->srv->so._socket(param->sostate, SASOCK(¶m->sinsr), SOCK_STREAM,
|
if ((param->remsock=param->srv->so._socket(param->sostate, SASOCK(¶m->sinsr), SOCK_STREAM,
|
||||||
#ifdef WITH_UN
|
#ifdef WITH_UN
|
||||||
*SAFAMILY(¶m->sinsr) == AF_UNIX? 0 :
|
*SAFAMILY(¶m->sinsr) == AF_UNIX? 0 :
|
||||||
@ -721,39 +773,50 @@ int doconnect(struct clientparam * param){
|
|||||||
param->sinsl = param->srv->extsa;
|
param->sinsl = param->srv->extsa;
|
||||||
}
|
}
|
||||||
*SAPORT(¶m->sinsl) = 0;
|
*SAPORT(¶m->sinsl) = 0;
|
||||||
setopts(param->remsock, param->srv->srvsockopts);
|
setopts(param->remsock, ¶m->srv->srvsockopts);
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
|
/* not on Windows, see the accepting side in proxymain.c */
|
||||||
param->srv->so._setsockopt(param->sostate, param->remsock, SOL_SOCKET, SO_LINGER, (char *)&lg, sizeof(lg));
|
param->srv->so._setsockopt(param->sostate, param->remsock, SOL_SOCKET, SO_LINGER, (char *)&lg, sizeof(lg));
|
||||||
#if defined SO_BINDTODEVICE
|
|
||||||
if(param->srv->obindtodevice) {
|
|
||||||
if(param->srv->so._setsockopt(param->sostate, param->remsock, SOL_SOCKET, SO_BINDTODEVICE, param->srv->obindtodevice, strlen(param->srv->obindtodevice) + 1))
|
|
||||||
return 12;
|
|
||||||
}
|
|
||||||
#elif defined IP_BOUND_IF
|
|
||||||
if(param->srv->obindtodevice) {
|
|
||||||
int idx;
|
|
||||||
idx = if_nametoindex(param->srv->obindtodevice);
|
|
||||||
if(!idx || (*SAFAMILY(¶m->sinsl) == AF_INET && param->srv->so._setsockopt(param->sostate, param->remsock, IPPROTO_IP, IP_BOUND_IF, &idx, sizeof(idx))))
|
|
||||||
return 12;
|
|
||||||
#ifndef NOIPV6
|
|
||||||
#ifndef IPV6_BOUND_IF
|
|
||||||
#define IPV6_BOUND_IF 125
|
|
||||||
#endif
|
|
||||||
if(*SAFAMILY(¶m->sinsl) == AF_INET6 && param->srv->so._setsockopt(param->sostate, param->remsock, IPPROTO_IPV6, IPV6_BOUND_IF, &idx, sizeof(idx))) return 12;
|
|
||||||
#endif
|
#endif
|
||||||
|
if(extbindtodevice(param, param->remsock)) return 12;
|
||||||
|
#ifdef IP_BIND_ADDRESS_NO_PORT
|
||||||
|
/* A port the kernel chooses is chosen by connect(), for the pair with
|
||||||
|
the destination: one in use towards another destination, or closing
|
||||||
|
from an earlier connection, does as well. bind() would take one of
|
||||||
|
its own, and with many connections find none left, or search long
|
||||||
|
for it. */
|
||||||
|
if((param->operation >= 256 || (param->operation & CONNECT) || param->redirected)
|
||||||
|
# ifdef WITH_UN
|
||||||
|
&& *SAFAMILY(¶m->sinsl) != AF_UNIX
|
||||||
|
# endif
|
||||||
|
){
|
||||||
|
int one = 1;
|
||||||
|
|
||||||
|
param->srv->so._setsockopt(param->sostate, param->remsock, IPPROTO_IP, IP_BIND_ADDRESS_NO_PORT, (char *)&one, sizeof(one));
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#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(bindrange(param, param->remsock, ¶m->sinsl, param->extport, &tries)==-1) {
|
||||||
return 12;
|
return 12;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(param->operation >= 256 || (param->operation & CONNECT) || param->redirected){
|
if(!(param->operation >= 256 || (param->operation & CONNECT) || param->redirected)) break;
|
||||||
if(connectwithpoll(param, param->remsock,(struct sockaddr *)¶m->sinsr,SASIZE(¶m->sinsr),conf.timeouts[CONNECT_TO])) {
|
if(!connectwithpoll(param, param->remsock,(struct sockaddr *)¶m->sinsr,SASIZE(¶m->sinsr),conf.timeouts[CONNECT_TO])) break;
|
||||||
return 13;
|
/* The port was free to bind, but the pair it makes with the destination
|
||||||
}
|
is still closing from an earlier connection through the same range,
|
||||||
|
which Windows refuses. Another port out of the range may do. Anything
|
||||||
|
else is the destination's own answer, and is not worth a second port.
|
||||||
|
The ports come out of the allowance the bind is already spending, so
|
||||||
|
the range is not searched any harder than one bind would search it and
|
||||||
|
the attempts parentretries makes are not multiplied. Counted here as
|
||||||
|
well, since the kernel option hands out a port without spending any of
|
||||||
|
the allowance. */
|
||||||
|
if(!param->extport || errno != EADDRINUSE || --tries <= 0) return 13;
|
||||||
|
param->srv->so._closesocket(param->sostate, param->remsock);
|
||||||
|
param->remsock = INVALID_SOCKET;
|
||||||
}
|
}
|
||||||
size = sizeof(param->sinsl);
|
size = sizeof(param->sinsl);
|
||||||
if(param->srv->so._getsockname(param->sostate, param->remsock, (struct sockaddr *)¶m->sinsl, &size)==-1) {return (15);}
|
if(param->srv->so._getsockname(param->sostate, param->remsock, (struct sockaddr *)¶m->sinsl, &size)==-1) {return (15);}
|
||||||
@ -767,10 +830,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
|
/* Bind sock to sa, taking the local port from the range if one is set. The
|
||||||
* range is packed as first | last << 16.
|
* range is packed as first | last << 16.
|
||||||
*
|
*
|
||||||
@ -778,11 +837,15 @@ int doconnect(struct clientparam * param){
|
|||||||
* are free. Where the option does not exist, or the kernel refuses it, or the
|
* 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
|
* 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.
|
* on failure, since the one picked may already be taken.
|
||||||
|
*
|
||||||
|
* *tries is how many ports may still be taken out of the range. A caller which
|
||||||
|
* has to come back for another port, because the one it was given turned out
|
||||||
|
* to be unusable for the connection it wanted, carries the same count along
|
||||||
|
* and so cannot spend more attempts than one call would.
|
||||||
*/
|
*/
|
||||||
int bindwithrange(struct clientparam *param, SOCKET sock, PROXYSOCKADDRTYPE *sa, uint32_t range)
|
static int bindrange(struct clientparam *param, SOCKET sock, PROXYSOCKADDRTYPE *sa, uint32_t range, int *tries)
|
||||||
{
|
{
|
||||||
uint16_t first, last;
|
uint16_t first, last;
|
||||||
int i;
|
|
||||||
|
|
||||||
if(!range) return param->srv->so._bind(param->sostate, sock, (struct sockaddr *)sa, SASIZE(sa));
|
if(!range) return param->srv->so._bind(param->sostate, sock, (struct sockaddr *)sa, SASIZE(sa));
|
||||||
|
|
||||||
@ -798,7 +861,8 @@ int bindwithrange(struct clientparam *param, SOCKET sock, PROXYSOCKADDRTYPE *sa,
|
|||||||
first = (uint16_t)(range & 0xffff);
|
first = (uint16_t)(range & 0xffff);
|
||||||
last = (uint16_t)(range >> 16);
|
last = (uint16_t)(range >> 16);
|
||||||
|
|
||||||
for(i = 0; i < RANGETRIES; i++){
|
while(*tries > 0){
|
||||||
|
(*tries)--;
|
||||||
*SAPORT(sa) = htons((uint16_t)(first + (myrand() % (unsigned)(last - first + 1))));
|
*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;
|
if(!param->srv->so._bind(param->sostate, sock, (struct sockaddr *)sa, SASIZE(sa))) return 0;
|
||||||
}
|
}
|
||||||
@ -810,100 +874,11 @@ int bindwithrange(struct clientparam *param, SOCKET sock, PROXYSOCKADDRTYPE *sa,
|
|||||||
return param->srv->so._bind(param->sostate, sock, (struct sockaddr *)sa, SASIZE(sa));
|
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
|
int bindwithrange(struct clientparam *param, SOCKET sock, PROXYSOCKADDRTYPE *sa, uint32_t range)
|
||||||
*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;
|
int tries = RANGETRIES;
|
||||||
unsigned char *pattern;
|
|
||||||
|
|
||||||
arglen = (int)strlen((char *)arg);
|
return bindrange(param, sock, sa, range, &tries);
|
||||||
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) {
|
||||||
|
|||||||
348
src/conf.c
348
src/conf.c
@ -9,7 +9,7 @@
|
|||||||
#include "proxy.h"
|
#include "proxy.h"
|
||||||
|
|
||||||
#ifdef WITH_HTTPSRV
|
#ifdef WITH_HTTPSRV
|
||||||
static int addhttprule(char *host, char *url, char *op, char *params);
|
static int addhttprule(char *op, char *host, char *url, char *params);
|
||||||
#endif
|
#endif
|
||||||
#include "mdhash.h"
|
#include "mdhash.h"
|
||||||
#ifdef WITH_SSL
|
#ifdef WITH_SSL
|
||||||
@ -18,6 +18,9 @@ void ssl_install(void);
|
|||||||
#ifdef WITH_PCRE
|
#ifdef WITH_PCRE
|
||||||
void pcre_install(void);
|
void pcre_install(void);
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef WITH_TRANSPARENT
|
||||||
|
void transparent_install(void);
|
||||||
|
#endif
|
||||||
#ifndef _WIN32
|
#ifndef _WIN32
|
||||||
#include <sys/resource.h>
|
#include <sys/resource.h>
|
||||||
#include <pwd.h>
|
#include <pwd.h>
|
||||||
@ -35,14 +38,18 @@ _3proxy_mutex_t bandlim_mutex;
|
|||||||
_3proxy_mutex_t connlim_mutex;
|
_3proxy_mutex_t connlim_mutex;
|
||||||
_3proxy_mutex_t tc_mutex;
|
_3proxy_mutex_t tc_mutex;
|
||||||
_3proxy_mutex_t config_mutex;
|
_3proxy_mutex_t config_mutex;
|
||||||
|
int runningservices = 0;
|
||||||
|
_3proxy_mutex_t runningservices_mutex;
|
||||||
|
_3proxy_sem_t serviceexited;
|
||||||
|
|
||||||
int haveerror = 0;
|
|
||||||
int linenum = 0;
|
int linenum = 0;
|
||||||
|
|
||||||
struct counter_header cheader = {"3CF", (time_t)0};
|
struct counter_header cheader = {"3CF", (time_t)0};
|
||||||
struct counter_record crecord;
|
struct counter_record crecord;
|
||||||
|
|
||||||
int mainfunc (int argc, char** argv);
|
int mainprepare(int argc, char** argv, void **service);
|
||||||
|
int mainrun(void *service);
|
||||||
|
void maindrop(void *service);
|
||||||
|
|
||||||
struct proxydef childdef = {NULL, 0, 0, S_NOSERVICE, ""};
|
struct proxydef childdef = {NULL, 0, 0, S_NOSERVICE, ""};
|
||||||
|
|
||||||
@ -72,8 +79,12 @@ DWORD WINAPI startsrv(LPVOID data) {
|
|||||||
#else
|
#else
|
||||||
void * startsrv(void * data) {
|
void * startsrv(void * data) {
|
||||||
#endif
|
#endif
|
||||||
struct child *d = (struct child *)data;
|
/* counted by start_proxy_thread() before the thread is created */
|
||||||
mainfunc(d->argc, (char **)d->argv);
|
mainrun(data);
|
||||||
|
_3proxy_mutex_lock(&runningservices_mutex);
|
||||||
|
runningservices--;
|
||||||
|
_3proxy_mutex_unlock(&runningservices_mutex);
|
||||||
|
_3proxy_sem_unlock(serviceexited);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -139,36 +150,45 @@ unsigned char * dologname (unsigned char *buf, unsigned char *name, const unsign
|
|||||||
return buf;
|
return buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* The service is set up here, in the thread reading the configuration, so
|
||||||
|
an error in its options stops the configuration; its own thread then
|
||||||
|
listens and serves. */
|
||||||
int start_proxy_thread(struct child * chp){
|
int start_proxy_thread(struct child * chp){
|
||||||
pthread_t thread;
|
pthread_t thread;
|
||||||
|
void *service;
|
||||||
|
int res;
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
HANDLE h;
|
HANDLE h;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
_3proxy_sem_lock(conf.threadinit);
|
if((res = mainprepare(chp->argc, (char **)chp->argv, &service))) {
|
||||||
|
fprintf(stderr, "Service not started on line: %d%s\n", linenum, res == 2? ": insufficient memory":"");
|
||||||
|
return(40);
|
||||||
|
}
|
||||||
|
_3proxy_mutex_lock(&runningservices_mutex);
|
||||||
|
runningservices++;
|
||||||
|
_3proxy_mutex_unlock(&runningservices_mutex);
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
#ifndef _WINCE
|
#ifndef _WINCE
|
||||||
h = (HANDLE)_beginthreadex((LPSECURITY_ATTRIBUTES )NULL, 16384+conf.stacksize, (void *)startsrv, (void *) chp, (DWORD)0, &thread);
|
h = (HANDLE)_beginthreadex((LPSECURITY_ATTRIBUTES )NULL, 16384+conf.stacksize, (void *)startsrv, service, (DWORD)0, &thread);
|
||||||
#else
|
#else
|
||||||
h = (HANDLE)CreateThread((LPSECURITY_ATTRIBUTES )NULL, 16384+conf.stacksize, (void *)startsrv, (void *) chp, (DWORD)0, &thread);
|
h = (HANDLE)CreateThread((LPSECURITY_ATTRIBUTES )NULL, 16384+conf.stacksize, (void *)startsrv, service, (DWORD)0, &thread);
|
||||||
#endif
|
#endif
|
||||||
if(h)CloseHandle(h);
|
if(h)CloseHandle(h);
|
||||||
|
else {
|
||||||
#else
|
#else
|
||||||
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)){
|
res = pthread_create(&thread, &pa, startsrv, service);
|
||||||
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);
|
||||||
|
if(res){
|
||||||
#endif
|
#endif
|
||||||
_3proxy_sem_lock(conf.threadinit);
|
_3proxy_mutex_lock(&runningservices_mutex);
|
||||||
_3proxy_sem_unlock(conf.threadinit);
|
runningservices--;
|
||||||
if(haveerror) {
|
_3proxy_mutex_unlock(&runningservices_mutex);
|
||||||
fprintf(stderr, "Service not started on line: %d%s\n", linenum, haveerror == 2? ": insufficient memory":"");
|
maindrop(service);
|
||||||
|
fprintf(stderr, "Failed to create service thread on line %d, try to set larger stacksize\n", linenum);
|
||||||
return(40);
|
return(40);
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
@ -184,6 +204,9 @@ static int h_proxy(int argc, unsigned char ** argv){
|
|||||||
childdef.port = 3128;
|
childdef.port = 3128;
|
||||||
childdef.isudp = 0;
|
childdef.isudp = 0;
|
||||||
childdef.service = S_PROXY;
|
childdef.service = S_PROXY;
|
||||||
|
#ifdef WITH_FTP
|
||||||
|
childdef.helpmessage = " -Xftp - fetch ftp:// URLs\n";
|
||||||
|
#endif
|
||||||
#ifdef NOIPV6
|
#ifdef NOIPV6
|
||||||
if(!resolvfunc || (resolvfunc == myresolver && !dns_table.poolsize)){
|
if(!resolvfunc || (resolvfunc == myresolver && !dns_table.poolsize)){
|
||||||
fprintf(stderr, "[line %d] Warning: no nserver/nscache configured, proxy may run very slow\n", linenum);
|
fprintf(stderr, "[line %d] Warning: no nserver/nscache configured, proxy may run very slow\n", linenum);
|
||||||
@ -261,10 +284,10 @@ static int h_proxy(int argc, unsigned char ** argv){
|
|||||||
else if(!strcmp((char *)argv[0], "admin")) {
|
else if(!strcmp((char *)argv[0], "admin")) {
|
||||||
/* The same service as httpsrv, with the administration pages
|
/* The same service as httpsrv, with the administration pages
|
||||||
declared for it. */
|
declared for it. */
|
||||||
if(addhttprule("*", "/C*", "admin_counters", NULL) ||
|
if(addhttprule("admin_counters", "*", "/C*", NULL) ||
|
||||||
addhttprule("*", "/R", "admin_reload", NULL) ||
|
addhttprule("admin_reload", "*", "/R", NULL) ||
|
||||||
addhttprule("*", "/S*", "admin_services", NULL) ||
|
addhttprule("admin_services", "*", "/S*", NULL) ||
|
||||||
addhttprule("*", "*", "admin", NULL)){
|
addhttprule("admin", "*", "**", NULL)){
|
||||||
fprintf(stderr, "Failed to declare the admin pages, line %d\n", linenum);
|
fprintf(stderr, "Failed to declare the admin pages, line %d\n", linenum);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@ -355,7 +378,8 @@ static int h_log(int argc, unsigned char ** argv){
|
|||||||
#ifndef _WIN32
|
#ifndef _WIN32
|
||||||
conf.logfunc = logsyslog;
|
conf.logfunc = logsyslog;
|
||||||
if(notchanged) return 0;
|
if(notchanged) return 0;
|
||||||
openlog((char *)conf.logtarget+1, LOG_PID, LOG_DAEMON);
|
/* connected now: after a chroot there is no /dev/log to find */
|
||||||
|
openlog((char *)conf.logtarget+1, LOG_PID | LOG_NDELAY, LOG_DAEMON);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
#ifdef WITH_ODBC
|
#ifdef WITH_ODBC
|
||||||
@ -799,8 +823,57 @@ struct redirdesc redirs[] = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
#ifdef WITH_HTTPSRV
|
#ifdef WITH_HTTPSRV
|
||||||
|
/* Headers a rule adds are written as one argument, the lines separated by a
|
||||||
|
backslash and an n, because a configuration line cannot hold a line ending.
|
||||||
|
Those two characters become a real one here. A line ending which reached the
|
||||||
|
argument as itself is dropped: what goes on the wire is decided here and not
|
||||||
|
by whatever produced the string. */
|
||||||
|
static unsigned char * parsehdrs(const unsigned char *arg)
|
||||||
|
{
|
||||||
|
unsigned char *out, *o;
|
||||||
|
const unsigned char *p;
|
||||||
|
size_t len = strlen((char *)arg);
|
||||||
|
|
||||||
|
out = malloc(len * 2 + 3);
|
||||||
|
if(!out) return NULL;
|
||||||
|
for(p = arg, o = out; *p; p++){
|
||||||
|
if(*p == '\\' && p[1] == 'n'){
|
||||||
|
*o++ = '\r';
|
||||||
|
*o++ = '\n';
|
||||||
|
p++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if(*p == '\r' || *p == '\n') continue;
|
||||||
|
*o++ = *p;
|
||||||
|
}
|
||||||
|
if(o == out || o[-1] != '\n'){
|
||||||
|
*o++ = '\r';
|
||||||
|
*o++ = '\n';
|
||||||
|
}
|
||||||
|
*o = 0;
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* An optional argument which a star, or nothing at all, leaves at its
|
||||||
|
default. */
|
||||||
|
static int optnum(int argc, unsigned char **argv, int at, int def)
|
||||||
|
{
|
||||||
|
if(argc <= at || !strcmp((char *)argv[at], "*")) return def;
|
||||||
|
return atoi((char *)argv[at]);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void freehttprule(struct httprule *rule)
|
||||||
|
{
|
||||||
|
if(rule->host.name) free(rule->host.name);
|
||||||
|
if(rule->url.name) free(rule->url.name);
|
||||||
|
if(rule->params) free(rule->params);
|
||||||
|
if(rule->ctype) free(rule->ctype);
|
||||||
|
if(rule->hdrs) free(rule->hdrs);
|
||||||
|
free(rule);
|
||||||
|
}
|
||||||
|
|
||||||
/* Installs one rule from code, for the pages a service predefines. */
|
/* Installs one rule from code, for the pages a service predefines. */
|
||||||
static int addhttprule(char *host, char *url, char *op, char *params)
|
static int addhttprule(char *op, char *host, char *url, char *params)
|
||||||
{
|
{
|
||||||
struct httprule *rule, *tail;
|
struct httprule *rule, *tail;
|
||||||
unsigned char hostbuf[64], urlbuf[128];
|
unsigned char hostbuf[64], urlbuf[128];
|
||||||
@ -808,6 +881,7 @@ static int addhttprule(char *host, char *url, char *op, char *params)
|
|||||||
rule = malloc(sizeof(struct httprule));
|
rule = malloc(sizeof(struct httprule));
|
||||||
if(!rule) return 1;
|
if(!rule) return 1;
|
||||||
memset(rule, 0, sizeof(struct httprule));
|
memset(rule, 0, sizeof(struct httprule));
|
||||||
|
rule->maxage = -1;
|
||||||
|
|
||||||
rule->op = httpopbyname((unsigned char *)op);
|
rule->op = httpopbyname((unsigned char *)op);
|
||||||
if(rule->op < 0){
|
if(rule->op < 0){
|
||||||
@ -817,7 +891,7 @@ static int addhttprule(char *host, char *url, char *op, char *params)
|
|||||||
|
|
||||||
strcpy((char *)hostbuf, host);
|
strcpy((char *)hostbuf, host);
|
||||||
strcpy((char *)urlbuf, url);
|
strcpy((char *)urlbuf, url);
|
||||||
if(parsepattern(&rule->host, hostbuf) || parsepattern(&rule->url, urlbuf)){
|
if(parsepattern(&rule->host, hostbuf) || parsepathpattern(&rule->url, urlbuf)){
|
||||||
free(rule->host.name);
|
free(rule->host.name);
|
||||||
free(rule);
|
free(rule);
|
||||||
return 1;
|
return 1;
|
||||||
@ -850,6 +924,82 @@ static int parserange(unsigned char *arg, uint32_t *range)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Scan a parent weight into its share of WEIGHTSCALE, as an integer: there is
|
||||||
|
* no floating point anywhere near a configuration file.
|
||||||
|
*
|
||||||
|
* A weight starting with 0 or . is a fraction of one, so .333 and 0.333 are
|
||||||
|
* both a third. Anything else is the old notation, thousandths, where 1000 is
|
||||||
|
* the whole share, and it may now carry more digits after a dot: 123.456 means
|
||||||
|
* the same as .123456. Either way at most 9 digits are kept, which is the
|
||||||
|
* resolution weights are held at.
|
||||||
|
*
|
||||||
|
* 1 followed by a point and nothing but zeroes is the one weight read as it
|
||||||
|
* looks rather than as thousandths: 1.0 is the whole share, where a bare 1 is
|
||||||
|
* a thousandth of it.
|
||||||
|
*
|
||||||
|
* A weight may also be written as a percentage, which is what a trailing %
|
||||||
|
* makes it: 50.5% is .505 is 505.
|
||||||
|
*/
|
||||||
|
static int parseweight(unsigned char * s, unsigned * weight){
|
||||||
|
static const unsigned pow10[10] = {1, 10, 100, 1000, 10000, 100000,
|
||||||
|
1000000, 10000000, 100000000, 1000000000};
|
||||||
|
unsigned char *p, *end;
|
||||||
|
uint64_t val = 0, res;
|
||||||
|
int ndigits = 0, atpoint = -1, after, percent = 0, fraction;
|
||||||
|
|
||||||
|
if(!s || !*s) return 1;
|
||||||
|
end = s + strlen((char *)s);
|
||||||
|
if(end[-1] == '%'){
|
||||||
|
percent = 1;
|
||||||
|
if(--end == s) return 1;
|
||||||
|
}
|
||||||
|
/* 1.0, with as many zeroes after it as anyone cares to write, is the one
|
||||||
|
weight read as it looks rather than as thousandths: the whole share,
|
||||||
|
where a bare 1 is a thousandth of it */
|
||||||
|
if(!percent && s[0] == '1' && s[1] == '.' && s[2]){
|
||||||
|
for(p = s + 2; *p == '0'; p++);
|
||||||
|
if(!*p){
|
||||||
|
*weight = WEIGHTSCALE;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fraction = (*s == '.' || *s == '0');
|
||||||
|
for(p = s; p < end; p++){
|
||||||
|
if(*p == '.'){
|
||||||
|
if(atpoint >= 0) return 1;
|
||||||
|
atpoint = ndigits;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if(*p < '0' || *p > '9') return 1;
|
||||||
|
if(ndigits == 18) return 1;
|
||||||
|
val = (val * 10) + (unsigned)(*p - '0');
|
||||||
|
ndigits++;
|
||||||
|
}
|
||||||
|
if(!ndigits || val > WEIGHTSCALE) return 1;
|
||||||
|
after = (atpoint < 0)? 0 : ndigits - atpoint;
|
||||||
|
if(percent){
|
||||||
|
/* a hundredth of the whole share for every 1% */
|
||||||
|
if(after > 7) return 1;
|
||||||
|
res = val * pow10[7 - after];
|
||||||
|
}
|
||||||
|
else if(fraction){
|
||||||
|
/* the digits before the point are the leading zero and add
|
||||||
|
nothing, so only the ones after it say what the share is */
|
||||||
|
if(atpoint < 0) return val? 1 : (*weight = 0, 0);
|
||||||
|
if(after > 9) return 1;
|
||||||
|
res = val * pow10[9 - after];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
/* thousandths, with the digits after the point carrying on
|
||||||
|
from them: three digits of a whole share, six more after */
|
||||||
|
if(after > 6) return 1;
|
||||||
|
res = val * pow10[6 - after];
|
||||||
|
}
|
||||||
|
if(res > WEIGHTSCALE) return 1;
|
||||||
|
*weight = (unsigned)res;
|
||||||
|
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;
|
||||||
@ -869,9 +1019,10 @@ static int h_parent(int argc, unsigned char **argv){
|
|||||||
return(21);
|
return(21);
|
||||||
}
|
}
|
||||||
memset(chains, 0, sizeof(struct chain));
|
memset(chains, 0, sizeof(struct chain));
|
||||||
chains->weight = (unsigned)atoi((char *)argv[1]);
|
/* 0 is the fallback weight: such a parent is only used once every
|
||||||
if(chains->weight == 0 || chains->weight >1000) {
|
weighted parent of its group has failed */
|
||||||
fprintf(stderr, "Chaining error: bad chain weight %u line %d\n", chains->weight, linenum);
|
if(parseweight(argv[1], &chains->weight)) {
|
||||||
|
fprintf(stderr, "Chaining error: bad chain weight %s line %d\n", argv[1], linenum);
|
||||||
free(chains);
|
free(chains);
|
||||||
return(3);
|
return(3);
|
||||||
}
|
}
|
||||||
@ -970,9 +1121,10 @@ static int h_http(int argc, unsigned char **argv){
|
|||||||
struct httprule *rule, *tail;
|
struct httprule *rule, *tail;
|
||||||
int op;
|
int op;
|
||||||
|
|
||||||
op = httpopbyname(argv[3]);
|
/* http OPERATION HOST URL [PARAMETERS] */
|
||||||
|
op = httpopbyname(argv[1]);
|
||||||
if(op < 0){
|
if(op < 0){
|
||||||
fprintf(stderr, "Unknown http operation: %s line %d\n", argv[3], linenum);
|
fprintf(stderr, "Unknown http operation: %s line %d\n", argv[1], linenum);
|
||||||
return(1);
|
return(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -980,23 +1132,75 @@ static int h_http(int argc, unsigned char **argv){
|
|||||||
if(!rule) return(21);
|
if(!rule) return(21);
|
||||||
memset(rule, 0, sizeof(struct httprule));
|
memset(rule, 0, sizeof(struct httprule));
|
||||||
rule->op = op;
|
rule->op = op;
|
||||||
|
rule->maxage = -1;
|
||||||
|
|
||||||
if(parsepattern(&rule->host, argv[1]) || parsepattern(&rule->url, argv[2])){
|
if(parsepattern(&rule->host, argv[2]) || parsepathpattern(&rule->url, argv[3])){
|
||||||
fprintf(stderr, "No memory for http rule, line %d\n", linenum);
|
fprintf(stderr, "No memory for http rule, line %d\n", linenum);
|
||||||
free(rule->host.name);
|
free(rule->host.name);
|
||||||
free(rule);
|
free(rule);
|
||||||
return(21);
|
return(21);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(argc > 4){
|
if(argc > 4 && (!strcmp((char *)argv[1], "file") || !strcmp((char *)argv[1], "cache"))){
|
||||||
|
/* PATH [TYPE [MAX-AGE [HEADERS [CODE]]]]. A star, or nothing,
|
||||||
|
leaves each of them out: the type is worked out from the name,
|
||||||
|
nothing is said about caching, no headers are added and the
|
||||||
|
answer is the usual 200. */
|
||||||
rule->params = (unsigned char *)strdup((char *)argv[4]);
|
rule->params = (unsigned char *)strdup((char *)argv[4]);
|
||||||
if(!rule->params){
|
if(argc > 5 && strcmp((char *)argv[5], "*"))
|
||||||
free(rule->host.name);
|
rule->ctype = (unsigned char *)strdup((char *)argv[5]);
|
||||||
free(rule->url.name);
|
rule->maxage = optnum(argc, argv, 6, -1);
|
||||||
free(rule);
|
if(argc > 7 && strcmp((char *)argv[7], "*"))
|
||||||
|
rule->hdrs = parsehdrs(argv[7]);
|
||||||
|
rule->code = optnum(argc, argv, 8, 0);
|
||||||
|
if(!rule->params
|
||||||
|
|| (argc > 5 && strcmp((char *)argv[5], "*") && !rule->ctype)
|
||||||
|
|| (argc > 7 && strcmp((char *)argv[7], "*") && !rule->hdrs)){
|
||||||
|
freehttprule(rule);
|
||||||
return(21);
|
return(21);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if(!strcmp((char *)argv[1], "reply")){
|
||||||
|
/* CODE [HEADERS], and no body at all. */
|
||||||
|
rule->code = optnum(argc, argv, 4, 200);
|
||||||
|
if(argc > 5 && strcmp((char *)argv[5], "*")){
|
||||||
|
rule->hdrs = parsehdrs(argv[5]);
|
||||||
|
if(!rule->hdrs){
|
||||||
|
freehttprule(rule);
|
||||||
|
return(21);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(argc > 4){
|
||||||
|
/* What follows the URL belongs to the operation, and an operation
|
||||||
|
such as redir reads more than one word of it. */
|
||||||
|
int i, len = 0;
|
||||||
|
|
||||||
|
for(i = 4; i < argc; i++) len += (int)strlen((char *)argv[i]) + 1;
|
||||||
|
rule->params = malloc(len);
|
||||||
|
if(rule->params){
|
||||||
|
int at = 0;
|
||||||
|
|
||||||
|
for(i = 4; i < argc; i++)
|
||||||
|
at += sprintf((char *)rule->params + at, "%s%s",
|
||||||
|
i > 4? " " : "", argv[i]);
|
||||||
|
}
|
||||||
|
if(!rule->params){
|
||||||
|
freehttprule(rule);
|
||||||
|
return(21);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(rule->code && (rule->code < 100 || rule->code > 599)){
|
||||||
|
fprintf(stderr, "Wrong http status: %d line %d\n", rule->code, linenum);
|
||||||
|
freehttprule(rule);
|
||||||
|
return(1);
|
||||||
|
}
|
||||||
|
if(rule->maxage < -1){
|
||||||
|
fprintf(stderr, "Wrong max-age, line %d\n", linenum);
|
||||||
|
freehttprule(rule);
|
||||||
|
return(1);
|
||||||
|
}
|
||||||
|
|
||||||
if(!conf.httprules) conf.httprules = rule;
|
if(!conf.httprules) conf.httprules = rule;
|
||||||
else {
|
else {
|
||||||
@ -1391,7 +1595,7 @@ static int h_ace(int argc, unsigned char **argv){
|
|||||||
return 5;
|
return 5;
|
||||||
}
|
}
|
||||||
*SAPORT(&acl->chains->addr) = htons((uint16_t)atoi((char *)argv[2]));
|
*SAPORT(&acl->chains->addr) = htons((uint16_t)atoi((char *)argv[2]));
|
||||||
acl->chains->weight = 1000;
|
acl->chains->weight = WEIGHTSCALE;
|
||||||
case ALLOW:
|
case ALLOW:
|
||||||
case DENY:
|
case DENY:
|
||||||
if(!conf.acl){
|
if(!conf.acl){
|
||||||
@ -1492,7 +1696,7 @@ static int h_ace(int argc, unsigned char **argv){
|
|||||||
tl->ace = acl;
|
tl->ace = acl;
|
||||||
|
|
||||||
if((acl->action == COUNTIN)||(acl->action == COUNTOUT)||(acl->action == COUNTALL)) {
|
if((acl->action == COUNTIN)||(acl->action == COUNTOUT)||(acl->action == COUNTALL)) {
|
||||||
unsigned long lim;
|
uint64_t lim = 0;
|
||||||
|
|
||||||
tl->comment = ( char *)argv[1];
|
tl->comment = ( char *)argv[1];
|
||||||
while(isdigit(*tl->comment))tl->comment++;
|
while(isdigit(*tl->comment))tl->comment++;
|
||||||
@ -1500,9 +1704,9 @@ static int h_ace(int argc, unsigned char **argv){
|
|||||||
tl->comment = strdup(tl->comment);
|
tl->comment = strdup(tl->comment);
|
||||||
|
|
||||||
sscanf((char *)argv[1], "%u", &tl->number);
|
sscanf((char *)argv[1], "%u", &tl->number);
|
||||||
sscanf((char *)argv[3], "%lu", &lim);
|
if(sscanf((char *)argv[3], "%"SCNu64"", &lim) != 1) lim = 0;
|
||||||
tl->type = getrotate(*argv[2]);
|
tl->type = getrotate(*argv[2]);
|
||||||
tl->traflim64 = ((uint64_t)lim)*(1024*1024);
|
tl->traflim64 = lim*(1024*1024);
|
||||||
if(!tl->traflim64) {
|
if(!tl->traflim64) {
|
||||||
free(tl);
|
free(tl);
|
||||||
freeacl(acl);
|
freeacl(acl);
|
||||||
@ -1621,6 +1825,11 @@ static int h_plugin(int argc, unsigned char **argv){
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef WITH_TRANSPARENT
|
||||||
|
if(argc >= 3 && !strcmp((char *)argv[2], "transparent_plugin")){
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
#ifdef NOPLUGINS
|
#ifdef NOPLUGINS
|
||||||
return 999;
|
return 999;
|
||||||
#else
|
#else
|
||||||
@ -1713,6 +1922,14 @@ static int h_setgid(int argc, unsigned char **argv){
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* What the C library reads on first use from files a chroot hides, read
|
||||||
|
while they can still be found: the time zone, without which every time
|
||||||
|
is UTC. Names looked up through the system cannot be helped this way, the
|
||||||
|
hosts file and resolv.conf are read again for lookups to come. */
|
||||||
|
static void prechroot(void){
|
||||||
|
tzset();
|
||||||
|
}
|
||||||
|
|
||||||
static int h_chroot(int argc, unsigned char **argv){
|
static int h_chroot(int argc, unsigned char **argv){
|
||||||
uid_t uid = 0;
|
uid_t uid = 0;
|
||||||
gid_t gid = 0;
|
gid_t gid = 0;
|
||||||
@ -1732,6 +1949,8 @@ static int h_chroot(int argc, unsigned char **argv){
|
|||||||
}
|
}
|
||||||
if(!chrootp){
|
if(!chrootp){
|
||||||
char *p;
|
char *p;
|
||||||
|
|
||||||
|
prechroot();
|
||||||
if(chroot((char *)argv[1])) {
|
if(chroot((char *)argv[1])) {
|
||||||
fprintf(stderr, "Unable to chroot %s", argv[1]);
|
fprintf(stderr, "Unable to chroot %s", argv[1]);
|
||||||
return(1);
|
return(1);
|
||||||
@ -1793,6 +2012,13 @@ int h_server_verify(int argc, unsigned char **argv);
|
|||||||
int h_no_server_verify(int argc, unsigned char **argv);
|
int h_no_server_verify(int argc, unsigned char **argv);
|
||||||
int h_client_mode(int argc, unsigned char **argv);
|
int h_client_mode(int argc, unsigned char **argv);
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef WITH_HTTPSRV
|
||||||
|
int h_http_content_type(int argc, unsigned char **argv);
|
||||||
|
#endif
|
||||||
|
#ifdef WITH_TRANSPARENT
|
||||||
|
int h_transparent(int argc, unsigned char **argv);
|
||||||
|
int h_notransparent(int argc, unsigned char **argv);
|
||||||
|
#endif
|
||||||
#ifdef WITH_PCRE
|
#ifdef WITH_PCRE
|
||||||
int h_pcre(int argc, unsigned char **argv);
|
int h_pcre(int argc, unsigned char **argv);
|
||||||
int h_pcre_rewrite(int argc, unsigned char **argv);
|
int h_pcre_rewrite(int argc, unsigned char **argv);
|
||||||
@ -1814,7 +2040,8 @@ struct commands commandhandlers[]={
|
|||||||
#endif
|
#endif
|
||||||
#ifdef WITH_HTTPSRV
|
#ifdef WITH_HTTPSRV
|
||||||
{NULL, "httpsrv", h_proxy, 1, 0},
|
{NULL, "httpsrv", h_proxy, 1, 0},
|
||||||
{NULL, "http", h_http, 4, 5},
|
{NULL, "http", h_http, 4, 0},
|
||||||
|
{NULL, "http_content_type", h_http_content_type, 3, 3},
|
||||||
#endif
|
#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},
|
||||||
@ -1922,6 +2149,10 @@ struct commands commandhandlers[]={
|
|||||||
{NULL, "ssl_client_mode", h_client_mode, 1, 2},
|
{NULL, "ssl_client_mode", h_client_mode, 1, 2},
|
||||||
{NULL, "ssl_certcache", h_certcache, 2, 2},
|
{NULL, "ssl_certcache", h_certcache, 2, 2},
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef WITH_TRANSPARENT
|
||||||
|
{NULL, "transparent", h_transparent, 1, 2},
|
||||||
|
{NULL, "notransparent", h_notransparent, 1, 1},
|
||||||
|
#endif
|
||||||
#ifdef WITH_PCRE
|
#ifdef WITH_PCRE
|
||||||
{NULL, "pcre", h_pcre, 4, 0},
|
{NULL, "pcre", h_pcre, 4, 0},
|
||||||
{NULL, "pcre_rewrite", h_pcre_rewrite, 5, 0},
|
{NULL, "pcre_rewrite", h_pcre_rewrite, 5, 0},
|
||||||
@ -1966,6 +2197,21 @@ int parsestr (unsigned char *str, unsigned char **argm, int nitems, unsigned cha
|
|||||||
argm[argc] = 0;
|
argm[argc] = 0;
|
||||||
return argc;
|
return argc;
|
||||||
case '$':
|
case '$':
|
||||||
|
/* Two dollars stand for one. That is how a literal dollar is
|
||||||
|
written where a file to include would otherwise be read, and
|
||||||
|
the second one is dropped here as a quote character is. */
|
||||||
|
if(str[1] == '$'){
|
||||||
|
str1 = str;
|
||||||
|
do {
|
||||||
|
*str1 = *(str1 + 1);
|
||||||
|
}while(*(str1++));
|
||||||
|
if(space){
|
||||||
|
argm[argc++] = str;
|
||||||
|
if(argc >= nitems) return argc;
|
||||||
|
space = 0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
if(comment){
|
if(comment){
|
||||||
if(space){
|
if(space){
|
||||||
argm[argc++] = str;
|
argm[argc++] = str;
|
||||||
@ -2048,6 +2294,7 @@ int readconfig(FILE * fp){
|
|||||||
|
|
||||||
if( !(buf = malloc(bufsize)) || ! (argv = malloc((NPARAMS + 1) * sizeof(unsigned char *))) ) {
|
if( !(buf = malloc(bufsize)) || ! (argv = malloc((NPARAMS + 1) * sizeof(unsigned char *))) ) {
|
||||||
fprintf(stderr, "No memory for configuration");
|
fprintf(stderr, "No memory for configuration");
|
||||||
|
if(buf) free(buf);
|
||||||
return(10);
|
return(10);
|
||||||
}
|
}
|
||||||
for (linenum = 1; fgets((char *)buf, STRINGBUF, fp); linenum++){
|
for (linenum = 1; fgets((char *)buf, STRINGBUF, fp); linenum++){
|
||||||
@ -2057,7 +2304,8 @@ int readconfig(FILE * fp){
|
|||||||
argc = parsestr (buf, argv, NPARAMS-1, &buf, &inbuf, &bufsize);
|
argc = parsestr (buf, argv, NPARAMS-1, &buf, &inbuf, &bufsize);
|
||||||
if(argc < 1) {
|
if(argc < 1) {
|
||||||
fprintf(stderr, "Parse error line %d\n", linenum);
|
fprintf(stderr, "Parse error line %d\n", linenum);
|
||||||
return(11);
|
res = 11;
|
||||||
|
goto DONE;
|
||||||
}
|
}
|
||||||
argv[argc] = NULL;
|
argv[argc] = NULL;
|
||||||
if(!strcmp((char *)argv[0], "end") && argc == 1) {
|
if(!strcmp((char *)argv[0], "end") && argc == 1) {
|
||||||
@ -2069,23 +2317,28 @@ int readconfig(FILE * fp){
|
|||||||
if(!strcmp((char *)argv[0], (char *)cm->command)){
|
if(!strcmp((char *)argv[0], (char *)cm->command)){
|
||||||
if(argc < cm->minargs || (cm->maxargs && argc > cm->maxargs)){
|
if(argc < cm->minargs || (cm->maxargs && argc > cm->maxargs)){
|
||||||
fprintf(stderr, "Command: '%s' wrong number of arguments , line %d\n", argv[0], linenum);
|
fprintf(stderr, "Command: '%s' wrong number of arguments , line %d\n", argv[0], linenum);
|
||||||
return(linenum);
|
res = linenum;
|
||||||
|
goto DONE;
|
||||||
}
|
}
|
||||||
res = (*cm->handler)(argc, argv);
|
res = (*cm->handler)(argc, argv);
|
||||||
if(res > 0){
|
if(res > 0){
|
||||||
fprintf(stderr, "Command: '%s' failed with code %d, line %d\n", argv[0], res, linenum);
|
fprintf(stderr, "Command: '%s' failed with code %d, line %d\n", argv[0], res, linenum);
|
||||||
return(linenum);
|
res = linenum;
|
||||||
|
goto DONE;
|
||||||
}
|
}
|
||||||
if(!res) break;
|
if(!res) break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(res != 1)continue;
|
if(res != 1)continue;
|
||||||
fprintf(stderr, "Unknown command: '%s' line %d\n", argv[0], linenum);
|
fprintf(stderr, "Unknown command: '%s' line %d\n", argv[0], linenum);
|
||||||
return(linenum);
|
res = linenum;
|
||||||
|
goto DONE;
|
||||||
}
|
}
|
||||||
|
res = 0;
|
||||||
|
DONE:
|
||||||
free(buf);
|
free(buf);
|
||||||
free(argv);
|
free(argv);
|
||||||
return 0;
|
return res;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2212,6 +2465,9 @@ int reload (void){
|
|||||||
#endif
|
#endif
|
||||||
#ifdef WITH_PCRE
|
#ifdef WITH_PCRE
|
||||||
pcre_install();
|
pcre_install();
|
||||||
|
#endif
|
||||||
|
#ifdef WITH_TRANSPARENT
|
||||||
|
transparent_install();
|
||||||
#endif
|
#endif
|
||||||
conf.paused++;
|
conf.paused++;
|
||||||
freeconf(&conf);
|
freeconf(&conf);
|
||||||
|
|||||||
@ -802,7 +802,7 @@ static struct property prop_pwlist[] = {
|
|||||||
static struct property prop_chain[] = {
|
static struct property prop_chain[] = {
|
||||||
{"addr", ef_chain_addr, TYPE_SA, "parent address"},
|
{"addr", ef_chain_addr, TYPE_SA, "parent address"},
|
||||||
{"type", ef_chain_type, TYPE_STRING, "parent type"},
|
{"type", ef_chain_type, TYPE_STRING, "parent type"},
|
||||||
{"weight", ef_chain_weight, TYPE_SHORT, "parent weight 0-1000"},
|
{"weight", ef_chain_weight, TYPE_INTEGER, "parent weight, 1000000000 is the whole share, 0 is a fallback"},
|
||||||
{"user", ef_chain_user, TYPE_STRING, "parent login"},
|
{"user", ef_chain_user, TYPE_STRING, "parent login"},
|
||||||
{"password", ef_chain_password, TYPE_PASSWORD, "parent password"},
|
{"password", ef_chain_password, TYPE_PASSWORD, "parent password"},
|
||||||
{"secure", ef_chain_secure, TYPE_INTEGER, "secure mode"},
|
{"secure", ef_chain_secure, TYPE_INTEGER, "secure mode"},
|
||||||
|
|||||||
48
src/dnspr.c
48
src/dnspr.c
@ -15,6 +15,12 @@
|
|||||||
|
|
||||||
#define BUFSIZE 16384
|
#define BUFSIZE 16384
|
||||||
|
|
||||||
|
/* An answer to the query asked: our id, a response, and the question
|
||||||
|
byte for byte. */
|
||||||
|
static int dnsanswer(const unsigned char *buf, int len, const unsigned char *q, int qend){
|
||||||
|
return len >= qend && buf[0] == q[0] && buf[1] == q[1] && (buf[2] & 0x80) &&
|
||||||
|
buf[4] == q[4] && buf[5] == q[5] && !memcmp(buf + 12, q + 12, qend - 12);
|
||||||
|
}
|
||||||
|
|
||||||
void * dnsprchild(struct clientparam* param) {
|
void * dnsprchild(struct clientparam* param) {
|
||||||
uint32_t ip = 0;
|
uint32_t ip = 0;
|
||||||
@ -45,7 +51,7 @@ void * dnsprchild(struct clientparam* param) {
|
|||||||
memcpy(buf, param->srv->udpbuf, i);
|
memcpy(buf, param->srv->udpbuf, i);
|
||||||
_3proxy_sem_unlock(udpinit);
|
_3proxy_sem_unlock(udpinit);
|
||||||
semlocked = 0;
|
semlocked = 0;
|
||||||
#ifdef _WIN32
|
#if defined(_WIN32) && !defined(SHARE_UDP_SOCKET)
|
||||||
if((param->clisock=param->srv->so._socket(param->sostate, AF_INET, SOCK_DGRAM, IPPROTO_UDP)) == INVALID_SOCKET) {
|
if((param->clisock=param->srv->so._socket(param->sostate, AF_INET, SOCK_DGRAM, IPPROTO_UDP)) == INVALID_SOCKET) {
|
||||||
RETURN(818);
|
RETURN(818);
|
||||||
}
|
}
|
||||||
@ -56,6 +62,8 @@ void * dnsprchild(struct clientparam* param) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#else
|
#else
|
||||||
|
/* The reply has to come from the address the query was sent to, which
|
||||||
|
is the listening socket. */
|
||||||
param->clisock = param->srv->srvsock;
|
param->clisock = param->srv->srvsock;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@ -144,6 +152,20 @@ void * dnsprchild(struct clientparam* param) {
|
|||||||
else ip = 0;
|
else ip = 0;
|
||||||
}
|
}
|
||||||
if(!ip && numservers){
|
if(!ip && numservers){
|
||||||
|
/* the question, to check the answer against; the name server is
|
||||||
|
asked under an id of our own, the client's put back in its answer */
|
||||||
|
unsigned char question[1024], cid[2];
|
||||||
|
int qend = (len < i)? len : i;
|
||||||
|
unsigned short id = (unsigned short)myrand();
|
||||||
|
PROXYSOCKADDRTYPE from;
|
||||||
|
time_t deadline;
|
||||||
|
int to;
|
||||||
|
|
||||||
|
cid[0] = buf[0];
|
||||||
|
cid[1] = buf[1];
|
||||||
|
buf[0] = (unsigned char)(id >> 8);
|
||||||
|
buf[1] = (unsigned char)(id & 0xff);
|
||||||
|
memcpy(question, buf, qend);
|
||||||
if((param->remsock=param->srv->so._socket(param->sostate, SASOCK(&nservers[0].addr), nservers[0].usetcp? SOCK_STREAM:SOCK_DGRAM, nservers[0].usetcp?IPPROTO_TCP:IPPROTO_UDP)) == INVALID_SOCKET) {
|
if((param->remsock=param->srv->so._socket(param->sostate, SASOCK(&nservers[0].addr), nservers[0].usetcp? SOCK_STREAM:SOCK_DGRAM, nservers[0].usetcp?IPPROTO_TCP:IPPROTO_UDP)) == INVALID_SOCKET) {
|
||||||
RETURN(818);
|
RETURN(818);
|
||||||
}
|
}
|
||||||
@ -172,9 +194,21 @@ void * dnsprchild(struct clientparam* param) {
|
|||||||
}
|
}
|
||||||
param->statscli64 += i;
|
param->statscli64 += i;
|
||||||
param->nwrites++;
|
param->nwrites++;
|
||||||
len = sockrecvfrom(param, param->remsock, (struct sockaddr *)¶m->sinsr, buf, BUFSIZE, conf.timeouts[DNS_TO]*1000);
|
/* over UDP only an answer from the name server asked is taken, any
|
||||||
if(len <= 13) {
|
other read past while the time for it lasts */
|
||||||
RETURN(821);
|
deadline = time(NULL) + conf.timeouts[DNS_TO];
|
||||||
|
for(;;){
|
||||||
|
to = (int)(deadline - time(NULL));
|
||||||
|
if(to <= 0) RETURN(821);
|
||||||
|
len = sockrecvfrom(param, param->remsock, (struct sockaddr *)&from, buf, BUFSIZE, to*1000);
|
||||||
|
if(len <= 13) {
|
||||||
|
RETURN(821);
|
||||||
|
}
|
||||||
|
if(nservers[0].usetcp) break;
|
||||||
|
if(*SAFAMILY(&from) == *SAFAMILY(¶m->sinsr) &&
|
||||||
|
!memcmp(SAADDR(&from), SAADDR(¶m->sinsr), SAADDRLEN(¶m->sinsr)) &&
|
||||||
|
*SAPORT(&from) == *SAPORT(¶m->sinsr) &&
|
||||||
|
dnsanswer(buf, len, question, qend)) break;
|
||||||
}
|
}
|
||||||
param->statssrv64 += len;
|
param->statssrv64 += len;
|
||||||
param->nreads++;
|
param->nreads++;
|
||||||
@ -186,7 +220,10 @@ void * dnsprchild(struct clientparam* param) {
|
|||||||
len -= 2;
|
len -= 2;
|
||||||
if(len < us) len += sockgetlinebuf(param, SERVER, buf+len, us - len, 0, conf.timeouts[SINGLEBYTE_L]);
|
if(len < us) len += sockgetlinebuf(param, SERVER, buf+len, us - len, 0, conf.timeouts[SINGLEBYTE_L]);
|
||||||
if(len != us) RETURN(832);
|
if(len != us) RETURN(832);
|
||||||
|
if(!dnsanswer(buf, len, question, qend)) RETURN(834);
|
||||||
}
|
}
|
||||||
|
buf[0] = cid[0];
|
||||||
|
buf[1] = cid[1];
|
||||||
if(buf[6] || buf[7]){
|
if(buf[6] || buf[7]){
|
||||||
if(socksendto(param, param->clisock, (struct sockaddr *)¶m->sincr, buf, len, conf.timeouts[SINGLEBYTE_L]*1000) != len){
|
if(socksendto(param, param->clisock, (struct sockaddr *)¶m->sincr, buf, len, conf.timeouts[SINGLEBYTE_L]*1000) != len){
|
||||||
RETURN(822);
|
RETURN(822);
|
||||||
@ -217,7 +254,8 @@ CLEANRET:
|
|||||||
}
|
}
|
||||||
if(bbuf)free(bbuf);
|
if(bbuf)free(bbuf);
|
||||||
if(host)free(host);
|
if(host)free(host);
|
||||||
#ifndef _WIN32
|
#if !defined(_WIN32) || defined(SHARE_UDP_SOCKET)
|
||||||
|
/* The socket belongs to the service, so the caller must not close it. */
|
||||||
param->clisock = INVALID_SOCKET;
|
param->clisock = INVALID_SOCKET;
|
||||||
#endif
|
#endif
|
||||||
return (NULL);
|
return (NULL);
|
||||||
|
|||||||
@ -7,6 +7,8 @@
|
|||||||
|
|
||||||
#include "proxy.h"
|
#include "proxy.h"
|
||||||
|
|
||||||
|
#ifdef WITH_FTP
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Read one FTP server response, skipping continuation lines (lines whose
|
* Read one FTP server response, skipping continuation lines (lines whose
|
||||||
* 4th character is '-' per RFC 959). Returns the line length on success,
|
* 4th character is '-' per RFC 959). Returns the line length on success,
|
||||||
@ -36,7 +38,7 @@ int ftplogin(struct clientparam *param, char *nbuf, int *innbuf) {
|
|||||||
if(i < 3) return 706;
|
if(i < 3) return 706;
|
||||||
buf[i] = 0;
|
buf[i] = 0;
|
||||||
if(atoi(buf)/100 != 2) {
|
if(atoi(buf)/100 != 2) {
|
||||||
*innbuf = i;
|
if(innbuf)*innbuf = i;
|
||||||
return 702;
|
return 702;
|
||||||
}
|
}
|
||||||
sprintf(buf, "USER %.128s\r\n", param->extusername?param->extusername:(unsigned char *)"anonymous");
|
sprintf(buf, "USER %.128s\r\n", param->extusername?param->extusername:(unsigned char *)"anonymous");
|
||||||
@ -75,6 +77,9 @@ int ftplogin(struct clientparam *param, char *nbuf, int *innbuf) {
|
|||||||
return 701;
|
return 701;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/* USER answered at once, logged in (230) or refused: that answer is
|
||||||
|
the client's, waiting for one to its PASS */
|
||||||
|
else if(innbuf)*innbuf = i;
|
||||||
if(res != 2) {
|
if(res != 2) {
|
||||||
return 700;
|
return 700;
|
||||||
}
|
}
|
||||||
@ -249,3 +254,5 @@ SOCKET ftpcommand(struct clientparam *param, unsigned char * command, unsigned c
|
|||||||
}
|
}
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|||||||
34
src/ftppr.c
34
src/ftppr.c
@ -8,6 +8,8 @@
|
|||||||
|
|
||||||
#include "proxy.h"
|
#include "proxy.h"
|
||||||
|
|
||||||
|
#ifdef WITH_FTP
|
||||||
|
|
||||||
#define RETURN(xxx) { param->res = xxx; goto CLEANRET; }
|
#define RETURN(xxx) { param->res = xxx; goto CLEANRET; }
|
||||||
#define BUFSIZE 2048
|
#define BUFSIZE 2048
|
||||||
|
|
||||||
@ -21,14 +23,18 @@ void * ftpprchild(struct clientparam* param) {
|
|||||||
SOCKET sc = INVALID_SOCKET, ss = INVALID_SOCKET, clidatasock = INVALID_SOCKET;
|
SOCKET sc = INVALID_SOCKET, ss = INVALID_SOCKET, clidatasock = INVALID_SOCKET;
|
||||||
SASIZETYPE sasize;
|
SASIZETYPE sasize;
|
||||||
char * req = NULL;
|
char * req = NULL;
|
||||||
|
#ifndef _WIN32
|
||||||
struct linger lg;
|
struct linger lg;
|
||||||
|
#endif
|
||||||
struct pollfd fds;
|
struct pollfd fds;
|
||||||
|
|
||||||
if(!(buf = malloc(BUFSIZE))) RETURN(876);
|
if(!(buf = malloc(BUFSIZE))) RETURN(876);
|
||||||
param->ctrlsock = param->clisock;
|
param->ctrlsock = param->clisock;
|
||||||
param->operation = CONNECT;
|
param->operation = CONNECT;
|
||||||
|
#ifndef _WIN32
|
||||||
lg.l_onoff = 1;
|
lg.l_onoff = 1;
|
||||||
lg.l_linger = conf.timeouts[STRING_L];;
|
lg.l_linger = conf.timeouts[STRING_L];
|
||||||
|
#endif
|
||||||
if(socksend(param, param->ctrlsock, (unsigned char *)"220 Ready\r\n", 11, conf.timeouts[STRING_S])!=11) {RETURN (801);}
|
if(socksend(param, param->ctrlsock, (unsigned char *)"220 Ready\r\n", 11, conf.timeouts[STRING_S])!=11) {RETURN (801);}
|
||||||
for(;;){
|
for(;;){
|
||||||
i = sockgetlinebuf(param, CLIENT, buf, BUFSIZE - 10, '\n', conf.timeouts[CONNECTION_S]);
|
i = sockgetlinebuf(param, CLIENT, buf, BUFSIZE - 10, '\n', conf.timeouts[CONNECTION_S]);
|
||||||
@ -64,6 +70,13 @@ void * ftpprchild(struct clientparam* param) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
else if (!strncasecmp((char *)buf, "PASS ", 5)){
|
else if (!strncasecmp((char *)buf, "PASS ", 5)){
|
||||||
|
/* The user name carries the server to log in to, and it arrives
|
||||||
|
with USER. Without it there is nothing to log in to, and what
|
||||||
|
follows would read the name and the host as if there were. */
|
||||||
|
if(!param->hostname || !param->extusername){
|
||||||
|
socksend(param, param->ctrlsock, (unsigned char *)"503 Login with USER first\r\n", 27, conf.timeouts[STRING_S]);
|
||||||
|
RETURN(805);
|
||||||
|
}
|
||||||
param->extpassword = (unsigned char *)strdup((char *)buf+5);
|
param->extpassword = (unsigned char *)strdup((char *)buf+5);
|
||||||
inbuf = BUFSIZE;
|
inbuf = BUFSIZE;
|
||||||
res = ftplogin(param, (char *)buf, &inbuf);
|
res = ftplogin(param, (char *)buf, &inbuf);
|
||||||
@ -119,7 +132,8 @@ void * ftpprchild(struct clientparam* param) {
|
|||||||
param->srv->so._closesocket(param->sostate, clidatasock);
|
param->srv->so._closesocket(param->sostate, clidatasock);
|
||||||
clidatasock = INVALID_SOCKET;
|
clidatasock = INVALID_SOCKET;
|
||||||
}
|
}
|
||||||
if ((clidatasock=socket(SASOCK(¶m->sincl), SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET) {RETURN(821);}
|
if ((clidatasock=param->srv->so._socket(param->sostate, SASOCK(¶m->sincl), SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET) {RETURN(821);}
|
||||||
|
if(intbindtodevice(param, clidatasock)){RETURN(822);}
|
||||||
*SAPORT(¶m->sincl) = 0;
|
*SAPORT(¶m->sincl) = 0;
|
||||||
if(bindwithrange(param, clidatasock, ¶m->sincl, param->intport)){RETURN(822);}
|
if(bindwithrange(param, clidatasock, ¶m->sincl, param->intport)){RETURN(822);}
|
||||||
if (pasv) {
|
if (pasv) {
|
||||||
@ -252,8 +266,11 @@ void * ftpprchild(struct clientparam* param) {
|
|||||||
}
|
}
|
||||||
sc = param->remsock;
|
sc = param->remsock;
|
||||||
param->remsock = ss;
|
param->remsock = ss;
|
||||||
|
#ifndef _WIN32
|
||||||
|
/* not on Windows, see the accepting side in proxymain.c */
|
||||||
param->srv->so._setsockopt(param->sostate, param->remsock, SOL_SOCKET, SO_LINGER, (char *)&lg, sizeof(lg));
|
param->srv->so._setsockopt(param->sostate, param->remsock, SOL_SOCKET, SO_LINGER, (char *)&lg, sizeof(lg));
|
||||||
param->srv->so._setsockopt(param->sostate, clidatasock, SOL_SOCKET, SO_LINGER, (char *)&lg, sizeof(lg));
|
param->srv->so._setsockopt(param->sostate, clidatasock, SOL_SOCKET, SO_LINGER, (char *)&lg, sizeof(lg));
|
||||||
|
#endif
|
||||||
param->clisock = clidatasock;
|
param->clisock = clidatasock;
|
||||||
res = mapsocket(param, conf.timeouts[CONNECTION_S]);
|
res = mapsocket(param, conf.timeouts[CONNECTION_S]);
|
||||||
if(param->remsock != INVALID_SOCKET) {
|
if(param->remsock != INVALID_SOCKET) {
|
||||||
@ -338,3 +355,16 @@ struct proxydef childdef = {
|
|||||||
};
|
};
|
||||||
#include "proxymain.c"
|
#include "proxymain.c"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
/* Built without FTP support. The service and the redirect naming it are
|
||||||
|
still known, so a configuration carrying them is read rather than
|
||||||
|
refused, and a client reaching one is turned away. */
|
||||||
|
void * ftpprchild(struct clientparam * param){
|
||||||
|
param->res = 878;
|
||||||
|
dolog(param, (unsigned char *)"ftp support is not built in");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|||||||
1286
src/httpsrv.c
1286
src/httpsrv.c
File diff suppressed because it is too large
Load Diff
14
src/imapp.c
14
src/imapp.c
@ -8,6 +8,8 @@
|
|||||||
|
|
||||||
#include "proxy.h"
|
#include "proxy.h"
|
||||||
|
|
||||||
|
#ifdef WITH_IMAPP
|
||||||
|
|
||||||
#define RETURN(xxx) { param->res = xxx; goto CLEANRET; }
|
#define RETURN(xxx) { param->res = xxx; goto CLEANRET; }
|
||||||
|
|
||||||
#define CL_LOGINCMD 0
|
#define CL_LOGINCMD 0
|
||||||
@ -250,3 +252,15 @@ struct proxydef childdef = {
|
|||||||
};
|
};
|
||||||
#include "proxymain.c"
|
#include "proxymain.c"
|
||||||
#endif
|
#endif
|
||||||
|
#else
|
||||||
|
|
||||||
|
/* Built without this proxy of its own. The command and the redirect
|
||||||
|
naming it are the STARTTLS proxy speaking that protocol, which
|
||||||
|
negotiates the same way and passes the session on: what "tlspr -Ximap"
|
||||||
|
does, under the name a configuration already uses. */
|
||||||
|
void * imappchild(struct clientparam * param){
|
||||||
|
param->starttls = S_IMAPP;
|
||||||
|
return (void *)tlsprchild;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|||||||
132
src/pcre.c
132
src/pcre.c
@ -220,6 +220,14 @@ struct pcre_filter_data {
|
|||||||
struct ace *acl;
|
struct ace *acl;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* What a connection matches with: pcre2_match() writes into its match
|
||||||
|
data, so a thread matching on the rule's own one would race every
|
||||||
|
other thread doing the same. */
|
||||||
|
struct pcre_client_data {
|
||||||
|
struct pcre_filter_data *pcrefd;
|
||||||
|
pcre2_match_data * match_data;
|
||||||
|
};
|
||||||
|
|
||||||
static void pcre_data_free(struct pcre_filter_data *pcrefd){
|
static void pcre_data_free(struct pcre_filter_data *pcrefd){
|
||||||
_3proxy_mutex_lock(&pcre_mutex);
|
_3proxy_mutex_lock(&pcre_mutex);
|
||||||
pcrefd->users--;
|
pcrefd->users--;
|
||||||
@ -253,18 +261,35 @@ static void* pcre_filter_open(void * idata, struct srvparam * param){
|
|||||||
static FILTER_ACTION pcre_filter_client(void *fo, struct clientparam * param, void** fc){
|
static FILTER_ACTION pcre_filter_client(void *fo, struct clientparam * param, void** fc){
|
||||||
int res;
|
int res;
|
||||||
struct ace tmpace;
|
struct ace tmpace;
|
||||||
|
struct pcre_filter_data *rule = (struct pcre_filter_data *)fo;
|
||||||
|
struct pcre_client_data *pcd;
|
||||||
|
|
||||||
*fc = fo;
|
*fc = fo;
|
||||||
if(!fo) return PASS;
|
if(!fo) return PASS;
|
||||||
#define pcrefd ((struct pcre_filter_data *)fo)
|
if(!rule->acl) return CONTINUE;
|
||||||
if(!pcrefd->acl) return CONTINUE;
|
|
||||||
memset (&tmpace, 0, sizeof(struct ace));
|
memset (&tmpace, 0, sizeof(struct ace));
|
||||||
tmpace.src = pcrefd->acl->src;
|
tmpace.src = rule->acl->src;
|
||||||
res = pl->ACLMatches(&tmpace, param);
|
res = pl->ACLMatches(&tmpace, param);
|
||||||
#undef pcrefd
|
if(!res) return PASS;
|
||||||
return (res)? CONTINUE:PASS;
|
/* the connection goes on with a match data of its own */
|
||||||
|
if(!(pcd = pl->mallocfunc(sizeof(struct pcre_client_data)))){
|
||||||
|
*fc = NULL;
|
||||||
|
return rule->action;
|
||||||
|
}
|
||||||
|
pcd->pcrefd = rule;
|
||||||
|
pcd->match_data = NULL;
|
||||||
|
if(rule->re && !(pcd->match_data = pcre2_match_data_create_from_pattern(rule->re, NULL))){
|
||||||
|
pl->freefunc(pcd);
|
||||||
|
*fc = NULL;
|
||||||
|
return rule->action;
|
||||||
|
}
|
||||||
|
*fc = pcd;
|
||||||
|
return CONTINUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* What a rewritten buffer keeps free for its caller to append to. */
|
||||||
|
#define PCRE_HEADROOM 1024
|
||||||
|
|
||||||
static FILTER_ACTION pcre_filter_buffer(void *fc, struct clientparam *param, unsigned char ** buf_p, int * bufsize_p, int offset, int * length_p){
|
static FILTER_ACTION pcre_filter_buffer(void *fc, struct clientparam *param, unsigned char ** buf_p, int * bufsize_p, int offset, int * length_p){
|
||||||
PCRE2_SIZE *ovector;
|
PCRE2_SIZE *ovector;
|
||||||
int count = 0;
|
int count = 0;
|
||||||
@ -274,7 +299,8 @@ static FILTER_ACTION pcre_filter_buffer(void *fc, struct clientparam *param, uns
|
|||||||
char * replace;
|
char * replace;
|
||||||
char * tmpbuf, *target, *newbuf;
|
char * tmpbuf, *target, *newbuf;
|
||||||
int nreplaces=0;
|
int nreplaces=0;
|
||||||
#define pcrefd ((struct pcre_filter_data *)fc)
|
struct pcre_client_data *pcd = (struct pcre_client_data *)fc;
|
||||||
|
#define pcrefd (pcd->pcrefd)
|
||||||
|
|
||||||
for(acl = pcrefd->acl; acl; acl=acl->next){
|
for(acl = pcrefd->acl; acl; acl=acl->next){
|
||||||
if(pl->ACLMatches(acl, param)){
|
if(pl->ACLMatches(acl, param)){
|
||||||
@ -287,9 +313,9 @@ static FILTER_ACTION pcre_filter_buffer(void *fc, struct clientparam *param, uns
|
|||||||
for(; offset < *length_p; nreplaces++){
|
for(; offset < *length_p; nreplaces++){
|
||||||
int repsz;
|
int repsz;
|
||||||
|
|
||||||
count = pcre2_match(pcrefd->re, (PCRE2_SPTR)*buf_p, *length_p, offset, 0, pcrefd->match_data, pcrefd->match_context);
|
count = pcre2_match(pcrefd->re, (PCRE2_SPTR)*buf_p, *length_p, offset, 0, pcd->match_data, pcrefd->match_context);
|
||||||
if(count <= 0) break;
|
if(count <= 0) break;
|
||||||
ovector = pcre2_get_ovector_pointer(pcrefd->match_data);
|
ovector = pcre2_get_ovector_pointer(pcd->match_data);
|
||||||
if(ovector[0] > (PCRE2_SIZE)*length_p || ovector[1] > (PCRE2_SIZE)*length_p || ovector[1] < ovector[0]) break;
|
if(ovector[0] > (PCRE2_SIZE)*length_p || ovector[1] > (PCRE2_SIZE)*length_p || ovector[1] < ovector[0]) break;
|
||||||
if(!(replace = pcrefd->replace) || param->nooverwritefilter) return pcrefd->action;
|
if(!(replace = pcrefd->replace) || param->nooverwritefilter) return pcrefd->action;
|
||||||
|
|
||||||
@ -324,12 +350,17 @@ static FILTER_ACTION pcre_filter_buffer(void *fc, struct clientparam *param, uns
|
|||||||
else if(*replace == '$' && isnumber(*(replace+1))){
|
else if(*replace == '$' && isnumber(*(replace+1))){
|
||||||
replace ++;
|
replace ++;
|
||||||
num = atoi(replace);
|
num = atoi(replace);
|
||||||
|
/* Past the digits first, and only then decide whether
|
||||||
|
the group is one to copy: the pass which measured
|
||||||
|
this string did it in that order, and a reference it
|
||||||
|
counted as nothing must not be written out as its
|
||||||
|
own digits here. */
|
||||||
|
while(isnumber(*replace)) replace++;
|
||||||
if(num > (count - 1)) continue;
|
if(num > (count - 1)) continue;
|
||||||
if(ovector[(num<<1)] == PCRE2_UNSET) continue;
|
if(ovector[(num<<1)] == PCRE2_UNSET) continue;
|
||||||
if(ovector[(num<<1) + 1] > (PCRE2_SIZE)*length_p || ovector[(num<<1)] > ovector[(num<<1) + 1]) continue;
|
if(ovector[(num<<1) + 1] > (PCRE2_SIZE)*length_p || ovector[(num<<1)] > ovector[(num<<1) + 1]) continue;
|
||||||
memcpy(target, *buf_p + ovector[(num<<1)], ovector[(num<<1) + 1] - ovector[(num<<1)]);
|
memcpy(target, *buf_p + ovector[(num<<1)], ovector[(num<<1) + 1] - ovector[(num<<1)]);
|
||||||
target += (ovector[(num<<1) + 1] - ovector[(num<<1)]);
|
target += (ovector[(num<<1) + 1] - ovector[(num<<1)]);
|
||||||
while(isnumber(*replace)) replace++;
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
*target++ = *replace++;
|
*target++ = *replace++;
|
||||||
@ -338,7 +369,13 @@ static FILTER_ACTION pcre_filter_buffer(void *fc, struct clientparam *param, uns
|
|||||||
repsz = (int)(target - tmpbuf);
|
repsz = (int)(target - tmpbuf);
|
||||||
memcpy(target, *buf_p + ovector[1], *length_p - ovector[1]);
|
memcpy(target, *buf_p + ovector[1], *length_p - ovector[1]);
|
||||||
if((ovector[0] + replen + 1) > *bufsize_p){
|
if((ovector[0] + replen + 1) > *bufsize_p){
|
||||||
newbuf = pl->mallocfunc(ovector[0] + replen + 1);
|
/* Room beyond what was produced: whoever asked for the
|
||||||
|
filtering usually has something of its own to add, and a
|
||||||
|
buffer sized to the last byte written leaves nowhere to
|
||||||
|
put it. The size reported is the size allocated. */
|
||||||
|
int newsize = ovector[0] + replen + 1 + PCRE_HEADROOM;
|
||||||
|
|
||||||
|
newbuf = pl->mallocfunc(newsize);
|
||||||
if(!newbuf){
|
if(!newbuf){
|
||||||
pl->freefunc(tmpbuf);
|
pl->freefunc(tmpbuf);
|
||||||
return CONTINUE;
|
return CONTINUE;
|
||||||
@ -346,7 +383,7 @@ static FILTER_ACTION pcre_filter_buffer(void *fc, struct clientparam *param, uns
|
|||||||
memcpy(newbuf, *buf_p, ovector[0]);
|
memcpy(newbuf, *buf_p, ovector[0]);
|
||||||
pl->freefunc(*buf_p);
|
pl->freefunc(*buf_p);
|
||||||
*buf_p = (unsigned char *)newbuf;
|
*buf_p = (unsigned char *)newbuf;
|
||||||
*bufsize_p = ovector[0] + replen + 1;
|
*bufsize_p = newsize;
|
||||||
}
|
}
|
||||||
memcpy(*buf_p + ovector[0], tmpbuf, replen);
|
memcpy(*buf_p + ovector[0], tmpbuf, replen);
|
||||||
pl->freefunc(tmpbuf);
|
pl->freefunc(tmpbuf);
|
||||||
@ -362,7 +399,13 @@ static FILTER_ACTION pcre_filter_buffer(void *fc, struct clientparam *param, uns
|
|||||||
#undef pcrefd
|
#undef pcrefd
|
||||||
}
|
}
|
||||||
|
|
||||||
static void pcre_filter_clear(void *fo){
|
static void pcre_filter_clear(void *fc){
|
||||||
|
struct pcre_client_data *pcd = (struct pcre_client_data *)fc;
|
||||||
|
|
||||||
|
if(pcd){
|
||||||
|
if(pcd->match_data) pcre2_match_data_free(pcd->match_data);
|
||||||
|
pl->freefunc(pcd);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void pcre_filter_close(void *fo){
|
static void pcre_filter_close(void *fo){
|
||||||
@ -629,6 +672,71 @@ static struct symbol regexp_symbols[] = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/* Compiling and matching for patterns outside the pcre commands: a host name
|
||||||
|
or a URL in an http rule, an access rule naming a host. They go through the
|
||||||
|
same compile, with whatever pcre_options is set to, so one kind of regular
|
||||||
|
expression is understood everywhere.
|
||||||
|
*/
|
||||||
|
void * pcre_pattern_compile(const unsigned char *pattern, char *errbuf, int errlen)
|
||||||
|
{
|
||||||
|
pcre2_code *re;
|
||||||
|
int errcode;
|
||||||
|
PCRE2_SIZE erroffset;
|
||||||
|
|
||||||
|
re = pcre2_compile((PCRE2_SPTR)pattern, PCRE2_ZERO_TERMINATED, pcre_options,
|
||||||
|
&errcode, &erroffset, NULL);
|
||||||
|
if(!re){
|
||||||
|
if(errbuf && errlen > 0){
|
||||||
|
PCRE2_UCHAR message[256];
|
||||||
|
|
||||||
|
pcre2_get_error_message(errcode, message, sizeof(message));
|
||||||
|
snprintf(errbuf, errlen, "%s at offset %d", (char *)message, (int)erroffset);
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
return re;
|
||||||
|
}
|
||||||
|
|
||||||
|
void pcre_pattern_free(void *re)
|
||||||
|
{
|
||||||
|
if(re) pcre2_code_free((pcre2_code *)re);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Returns the number of captures placed, or 0 when the subject does not
|
||||||
|
match. Element 0 is the whole match. The match data is per call: a rule is
|
||||||
|
matched from several threads at once.
|
||||||
|
*/
|
||||||
|
int pcre_pattern_match(void *re, const unsigned char *subject, struct capture *caps, int maxcaps)
|
||||||
|
{
|
||||||
|
pcre2_match_data *match_data;
|
||||||
|
PCRE2_SIZE *ovector;
|
||||||
|
int count, i, placed = 0;
|
||||||
|
|
||||||
|
if(!re || !subject) return 0;
|
||||||
|
match_data = pcre2_match_data_create_from_pattern((pcre2_code *)re, NULL);
|
||||||
|
if(!match_data) return 0;
|
||||||
|
|
||||||
|
count = pcre2_match((pcre2_code *)re, (PCRE2_SPTR)subject, PCRE2_ZERO_TERMINATED,
|
||||||
|
0, 0, match_data, NULL);
|
||||||
|
if(count > 0){
|
||||||
|
ovector = pcre2_get_ovector_pointer(match_data);
|
||||||
|
if(count > maxcaps) count = maxcaps;
|
||||||
|
for(i = 0; i < count; i++){
|
||||||
|
if(ovector[i*2] == PCRE2_UNSET){
|
||||||
|
caps[i].start = 0;
|
||||||
|
caps[i].len = 0;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
caps[i].start = (int)ovector[i*2];
|
||||||
|
caps[i].len = (int)(ovector[i*2+1] - ovector[i*2]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
placed = count;
|
||||||
|
}
|
||||||
|
pcre2_match_data_free(match_data);
|
||||||
|
return placed;
|
||||||
|
}
|
||||||
|
|
||||||
void pcre_install(void){
|
void pcre_install(void){
|
||||||
|
|
||||||
struct filter *flt, *tmpflt;
|
struct filter *flt, *tmpflt;
|
||||||
|
|||||||
@ -286,7 +286,7 @@ void processcallbacks(struct fp_stream *fps, int what, char *msg, int size){
|
|||||||
case GOT_SMTP_REQ:
|
case GOT_SMTP_REQ:
|
||||||
case GOT_SMTP_DATA:
|
case GOT_SMTP_DATA:
|
||||||
fps->state = FLUSH_DATA;
|
fps->state = FLUSH_DATA;
|
||||||
pl->socksend(fps->fpd.cp->sostate,fps->fpd.cp->clisock, (unsigned char *)fp_stringtable[1], (int)strlen((char *)fp_stringtable[1]), pl->conf->timeouts[STRING_S]);
|
pl->socksend(NULL, fps->fpd.cp->clisock, (unsigned char *)fp_stringtable[1], (int)strlen((char *)fp_stringtable[1]), pl->conf->timeouts[STRING_S]);
|
||||||
fps->state = state;
|
fps->state = state;
|
||||||
break;
|
break;
|
||||||
case GOT_HTTP_REQUEST:
|
case GOT_HTTP_REQUEST:
|
||||||
@ -298,7 +298,7 @@ void processcallbacks(struct fp_stream *fps, int what, char *msg, int size){
|
|||||||
case GOT_HTTP_SRVDATA:
|
case GOT_HTTP_SRVDATA:
|
||||||
if(!fps->serversent){
|
if(!fps->serversent){
|
||||||
fps->state = FLUSH_DATA;
|
fps->state = FLUSH_DATA;
|
||||||
pl->socksend(fps->fpd.cp->sostate, fps->fpd.cp->clisock, (unsigned char *)fp_stringtable[0], (int)strlen((char *)fp_stringtable[0]), pl->conf->timeouts[STRING_S]);
|
pl->socksend(NULL, fps->fpd.cp->clisock, (unsigned char *)fp_stringtable[0], (int)strlen((char *)fp_stringtable[0]), pl->conf->timeouts[STRING_S]);
|
||||||
fps->state = state;
|
fps->state = state;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -306,7 +306,7 @@ void processcallbacks(struct fp_stream *fps, int what, char *msg, int size){
|
|||||||
case GOT_FTP_REQ:
|
case GOT_FTP_REQ:
|
||||||
case GOT_FTP_SRVDATA:
|
case GOT_FTP_SRVDATA:
|
||||||
fps->state = FLUSH_DATA;
|
fps->state = FLUSH_DATA;
|
||||||
pl->socksend(fps->fpd.cp->sostate, fps->fpd.cp->ctrlsock, (unsigned char *)fp_stringtable[1], (int)strlen((char *)fp_stringtable[1]), pl->conf->timeouts[STRING_S]);
|
pl->socksend(NULL, fps->fpd.cp->ctrlsock, (unsigned char *)fp_stringtable[1], (int)strlen((char *)fp_stringtable[1]), pl->conf->timeouts[STRING_S]);
|
||||||
fps->state = state;
|
fps->state = state;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@ -358,7 +358,7 @@ static int copyfdtosock(struct fp_stream * fps, DIRECTION which, long len){
|
|||||||
if(fps->serversent >= fps->srvhdrwritten){
|
if(fps->serversent >= fps->srvhdrwritten){
|
||||||
sprintf(fps->buf, "%lx\r\n", len);
|
sprintf(fps->buf, "%lx\r\n", len);
|
||||||
sendchunk = (int)strlen(fps->buf);
|
sendchunk = (int)strlen(fps->buf);
|
||||||
if(pl->socksend(fps->fpd.cp->sostate, fps->fpd.cp->clisock, (unsigned char *)fps->buf, sendchunk, pl->conf->timeouts[STRING_S]) != sendchunk){
|
if(pl->socksend(NULL, fps->fpd.cp->clisock, (unsigned char *)fps->buf, sendchunk, pl->conf->timeouts[STRING_S]) != sendchunk){
|
||||||
return -4;
|
return -4;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -397,13 +397,13 @@ static int copyfdtosock(struct fp_stream * fps, DIRECTION which, long len){
|
|||||||
#endif
|
#endif
|
||||||
return -3;
|
return -3;
|
||||||
}
|
}
|
||||||
if(pl->socksend(fps->fpd.cp->sostate, sock, (unsigned char *)fps->buf, res, pl->conf->timeouts[STRING_S]) != res) {
|
if(pl->socksend(NULL, sock, (unsigned char *)fps->buf, res, pl->conf->timeouts[STRING_S]) != res) {
|
||||||
return -4;
|
return -4;
|
||||||
}
|
}
|
||||||
len -= res;
|
len -= res;
|
||||||
}
|
}
|
||||||
if(sendchunk){
|
if(sendchunk){
|
||||||
if(pl->socksend(fps->fpd.cp->sostate, sock, (unsigned char *)"\r\n", 2, pl->conf->timeouts[STRING_S]) != 2)
|
if(pl->socksend(NULL, sock, (unsigned char *)"\r\n", 2, pl->conf->timeouts[STRING_S]) != 2)
|
||||||
return -4;
|
return -4;
|
||||||
}
|
}
|
||||||
fps->state = state;
|
fps->state = state;
|
||||||
|
|||||||
@ -61,14 +61,9 @@ char **load_string(FILE *f,int max_count_str, int *countloadstr,
|
|||||||
|
|
||||||
if (strstr(tmpbuf1,"[end]")==NULL)
|
if (strstr(tmpbuf1,"[end]")==NULL)
|
||||||
{
|
{
|
||||||
/* find and replace \n \r*/
|
/* the line's end cut off: what the file says with \n is what is
|
||||||
rstr = tmpbuf1;
|
sent */
|
||||||
while (*rstr!='\0')
|
tmpbuf1[strcspn(tmpbuf1, "\r\n")] = '\0';
|
||||||
{
|
|
||||||
if (*rstr=='\r' || *rstr=='\n' )
|
|
||||||
{ *rstr='\0'; rstr++; }
|
|
||||||
rstr++;
|
|
||||||
}
|
|
||||||
|
|
||||||
while ((rstr=strstr(tmpbuf1,"\\n")))
|
while ((rstr=strstr(tmpbuf1,"\\n")))
|
||||||
{
|
{
|
||||||
|
|||||||
@ -1,6 +0,0 @@
|
|||||||
# TransparentPlugin
|
|
||||||
# Works on Linux (with netfilter), BSD and macOS (without netfilter support)
|
|
||||||
|
|
||||||
add_3proxy_plugin(TransparentPlugin
|
|
||||||
SOURCES transparent_plugin.c
|
|
||||||
)
|
|
||||||
@ -1 +0,0 @@
|
|||||||
include Makefile.var
|
|
||||||
@ -1,10 +0,0 @@
|
|||||||
all: $(BUILDDIR)TransparentPlugin$(DLSUFFICS)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
transparent_plugin$(OBJSUFFICS): transparent_plugin.c
|
|
||||||
$(CC) $(CFLAGS) $(DCFLAGS) transparent_plugin.c
|
|
||||||
|
|
||||||
|
|
||||||
$(BUILDDIR)TransparentPlugin$(DLSUFFICS): transparent_plugin$(OBJSUFFICS)
|
|
||||||
$(LN) $(LNOUT)../../$(BUILDDIR)TransparentPlugin$(DLSUFFICS) $(LDFLAGS) $(DLFLAGS) transparent_plugin$(OBJSUFFICS)
|
|
||||||
@ -1,128 +0,0 @@
|
|||||||
/*
|
|
||||||
3APA3A simplest proxy server
|
|
||||||
(c) 2002-2026 by Vladimir Dubrovin <vlad@3proxy.org>
|
|
||||||
|
|
||||||
please read License Agreement
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
#ifdef WITH_NETFILTER
|
|
||||||
#include <sys/utsname.h>
|
|
||||||
#endif
|
|
||||||
#include "../../structures.h"
|
|
||||||
#include "../../proxy.h"
|
|
||||||
#ifdef WITH_NETFILTER
|
|
||||||
#include <sys/types.h>
|
|
||||||
#include <sys/socket.h>
|
|
||||||
#include <limits.h>
|
|
||||||
#include <linux/netfilter_ipv4.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
static struct pluginlink * pl;
|
|
||||||
|
|
||||||
static int transparent_loaded = 0;
|
|
||||||
|
|
||||||
static void* transparent_filter_open(void * idata, struct srvparam * param){
|
|
||||||
return idata;
|
|
||||||
}
|
|
||||||
|
|
||||||
static FILTER_ACTION transparent_filter_client(void *fo, struct clientparam * param, void** fc){
|
|
||||||
|
|
||||||
char addrbuf[64];
|
|
||||||
|
|
||||||
#ifdef WITH_NETFILTER
|
|
||||||
socklen_t len;
|
|
||||||
|
|
||||||
len = sizeof(param->req);
|
|
||||||
#ifdef SO_ORIGINAL_DST
|
|
||||||
|
|
||||||
if(getsockopt(param->clisock,
|
|
||||||
#ifndef NOIPV6
|
|
||||||
#ifdef SOL_IPV6
|
|
||||||
*SAFAMILY(¶m->sincr) == AF_INET6?SOL_IPV6:
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
SOL_IP, SO_ORIGINAL_DST,(struct sockaddr *) ¶m->req, &len) || !memcmp((char *)SAADDR(¶m->req), "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", SAADDRLEN(¶m->req))){
|
|
||||||
return PASS;
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
#error No SO_ORIGINAL_DST defined
|
|
||||||
param->srv->logfunc(param, (unsigned char *)"transparent_plugin: No SO_ORIGINAL_DST defined");
|
|
||||||
return REJECT;
|
|
||||||
#endif
|
|
||||||
#else
|
|
||||||
if(*SAFAMILY(¶m->sincl) == AF_INET || *SAFAMILY(¶m->sincl) == AF_INET6){
|
|
||||||
param->req = param->sincl;
|
|
||||||
param->sincl = param->srv->intsa;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
pl->myinet_ntop(*SAFAMILY(¶m->req), SAADDR(¶m->req), (char *)addrbuf, sizeof(addrbuf));
|
|
||||||
if(param->hostname) pl->freefunc(param->hostname);
|
|
||||||
param->hostname = (unsigned char *)pl->strdupfunc(addrbuf);
|
|
||||||
param->sinsr = param->req;
|
|
||||||
return PASS;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static void transparent_filter_clear(void *fo){
|
|
||||||
}
|
|
||||||
|
|
||||||
static void transparent_filter_close(void *fo){
|
|
||||||
}
|
|
||||||
|
|
||||||
static struct filter transparent_filter = {
|
|
||||||
NULL,
|
|
||||||
"Transparent filter",
|
|
||||||
"Transparent filter",
|
|
||||||
transparent_filter_open,
|
|
||||||
transparent_filter_client,
|
|
||||||
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
|
|
||||||
transparent_filter_clear,
|
|
||||||
transparent_filter_close
|
|
||||||
};
|
|
||||||
|
|
||||||
static int h_transparent(int argc, unsigned char **argv){
|
|
||||||
transparent_filter.filter_open = transparent_filter_open;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int h_notransparent(int argc, unsigned char **argv){
|
|
||||||
transparent_filter.filter_open = NULL;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static struct commands transparent_commandhandlers[] = {
|
|
||||||
{transparent_commandhandlers+1, "transparent", h_transparent, 1, 1},
|
|
||||||
{NULL, "notransparent", h_notransparent, 1, 1}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
#ifdef WATCOM
|
|
||||||
#pragma aux transparent_plugin "*" parm caller [ ] value struct float struct routine [eax] modify [eax ecx edx]
|
|
||||||
#undef PLUGINCALL
|
|
||||||
#define PLUGINCALL
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
PLUGINAPI int PLUGINCALL transparent_plugin (struct pluginlink * pluginlink,
|
|
||||||
int argc, char** argv){
|
|
||||||
pl = pluginlink;
|
|
||||||
if(!transparent_loaded){
|
|
||||||
transparent_loaded = 1;
|
|
||||||
transparent_filter.next = pl->conf->filters;
|
|
||||||
pl->conf->filters = &transparent_filter;
|
|
||||||
transparent_commandhandlers[1].next = pl->commandhandlers->next;
|
|
||||||
pl->commandhandlers->next = transparent_commandhandlers;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
}
|
|
||||||
#ifdef __cplusplus
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
14
src/pop3p.c
14
src/pop3p.c
@ -8,6 +8,8 @@
|
|||||||
|
|
||||||
#include "proxy.h"
|
#include "proxy.h"
|
||||||
|
|
||||||
|
#ifdef WITH_POP3P
|
||||||
|
|
||||||
#define RETURN(xxx) { param->res = xxx; goto CLEANRET; }
|
#define RETURN(xxx) { param->res = xxx; goto CLEANRET; }
|
||||||
|
|
||||||
#ifdef WITHMAIN
|
#ifdef WITHMAIN
|
||||||
@ -88,3 +90,15 @@ struct proxydef childdef = {
|
|||||||
};
|
};
|
||||||
#include "proxymain.c"
|
#include "proxymain.c"
|
||||||
#endif
|
#endif
|
||||||
|
#else
|
||||||
|
|
||||||
|
/* Built without this proxy of its own. The command and the redirect
|
||||||
|
naming it are the STARTTLS proxy speaking that protocol, which
|
||||||
|
negotiates the same way and passes the session on: what "tlspr -Xpop3"
|
||||||
|
does, under the name a configuration already uses. */
|
||||||
|
void * pop3pchild(struct clientparam * param){
|
||||||
|
param->starttls = S_POP3P;
|
||||||
|
return (void *)tlsprchild;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|||||||
97
src/proxy.c
97
src/proxy.c
@ -132,8 +132,16 @@ char * proxy_stringtable[] = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
#define LINESIZE 32768
|
#define LINESIZE 32768
|
||||||
|
/* "Content-Length: " plus 20 digits plus CRLF and a NUL, rounded up */
|
||||||
|
#define CLHDRSIZE 48
|
||||||
|
/* what the headers this proxy adds of its own can come to: a Forwarded or
|
||||||
|
Via with a host name in it, a Connection, a Proxy-support and a
|
||||||
|
Proxy-Authorization carrying an encoded user and password */
|
||||||
|
#define HDRRESERVE 2048
|
||||||
#define BUFSIZE (LINESIZE*2)
|
#define BUFSIZE (LINESIZE*2)
|
||||||
#define FTPBUFSIZE 1536
|
#define FTPBUFSIZE 1536
|
||||||
|
/* seconds a connection waits for its next request before its buffer is freed */
|
||||||
|
#define IDLEBUF 1
|
||||||
|
|
||||||
#define PST_COUNT (sizeof(proxy_stringtable)/sizeof(proxy_stringtable[0]) - 1)
|
#define PST_COUNT (sizeof(proxy_stringtable)/sizeof(proxy_stringtable[0]) - 1)
|
||||||
static int proxy_stringtable_len[PST_COUNT];
|
static int proxy_stringtable_len[PST_COUNT];
|
||||||
@ -151,6 +159,34 @@ static int send_st(struct clientparam *param, int idx){
|
|||||||
return socksend(param, param->clisock, (unsigned char *)proxy_stringtable[idx], pst_len(idx), conf.timeouts[STRING_S]);
|
return socksend(param, param->clisock, (unsigned char *)proxy_stringtable[idx], pst_len(idx), conf.timeouts[STRING_S]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Makes room in a buffer whose size is tracked. A filter may hand back one
|
||||||
|
holding exactly what it produced, so nothing may be added to it without
|
||||||
|
asking for the room first. Returns 1 when the room cannot be had. */
|
||||||
|
static int growbuf(unsigned char **buf, int *bufsize, int need){
|
||||||
|
unsigned char *newbuf;
|
||||||
|
|
||||||
|
if(need <= *bufsize) return 0;
|
||||||
|
need += BUFSIZE; /* for what follows too, not just this */
|
||||||
|
if(!(newbuf = realloc(*buf, need))) return 1;
|
||||||
|
*buf = newbuf;
|
||||||
|
*bufsize = need;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Whether a request line names the host the previous one did, whatever its
|
||||||
|
method: the connection to the server is kept for the same host only, and
|
||||||
|
a POST after a GET to it is no reason to open another one. */
|
||||||
|
static int samehost(const unsigned char *line, int len, const unsigned char *host, int hostlen){
|
||||||
|
const unsigned char *p = (const unsigned char *)memchr(line, ' ', len);
|
||||||
|
|
||||||
|
if(!p || hostlen <= 0) return 0;
|
||||||
|
p++;
|
||||||
|
if(!strncasecmp((const char *)p, "http://", 7)) p += 7;
|
||||||
|
if((int)(p - line) + hostlen >= len || strncasecmp((const char *)p, (const char *)host, hostlen))
|
||||||
|
return 0;
|
||||||
|
return p[hostlen] == '/' || p[hostlen] == ' ';
|
||||||
|
}
|
||||||
|
|
||||||
static void freeptr(void *p){
|
static void freeptr(void *p){
|
||||||
void **pp = (void **)p;
|
void **pp = (void **)p;
|
||||||
if(*pp) { free(*pp); *pp = NULL; }
|
if(*pp) { free(*pp); *pp = NULL; }
|
||||||
@ -180,11 +216,16 @@ static void logurl(struct clientparam * param, char * buf, char * req, int ftp){
|
|||||||
char *sb;
|
char *sb;
|
||||||
char *se;
|
char *se;
|
||||||
int len;
|
int len;
|
||||||
|
char *own = NULL;
|
||||||
|
|
||||||
if(!buf) req = NULL;
|
if(param->res == 555 || param->res == 508) return;
|
||||||
if(req) {
|
if(req) {
|
||||||
len = (int)strlen(req);
|
len = (int)strlen(req);
|
||||||
if(len > (LINESIZE - 1)) len = LINESIZE - 1;
|
if(len > (LINESIZE - 1)) len = LINESIZE - 1;
|
||||||
|
/* a tunnel gave its buffer back before it was relayed */
|
||||||
|
if(!buf && !(buf = own = malloc(LINESIZE))) req = NULL;
|
||||||
|
}
|
||||||
|
if(req) {
|
||||||
memcpy(buf, req, len + 1);
|
memcpy(buf, req, len + 1);
|
||||||
buf[LINESIZE - 1] = 0;
|
buf[LINESIZE - 1] = 0;
|
||||||
sb = strchr(buf, '\r');
|
sb = strchr(buf, '\r');
|
||||||
@ -193,7 +234,8 @@ static void logurl(struct clientparam * param, char * buf, char * req, int ftp){
|
|||||||
memmove(se, sb, strlen(sb)+1);
|
memmove(se, sb, strlen(sb)+1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(param->res != 555 && param->res != 508)dolog(param, (unsigned char *)(req?buf:NULL));
|
dolog(param, (unsigned char *)(req?buf:NULL));
|
||||||
|
free(own);
|
||||||
}
|
}
|
||||||
|
|
||||||
void decodeurl(unsigned char *s, int allowcr){
|
void decodeurl(unsigned char *s, int allowcr){
|
||||||
@ -265,7 +307,7 @@ void * proxychild(struct clientparam* param) {
|
|||||||
int clhdrofs_req = -1;
|
int clhdrofs_req = -1;
|
||||||
int isconnect = 0;
|
int isconnect = 0;
|
||||||
int redirect = 0;
|
int redirect = 0;
|
||||||
int prefix = 0, ckeepalive=0;
|
int prefix = 0, hostofs = 0, ckeepalive=0;
|
||||||
int ftp = 0;
|
int ftp = 0;
|
||||||
int ssoff = 0;
|
int ssoff = 0;
|
||||||
#ifndef ANONYMOUS
|
#ifndef ANONYMOUS
|
||||||
@ -289,18 +331,24 @@ void * proxychild(struct clientparam* param) {
|
|||||||
|
|
||||||
|
|
||||||
if(param->remsock != INVALID_SOCKET) haveconnection = 1;
|
if(param->remsock != INVALID_SOCKET) haveconnection = 1;
|
||||||
if(!(buf = malloc(BUFSIZE))) {RETURN(21);}
|
|
||||||
bufsize = BUFSIZE;
|
|
||||||
#ifndef ANONYMOUS
|
#ifndef ANONYMOUS
|
||||||
anonymous = param->srv->anonymous;
|
anonymous = param->srv->anonymous;
|
||||||
#endif
|
#endif
|
||||||
for(;;){
|
for(;;){
|
||||||
memset(buf, 0, bufsize);
|
|
||||||
inbuf = 0;
|
inbuf = 0;
|
||||||
chunkedcli = 0;
|
chunkedcli = 0;
|
||||||
clhdrofs_req = -1;
|
clhdrofs_req = -1;
|
||||||
param->nolongdatfilter = 0;
|
param->nolongdatfilter = 0;
|
||||||
|
|
||||||
|
/* A connection waiting a while for its next request, or a tunnel, holds
|
||||||
|
no buffer: it is taken again once a request starts coming. */
|
||||||
|
if(buf && param->cliinbuf == param->clioffset){
|
||||||
|
memset(fds, 0, sizeof(fds));
|
||||||
|
fds[0].fd = param->clisock;
|
||||||
|
fds[0].events = POLLIN;
|
||||||
|
if(param->srv->so._poll(param->sostate, fds, 1, IDLEBUF * 1000) == 0) freeptr(&buf);
|
||||||
|
}
|
||||||
|
if(buf) memset(buf, 0, bufsize);
|
||||||
|
|
||||||
if(keepalive && (param->cliinbuf == param->clioffset) && (param->remsock != INVALID_SOCKET)){
|
if(keepalive && (param->cliinbuf == param->clioffset) && (param->remsock != INVALID_SOCKET)){
|
||||||
memset(fds, 0, sizeof(fds));
|
memset(fds, 0, sizeof(fds));
|
||||||
@ -326,6 +374,13 @@ for(;;){
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(!buf){
|
||||||
|
if(sockgetcharcli(param, conf.timeouts[STRING_L], 0) == EOF) RETURN((keepalive)?555:508);
|
||||||
|
param->clioffset--;
|
||||||
|
if(!(buf = malloc(BUFSIZE))) {RETURN(21);}
|
||||||
|
bufsize = BUFSIZE;
|
||||||
|
memset(buf, 0, bufsize);
|
||||||
|
}
|
||||||
i = sockgetlinebuf(param, CLIENT, buf, LINESIZE - 1, '\n', conf.timeouts[STRING_L]);
|
i = sockgetlinebuf(param, CLIENT, buf, LINESIZE - 1, '\n', conf.timeouts[STRING_L]);
|
||||||
if(i<=0) {
|
if(i<=0) {
|
||||||
RETURN((keepalive)?555:(i)?507:508);
|
RETURN((keepalive)?555:(i)?507:508);
|
||||||
@ -334,7 +389,7 @@ for(;;){
|
|||||||
buf[i] = 0;
|
buf[i] = 0;
|
||||||
|
|
||||||
if(req) {
|
if(req) {
|
||||||
if(!param->transparent && !param->srv->transparent && (i<=prefix || strncasecmp((char *)buf, (char *)req, prefix))){
|
if(!param->transparent && !param->srv->transparent && !samehost(buf, i, req + hostofs, prefix - hostofs)){
|
||||||
ckeepalive = 0;
|
ckeepalive = 0;
|
||||||
if(param->remsock != INVALID_SOCKET){
|
if(param->remsock != INVALID_SOCKET){
|
||||||
param->srv->so._shutdown(param->sostate, param->remsock, SHUT_RDWR);
|
param->srv->so._shutdown(param->sostate, param->remsock, SHUT_RDWR);
|
||||||
@ -365,10 +420,13 @@ for(;;){
|
|||||||
if (!strncasecmp((char *)sb, "http://", 7)) {
|
if (!strncasecmp((char *)sb, "http://", 7)) {
|
||||||
sb += 7;
|
sb += 7;
|
||||||
}
|
}
|
||||||
|
#ifdef WITH_FTP
|
||||||
else if (!strncasecmp((char *)sb, "ftp://", 6)) {
|
else if (!strncasecmp((char *)sb, "ftp://", 6)) {
|
||||||
|
if(!param->srv->allowftp) RETURN(513);
|
||||||
ftp = 1;
|
ftp = 1;
|
||||||
sb += 6;
|
sb += 6;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
else if(*sb == '/') {
|
else if(*sb == '/') {
|
||||||
param->transparent = 1;
|
param->transparent = 1;
|
||||||
}
|
}
|
||||||
@ -389,6 +447,7 @@ for(;;){
|
|||||||
*se = 0;
|
*se = 0;
|
||||||
}
|
}
|
||||||
prefix = (int)(se - buf);
|
prefix = (int)(se - buf);
|
||||||
|
hostofs = (int)(sb - buf);
|
||||||
su = (unsigned char*)strrchr((char *)sb, '@');
|
su = (unsigned char*)strrchr((char *)sb, '@');
|
||||||
if(su) {
|
if(su) {
|
||||||
su = (unsigned char *)strdup((char *)sb);
|
su = (unsigned char *)strdup((char *)sb);
|
||||||
@ -648,6 +707,10 @@ for(;;){
|
|||||||
RETURN(0);
|
RETURN(0);
|
||||||
}
|
}
|
||||||
if(action != PASS) RETURN(517);
|
if(action != PASS) RETURN(517);
|
||||||
|
/* A filter may have returned a buffer sized to exactly what it produced.
|
||||||
|
The headers this proxy adds of its own go in after it, so the room for
|
||||||
|
them is taken back before anything is written. */
|
||||||
|
if(growbuf(&buf, &bufsize, inbuf + HDRRESERVE)) RETURN(21);
|
||||||
param->nolongdatfilter = 0;
|
param->nolongdatfilter = 0;
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
@ -681,12 +744,14 @@ for(;;){
|
|||||||
contentlength64 = param->cliinbuf;
|
contentlength64 = param->cliinbuf;
|
||||||
param->nolongdatfilter = 1;
|
param->nolongdatfilter = 1;
|
||||||
}
|
}
|
||||||
|
if(growbuf(&buf, &bufsize, (int)strlen((char *)buf) + CLHDRSIZE)) RETURN(21);
|
||||||
sprintf((char*)buf+strlen((char *)buf), "Content-Length: %"PRIu64"\r\n", contentlength64);
|
sprintf((char*)buf+strlen((char *)buf), "Content-Length: %"PRIu64"\r\n", contentlength64);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef WITH_FTP
|
||||||
if(ftp && param->redirtype != R_HTTP){
|
if(ftp && param->redirtype != R_HTTP){
|
||||||
SOCKET s;
|
SOCKET s;
|
||||||
int mode = 0;
|
int mode = 0;
|
||||||
@ -938,12 +1003,14 @@ for(;;){
|
|||||||
}
|
}
|
||||||
RETURN(res);
|
RETURN(res);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
if(isconnect && param->redirtype != R_HTTP) {
|
if(isconnect && param->redirtype != R_HTTP) {
|
||||||
if(param->redirectfunc) {
|
if(param->redirectfunc) {
|
||||||
freeptr(&req); freeptr(&buf); freeptr(&ftpbase);
|
freeptr(&req); freeptr(&buf); freeptr(&ftpbase);
|
||||||
return (void *)param->redirectfunc;
|
return (void *)param->redirectfunc;
|
||||||
}
|
}
|
||||||
|
freeptr(&buf);
|
||||||
param->res = mapsocket(param, conf.timeouts[CONNECTION_L]);
|
param->res = mapsocket(param, conf.timeouts[CONNECTION_L]);
|
||||||
RETURN(param->res);
|
RETURN(param->res);
|
||||||
}
|
}
|
||||||
@ -1158,6 +1225,7 @@ for(;;){
|
|||||||
RETURN(0);
|
RETURN(0);
|
||||||
}
|
}
|
||||||
if(action != PASS) RETURN(517);
|
if(action != PASS) RETURN(517);
|
||||||
|
if(growbuf(&buf, &bufsize, inbuf + HDRRESERVE)) RETURN(21);
|
||||||
|
|
||||||
param->nolongdatfilter = 0;
|
param->nolongdatfilter = 0;
|
||||||
|
|
||||||
@ -1181,6 +1249,7 @@ for(;;){
|
|||||||
}
|
}
|
||||||
if(action != PASS) RETURN(517);
|
if(action != PASS) RETURN(517);
|
||||||
contentlength64 = param->srvinbuf;
|
contentlength64 = param->srvinbuf;
|
||||||
|
if(growbuf(&buf, &bufsize, (int)strlen((char *)buf) + CLHDRSIZE)) RETURN(21);
|
||||||
sprintf((char*)buf+strlen((char *)buf), "Content-Length: %"PRIu64"\r\n", contentlength64);
|
sprintf((char*)buf+strlen((char *)buf), "Content-Length: %"PRIu64"\r\n", contentlength64);
|
||||||
hascontent = 1;
|
hascontent = 1;
|
||||||
}
|
}
|
||||||
@ -1253,6 +1322,7 @@ for(;;){
|
|||||||
} while(param->chunked);
|
} while(param->chunked);
|
||||||
}
|
}
|
||||||
if(isconnect && res == 200 && param->operation){
|
if(isconnect && res == 200 && param->operation){
|
||||||
|
freeptr(&buf);
|
||||||
RETURN (mapsocket(param, conf.timeouts[CONNECTION_S]));
|
RETURN (mapsocket(param, conf.timeouts[CONNECTION_S]));
|
||||||
}
|
}
|
||||||
else if(isconnect){
|
else if(isconnect){
|
||||||
@ -1271,6 +1341,16 @@ REQUESTEND:
|
|||||||
RETURN(0);
|
RETURN(0);
|
||||||
}
|
}
|
||||||
if(param->transparent && (!ckeepalive || !keepalive)) {RETURN (0);}
|
if(param->transparent && (!ckeepalive || !keepalive)) {RETURN (0);}
|
||||||
|
/* Another service read this request and handed it here to be answered. It
|
||||||
|
keeps the connection and decides what the next request on it is, so this
|
||||||
|
one is done. Whatever was opened towards the server stays open in param
|
||||||
|
for the next one. */
|
||||||
|
if(param->onerequest){
|
||||||
|
/* 2 says the client connection may carry another request, 1 that it may
|
||||||
|
not, which is what the service holding it needs to know. */
|
||||||
|
param->onerequest = (ckeepalive && keepalive)? 2 : 1;
|
||||||
|
RETURN(0);
|
||||||
|
}
|
||||||
logurl(param, (char *)buf, (char *)req, ftp);
|
logurl(param, (char *)buf, (char *)req, ftp);
|
||||||
param->status = 0;
|
param->status = 0;
|
||||||
|
|
||||||
@ -1316,6 +1396,9 @@ struct proxydef childdef = {
|
|||||||
S_PROXY,
|
S_PROXY,
|
||||||
"-a - anonymous proxy\r\n"
|
"-a - anonymous proxy\r\n"
|
||||||
"-a1 - anonymous proxy with random client IP spoofing\r\n"
|
"-a1 - anonymous proxy with random client IP spoofing\r\n"
|
||||||
|
#ifdef WITH_FTP
|
||||||
|
"-Xftp - fetch ftp:// URLs\r\n"
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
#include "proxymain.c"
|
#include "proxymain.c"
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
69
src/proxy.h
69
src/proxy.h
@ -86,6 +86,10 @@
|
|||||||
#ifndef EINPROGRESS
|
#ifndef EINPROGRESS
|
||||||
#define EINPROGRESS WSAEWOULDBLOCK
|
#define EINPROGRESS WSAEWOULDBLOCK
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef EADDRINUSE
|
||||||
|
#undef EADDRINUSE
|
||||||
|
#endif
|
||||||
|
#define EADDRINUSE WSAEADDRINUSE
|
||||||
#define EINTR WSAEWOULDBLOCK
|
#define EINTR WSAEWOULDBLOCK
|
||||||
#define SLEEPTIME 1
|
#define SLEEPTIME 1
|
||||||
#define usleep Sleep
|
#define usleep Sleep
|
||||||
@ -152,8 +156,24 @@ void daemonize(void);
|
|||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/* A UDP service answers from the address the client sent to, so the reply has
|
||||||
|
to leave the listening socket. Sharing that socket with the request handler
|
||||||
|
is the simple way, and what every Unix build does.
|
||||||
|
|
||||||
|
Older Windows cannot have two operations in flight on one socket, so a
|
||||||
|
build for it binds a second socket to the same address instead, which needs
|
||||||
|
SO_REUSEADDR on the listening socket as well. A build for those versions
|
||||||
|
asks for that with NO_SHARE_UDP_SOCKET, as Makefile.watcom does.
|
||||||
|
*/
|
||||||
|
#ifndef SHARE_UDP_SOCKET
|
||||||
|
#ifndef NO_SHARE_UDP_SOCKET
|
||||||
|
#define SHARE_UDP_SOCKET
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifndef _WIN32
|
#ifndef _WIN32
|
||||||
size_t threadstacksize(int extra);
|
size_t threadstacksize(int extra);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef WITH_ODBC
|
#ifdef WITH_ODBC
|
||||||
@ -171,7 +191,8 @@ size_t threadstacksize(int extra);
|
|||||||
#ifndef strncasecmp
|
#ifndef strncasecmp
|
||||||
#define strncasecmp strnicmp
|
#define strncasecmp strnicmp
|
||||||
#endif
|
#endif
|
||||||
#define seterrno3(x) _set_errno(x)
|
/* errno is WSAGetLastError() here */
|
||||||
|
#define seterrno3(x) WSASetLastError(x)
|
||||||
#else
|
#else
|
||||||
#define seterrno3(x) (errno = x)
|
#define seterrno3(x) (errno = x)
|
||||||
#endif
|
#endif
|
||||||
@ -219,6 +240,11 @@ extern struct extparam conf;
|
|||||||
|
|
||||||
extern int timeouts[12];
|
extern int timeouts[12];
|
||||||
|
|
||||||
|
/* directions sockmap() passes with splice(): -s1, -s2, -s3; with
|
||||||
|
SPLICE_AUTO (-s alone, -s5 to -s7) only when it pays */
|
||||||
|
#define SPLICE_DOWNLOAD 1
|
||||||
|
#define SPLICE_UPLOAD 2
|
||||||
|
#define SPLICE_AUTO 4
|
||||||
int sockmap(struct clientparam * param, int timeo, int usesplice);
|
int sockmap(struct clientparam * param, int timeo, int usesplice);
|
||||||
int udpsockmap(struct clientparam * param, int timeo);
|
int udpsockmap(struct clientparam * param, int timeo);
|
||||||
int udpbind(struct clientparam * param);
|
int udpbind(struct clientparam * param);
|
||||||
@ -302,13 +328,23 @@ extern _3proxy_sem_t udpinit;
|
|||||||
#define _3proxy_sem_init(x, count, maxcount) (((x) = CreateSemaphore(NULL, (count), (maxcount), NULL))? 0 : 1)
|
#define _3proxy_sem_init(x, count, maxcount) (((x) = CreateSemaphore(NULL, (count), (maxcount), NULL))? 0 : 1)
|
||||||
#define _3proxy_sem_lock(x) WaitForSingleObject(x, INFINITE)
|
#define _3proxy_sem_lock(x) WaitForSingleObject(x, INFINITE)
|
||||||
#define _3proxy_sem_unlock(x) ReleaseSemaphore(x, 1, NULL)
|
#define _3proxy_sem_unlock(x) ReleaseSemaphore(x, 1, NULL)
|
||||||
|
#define _3proxy_sem_init_timed(x, count, maxcount) _3proxy_sem_init(x, (count), (maxcount))
|
||||||
|
#define _3proxy_sem_timedlock(x, ms) (WaitForSingleObject(x, ms) != WAIT_OBJECT_0)
|
||||||
|
#define _3proxy_sem_destroy(x) CloseHandle(x)
|
||||||
#else
|
#else
|
||||||
int _3proxy_sem_init_f(_3proxy_sem_t *sem, unsigned count, unsigned maxcount);
|
int _3proxy_sem_init_f(_3proxy_sem_t *sem, unsigned count, unsigned maxcount, int timed);
|
||||||
void _3proxy_sem_lock_f(_3proxy_sem_t *sem);
|
void _3proxy_sem_lock_f(_3proxy_sem_t *sem);
|
||||||
|
int _3proxy_sem_timedlock_f(_3proxy_sem_t *sem, unsigned ms);
|
||||||
void _3proxy_sem_unlock_f(_3proxy_sem_t *sem);
|
void _3proxy_sem_unlock_f(_3proxy_sem_t *sem);
|
||||||
#define _3proxy_sem_init(x, count, maxcount) _3proxy_sem_init_f(&x, (count), (maxcount))
|
void _3proxy_sem_destroy_f(_3proxy_sem_t *sem);
|
||||||
|
#define _3proxy_sem_init(x, count, maxcount) _3proxy_sem_init_f(&x, (count), (maxcount), 0)
|
||||||
|
/* for a semaphore waited on with a timeout: the wait is not stretched or cut
|
||||||
|
short by the system time being changed, where the platform allows it */
|
||||||
|
#define _3proxy_sem_init_timed(x, count, maxcount) _3proxy_sem_init_f(&x, (count), (maxcount), 1)
|
||||||
#define _3proxy_sem_lock(x) _3proxy_sem_lock_f(&x)
|
#define _3proxy_sem_lock(x) _3proxy_sem_lock_f(&x)
|
||||||
|
#define _3proxy_sem_timedlock(x, ms) _3proxy_sem_timedlock_f(&x, (ms))
|
||||||
#define _3proxy_sem_unlock(x) _3proxy_sem_unlock_f(&x)
|
#define _3proxy_sem_unlock(x) _3proxy_sem_unlock_f(&x)
|
||||||
|
#define _3proxy_sem_destroy(x) _3proxy_sem_destroy_f(&x)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
@ -370,8 +406,25 @@ 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);
|
||||||
|
void nonblock(SOCKET sock);
|
||||||
|
int acceptagain(void);
|
||||||
int bindwithrange(struct clientparam *param, SOCKET sock, PROXYSOCKADDRTYPE *sa, uint32_t range);
|
int bindwithrange(struct clientparam *param, SOCKET sock, PROXYSOCKADDRTYPE *sa, uint32_t range);
|
||||||
|
int bindtodevice(struct sockfuncs *sf, void *state, SOCKET sock, const char *device, int family);
|
||||||
|
int extbindtodevice(struct clientparam *param, SOCKET sock);
|
||||||
|
int intbindtodevice(struct clientparam *param, SOCKET sock);
|
||||||
|
#ifdef WITH_PCRE
|
||||||
|
/* One regular expression implementation for the whole program: the pcre
|
||||||
|
commands and every pattern that carries a pcre: prefix. */
|
||||||
|
void * pcre_pattern_compile(const unsigned char *pattern, char *errbuf, int errlen);
|
||||||
|
void pcre_pattern_free(void *re);
|
||||||
|
int pcre_pattern_match(void *re, const unsigned char *subject, struct capture *caps, int maxcaps);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
int pushbackcli(struct clientparam * param, const unsigned char * data, int len);
|
||||||
int parsepattern(struct hostname *h, unsigned char *arg);
|
int parsepattern(struct hostname *h, unsigned char *arg);
|
||||||
|
int parsepathpattern(struct hostname *h, unsigned char *arg);
|
||||||
|
int patternmatchcaps(const struct hostname *h, const unsigned char *str,
|
||||||
|
struct capture *caps, int *ncaps);
|
||||||
int patternmatch(const struct hostname *h, const unsigned char *str);
|
int patternmatch(const struct hostname *h, const unsigned char *str);
|
||||||
int patternmatchpos(const struct hostname *h, const unsigned char *str, int *start, int *len);
|
int patternmatchpos(const struct hostname *h, const unsigned char *str, int *start, int *len);
|
||||||
void applyportranges(struct clientparam * param, struct ace * acentry);
|
void applyportranges(struct clientparam * param, struct ace * acentry);
|
||||||
@ -422,6 +475,10 @@ struct dictionary;
|
|||||||
struct node;
|
struct node;
|
||||||
struct property;
|
struct property;
|
||||||
extern _3proxy_mutex_t config_mutex;
|
extern _3proxy_mutex_t config_mutex;
|
||||||
|
/* services running, and a post each time one of them has finished */
|
||||||
|
extern int runningservices;
|
||||||
|
extern _3proxy_mutex_t runningservices_mutex;
|
||||||
|
extern _3proxy_sem_t serviceexited;
|
||||||
extern _3proxy_mutex_t bandlim_mutex;
|
extern _3proxy_mutex_t bandlim_mutex;
|
||||||
extern _3proxy_mutex_t connlim_mutex;
|
extern _3proxy_mutex_t connlim_mutex;
|
||||||
extern _3proxy_mutex_t tc_mutex;
|
extern _3proxy_mutex_t tc_mutex;
|
||||||
@ -432,7 +489,7 @@ extern struct datatype datatypes[64];
|
|||||||
extern struct commands commandhandlers[];
|
extern struct commands commandhandlers[];
|
||||||
|
|
||||||
#ifdef WITHSPLICE
|
#ifdef WITHSPLICE
|
||||||
#define mapsocket(a,b) ((a->srv->usesplice && !a->ndatfilterssrv && !a->ndatfilterscli)?sockmap(a,b,1):sockmap(a,b,0))
|
#define mapsocket(a,b) ((a->srv->usesplice && !a->ndatfilterssrv && !a->ndatfilterscli)?sockmap(a,b,a->srv->usesplice):sockmap(a,b,0))
|
||||||
#else
|
#else
|
||||||
#define mapsocket(a,b) sockmap(a,b, 0)
|
#define mapsocket(a,b) sockmap(a,b, 0)
|
||||||
#endif
|
#endif
|
||||||
@ -452,8 +509,10 @@ extern struct socketoptions {
|
|||||||
int opt;
|
int opt;
|
||||||
char * optname;
|
char * optname;
|
||||||
} sockopts[];
|
} sockopts[];
|
||||||
void setopts(SOCKET s, int opts);
|
int getopts(const char *s, struct sockoptset *opts);
|
||||||
|
void setopts(SOCKET s, const struct sockoptset *opts);
|
||||||
char * printopts(char *sep);
|
char * printopts(char *sep);
|
||||||
|
char * printfeatures(char *sep);
|
||||||
|
|
||||||
#ifdef _WINCE
|
#ifdef _WINCE
|
||||||
char * CEToUnicode (const char *str);
|
char * CEToUnicode (const char *str);
|
||||||
|
|||||||
2161
src/proxymain.c
2161
src/proxymain.c
File diff suppressed because it is too large
Load Diff
136
src/redirect.c
136
src/redirect.c
@ -273,45 +273,121 @@ void applyportranges(struct clientparam * param, struct ace * acentry){
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void chainaddr(struct chain * cur, PROXYSOCKADDRTYPE * sa){
|
||||||
|
PROXYSOCKADDRTYPE fresh;
|
||||||
|
|
||||||
|
*sa = cur->addr;
|
||||||
|
if(resolvfunc != myresolver) return;
|
||||||
|
if(!cur->exthost || SAISNULL(&cur->addr)) return;
|
||||||
|
#ifdef WITH_UN
|
||||||
|
if(*SAFAMILY(&cur->addr) == AF_UNIX) return;
|
||||||
|
#endif
|
||||||
|
if(afdetect(cur->exthost) != -1) return;
|
||||||
|
memset(&fresh, 0, sizeof(fresh));
|
||||||
|
if(!getip46(46, cur->exthost, (struct sockaddr *)&fresh)) return;
|
||||||
|
*SAPORT(&fresh) = *SAPORT(&cur->addr);
|
||||||
|
*sa = fresh;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int parentfailed(struct clientparam * param, struct chain * ch){
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for(i = 0; i < param->nfailedparents; i++)
|
||||||
|
if(param->failedparents[i] == ch) return 1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Remember a parent which could not be used for this connection, so that a
|
||||||
|
* retry picks another member of its group. The list is per connection: a
|
||||||
|
* parent which is down for one client is not taken away from the others.
|
||||||
|
*/
|
||||||
|
static void parentfail(struct clientparam * param, struct chain * ch){
|
||||||
|
if(!ch || param->nfailedparents >= MAXFAILEDPARENTS) return;
|
||||||
|
if(parentfailed(param, ch)) return;
|
||||||
|
param->failedparents[param->nfailedparents++] = ch;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Pick the parent to use for one group.
|
||||||
|
*
|
||||||
|
* A group is the members whose weights add up to WEIGHTSCALE, together with
|
||||||
|
* the zero weight members among them. *after is left pointing at the group
|
||||||
|
* after this one, or at NULL. A group which lands within WEIGHTFUZZ of the
|
||||||
|
* whole share counts as a whole one, so that 333 three times over is a group
|
||||||
|
* rather than a group and a remainder of a thousandth.
|
||||||
|
*
|
||||||
|
* A member which already failed for this connection is not offered again and
|
||||||
|
* its weight is given to the others, so a retry goes somewhere else. Zero
|
||||||
|
* weight members are the fallback: they are only offered once every weighted
|
||||||
|
* member of the group has failed, and they never take part in the share.
|
||||||
|
* Where the weights add up to plainly less than the whole share the remainder
|
||||||
|
* keeps its meaning of "no parent at all" and is still counted, so such a
|
||||||
|
* group can still leave the connection direct.
|
||||||
|
*
|
||||||
|
* Returns NULL when the group adds no parent. *exhausted tells the two cases
|
||||||
|
* apart: it is set when the group had parents and all of them are gone, which
|
||||||
|
* is a failure rather than a reason to connect directly.
|
||||||
|
*/
|
||||||
|
static struct chain * pickchain(struct clientparam * param, struct chain * group,
|
||||||
|
struct chain ** after, int * exhausted){
|
||||||
|
struct chain *cur;
|
||||||
|
uint64_t total = 0, avail = 0, slack;
|
||||||
|
uint64_t r;
|
||||||
|
|
||||||
|
*exhausted = 0;
|
||||||
|
for(cur = group; cur; cur = cur->next){
|
||||||
|
if(total + WEIGHTFUZZ >= WEIGHTSCALE && cur->weight) break;
|
||||||
|
total += cur->weight;
|
||||||
|
if(cur->weight && !parentfailed(param, cur)) avail += cur->weight;
|
||||||
|
}
|
||||||
|
*after = cur;
|
||||||
|
|
||||||
|
if(avail){
|
||||||
|
slack = (total + WEIGHTFUZZ < WEIGHTSCALE)? WEIGHTSCALE - total : 0;
|
||||||
|
r = ((uint64_t)myrand() << 32 | myrand()) % (avail + slack);
|
||||||
|
for(cur = group; cur != *after; cur = cur->next){
|
||||||
|
if(!cur->weight || parentfailed(param, cur)) continue;
|
||||||
|
if(r < cur->weight) return cur;
|
||||||
|
r -= cur->weight;
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
for(cur = group; cur != *after; cur = cur->next){
|
||||||
|
if(!cur->weight && !parentfailed(param, cur)) return cur;
|
||||||
|
}
|
||||||
|
if(total) *exhausted = 1;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
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 res;
|
int res;
|
||||||
int done = 0;
|
|
||||||
int ha = 0;
|
int ha = 0;
|
||||||
struct chain * cur;
|
struct chain * cur;
|
||||||
|
struct chain * after;
|
||||||
struct chain * redir = NULL;
|
struct chain * redir = NULL;
|
||||||
int r2;
|
|
||||||
int saved = 0;
|
int saved = 0;
|
||||||
|
|
||||||
if((SAISNULL(¶m->req) || !*SAPORT(¶m->req)) && param->operation != UDPASSOC) {
|
if((SAISNULL(¶m->req) || !*SAPORT(¶m->req)) && param->operation != UDPASSOC) {
|
||||||
return 100;
|
return 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
r2 = (myrand()%1000);
|
for(cur = acentry->chains; cur; cur = after){
|
||||||
|
struct chain * sel;
|
||||||
|
int exhausted;
|
||||||
|
|
||||||
for(cur = acentry->chains; cur; cur=cur->next){
|
sel = pickchain(param, cur, &after, &exhausted);
|
||||||
if(((weight = weight - cur->weight) > r2)|| done) {
|
/* every parent of the group is gone: connecting direct instead
|
||||||
if(weight <= 0) {
|
would be a way around the rule, so the request fails */
|
||||||
weight += 1000;
|
if(exhausted) return 13;
|
||||||
done = 0;
|
if(!sel) continue;
|
||||||
r2 = (myrand()%1000);
|
cur = sel;
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if(cur->type != R_EXTIP && cur->type != R_HA &&
|
if(cur->type != R_EXTIP && cur->type != R_HA &&
|
||||||
cur->type != R_EXTPORT && cur->type != R_INTPORT) param->redirected++;
|
cur->type != R_EXTPORT && cur->type != R_INTPORT) param->redirected++;
|
||||||
done = 1;
|
|
||||||
if(weight <= 0) {
|
|
||||||
weight += 1000;
|
|
||||||
done = 0;
|
|
||||||
r2 = (myrand()%1000);
|
|
||||||
}
|
|
||||||
if(!connected){
|
if(!connected){
|
||||||
if(cur->type == R_EXTPORT || cur->type == R_INTPORT){
|
if(cur->type == R_EXTPORT || cur->type == R_INTPORT){
|
||||||
if(cur->type == R_EXTPORT) param->extport = cur->range;
|
if(cur->type == R_EXTPORT) param->extport = cur->range;
|
||||||
else param->intport = cur->range;
|
else param->intport = cur->range;
|
||||||
if(cur->next)continue;
|
if(after)continue;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
if(cur->type == R_EXTIP){
|
if(cur->type == R_EXTIP){
|
||||||
@ -333,7 +409,7 @@ int handleredirect(struct clientparam * param, struct ace * acentry){
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
if(cur->next)continue;
|
if(after)continue;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
else if(SAISNULL(&cur->addr) && !*SAPORT(&cur->addr)){
|
else if(SAISNULL(&cur->addr) && !*SAPORT(&cur->addr)){
|
||||||
@ -359,18 +435,18 @@ int handleredirect(struct clientparam * param, struct ace * acentry){
|
|||||||
if(cur->type == R_HA){
|
if(cur->type == R_HA){
|
||||||
ha = 1;
|
ha = 1;
|
||||||
}
|
}
|
||||||
if(cur->next)continue;
|
if(after)continue;
|
||||||
if(!ha) return 0;
|
if(!ha) return 0;
|
||||||
if(param->operation == UDPASSOC) return 0;
|
if(param->operation == UDPASSOC) return 0;
|
||||||
}
|
}
|
||||||
else if(!*SAPORT(&cur->addr) && !SAISNULL(&cur->addr)) {
|
else if(!*SAPORT(&cur->addr) && !SAISNULL(&cur->addr)) {
|
||||||
uint16_t port = *SAPORT(¶m->sinsr);
|
uint16_t port = *SAPORT(¶m->sinsr);
|
||||||
param->sinsr = cur->addr;
|
chainaddr(cur, ¶m->sinsr);
|
||||||
*SAPORT(¶m->sinsr) = port;
|
*SAPORT(¶m->sinsr) = port;
|
||||||
}
|
}
|
||||||
else if(SAISNULL(&cur->addr) && *SAPORT(&cur->addr)) *SAPORT(¶m->sinsr) = *SAPORT(&cur->addr);
|
else if(SAISNULL(&cur->addr) && *SAPORT(&cur->addr)) *SAPORT(¶m->sinsr) = *SAPORT(&cur->addr);
|
||||||
else {
|
else {
|
||||||
param->sinsr = cur->addr;
|
chainaddr(cur, ¶m->sinsr);
|
||||||
}
|
}
|
||||||
if(param->operation == UDPASSOC){
|
if(param->operation == UDPASSOC){
|
||||||
SOCKET s;
|
SOCKET s;
|
||||||
@ -381,6 +457,7 @@ int handleredirect(struct clientparam * param, struct ace * acentry){
|
|||||||
saved = 1;
|
saved = 1;
|
||||||
}
|
}
|
||||||
if((res = alwaysauth(param))){
|
if((res = alwaysauth(param))){
|
||||||
|
parentfail(param, cur);
|
||||||
return (res >= 10)? res : 60+res;
|
return (res >= 10)? res : 60+res;
|
||||||
}
|
}
|
||||||
if(ha) {
|
if(ha) {
|
||||||
@ -400,8 +477,14 @@ int handleredirect(struct clientparam * param, struct ace * acentry){
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
res = (redir)?clientnegotiate(redir, param, (struct sockaddr *)&cur->addr, cur->exthost):0;
|
PROXYSOCKADDRTYPE next;
|
||||||
if(res) return res;
|
|
||||||
|
chainaddr(cur, &next);
|
||||||
|
res = (redir)?clientnegotiate(redir, param, (struct sockaddr *)&next, cur->exthost):0;
|
||||||
|
if(res) {
|
||||||
|
parentfail(param, cur);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
redir = cur;
|
redir = cur;
|
||||||
param->redirtype = redir->type;
|
param->redirtype = redir->type;
|
||||||
@ -425,6 +508,7 @@ int handleredirect(struct clientparam * param, struct ace * acentry){
|
|||||||
|
|
||||||
if(!connected || !redir) return 0;
|
if(!connected || !redir) return 0;
|
||||||
res = clientnegotiate(redir, param, (struct sockaddr *)¶m->req, param->hostname);
|
res = clientnegotiate(redir, param, (struct sockaddr *)¶m->req, param->hostname);
|
||||||
|
if(res) parentfail(param, redir);
|
||||||
if(saved){
|
if(saved){
|
||||||
SOCKET s;
|
SOCKET s;
|
||||||
|
|
||||||
|
|||||||
@ -167,6 +167,7 @@ uint32_t udpresolve(int af, unsigned char * name, unsigned char * value, uint32_
|
|||||||
k += 4;
|
k += 4;
|
||||||
if(na > 255) na = 255; /* somebody is very evil */
|
if(na > 255) na = 255; /* somebody is very evil */
|
||||||
for (j = 0; j < na; j++) { /* now there should be answers */
|
for (j = 0; j < na; j++) { /* now there should be answers */
|
||||||
|
if(k >= len) break;
|
||||||
while(buf[k] < 192 && buf[k] !=0 && (k+buf[k]+14) < len) k+= (buf[k] + 1);
|
while(buf[k] < 192 && buf[k] !=0 && (k+buf[k]+14) < len) k+= (buf[k] + 1);
|
||||||
if(!buf[k]) k--;
|
if(!buf[k]) k--;
|
||||||
if((k+(af == AF_INET6?28:16)) > len) {
|
if((k+(af == AF_INET6?28:16)) > len) {
|
||||||
@ -195,15 +196,17 @@ uint32_t udpresolve(int af, unsigned char * name, unsigned char * value, uint32_
|
|||||||
k+= (12 + flen);
|
k+= (12 + flen);
|
||||||
continue; /* we need A PTR */
|
continue; /* we need A PTR */
|
||||||
}
|
}
|
||||||
for (s2 = buf + k + 12; s2 < (buf + k + 12 + len) && *s2; ){
|
s1 = buf + k + 12 + flen; /* the end of this record's data, inside the answer */
|
||||||
s1 = s2 + ((unsigned)*s2) + 1;
|
for (s2 = buf + k + 12; s2 < s1 && *s2 && *s2 < 64; ){
|
||||||
|
unsigned char *next = s2 + ((unsigned)*s2) + 1;
|
||||||
*s2 = '.';
|
*s2 = '.';
|
||||||
s2 = s1;
|
s2 = next;
|
||||||
}
|
}
|
||||||
*s2 = 0;
|
if(s2 >= s1 || *s2 || s2 == buf + k + 12)
|
||||||
|
break; /* the name runs past its data, is compressed or empty */
|
||||||
if(param->username)free(param->username);
|
if(param->username)free(param->username);
|
||||||
param->username = (unsigned char *)strdup ((char *)buf + k + 13);
|
param->username = (unsigned char *)strdup ((char *)buf + k + 13);
|
||||||
|
|
||||||
return udpresolve(af,param->username, value, NULL, NULL, 2);
|
return udpresolve(af,param->username, value, NULL, NULL, 2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
110
src/sem.c
Normal file
110
src/sem.c
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
/*
|
||||||
|
3APA3A simplest proxy server
|
||||||
|
(c) 2002-2026 by Vladimir Dubrovin <vlad@3proxy.org>
|
||||||
|
|
||||||
|
please read License Agreement
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Counting semaphores for POSIX systems, built on a mutex and a condition
|
||||||
|
variable: unnamed POSIX semaphores are not available everywhere (macOS)
|
||||||
|
and have no timed wait there. Windows uses its own semaphores, see proxy.h. */
|
||||||
|
|
||||||
|
#include "proxy.h"
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
|
/* macOS has CLOCK_MONOTONIC but no pthread_condattr_setclock(); a relative
|
||||||
|
wait is used there instead, which the system time does not affect either */
|
||||||
|
#if defined(CLOCK_MONOTONIC) && !defined(__APPLE__)
|
||||||
|
#define SEM_MONOTONIC
|
||||||
|
#endif
|
||||||
|
|
||||||
|
int _3proxy_sem_init_f(_3proxy_sem_t *sem, unsigned count, unsigned maxcount, int timed){
|
||||||
|
int res;
|
||||||
|
|
||||||
|
sem->count = count;
|
||||||
|
sem->maxcount = maxcount;
|
||||||
|
sem->monotonic = 0;
|
||||||
|
if(pthread_mutex_init(&sem->mutex, NULL)) return 1;
|
||||||
|
#ifdef SEM_MONOTONIC
|
||||||
|
if(timed){
|
||||||
|
pthread_condattr_t attr;
|
||||||
|
|
||||||
|
if(!pthread_condattr_init(&attr)){
|
||||||
|
if(!pthread_condattr_setclock(&attr, CLOCK_MONOTONIC)) sem->monotonic = 1;
|
||||||
|
res = pthread_cond_init(&sem->cond, &attr);
|
||||||
|
pthread_condattr_destroy(&attr);
|
||||||
|
}
|
||||||
|
else res = pthread_cond_init(&sem->cond, NULL);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
#endif
|
||||||
|
res = pthread_cond_init(&sem->cond, NULL);
|
||||||
|
if(res){
|
||||||
|
pthread_mutex_destroy(&sem->mutex);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _3proxy_sem_lock_f(_3proxy_sem_t *sem){
|
||||||
|
pthread_mutex_lock(&sem->mutex);
|
||||||
|
while(!sem->count) pthread_cond_wait(&sem->cond, &sem->mutex);
|
||||||
|
sem->count--;
|
||||||
|
pthread_mutex_unlock(&sem->mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Returns 0 with the semaphore taken, nonzero if ms passed first. */
|
||||||
|
int _3proxy_sem_timedlock_f(_3proxy_sem_t *sem, unsigned ms){
|
||||||
|
struct timespec ts;
|
||||||
|
int res = 0;
|
||||||
|
|
||||||
|
#ifdef __APPLE__
|
||||||
|
ts.tv_sec = ms / 1000;
|
||||||
|
ts.tv_nsec = (long)(ms % 1000) * 1000000;
|
||||||
|
pthread_mutex_lock(&sem->mutex);
|
||||||
|
while(!sem->count && !res) res = pthread_cond_timedwait_relative_np(&sem->cond, &sem->mutex, &ts);
|
||||||
|
#else
|
||||||
|
/* the deadline is on the clock the condition variable was set up with */
|
||||||
|
#ifdef SEM_MONOTONIC
|
||||||
|
if(sem->monotonic) clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||||
|
else
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
struct timeval tv;
|
||||||
|
|
||||||
|
gettimeofday(&tv, NULL);
|
||||||
|
ts.tv_sec = tv.tv_sec;
|
||||||
|
ts.tv_nsec = tv.tv_usec * 1000;
|
||||||
|
}
|
||||||
|
ts.tv_sec += ms / 1000;
|
||||||
|
ts.tv_nsec += (long)(ms % 1000) * 1000000;
|
||||||
|
if(ts.tv_nsec >= 1000000000){
|
||||||
|
ts.tv_sec++;
|
||||||
|
ts.tv_nsec -= 1000000000;
|
||||||
|
}
|
||||||
|
pthread_mutex_lock(&sem->mutex);
|
||||||
|
while(!sem->count && !res) res = pthread_cond_timedwait(&sem->cond, &sem->mutex, &ts);
|
||||||
|
#endif
|
||||||
|
if(sem->count){
|
||||||
|
sem->count--;
|
||||||
|
res = 0;
|
||||||
|
}
|
||||||
|
pthread_mutex_unlock(&sem->mutex);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _3proxy_sem_unlock_f(_3proxy_sem_t *sem){
|
||||||
|
pthread_mutex_lock(&sem->mutex);
|
||||||
|
if(sem->count < sem->maxcount){
|
||||||
|
sem->count++;
|
||||||
|
pthread_cond_signal(&sem->cond);
|
||||||
|
}
|
||||||
|
pthread_mutex_unlock(&sem->mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _3proxy_sem_destroy_f(_3proxy_sem_t *sem){
|
||||||
|
pthread_cond_destroy(&sem->cond);
|
||||||
|
pthread_mutex_destroy(&sem->mutex);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
21
src/smtpp.c
21
src/smtpp.c
@ -8,6 +8,8 @@
|
|||||||
|
|
||||||
#include "proxy.h"
|
#include "proxy.h"
|
||||||
|
|
||||||
|
#ifdef WITH_SMTPP
|
||||||
|
|
||||||
#define RETURN(xxx) { param->res = xxx; goto CLEANRET; }
|
#define RETURN(xxx) { param->res = xxx; goto CLEANRET; }
|
||||||
|
|
||||||
#ifdef WITHMAIN
|
#ifdef WITHMAIN
|
||||||
@ -159,7 +161,10 @@ void * smtppchild(struct clientparam* param) {
|
|||||||
i = de64(buf,username,255);
|
i = de64(buf,username,255);
|
||||||
if(i < 1) {RETURN(664);}
|
if(i < 1) {RETURN(664);}
|
||||||
username[i] = 0;
|
username[i] = 0;
|
||||||
parseconnusername((char *)username, param, 0, 587);
|
/* The name has to carry the host to connect to, and the answer says
|
||||||
|
whether it did: without one there is nowhere to go, and what follows
|
||||||
|
reads the name as if there were. */
|
||||||
|
if(parseconnusername((char *)username, param, 0, 587)) {RETURN(669);}
|
||||||
socksend(param, param->clisock, (unsigned char *)"334 UGFzc3dvcmQ6\r\n", 18,conf.timeouts[STRING_S]);
|
socksend(param, param->clisock, (unsigned char *)"334 UGFzc3dvcmQ6\r\n", 18,conf.timeouts[STRING_S]);
|
||||||
i = sockgetlinebuf(param, CLIENT, buf, sizeof(buf) - 10, '\n', conf.timeouts[STRING_S]);
|
i = sockgetlinebuf(param, CLIENT, buf, sizeof(buf) - 10, '\n', conf.timeouts[STRING_S]);
|
||||||
if(i < 2) {RETURN(665);}
|
if(i < 2) {RETURN(665);}
|
||||||
@ -184,7 +189,7 @@ void * smtppchild(struct clientparam* param) {
|
|||||||
}
|
}
|
||||||
if(i < 3 || *username) {RETURN(668);}
|
if(i < 3 || *username) {RETURN(668);}
|
||||||
username[i] = 0;
|
username[i] = 0;
|
||||||
parseconnusername((char *)username+1, param, 0, 587);
|
if(parseconnusername((char *)username+1, param, 0, 587)) {RETURN(670);}
|
||||||
res = (int)strlen((char *)username+1) + 2;
|
res = (int)strlen((char *)username+1) + 2;
|
||||||
if(res < i){
|
if(res < i){
|
||||||
if(param->extpassword) free(param->extpassword);
|
if(param->extpassword) free(param->extpassword);
|
||||||
@ -339,3 +344,15 @@ struct proxydef childdef = {
|
|||||||
};
|
};
|
||||||
#include "proxymain.c"
|
#include "proxymain.c"
|
||||||
#endif
|
#endif
|
||||||
|
#else
|
||||||
|
|
||||||
|
/* Built without this proxy of its own. The command and the redirect
|
||||||
|
naming it are the STARTTLS proxy speaking that protocol, which
|
||||||
|
negotiates the same way and passes the session on: what "tlspr -Xsmtp"
|
||||||
|
does, under the name a configuration already uses. */
|
||||||
|
void * smtppchild(struct clientparam * param){
|
||||||
|
param->starttls = S_SMTPP;
|
||||||
|
return (void *)tlsprchild;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|||||||
@ -60,14 +60,21 @@ int sockrecvfrom(struct clientparam *param, SOCKET sock, struct sockaddr * sin,
|
|||||||
|
|
||||||
fds.fd = sock;
|
fds.fd = sock;
|
||||||
fds.events = POLLIN;
|
fds.events = POLLIN;
|
||||||
if(conf.timetoexit) return EOF;
|
for(;;){
|
||||||
res = param?param->srv->so._poll(param->sostate, &fds, 1, to):so._poll(so.state, &fds, 1, to);
|
if(conf.timetoexit) return EOF;
|
||||||
if (res<1) return 0;
|
res = param?param->srv->so._poll(param->sostate, &fds, 1, to):so._poll(so.state, &fds, 1, to);
|
||||||
sasize = SASIZE(sin);
|
if (res<1) return 0;
|
||||||
do {
|
sasize = sizeof(PROXYSOCKADDRTYPE);
|
||||||
res = param?param->srv->so._recvfrom(param->sostate, sock, (char *)buf, bufsize, 0, (struct sockaddr *)sin, &sasize):so._recvfrom(so.state, sock, (char *)buf, bufsize, 0, (struct sockaddr *)sin, &sasize);
|
/* sin NULL for a stream socket: nothing to learn the address into */
|
||||||
} while (res < 0 && (errno == EAGAIN || errno == EINTR));
|
do {
|
||||||
return res;
|
res = param?param->srv->so._recvfrom(param->sostate, sock, (char *)buf, bufsize, 0, (struct sockaddr *)sin, sin? &sasize : NULL):so._recvfrom(so.state, sock, (char *)buf, bufsize, 0, (struct sockaddr *)sin, sin? &sasize : NULL);
|
||||||
|
} while (res < 0 && errno == EINTR);
|
||||||
|
/* readable and still nothing to read: over TLS a record not yet
|
||||||
|
whole. Read again, without polling, it would only be the same until
|
||||||
|
the rest came, a loop taking all of a CPU and never timing out: the
|
||||||
|
socket is polled for it instead. */
|
||||||
|
if(res >= 0 || errno != EAGAIN) return res;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int sockgetcharcli(struct clientparam * param, int timeosec, int timeousec){
|
int sockgetcharcli(struct clientparam * param, int timeosec, int timeousec){
|
||||||
@ -88,6 +95,35 @@ int sockgetcharcli(struct clientparam * param, int timeosec, int timeousec){
|
|||||||
return (int)*param->clibuf;
|
return (int)*param->clibuf;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Put bytes back in front of whatever the client has not been read yet, so a
|
||||||
|
service which has already taken a request off the socket can hand it to
|
||||||
|
another one, which reads it the way it reads anything else. */
|
||||||
|
int pushbackcli(struct clientparam * param, const unsigned char * data, int len){
|
||||||
|
unsigned left = 0;
|
||||||
|
unsigned need;
|
||||||
|
|
||||||
|
if(len <= 0) return 0;
|
||||||
|
if(param->clibuf) left = param->cliinbuf - param->clioffset;
|
||||||
|
need = (unsigned)len + left;
|
||||||
|
|
||||||
|
if(!param->clibuf){
|
||||||
|
if(!(param->clibuf = malloc(need > SRVBUFSIZE? need : SRVBUFSIZE))) return 1;
|
||||||
|
param->clibufsize = need > SRVBUFSIZE? need : SRVBUFSIZE;
|
||||||
|
}
|
||||||
|
else if(param->clibufsize < need){
|
||||||
|
unsigned char *nb = realloc(param->clibuf, need);
|
||||||
|
|
||||||
|
if(!nb) return 1;
|
||||||
|
param->clibuf = nb;
|
||||||
|
param->clibufsize = need;
|
||||||
|
}
|
||||||
|
if(left) memmove(param->clibuf + len, param->clibuf + param->clioffset, left);
|
||||||
|
memcpy(param->clibuf, data, (size_t)len);
|
||||||
|
param->clioffset = 0;
|
||||||
|
param->cliinbuf = need;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
unsigned long sockfillbuffcli(struct clientparam * param, unsigned long size, int timeosec){
|
unsigned long sockfillbuffcli(struct clientparam * param, unsigned long size, int timeosec){
|
||||||
int len;
|
int len;
|
||||||
|
|
||||||
|
|||||||
271
src/sockmap.c
271
src/sockmap.c
@ -39,6 +39,18 @@ ssize_t splice(int fd_in, loff_t *off_in, int fd_out, loff_t *off_out, size_t le
|
|||||||
|
|
||||||
#define MAXSPLICE 65536
|
#define MAXSPLICE 65536
|
||||||
|
|
||||||
|
/* With SPLICE_AUTO, when a direction allowed to splice actually does
|
||||||
|
(without it, an allowed direction always does).
|
||||||
|
A body of known length is spliced if at least SPLICE_MIN of it is left
|
||||||
|
to read once what is already buffered is counted: a smaller one, or one
|
||||||
|
read whole with its headers, is cheaper to copy.
|
||||||
|
A stream of unknown length starts copied and is spliced after
|
||||||
|
SPLICE_STREAK reads in a row of at least SPLICE_MIN each, and goes back to
|
||||||
|
copying after SPLICE_STREAK+1 splices in a row of less than SPLICE_SMALL. */
|
||||||
|
#define SPLICE_MIN 16384
|
||||||
|
#define SPLICE_SMALL 4096
|
||||||
|
#define SPLICE_STREAK 2
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#define MIN(a,b) ((a>b)?b:a)
|
#define MIN(a,b) ((a>b)?b:a)
|
||||||
@ -49,6 +61,31 @@ ssize_t splice(int fd_in, loff_t *off_in, int fd_out, loff_t *off_out, size_t le
|
|||||||
before the sockets are closed. */
|
before the sockets are closed. */
|
||||||
#define SESSIONEND (!halfclose && (CLIENTTERMREAD || SERVERTERMREAD))
|
#define SESSIONEND (!halfclose && (CLIENTTERMREAD || SERVERTERMREAD))
|
||||||
|
|
||||||
|
/* A hang-up on a socket there is data to write to. With poll() it comes
|
||||||
|
once both sides are shut down, and the write tried then fails, which ends
|
||||||
|
it. WSAPoll reports it as soon as the peer has half-closed, a peer still
|
||||||
|
reading, and keeps reporting it whether the socket has room or not: room
|
||||||
|
is waited for with select() then, up to timeo seconds. Returns whether to
|
||||||
|
try the write. */
|
||||||
|
static int hupwrite(SOCKET s, short revents, int timeo){
|
||||||
|
#ifdef _WIN32
|
||||||
|
if(!(revents & POLLOUT)){
|
||||||
|
fd_set wfd;
|
||||||
|
struct timeval tv;
|
||||||
|
|
||||||
|
FD_ZERO(&wfd);
|
||||||
|
FD_SET(s, &wfd);
|
||||||
|
tv.tv_sec = timeo;
|
||||||
|
tv.tv_usec = 0;
|
||||||
|
if(select(0, NULL, &wfd, NULL, &tv) < 1) return 0;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* usesplice is a mask of the directions passed with splice() rather than
|
||||||
|
through a buffer: SPLICE_DOWNLOAD, server to client, and SPLICE_UPLOAD,
|
||||||
|
client to server. */
|
||||||
int sockmap(struct clientparam * param, int timeo, int usesplice){
|
int sockmap(struct clientparam * param, int timeo, int usesplice){
|
||||||
int halfclose = param->srv->halfclose;
|
int halfclose = param->srv->halfclose;
|
||||||
uint64_t fromclient=0x7fffffffffffffff, fromserver =0x7fffffffffffffff;
|
uint64_t fromclient=0x7fffffffffffffff, fromserver =0x7fffffffffffffff;
|
||||||
@ -60,13 +97,16 @@ int sockmap(struct clientparam * param, int timeo, int usesplice){
|
|||||||
#endif
|
#endif
|
||||||
int HASERROR=0;
|
int HASERROR=0;
|
||||||
int CLIENTTERMREAD = 0, CLIENTTERMWRITE = 0, SERVERTERMREAD = 0, SERVERTERMWRITE = 0;
|
int CLIENTTERMREAD = 0, CLIENTTERMWRITE = 0, SERVERTERMREAD = 0, SERVERTERMWRITE = 0;
|
||||||
|
/* the side has hung up: closed by the peer after its own was shut down */
|
||||||
|
int CLIENTHUP = 0, SERVERHUP = 0;
|
||||||
|
/* whether the side's socket is in this round's poll() */
|
||||||
|
int clipolled = 1, srvpolled = 1;
|
||||||
int after = 0;
|
int after = 0;
|
||||||
struct pollfd fds[8];
|
struct pollfd fds[8];
|
||||||
int fdsc = 0;
|
int fdsc = 0;
|
||||||
int sleeptime = 0;
|
int sleeptime = 0;
|
||||||
FILTER_ACTION action;
|
FILTER_ACTION action;
|
||||||
int res = 0;
|
int res = 0;
|
||||||
SASIZETYPE sasize;
|
|
||||||
int needaction = 0;
|
int needaction = 0;
|
||||||
int graceclinum=0, gracesrvnum=0, graceclitraf=0, gracesrvtraf=0;
|
int graceclinum=0, gracesrvnum=0, graceclitraf=0, gracesrvtraf=0;
|
||||||
time_t gracetime = 0;
|
time_t gracetime = 0;
|
||||||
@ -76,16 +116,30 @@ int sockmap(struct clientparam * param, int timeo, int usesplice){
|
|||||||
#ifdef WITHSPLICE
|
#ifdef WITHSPLICE
|
||||||
uint64_t inclientpipe = 0, inserverpipe = 0;
|
uint64_t inclientpipe = 0, inserverpipe = 0;
|
||||||
int TOCLIENTPIPE = 0, FROMCLIENTPIPE = 0, TOSERVERPIPE = 0, FROMSERVERPIPE = 0;
|
int TOCLIENTPIPE = 0, FROMCLIENTPIPE = 0, TOSERVERPIPE = 0, FROMSERVERPIPE = 0;
|
||||||
int pipesrv[2] = {-1,-1};
|
/* The client's data goes through pipecli, the server's through pipesrv.
|
||||||
int pipecli[2] = {-1,-1};
|
They belong to the connection: made when first needed, kept for the
|
||||||
|
next mapping, closed with the connection. */
|
||||||
|
int *pipecli = param->pipecli, *pipesrv = param->pipesrv;
|
||||||
|
/* allowed to splice (-s), splicing now, and a run of reads counted
|
||||||
|
towards switching; wantbuf: go back to copying once the pipe is empty */
|
||||||
|
int allowcli = 0, allowsrv = 0, splicecli = 0, splicesrv = 0, autosplice;
|
||||||
|
int streakcli = 0, streaksrv = 0, wantbufcli = 0, wantbufsrv = 0;
|
||||||
|
|
||||||
if((!param->nolongdatfilter && (param->ndatfilterscli > 0 || param->ndatfilterssrv))) usesplice = 0;
|
if((!param->nolongdatfilter && (param->ndatfilterscli > 0 || param->ndatfilterssrv))) usesplice = 0;
|
||||||
if(usesplice){
|
allowcli = (usesplice & SPLICE_UPLOAD) != 0;
|
||||||
TOCLIENTPIPE = FROMCLIENTPIPE = TOSERVERPIPE = FROMSERVERPIPE = 1;
|
allowsrv = (usesplice & SPLICE_DOWNLOAD) != 0;
|
||||||
TOCLIENTBUF = TOSERVERBUF = 0;
|
autosplice = (usesplice & SPLICE_AUTO) != 0;
|
||||||
if(pipe2(pipecli, O_NONBLOCK) < 0) RETURN (21);
|
if(!autosplice){
|
||||||
if(pipe2(pipesrv, O_NONBLOCK) < 0) RETURN (21);
|
splicecli = allowcli;
|
||||||
|
splicesrv = allowsrv;
|
||||||
}
|
}
|
||||||
|
TOCLIENTPIPE = FROMCLIENTPIPE = allowcli;
|
||||||
|
TOSERVERPIPE = FROMSERVERPIPE = allowsrv;
|
||||||
|
#else
|
||||||
|
#define splicecli 0
|
||||||
|
#define splicesrv 0
|
||||||
|
#define wantbufcli 0
|
||||||
|
#define wantbufsrv 0
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
inserverbuf = param->srvinbuf - param->srvoffset;
|
inserverbuf = param->srvinbuf - param->srvoffset;
|
||||||
@ -108,16 +162,16 @@ int sockmap(struct clientparam * param, int timeo, int usesplice){
|
|||||||
if(inserverbuf >= fromserver) FROMSERVER = 0;
|
if(inserverbuf >= fromserver) FROMSERVER = 0;
|
||||||
if(inclientbuf >= fromclient) FROMCLIENT = 0;
|
if(inclientbuf >= fromclient) FROMCLIENT = 0;
|
||||||
#ifdef WITHSPLICE
|
#ifdef WITHSPLICE
|
||||||
if(!usesplice)
|
/* a body of known length large enough is spliced from the start */
|
||||||
|
if(autosplice && allowsrv && param->waitserver64 && fromserver > inserverbuf && fromserver - inserverbuf >= SPLICE_MIN) splicesrv = 1;
|
||||||
|
if(autosplice && allowcli && param->waitclient64 && fromclient > inclientbuf && fromclient - inclientbuf >= SPLICE_MIN) splicecli = 1;
|
||||||
#endif
|
#endif
|
||||||
{
|
/* a spliced direction needs no buffer, only what was read before */
|
||||||
if(fromserver && !param->srvbuf && (!(param->srvbuf=malloc(SRVBUFSIZE)) || !(param->srvbufsize = SRVBUFSIZE))){
|
if(!splicesrv && fromserver && !param->srvbuf && (!(param->srvbuf=malloc(SRVBUFSIZE)) || !(param->srvbufsize = SRVBUFSIZE))){
|
||||||
RETURN (21);
|
RETURN (21);
|
||||||
}
|
}
|
||||||
if(fromclient && !param->clibuf && (!(param->clibuf=malloc(SRVBUFSIZE)) || !(param->clibufsize = SRVBUFSIZE))){
|
if(!splicecli && fromclient && !param->clibuf && (!(param->clibuf=malloc(SRVBUFSIZE)) || !(param->clibufsize = SRVBUFSIZE))){
|
||||||
RETURN (21);
|
RETURN (21);
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
if(param->srvinbuf == param->srvoffset) param->srvinbuf =param->srvoffset = 0;
|
if(param->srvinbuf == param->srvoffset) param->srvinbuf =param->srvoffset = 0;
|
||||||
if(param->cliinbuf == param->clioffset) param->cliinbuf =param->clioffset = 0;
|
if(param->cliinbuf == param->clioffset) param->cliinbuf =param->clioffset = 0;
|
||||||
@ -220,8 +274,9 @@ log("send to server from buf");
|
|||||||
param->clioffset = param->cliinbuf = 0;
|
param->clioffset = param->cliinbuf = 0;
|
||||||
if(fromclient) TOCLIENTBUF = 1;
|
if(fromclient) TOCLIENTBUF = 1;
|
||||||
}
|
}
|
||||||
sasize = SASIZE(¶m->sinsr);
|
/* a connected stream: no address, which a Unix socket on Linux
|
||||||
res = param->srv->so._sendto(param->sostate, param->remsock, (char *)param->clibuf + param->clioffset, (int)MIN(inclientbuf, fromclient), 0, (struct sockaddr*)¶m->sinsr, sasize);
|
would refuse */
|
||||||
|
res = param->srv->so._sendto(param->sostate, param->remsock, (char *)param->clibuf + param->clioffset, (int)MIN(inclientbuf, fromclient), 0, NULL, 0);
|
||||||
if(res <= 0) {
|
if(res <= 0) {
|
||||||
TOSERVER = 0;
|
TOSERVER = 0;
|
||||||
if(errno && errno != EAGAIN && errno != EINTR){
|
if(errno && errno != EAGAIN && errno != EINTR){
|
||||||
@ -267,8 +322,7 @@ log("send to client from buf");
|
|||||||
if(fromserver) TOSERVERBUF = 1;
|
if(fromserver) TOSERVERBUF = 1;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
sasize = SASIZE(¶m->sincr);
|
res = param->srv->so._sendto(param->sostate, param->clisock, (char *)param->srvbuf + param->srvoffset, (int)MIN(inserverbuf,fromserver), 0, NULL, 0);
|
||||||
res = param->srv->so._sendto(param->sostate, param->clisock, (char *)param->srvbuf + param->srvoffset, (int)MIN(inserverbuf,fromserver), 0, (struct sockaddr*)¶m->sincr, sasize);
|
|
||||||
if(res <= 0) {
|
if(res <= 0) {
|
||||||
TOCLIENT = 0;
|
TOCLIENT = 0;
|
||||||
if(errno && errno != EAGAIN && errno != EINTR){
|
if(errno && errno != EAGAIN && errno != EINTR){
|
||||||
@ -292,8 +346,21 @@ log("done send to client from buf");
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
#ifdef WITHSPLICE
|
#ifdef WITHSPLICE
|
||||||
if(usesplice){
|
/* a direction going back to copying does once its pipe is empty */
|
||||||
if(inclientpipe && !inclientbuf && FROMCLIENTPIPE && TOSERVER){
|
if(wantbufcli && !inclientpipe){
|
||||||
|
splicecli = wantbufcli = streakcli = 0;
|
||||||
|
if(!param->clibuf && (!(param->clibuf=malloc(SRVBUFSIZE)) || !(param->clibufsize = SRVBUFSIZE))) RETURN (21);
|
||||||
|
if(param->cliinbuf < param->clibufsize) TOCLIENTBUF = 1;
|
||||||
|
}
|
||||||
|
if(wantbufsrv && !inserverpipe){
|
||||||
|
splicesrv = wantbufsrv = streaksrv = 0;
|
||||||
|
if(!param->srvbuf && (!(param->srvbuf=malloc(SRVBUFSIZE)) || !(param->srvbufsize = SRVBUFSIZE))) RETURN (21);
|
||||||
|
if(param->srvinbuf < param->srvbufsize) TOSERVERBUF = 1;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
/* each direction goes through its pipe if spliced, through its
|
||||||
|
buffer otherwise; what was in the buffer before is sent first */
|
||||||
|
if(splicecli && inclientpipe && !inclientbuf && FROMCLIENTPIPE && TOSERVER){
|
||||||
#ifdef WITHLOG
|
#ifdef WITHLOG
|
||||||
log("send to server from pipe");
|
log("send to server from pipe");
|
||||||
#endif
|
#endif
|
||||||
@ -330,7 +397,7 @@ log(logbuf);
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(inserverpipe && !inserverbuf && FROMSERVERPIPE && TOCLIENT){
|
if(splicesrv && inserverpipe && !inserverbuf && FROMSERVERPIPE && TOCLIENT){
|
||||||
#ifdef WITHLOG
|
#ifdef WITHLOG
|
||||||
log("send to client from pipe");
|
log("send to client from pipe");
|
||||||
#endif
|
#endif
|
||||||
@ -360,12 +427,17 @@ log(logbuf);
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(fromclient>inclientpipe && FROMCLIENT && TOCLIENTPIPE && !SESSIONEND){
|
if(splicecli && !wantbufcli && fromclient>inclientpipe && FROMCLIENT && TOCLIENTPIPE && !SESSIONEND){
|
||||||
int error;
|
|
||||||
socklen_t len=sizeof(error);
|
|
||||||
#ifdef WITHLOG
|
#ifdef WITHLOG
|
||||||
log("read from client to pipe");
|
log("read from client to pipe");
|
||||||
#endif
|
#endif
|
||||||
|
if(pipecli[0] < 0 && pipe2(pipecli, O_NONBLOCK) < 0){
|
||||||
|
/* no pipe to be had: copy instead */
|
||||||
|
allowcli = splicecli = TOCLIENTPIPE = FROMCLIENTPIPE = 0;
|
||||||
|
if(!param->clibuf && (!(param->clibuf=malloc(SRVBUFSIZE)) || !(param->clibufsize = SRVBUFSIZE))) RETURN (21);
|
||||||
|
if(param->cliinbuf < param->clibufsize) TOCLIENTBUF = 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
errno = 0;
|
errno = 0;
|
||||||
res = splice(param->clisock, NULL, pipecli[1], NULL, (int)MIN((uint64_t)MAXSPLICE - inclientpipe, (uint64_t)fromclient-inclientpipe), SPLICE_F_NONBLOCK|SPLICE_F_MOVE);
|
res = splice(param->clisock, NULL, pipecli[1], NULL, (int)MIN((uint64_t)MAXSPLICE - inclientpipe, (uint64_t)fromclient-inclientpipe), SPLICE_F_NONBLOCK|SPLICE_F_MOVE);
|
||||||
#ifdef WITHLOG
|
#ifdef WITHLOG
|
||||||
@ -396,19 +468,27 @@ log("done read from client to pipe");
|
|||||||
graceclinum++;
|
graceclinum++;
|
||||||
graceclitraf += res;
|
graceclitraf += res;
|
||||||
gracesrvnum = gracesrvtraf = 0;
|
gracesrvnum = gracesrvtraf = 0;
|
||||||
|
if(autosplice && !param->waitclient64){
|
||||||
|
if(res >= SPLICE_SMALL) streakcli = 0;
|
||||||
|
else if(++streakcli > SPLICE_STREAK) wantbufcli = 1;
|
||||||
|
}
|
||||||
inclientpipe += res;
|
inclientpipe += res;
|
||||||
if(inclientpipe >= MAXSPLICE) TOCLIENTPIPE = 0;
|
if(inclientpipe >= MAXSPLICE) TOCLIENTPIPE = 0;
|
||||||
needaction = 0;
|
needaction = 0;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(fromserver > inserverpipe && FROMSERVER && TOSERVERPIPE && !SESSIONEND){
|
if(splicesrv && !wantbufsrv && fromserver > inserverpipe && FROMSERVER && TOSERVERPIPE && !SESSIONEND){
|
||||||
int error;
|
|
||||||
socklen_t len=sizeof(error);
|
|
||||||
errno = 0;
|
|
||||||
#ifdef WITHLOG
|
#ifdef WITHLOG
|
||||||
log("read from server to pipe\n");
|
log("read from server to pipe\n");
|
||||||
#endif
|
#endif
|
||||||
|
if(pipesrv[0] < 0 && pipe2(pipesrv, O_NONBLOCK) < 0){
|
||||||
|
allowsrv = splicesrv = TOSERVERPIPE = FROMSERVERPIPE = 0;
|
||||||
|
if(!param->srvbuf && (!(param->srvbuf=malloc(SRVBUFSIZE)) || !(param->srvbufsize = SRVBUFSIZE))) RETURN (21);
|
||||||
|
if(param->srvinbuf < param->srvbufsize) TOSERVERBUF = 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
errno = 0;
|
||||||
res = splice(param->remsock, NULL, pipesrv[1], NULL, MIN(MAXSPLICE - inserverpipe, fromserver - inserverpipe), SPLICE_F_NONBLOCK|SPLICE_F_MOVE);
|
res = splice(param->remsock, NULL, pipesrv[1], NULL, MIN(MAXSPLICE - inserverpipe, fromserver - inserverpipe), SPLICE_F_NONBLOCK|SPLICE_F_MOVE);
|
||||||
#ifdef WITHLOG
|
#ifdef WITHLOG
|
||||||
log("server to pipe splice finished\n");
|
log("server to pipe splice finished\n");
|
||||||
@ -438,6 +518,10 @@ log("done read from server to pipe\n");
|
|||||||
gracesrvnum++;
|
gracesrvnum++;
|
||||||
gracesrvtraf += res;
|
gracesrvtraf += res;
|
||||||
graceclinum = graceclitraf = 0;
|
graceclinum = graceclitraf = 0;
|
||||||
|
if(autosplice && !param->waitserver64){
|
||||||
|
if(res >= SPLICE_SMALL) streaksrv = 0;
|
||||||
|
else if(++streaksrv > SPLICE_STREAK) wantbufsrv = 1;
|
||||||
|
}
|
||||||
param->nreads++;
|
param->nreads++;
|
||||||
param->statssrv64 += res;
|
param->statssrv64 += res;
|
||||||
inserverpipe += res;
|
inserverpipe += res;
|
||||||
@ -452,15 +536,15 @@ log("done read from server to pipe\n");
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
|
||||||
#endif
|
#endif
|
||||||
{
|
{
|
||||||
if(fromclient > inclientbuf && FROMCLIENT && TOCLIENTBUF && !SESSIONEND){
|
if(!splicecli && fromclient > inclientbuf && FROMCLIENT && TOCLIENTBUF && !SESSIONEND){
|
||||||
#ifdef WITHLOG
|
#ifdef WITHLOG
|
||||||
log("read from client to buf");
|
log("read from client to buf");
|
||||||
#endif
|
#endif
|
||||||
sasize = sizeof(param->sincr);
|
/* no address: it would overwrite the client's with what a
|
||||||
res = param->srv->so._recvfrom(param->sostate, param->clisock, (char *)param->clibuf + param->cliinbuf, (int)MIN((uint64_t)param->clibufsize - param->cliinbuf, fromclient-inclientbuf), 0, (struct sockaddr *)¶m->sincr, &sasize);
|
stream socket gives, an empty one for a Unix socket */
|
||||||
|
res = param->srv->so._recvfrom(param->sostate, param->clisock, (char *)param->clibuf + param->cliinbuf, (int)MIN((uint64_t)param->clibufsize - param->cliinbuf, fromclient-inclientbuf), 0, NULL, NULL);
|
||||||
if(res <= 0) {
|
if(res <= 0) {
|
||||||
FROMCLIENT = 0;
|
FROMCLIENT = 0;
|
||||||
if(res == 0) {
|
if(res == 0) {
|
||||||
@ -480,6 +564,17 @@ log("done read from client to buf");
|
|||||||
graceclinum++;
|
graceclinum++;
|
||||||
graceclitraf += res;
|
graceclitraf += res;
|
||||||
gracesrvnum = gracesrvtraf = 0;
|
gracesrvnum = gracesrvtraf = 0;
|
||||||
|
#ifdef WITHSPLICE
|
||||||
|
if(autosplice && allowcli && !param->waitclient64){
|
||||||
|
if(res < SPLICE_MIN) streakcli = 0;
|
||||||
|
else if(++streakcli >= SPLICE_STREAK){
|
||||||
|
/* a stream: splice from now on, after what is
|
||||||
|
buffered has been sent */
|
||||||
|
splicecli = TOCLIENTPIPE = FROMCLIENTPIPE = 1;
|
||||||
|
streakcli = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
inclientbuf += res;
|
inclientbuf += res;
|
||||||
param->cliinbuf += res;
|
param->cliinbuf += res;
|
||||||
if(param->clibufsize == param->cliinbuf) TOCLIENTBUF = 0;
|
if(param->clibufsize == param->cliinbuf) TOCLIENTBUF = 0;
|
||||||
@ -488,12 +583,11 @@ log("done read from client to buf");
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(fromserver > inserverbuf && FROMSERVER && TOSERVERBUF && !SESSIONEND){
|
if(!splicesrv && fromserver > inserverbuf && FROMSERVER && TOSERVERBUF && !SESSIONEND){
|
||||||
#ifdef WITHLOG
|
#ifdef WITHLOG
|
||||||
log("read from server to buf");
|
log("read from server to buf");
|
||||||
#endif
|
#endif
|
||||||
sasize = sizeof(param->sinsr);
|
res = param->srv->so._recvfrom(param->sostate, param->remsock, (char *)param->srvbuf + param->srvinbuf, (int)MIN((uint64_t)param->srvbufsize - param->srvinbuf, fromserver-inserverbuf), 0, NULL, NULL);
|
||||||
res = param->srv->so._recvfrom(param->sostate, param->remsock, (char *)param->srvbuf + param->srvinbuf, (int)MIN((uint64_t)param->srvbufsize - param->srvinbuf, fromserver-inserverbuf), 0, (struct sockaddr *)¶m->sinsr, &sasize);
|
|
||||||
if(res <= 0) {
|
if(res <= 0) {
|
||||||
FROMSERVER = 0;
|
FROMSERVER = 0;
|
||||||
if(res == 0) {
|
if(res == 0) {
|
||||||
@ -513,6 +607,15 @@ log("done read from server to buf");
|
|||||||
gracesrvnum++;
|
gracesrvnum++;
|
||||||
gracesrvtraf += res;
|
gracesrvtraf += res;
|
||||||
graceclinum = graceclitraf = 0;
|
graceclinum = graceclitraf = 0;
|
||||||
|
#ifdef WITHSPLICE
|
||||||
|
if(autosplice && allowsrv && !param->waitserver64){
|
||||||
|
if(res < SPLICE_MIN) streaksrv = 0;
|
||||||
|
else if(++streaksrv >= SPLICE_STREAK){
|
||||||
|
splicesrv = TOSERVERPIPE = FROMSERVERPIPE = 1;
|
||||||
|
streaksrv = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
param->nreads++;
|
param->nreads++;
|
||||||
param->statssrv64 += res;
|
param->statssrv64 += res;
|
||||||
inserverbuf += res;
|
inserverbuf += res;
|
||||||
@ -553,15 +656,8 @@ log("done read from server to buf");
|
|||||||
// if(!CLIENTTERMREAD || !CLIENTTERMWRITE){
|
// if(!CLIENTTERMREAD || !CLIENTTERMWRITE){
|
||||||
if(!after){
|
if(!after){
|
||||||
fds[fdsc].fd = param->clisock;
|
fds[fdsc].fd = param->clisock;
|
||||||
if(fromclient && !CLIENTTERMREAD && !FROMCLIENT && !SESSIONEND && ((
|
if(fromclient && !CLIENTTERMREAD && !FROMCLIENT && !SESSIONEND &&
|
||||||
#ifdef WITHSPLICE
|
((!splicecli && TOCLIENTBUF) || (splicecli && !wantbufcli)))
|
||||||
!usesplice &&
|
|
||||||
#endif
|
|
||||||
TOCLIENTBUF)
|
|
||||||
#ifdef WITHSPLICE
|
|
||||||
|| (usesplice)
|
|
||||||
#endif
|
|
||||||
))
|
|
||||||
cli_events |= POLLIN;
|
cli_events |= POLLIN;
|
||||||
if(!TOCLIENT && !CLIENTTERMWRITE && (inserverbuf
|
if(!TOCLIENT && !CLIENTTERMWRITE && (inserverbuf
|
||||||
#ifdef WITHSPLICE
|
#ifdef WITHSPLICE
|
||||||
@ -569,6 +665,11 @@ log("done read from server to buf");
|
|||||||
#endif
|
#endif
|
||||||
))
|
))
|
||||||
cli_events |= POLLOUT;
|
cli_events |= POLLOUT;
|
||||||
|
/* poll() reports a hang-up whatever is asked, and would
|
||||||
|
return at once every time: the socket is left out then,
|
||||||
|
rather than given as an invalid one, which WSAPoll takes
|
||||||
|
for an error */
|
||||||
|
clipolled = cli_events || !CLIENTHUP;
|
||||||
if(cli_events){
|
if(cli_events){
|
||||||
fds[fdsc].fd = param->clisock;
|
fds[fdsc].fd = param->clisock;
|
||||||
fds[fdsc].events = cli_events;
|
fds[fdsc].events = cli_events;
|
||||||
@ -580,6 +681,8 @@ log("wait writing to client");
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if(!clipolled){
|
||||||
|
}
|
||||||
else if(cli_events){
|
else if(cli_events){
|
||||||
if(fds[fdsc].revents & (POLLERR|POLLNVAL)) {
|
if(fds[fdsc].revents & (POLLERR|POLLNVAL)) {
|
||||||
CLIENTTERMREAD = 1;
|
CLIENTTERMREAD = 1;
|
||||||
@ -601,27 +704,27 @@ log("ready to write to client");
|
|||||||
}
|
}
|
||||||
if(fds[fdsc].revents & (POLLHUP)) {
|
if(fds[fdsc].revents & (POLLHUP)) {
|
||||||
if(fds[fdsc].events & POLLIN) FROMCLIENT = 1;
|
if(fds[fdsc].events & POLLIN) FROMCLIENT = 1;
|
||||||
if(fds[fdsc].events & POLLOUT) CLIENTTERMWRITE = 1;
|
if(fds[fdsc].events & POLLOUT){
|
||||||
|
if(hupwrite(param->clisock, fds[fdsc].revents, timeo)) TOCLIENT = 1;
|
||||||
|
else CLIENTTERMWRITE = 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if(fds[fdsc].revents & (POLLERR|POLLNVAL|POLLHUP)) {
|
} else if(fds[fdsc].revents & (POLLERR|POLLNVAL)) {
|
||||||
CLIENTTERMREAD = 1;
|
CLIENTTERMREAD = 1;
|
||||||
CLIENTTERMWRITE = 1;
|
CLIENTTERMWRITE = 1;
|
||||||
|
} else if(fds[fdsc].revents & POLLHUP) {
|
||||||
|
/* what the client sent before it closed is still to be
|
||||||
|
read, once the buffer has room for it */
|
||||||
|
CLIENTHUP = 1;
|
||||||
}
|
}
|
||||||
fdsc++;
|
if(clipolled) fdsc++;
|
||||||
|
|
||||||
// if(!SERVERTERMREAD || !SERVERTERMWRITE){
|
// if(!SERVERTERMREAD || !SERVERTERMWRITE){
|
||||||
if(!after){
|
if(!after){
|
||||||
fds[fdsc].fd = param->remsock;
|
fds[fdsc].fd = param->remsock;
|
||||||
if(fromserver && !SERVERTERMREAD && !FROMSERVER && !SESSIONEND && ((
|
if(fromserver && !SERVERTERMREAD && !FROMSERVER && !SESSIONEND &&
|
||||||
#ifdef WITHSPLICE
|
((!splicesrv && TOSERVERBUF) || (splicesrv && !wantbufsrv)))
|
||||||
!usesplice &&
|
|
||||||
#endif
|
|
||||||
TOSERVERBUF)
|
|
||||||
#ifdef WITHSPLICE
|
|
||||||
|| (usesplice)
|
|
||||||
#endif
|
|
||||||
))
|
|
||||||
srv_events |= POLLIN;
|
srv_events |= POLLIN;
|
||||||
if(!TOSERVER && !SERVERTERMWRITE && (inclientbuf
|
if(!TOSERVER && !SERVERTERMWRITE && (inclientbuf
|
||||||
#ifdef WITHSPLICE
|
#ifdef WITHSPLICE
|
||||||
@ -629,6 +732,7 @@ log("ready to write to client");
|
|||||||
#endif
|
#endif
|
||||||
))
|
))
|
||||||
srv_events |= POLLOUT;
|
srv_events |= POLLOUT;
|
||||||
|
srvpolled = srv_events || !SERVERHUP;
|
||||||
if(srv_events){
|
if(srv_events){
|
||||||
fds[fdsc].fd = param->remsock;
|
fds[fdsc].fd = param->remsock;
|
||||||
fds[fdsc].events = srv_events;
|
fds[fdsc].events = srv_events;
|
||||||
@ -640,6 +744,8 @@ log("wait writing from server");
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if(!srvpolled){
|
||||||
|
}
|
||||||
else if(srv_events){
|
else if(srv_events){
|
||||||
if(fds[fdsc].revents & (POLLERR|POLLNVAL)) {
|
if(fds[fdsc].revents & (POLLERR|POLLNVAL)) {
|
||||||
#ifdef WITHLOG
|
#ifdef WITHLOG
|
||||||
@ -668,18 +774,24 @@ log("ready to write to server");
|
|||||||
log("server terminated connection");
|
log("server terminated connection");
|
||||||
#endif
|
#endif
|
||||||
if(fds[fdsc].events & POLLIN) FROMSERVER = 1;
|
if(fds[fdsc].events & POLLIN) FROMSERVER = 1;
|
||||||
if(fds[fdsc].events & POLLOUT) SERVERTERMWRITE = 1;
|
if(fds[fdsc].events & POLLOUT){
|
||||||
|
if(hupwrite(param->remsock, fds[fdsc].revents, timeo)) TOSERVER = 1;
|
||||||
|
else SERVERTERMWRITE = 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if(fds[fdsc].revents & (POLLERR|POLLNVAL|POLLHUP)) {
|
} else if(fds[fdsc].revents & (POLLERR|POLLNVAL)) {
|
||||||
SERVERTERMREAD = 1;
|
SERVERTERMREAD = 1;
|
||||||
SERVERTERMWRITE = 1;
|
SERVERTERMWRITE = 1;
|
||||||
|
} else if(fds[fdsc].revents & POLLHUP) {
|
||||||
|
/* the rest of the answer may still be waiting to be read */
|
||||||
|
SERVERHUP = 1;
|
||||||
}
|
}
|
||||||
fdsc++;
|
if(srvpolled) fdsc++;
|
||||||
// }
|
// }
|
||||||
#ifdef WITHSPLICE
|
#ifdef WITHSPLICE
|
||||||
if(usesplice){
|
{
|
||||||
if(fromclient>inclientpipe && !TOCLIENTPIPE && inclientpipe < MAXSPLICE){
|
if(splicecli && fromclient>inclientpipe && !TOCLIENTPIPE && inclientpipe < MAXSPLICE){
|
||||||
if(!after){
|
if(!after){
|
||||||
#ifdef WITHLOG
|
#ifdef WITHLOG
|
||||||
log("wait writing to client pipe");
|
log("wait writing to client pipe");
|
||||||
@ -700,7 +812,7 @@ log("ready to write to client pipe");
|
|||||||
}
|
}
|
||||||
fdsc++;
|
fdsc++;
|
||||||
}
|
}
|
||||||
if(inclientpipe && !FROMCLIENTPIPE){
|
if(splicecli && inclientpipe && !FROMCLIENTPIPE){
|
||||||
if(!after){
|
if(!after){
|
||||||
#ifdef WITHLOG
|
#ifdef WITHLOG
|
||||||
log("wait reading from client pipe");
|
log("wait reading from client pipe");
|
||||||
@ -719,7 +831,7 @@ log("ready reading from client pipe");
|
|||||||
}
|
}
|
||||||
fdsc++;
|
fdsc++;
|
||||||
}
|
}
|
||||||
if(fromserver>inserverpipe && !TOSERVERPIPE && inserverpipe < MAXSPLICE){
|
if(splicesrv && fromserver>inserverpipe && !TOSERVERPIPE && inserverpipe < MAXSPLICE){
|
||||||
if(!after){
|
if(!after){
|
||||||
#ifdef WITHLOG
|
#ifdef WITHLOG
|
||||||
log("wait writing to server pipe");
|
log("wait writing to server pipe");
|
||||||
@ -738,7 +850,7 @@ log("ready writing to server pipe");
|
|||||||
}
|
}
|
||||||
fdsc++;
|
fdsc++;
|
||||||
}
|
}
|
||||||
if(inserverpipe && !FROMSERVERPIPE){
|
if(splicesrv && inserverpipe && !FROMSERVERPIPE){
|
||||||
if(!after){
|
if(!after){
|
||||||
#ifdef WITHLOG
|
#ifdef WITHLOG
|
||||||
log("wait reading from server pipe");
|
log("wait reading from server pipe");
|
||||||
@ -819,18 +931,29 @@ log("timeout");
|
|||||||
else if(inclientpipe || inserverpipe) res = 94;
|
else if(inclientpipe || inserverpipe) res = 94;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if((param->nwrites > 0 && !SERVERTERMWRITE) || (param->nreads > 0 && !CLIENTTERMWRITE))
|
|
||||||
usleep(SLEEPTIME * 10);
|
|
||||||
|
|
||||||
CLEANRET:
|
CLEANRET:
|
||||||
|
|
||||||
#ifdef WITHSPLICE
|
#ifdef WITHSPLICE
|
||||||
if(pipecli[0] >= 0) close(pipecli[0]);
|
/* empty pipes stay with the connection for its next mapping; one still
|
||||||
if(pipecli[1] >= 0) close(pipecli[1]);
|
holding data, left by an error or a timeout, cannot be used again */
|
||||||
if(pipesrv[0] >= 0) close(pipesrv[0]);
|
if(inclientpipe && pipecli[0] >= 0){
|
||||||
if(pipesrv[1] >= 0) close(pipesrv[1]);
|
close(pipecli[0]);
|
||||||
|
close(pipecli[1]);
|
||||||
|
pipecli[0] = pipecli[1] = -1;
|
||||||
|
}
|
||||||
|
if(inserverpipe && pipesrv[0] >= 0){
|
||||||
|
close(pipesrv[0]);
|
||||||
|
close(pipesrv[1]);
|
||||||
|
pipesrv[0] = pipesrv[1] = -1;
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
#undef SESSIONEND
|
#undef SESSIONEND
|
||||||
|
#ifndef WITHSPLICE
|
||||||
|
#undef splicecli
|
||||||
|
#undef splicesrv
|
||||||
|
#undef wantbufcli
|
||||||
|
#undef wantbufsrv
|
||||||
|
#endif
|
||||||
|
|||||||
25
src/socks.c
25
src/socks.c
@ -184,7 +184,8 @@ void * sockschild(struct clientparam* param) {
|
|||||||
param->operation = command == 2?BIND:UDPASSOC;
|
param->operation = command == 2?BIND:UDPASSOC;
|
||||||
if(command == 2){
|
if(command == 2){
|
||||||
if ((param->remsock=param->srv->so._socket(param->sostate, SASOCK(¶m->req), SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET) {RETURN (11);}
|
if ((param->remsock=param->srv->so._socket(param->sostate, SASOCK(¶m->req), SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET) {RETURN (11);}
|
||||||
setopts(param->remsock, param->srv->lissockopts);
|
setopts(param->remsock, ¶m->srv->lissockopts);
|
||||||
|
if(extbindtodevice(param, param->remsock)) {RETURN (12);}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -229,7 +230,7 @@ fprintf(stderr, "%hu bound to communicate with server\n", *SAPORT(¶m->sinsl)
|
|||||||
fflush(stderr);
|
fflush(stderr);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
sasize = SASIZE(¶m->sinsl);
|
sasize = sizeof(param->sinsl);
|
||||||
param->srv->so._getsockname(param->sostate, param->remsock, (struct sockaddr *)¶m->sinsl, &sasize);
|
param->srv->so._getsockname(param->sostate, param->remsock, (struct sockaddr *)¶m->sinsl, &sasize);
|
||||||
}
|
}
|
||||||
if(command == 3) {
|
if(command == 3) {
|
||||||
@ -244,11 +245,12 @@ fflush(stderr);
|
|||||||
#else
|
#else
|
||||||
fcntl(param->clisock, F_SETFL, O_NONBLOCK | fcntl(param->clisock, F_GETFL));
|
fcntl(param->clisock, F_SETFL, O_NONBLOCK | fcntl(param->clisock, F_GETFL));
|
||||||
#endif
|
#endif
|
||||||
|
if(intbindtodevice(param, param->clisock)) {RETURN (12);}
|
||||||
sin = param->sincl;
|
sin = param->sincl;
|
||||||
*SAPORT(&sin) = 0;
|
*SAPORT(&sin) = 0;
|
||||||
/* the port the client is told to send its datagrams to */
|
/* the port the client is told to send its datagrams to */
|
||||||
if(bindwithrange(param, param->clisock, &sin, param->intport)) {RETURN (12);}
|
if(bindwithrange(param, param->clisock, &sin, param->intport)) {RETURN (12);}
|
||||||
sasize = SASIZE(&sin);
|
sasize = sizeof(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
|
||||||
fprintf(stderr, "%hu binded to communicate with client\n",
|
fprintf(stderr, "%hu binded to communicate with client\n",
|
||||||
@ -270,12 +272,17 @@ CLEANRET:
|
|||||||
int repcode;
|
int repcode;
|
||||||
|
|
||||||
sasize = sizeof(sin);
|
sasize = sizeof(sin);
|
||||||
if(command != 3 && param->remsock != INVALID_SOCKET) param->srv->so._getsockname(param->sostate, param->remsock, (struct sockaddr *)&sin, &sasize);
|
/* CONNECT and BIND report the address of the socket towards the
|
||||||
if(command != 3 && !SAISNULL(¶m->srv->extNat)){
|
destination, UDP ASSOCIATE the one the client sends its datagrams
|
||||||
uint16_t port;
|
to, as does a request which never got a socket of its own */
|
||||||
port = *SAPORT(&sin);
|
if(command != 3 && param->remsock != INVALID_SOCKET){
|
||||||
sin = param->srv->extNat;
|
param->srv->so._getsockname(param->sostate, param->remsock, (struct sockaddr *)&sin, &sasize);
|
||||||
*SAPORT(&sin) = port;
|
if(!SAISNULL(¶m->srv->extNat)){
|
||||||
|
uint16_t port;
|
||||||
|
port = *SAPORT(&sin);
|
||||||
|
sin = param->srv->extNat;
|
||||||
|
*SAPORT(&sin) = port;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
param->srv->so._getsockname(param->sostate, param->clisock, (struct sockaddr *)&sin, &sasize);
|
param->srv->so._getsockname(param->sostate, param->clisock, (struct sockaddr *)&sin, &sasize);
|
||||||
|
|||||||
63
src/ssl.c
63
src/ssl.c
@ -74,6 +74,9 @@ static int client_mode = 0;
|
|||||||
struct SSLsock {
|
struct SSLsock {
|
||||||
SOCKET s;
|
SOCKET s;
|
||||||
SSL_CONN conn;
|
SSL_CONN conn;
|
||||||
|
/* what a read and a write which could not be done wait for, POLLIN
|
||||||
|
or POLLOUT, 0 once one is: TLS may have to write to read */
|
||||||
|
short rwant, wwant;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct SSLstate {
|
struct SSLstate {
|
||||||
@ -102,10 +105,12 @@ static void addSSL(
|
|||||||
if (cli_s != INVALID_SOCKET){
|
if (cli_s != INVALID_SOCKET){
|
||||||
SOSTATE->cli.s = cli_s;
|
SOSTATE->cli.s = cli_s;
|
||||||
SOSTATE->cli.conn = cli_conn;
|
SOSTATE->cli.conn = cli_conn;
|
||||||
|
SOSTATE->cli.rwant = SOSTATE->cli.wwant = 0;
|
||||||
}
|
}
|
||||||
if (srv_s != INVALID_SOCKET){
|
if (srv_s != INVALID_SOCKET){
|
||||||
SOSTATE->srv.s = srv_s;
|
SOSTATE->srv.s = srv_s;
|
||||||
SOSTATE->srv.conn = srv_conn;
|
SOSTATE->srv.conn = srv_conn;
|
||||||
|
SOSTATE->srv.rwant = SOSTATE->srv.wwant = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -136,12 +141,14 @@ static ssize_t ssl_send(void *state, SOCKET s, const void *msg, size_t len, int
|
|||||||
int res, err;
|
int res, err;
|
||||||
if((res = ssl_write(sslq->conn, (void *)msg, len)) <= 0){
|
if((res = ssl_write(sslq->conn, (void *)msg, len)) <= 0){
|
||||||
err = SSL_get_error((SSL *)((ssl_conn*)sslq->conn)->ssl, res);
|
err = SSL_get_error((SSL *)((ssl_conn*)sslq->conn)->ssl, res);
|
||||||
if (err == SSL_ERROR_WANT_WRITE){
|
if (err == SSL_ERROR_WANT_WRITE || err == SSL_ERROR_WANT_READ){
|
||||||
|
sslq->wwant = (err == SSL_ERROR_WANT_READ)? POLLIN : POLLOUT;
|
||||||
seterrno3(EAGAIN);
|
seterrno3(EAGAIN);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
else seterrno3(err);
|
else seterrno3(err);
|
||||||
}
|
}
|
||||||
|
else sslq->wwant = 0;
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -158,14 +165,16 @@ static ssize_t ssl_sendto(void *state, SOCKET s, const void *msg, size_t len, in
|
|||||||
|
|
||||||
if ((sslq = searchSSL(state, s))){
|
if ((sslq = searchSSL(state, s))){
|
||||||
int res, err;
|
int res, err;
|
||||||
if((res = ssl_write(sslq->conn, (void *)msg, len)) <= 0) {
|
if((res = ssl_write(sslq->conn, (void *)msg, len)) <= 0){
|
||||||
err = SSL_get_error((SSL *)((ssl_conn*)sslq->conn)->ssl, res);
|
err = SSL_get_error((SSL *)((ssl_conn*)sslq->conn)->ssl, res);
|
||||||
if (err == SSL_ERROR_WANT_WRITE){
|
if (err == SSL_ERROR_WANT_WRITE || err == SSL_ERROR_WANT_READ){
|
||||||
|
sslq->wwant = (err == SSL_ERROR_WANT_READ)? POLLIN : POLLOUT;
|
||||||
seterrno3(EAGAIN);
|
seterrno3(EAGAIN);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
else seterrno3(err);
|
else seterrno3(err);
|
||||||
}
|
}
|
||||||
|
else sslq->wwant = 0;
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -183,12 +192,14 @@ static ssize_t ssl_recvfrom(void *state, SOCKET s, void *msg, size_t len, int f
|
|||||||
int res, err;
|
int res, err;
|
||||||
if((res = ssl_read(sslq->conn, (void *)msg, len)) <= 0) {
|
if((res = ssl_read(sslq->conn, (void *)msg, len)) <= 0) {
|
||||||
err = SSL_get_error((SSL *)((ssl_conn*)sslq->conn)->ssl, res);
|
err = SSL_get_error((SSL *)((ssl_conn*)sslq->conn)->ssl, res);
|
||||||
if (err == SSL_ERROR_WANT_READ) {
|
if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) {
|
||||||
|
sslq->rwant = (err == SSL_ERROR_WANT_WRITE)? POLLOUT : POLLIN;
|
||||||
seterrno3(EAGAIN);
|
seterrno3(EAGAIN);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
else seterrno3(err);
|
else seterrno3(err);
|
||||||
}
|
}
|
||||||
|
else sslq->rwant = 0;
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
return sso._recvfrom(sso.state, s, msg, len, flags, from, fromlen);
|
return sso._recvfrom(sso.state, s, msg, len, flags, from, fromlen);
|
||||||
@ -205,12 +216,14 @@ static ssize_t ssl_recv(void *state, SOCKET s, void *msg, size_t len, int flags)
|
|||||||
int res, err;
|
int res, err;
|
||||||
if((res = ssl_read(sslq->conn, (void *)msg, len)) <= 0) {
|
if((res = ssl_read(sslq->conn, (void *)msg, len)) <= 0) {
|
||||||
err = SSL_get_error((SSL *)((ssl_conn*)sslq->conn)->ssl, res);
|
err = SSL_get_error((SSL *)((ssl_conn*)sslq->conn)->ssl, res);
|
||||||
if (err == SSL_ERROR_WANT_READ) {
|
if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) {
|
||||||
|
sslq->rwant = (err == SSL_ERROR_WANT_WRITE)? POLLOUT : POLLIN;
|
||||||
seterrno3(EAGAIN);
|
seterrno3(EAGAIN);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
else seterrno3(err);
|
else seterrno3(err);
|
||||||
}
|
}
|
||||||
|
else sslq->rwant = 0;
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -235,6 +248,8 @@ static int ssl_poll(void *state, struct pollfd *fds, nfds_t nfds, int timeout){
|
|||||||
struct SSLsock *sslq = NULL;
|
struct SSLsock *sslq = NULL;
|
||||||
unsigned int i;
|
unsigned int i;
|
||||||
int ret = 0;
|
int ret = 0;
|
||||||
|
short own[64], *events = own, ev;
|
||||||
|
|
||||||
for(i = 0; i < nfds; i++){
|
for(i = 0; i < nfds; i++){
|
||||||
if((fds[i].events & POLLIN) && (sslq = searchSSL(state, fds[i].fd)) && ssl_pending(sslq->conn)){
|
if((fds[i].events & POLLIN) && (sslq = searchSSL(state, fds[i].fd)) && ssl_pending(sslq->conn)){
|
||||||
fds[i].revents = POLLIN;
|
fds[i].revents = POLLIN;
|
||||||
@ -243,8 +258,29 @@ static int ssl_poll(void *state, struct pollfd *fds, nfds_t nfds, int timeout){
|
|||||||
else fds[i].revents = 0;
|
else fds[i].revents = 0;
|
||||||
}
|
}
|
||||||
if(ret) return ret;
|
if(ret) return ret;
|
||||||
|
for(i = 0; i < nfds; i++) if((sslq = searchSSL(state, fds[i].fd)) && (sslq->rwant || sslq->wwant)) break;
|
||||||
|
if(i == nfds) return sso._poll(state, fds, nfds, timeout);
|
||||||
|
|
||||||
|
/* A read or a write TLS could not do waits for what TLS needs, a read
|
||||||
|
for the socket to be writable: whatever that gets is the caller's
|
||||||
|
chance to try again, revents what it asked for, and its events are
|
||||||
|
given back as they were. */
|
||||||
|
if(nfds > sizeof(own)/sizeof(own[0]) && !(events = malloc(nfds * sizeof(*events)))) return -1;
|
||||||
|
for(i = 0; i < nfds; i++){
|
||||||
|
events[i] = fds[i].events;
|
||||||
|
if(!(sslq = searchSSL(state, fds[i].fd))) continue;
|
||||||
|
ev = fds[i].events & ~(POLLIN|POLLOUT);
|
||||||
|
if(fds[i].events & POLLIN) ev |= sslq->rwant? sslq->rwant : POLLIN;
|
||||||
|
if(fds[i].events & POLLOUT) ev |= sslq->wwant? sslq->wwant : POLLOUT;
|
||||||
|
fds[i].events = ev;
|
||||||
|
}
|
||||||
ret = sso._poll(state, fds, nfds, timeout);
|
ret = sso._poll(state, fds, nfds, timeout);
|
||||||
|
for(i = 0; i < nfds; i++){
|
||||||
|
if(ret > 0 && fds[i].events != events[i] && (fds[i].revents & ~(POLLERR|POLLHUP|POLLNVAL)))
|
||||||
|
fds[i].revents = events[i] | (fds[i].revents & (POLLERR|POLLHUP|POLLNVAL));
|
||||||
|
fds[i].events = events[i];
|
||||||
|
}
|
||||||
|
if(events != own) free(events);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -409,7 +445,7 @@ SSL_CONN dosrvcon(struct clientparam* param, SSL_CERT* cert){
|
|||||||
}
|
}
|
||||||
|
|
||||||
SSL_set_mode((SSL *)((ssl_conn *)ServerConn)->ssl, SSL_MODE_ENABLE_PARTIAL_WRITE|SSL_MODE_AUTO_RETRY);
|
SSL_set_mode((SSL *)((ssl_conn *)ServerConn)->ssl, SSL_MODE_ENABLE_PARTIAL_WRITE|SSL_MODE_AUTO_RETRY);
|
||||||
SSL_set_read_ahead((SSL *)((ssl_conn *)ServerConn)->ssl, 0);
|
SSL_set_read_ahead((SSL *)((ssl_conn *)ServerConn)->ssl, SSL_READAHEAD);
|
||||||
|
|
||||||
return ServerConn;
|
return ServerConn;
|
||||||
}
|
}
|
||||||
@ -442,7 +478,7 @@ int domitm(struct clientparam* param){
|
|||||||
}
|
}
|
||||||
|
|
||||||
SSL_set_mode((SSL *)((ssl_conn *)ClientConn)->ssl, SSL_MODE_ENABLE_PARTIAL_WRITE|SSL_MODE_AUTO_RETRY);
|
SSL_set_mode((SSL *)((ssl_conn *)ClientConn)->ssl, SSL_MODE_ENABLE_PARTIAL_WRITE|SSL_MODE_AUTO_RETRY);
|
||||||
SSL_set_read_ahead((SSL *)((ssl_conn *)ClientConn)->ssl, 0);
|
SSL_set_read_ahead((SSL *)((ssl_conn *)ClientConn)->ssl, SSL_READAHEAD);
|
||||||
|
|
||||||
addSSL(param->clisock, ClientConn, param->remsock, ServerConn, param);
|
addSSL(param->clisock, ClientConn, param->remsock, ServerConn, param);
|
||||||
|
|
||||||
@ -772,14 +808,14 @@ static FILTER_ACTION ssl_filter_client(void *fo, struct clientparam * param, voi
|
|||||||
return REJECT;
|
return REJECT;
|
||||||
}
|
}
|
||||||
SSL_set_mode((SSL *)((ssl_conn *)ClientConn)->ssl, SSL_MODE_ENABLE_PARTIAL_WRITE|SSL_MODE_AUTO_RETRY);
|
SSL_set_mode((SSL *)((ssl_conn *)ClientConn)->ssl, SSL_MODE_ENABLE_PARTIAL_WRITE|SSL_MODE_AUTO_RETRY);
|
||||||
SSL_set_read_ahead((SSL *)((ssl_conn *)ClientConn)->ssl, 0);
|
SSL_set_read_ahead((SSL *)((ssl_conn *)ClientConn)->ssl, SSL_READAHEAD);
|
||||||
addSSL(param->clisock, ClientConn, INVALID_SOCKET, NULL, param);
|
addSSL(param->clisock, ClientConn, INVALID_SOCKET, NULL, param);
|
||||||
}
|
}
|
||||||
return CONTINUE;
|
return CONTINUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
static FILTER_ACTION ssl_filter_connect(void *fc, struct clientparam * param){
|
static FILTER_ACTION ssl_filter_connect(void *fc, struct clientparam * param){
|
||||||
if(PCONF->cli && !client_mode) {
|
if(PCONF->cli && !PCONF->client_mode) {
|
||||||
if(docli(param)) {
|
if(docli(param)) {
|
||||||
return REJECT;
|
return REJECT;
|
||||||
}
|
}
|
||||||
@ -788,7 +824,7 @@ static FILTER_ACTION ssl_filter_connect(void *fc, struct clientparam * param){
|
|||||||
}
|
}
|
||||||
|
|
||||||
static FILTER_ACTION ssl_filter_afterauth(void *fc, struct clientparam * param){
|
static FILTER_ACTION ssl_filter_afterauth(void *fc, struct clientparam * param){
|
||||||
if(PCONF->cli && client_mode == 1) {
|
if(PCONF->cli && PCONF->client_mode == 1) {
|
||||||
if(docli(param)) {
|
if(docli(param)) {
|
||||||
return REJECT;
|
return REJECT;
|
||||||
}
|
}
|
||||||
@ -806,7 +842,7 @@ static FILTER_ACTION ssl_filter_predata(void *fc, struct clientparam * param){
|
|||||||
if(!param->redirectfunc) param->redirectfunc = proxyfunc;
|
if(!param->redirectfunc) param->redirectfunc = proxyfunc;
|
||||||
return CONTINUE;
|
return CONTINUE;
|
||||||
}
|
}
|
||||||
else if(PCONF->cli && client_mode == 2) {
|
else if(PCONF->cli && PCONF->client_mode == 2) {
|
||||||
if(docli(param)) {
|
if(docli(param)) {
|
||||||
return REJECT;
|
return REJECT;
|
||||||
}
|
}
|
||||||
@ -815,7 +851,7 @@ static FILTER_ACTION ssl_filter_predata(void *fc, struct clientparam * param){
|
|||||||
}
|
}
|
||||||
|
|
||||||
static FILTER_ACTION ssl_parent(struct clientparam * param){
|
static FILTER_ACTION ssl_parent(struct clientparam * param){
|
||||||
if(PCONF->cli && client_mode == 3) {
|
if(PCONF->cli && PCONF->client_mode == 3) {
|
||||||
if(docli(param)) {
|
if(docli(param)) {
|
||||||
return REJECT;
|
return REJECT;
|
||||||
}
|
}
|
||||||
@ -867,6 +903,9 @@ static void ssl_filter_close(void *fo){
|
|||||||
free(CONFIG->client_ca_file);
|
free(CONFIG->client_ca_file);
|
||||||
free(CONFIG->client_ca_dir);
|
free(CONFIG->client_ca_dir);
|
||||||
free(CONFIG->client_ca_store);
|
free(CONFIG->client_ca_store);
|
||||||
|
free(CONFIG->server_ca_file);
|
||||||
|
free(CONFIG->server_ca_dir);
|
||||||
|
free(CONFIG->server_ca_store);
|
||||||
free(CONFIG->client_sni);
|
free(CONFIG->client_sni);
|
||||||
if(CONFIG->client_alpn_protos.protos_len){
|
if(CONFIG->client_alpn_protos.protos_len){
|
||||||
free(CONFIG->client_alpn_protos.protos);
|
free(CONFIG->client_alpn_protos.protos);
|
||||||
|
|||||||
10
src/ssl.h
10
src/ssl.h
@ -76,6 +76,16 @@ int ssl_read(SSL_CONN connection, void * buf, int bufsize);
|
|||||||
int ssl_write(SSL_CONN connection, void * buf, int bufsize);
|
int ssl_write(SSL_CONN connection, void * buf, int bufsize);
|
||||||
int ssl_pending(SSL_CONN connection);
|
int ssl_pending(SSL_CONN connection);
|
||||||
|
|
||||||
|
/* TLS records are read ahead, a buffer at a time rather than two reads for
|
||||||
|
each record, where what is read ahead and not yet decrypted shows as
|
||||||
|
pending (SSL_has_pending(), OpenSSL 1.1.0 and later): poll() would wait
|
||||||
|
on it otherwise. */
|
||||||
|
#if !defined(WITH_WOLFSSL) && defined(OPENSSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER >= 0x10100000L
|
||||||
|
#define SSL_READAHEAD 1
|
||||||
|
#else
|
||||||
|
#define SSL_READAHEAD 0
|
||||||
|
#endif
|
||||||
|
|
||||||
//
|
//
|
||||||
// Release of opaque structures
|
// Release of opaque structures
|
||||||
//
|
//
|
||||||
|
|||||||
47
src/ssllib.c
47
src/ssllib.c
@ -22,6 +22,7 @@
|
|||||||
#include <wolfssl/openssl/pem.h>
|
#include <wolfssl/openssl/pem.h>
|
||||||
#include <wolfssl/openssl/ssl.h>
|
#include <wolfssl/openssl/ssl.h>
|
||||||
#include <wolfssl/openssl/err.h>
|
#include <wolfssl/openssl/err.h>
|
||||||
|
#include <wolfssl/openssl/bn.h>
|
||||||
#else
|
#else
|
||||||
#include <openssl/crypto.h>
|
#include <openssl/crypto.h>
|
||||||
#include <openssl/x509.h>
|
#include <openssl/x509.h>
|
||||||
@ -29,6 +30,7 @@
|
|||||||
#include <openssl/pem.h>
|
#include <openssl/pem.h>
|
||||||
#include <openssl/ssl.h>
|
#include <openssl/ssl.h>
|
||||||
#include <openssl/err.h>
|
#include <openssl/err.h>
|
||||||
|
#include <openssl/bn.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "proxy.h"
|
#include "proxy.h"
|
||||||
@ -115,6 +117,31 @@ static int add_ext(X509 *cert, int nid, const char *value)
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/* A serial number of the copy's own, when the source's cannot be set back:
|
||||||
|
* positive, from the hash of the source certificate, so the same for the
|
||||||
|
* same certificate and different for another. wolfSSL built with
|
||||||
|
* WOLFSSL_QT, as Ubuntu 26.04 has it, returns a serial number without the
|
||||||
|
* ASN.1 header its own X509_set_serialNumber() requires.
|
||||||
|
*/
|
||||||
|
static int set_serial_hash(X509 *cert, const unsigned char *hash)
|
||||||
|
{
|
||||||
|
unsigned char serial[16];
|
||||||
|
BIGNUM *bn;
|
||||||
|
ASN1_INTEGER *ai;
|
||||||
|
int res = 0;
|
||||||
|
|
||||||
|
memcpy(serial, hash, sizeof(serial));
|
||||||
|
serial[0] &= 0x7f;
|
||||||
|
if(!serial[0]) serial[0] = 1;
|
||||||
|
if(!(bn = BN_bin2bn(serial, sizeof(serial), NULL))) return 0;
|
||||||
|
if((ai = BN_to_ASN1_INTEGER(bn, NULL))){
|
||||||
|
res = X509_set_serialNumber(cert, ai);
|
||||||
|
ASN1_INTEGER_free(ai);
|
||||||
|
}
|
||||||
|
BN_free(bn);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
SSL_CERT ssl_copy_cert(SSL_CERT cert, SSL_CONFIG *config)
|
SSL_CERT ssl_copy_cert(SSL_CERT cert, SSL_CONFIG *config)
|
||||||
{
|
{
|
||||||
int err = -1;
|
int err = -1;
|
||||||
@ -162,7 +189,8 @@ SSL_CERT ssl_copy_cert(SSL_CERT cert, SSL_CONFIG *config)
|
|||||||
}
|
}
|
||||||
/* v3 is required, extensions are added below */
|
/* v3 is required, extensions are added below */
|
||||||
X509_set_version(dst_cert, 2);
|
X509_set_version(dst_cert, 2);
|
||||||
if(!X509_set_serialNumber(dst_cert, X509_get_serialNumber(src_cert))
|
if((!X509_set_serialNumber(dst_cert, X509_get_serialNumber(src_cert))
|
||||||
|
&& !set_serial_hash(dst_cert, hash_sha256))
|
||||||
|| !X509_set_subject_name(dst_cert, X509_get_subject_name(src_cert))
|
|| !X509_set_subject_name(dst_cert, X509_get_subject_name(src_cert))
|
||||||
|| !X509_set_issuer_name(dst_cert, X509_get_subject_name(config->CA_cert))){
|
|| !X509_set_issuer_name(dst_cert, X509_get_subject_name(config->CA_cert))){
|
||||||
X509_free(dst_cert);
|
X509_free(dst_cert);
|
||||||
@ -261,8 +289,25 @@ int ssl_write(SSL_CONN connection, void * buf, int bufsize)
|
|||||||
int ssl_pending(SSL_CONN connection)
|
int ssl_pending(SSL_CONN connection)
|
||||||
{
|
{
|
||||||
ssl_conn *conn = (ssl_conn *) connection;
|
ssl_conn *conn = (ssl_conn *) connection;
|
||||||
|
#if SSL_READAHEAD
|
||||||
|
char c;
|
||||||
|
int res;
|
||||||
|
|
||||||
|
if(SSL_pending(conn->ssl) > 0) return 1;
|
||||||
|
if(!SSL_has_pending(conn->ssl)) return 0;
|
||||||
|
/* Read ahead and not decrypted: a record, or only a part of one. A
|
||||||
|
part is no data, and saying so would have poll() return at once,
|
||||||
|
again and again, without its timeout, until the rest came: it is
|
||||||
|
pending only once the record is whole (or the connection ended),
|
||||||
|
the socket polled for the rest otherwise. The sockets are not
|
||||||
|
blocking, so this does not wait. */
|
||||||
|
res = SSL_peek(conn->ssl, &c, 1);
|
||||||
|
if(res > 0) return 1;
|
||||||
|
res = SSL_get_error(conn->ssl, res);
|
||||||
|
return res != SSL_ERROR_WANT_READ && res != SSL_ERROR_WANT_WRITE;
|
||||||
|
#else
|
||||||
return SSL_pending(conn->ssl);
|
return SSL_pending(conn->ssl);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void ssl_conn_free(SSL_CONN connection)
|
void ssl_conn_free(SSL_CONN connection)
|
||||||
|
|||||||
@ -31,8 +31,8 @@ unsigned char * strings[] = {
|
|||||||
/* 18 */ (unsigned char *)"AUTO",
|
/* 18 */ (unsigned char *)"AUTO",
|
||||||
/* 19 */ (unsigned char *)"TLSPR",
|
/* 19 */ (unsigned char *)"TLSPR",
|
||||||
/* 20 */ (unsigned char *)"IMAPP",
|
/* 20 */ (unsigned char *)"IMAPP",
|
||||||
/* 21 */ (unsigned char *)"ZOMBIE",
|
/* 21 */ (unsigned char *)"HTTPSRV",
|
||||||
/* 22 */ NULL,
|
/* 22 */ (unsigned char *)"ZOMBIE",
|
||||||
/* 23 */ NULL,
|
/* 23 */ NULL,
|
||||||
/* 24 */ NULL,
|
/* 24 */ NULL,
|
||||||
#ifndef TPROXY_CONF
|
#ifndef TPROXY_CONF
|
||||||
|
|||||||
@ -47,6 +47,7 @@ typedef struct _3proxy_sem_s {
|
|||||||
pthread_cond_t cond;
|
pthread_cond_t cond;
|
||||||
unsigned count;
|
unsigned count;
|
||||||
unsigned maxcount;
|
unsigned maxcount;
|
||||||
|
int monotonic; /* timed waits measured on CLOCK_MONOTONIC */
|
||||||
} _3proxy_sem_t;
|
} _3proxy_sem_t;
|
||||||
#else
|
#else
|
||||||
#include <winsock2.h>
|
#include <winsock2.h>
|
||||||
@ -62,6 +63,13 @@ typedef struct _3proxy_sem_s {
|
|||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
#define MAXBANDLIMS 10
|
#define MAXBANDLIMS 10
|
||||||
|
#define MAXFAILEDPARENTS 16
|
||||||
|
/* Parent weights are kept as a share of WEIGHTSCALE, which is what the
|
||||||
|
weights of a group add up to. WEIGHTFUZZ is how far short of it a group may
|
||||||
|
fall and still be taken for a whole one, a thousandth of the share: three
|
||||||
|
weights of 333, or of .333333333, otherwise leave a remainder. */
|
||||||
|
#define WEIGHTSCALE 1000000000u
|
||||||
|
#define WEIGHTFUZZ 1000000u
|
||||||
|
|
||||||
#ifdef WITH_POLL
|
#ifdef WITH_POLL
|
||||||
#include <poll.h>
|
#include <poll.h>
|
||||||
@ -105,6 +113,7 @@ int
|
|||||||
|
|
||||||
#ifdef WITH_UN
|
#ifdef WITH_UN
|
||||||
#include <sys/un.h>
|
#include <sys/un.h>
|
||||||
|
#include <stddef.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#define ALLOW 0
|
#define ALLOW 0
|
||||||
@ -152,7 +161,9 @@ int
|
|||||||
#define UN_SAADDR(sa) (((struct sockaddr_un *)sa)->sun_family == AF_UNIX)? (unsigned char *)((struct sockaddr_un *)sa)->sun_path :
|
#define UN_SAADDR(sa) (((struct sockaddr_un *)sa)->sun_family == AF_UNIX)? (unsigned char *)((struct sockaddr_un *)sa)->sun_path :
|
||||||
#define UN_SAADDRLEN(sa) (((struct sockaddr_un *)sa)->sun_family == AF_UNIX)? (int)sizeof(((struct sockaddr_un *)sa)->sun_path) :
|
#define UN_SAADDRLEN(sa) (((struct sockaddr_un *)sa)->sun_family == AF_UNIX)? (int)sizeof(((struct sockaddr_un *)sa)->sun_path) :
|
||||||
#define UN_SASOCK(sa) (((struct sockaddr_un *)sa)->sun_family == AF_UNIX)? PF_UNIX :
|
#define UN_SASOCK(sa) (((struct sockaddr_un *)sa)->sun_family == AF_UNIX)? PF_UNIX :
|
||||||
#define UN_SASIZE(sa) (((struct sockaddr_un *)sa)->sun_family == AF_UNIX)? sizeof(struct sockaddr_un) :
|
/* an abstract name (Linux) is its bytes after the leading 0, no more:
|
||||||
|
padded with zeros it would be another name */
|
||||||
|
#define UN_SASIZE(sa) (((struct sockaddr_un *)sa)->sun_family == AF_UNIX)? (((struct sockaddr_un *)sa)->sun_path[0] || !((struct sockaddr_un *)sa)->sun_path[1]? sizeof(struct sockaddr_un) : offsetof(struct sockaddr_un, sun_path) + 1 + strlen(((struct sockaddr_un *)sa)->sun_path + 1)) :
|
||||||
#define UN_SAISNULL(sa) (((struct sockaddr_un *)sa)->sun_family == AF_UNIX)? (((struct sockaddr_un *)sa)->sun_path[0] == 0 && ((struct sockaddr_un *)sa)->sun_path[1] == 0) :
|
#define UN_SAISNULL(sa) (((struct sockaddr_un *)sa)->sun_family == AF_UNIX)? (((struct sockaddr_un *)sa)->sun_path[0] == 0 && ((struct sockaddr_un *)sa)->sun_path[1] == 0) :
|
||||||
#else
|
#else
|
||||||
#define UN_SAPORT(sa)
|
#define UN_SAPORT(sa)
|
||||||
@ -336,7 +347,7 @@ struct chain {
|
|||||||
unsigned char * exthost;
|
unsigned char * exthost;
|
||||||
unsigned char * extuser;
|
unsigned char * extuser;
|
||||||
unsigned char * extpass;
|
unsigned char * extpass;
|
||||||
unsigned short weight;
|
unsigned weight;
|
||||||
unsigned short cidr;
|
unsigned short cidr;
|
||||||
/* local port range for extport/intport, first in the low half */
|
/* local port range for extport/intport, first in the low half */
|
||||||
uint32_t range;
|
uint32_t range;
|
||||||
@ -350,21 +361,55 @@ struct period {
|
|||||||
|
|
||||||
#define MATCHBEGIN 1
|
#define MATCHBEGIN 1
|
||||||
#define MATCHEND 2
|
#define MATCHEND 2
|
||||||
|
/* A pattern is either the star form above, matched by matchtype, or a regular
|
||||||
|
expression compiled once when the configuration is read. */
|
||||||
|
#define MATCHGLOB 4 /* stars anywhere: * within a path element, ** across */
|
||||||
|
#define MATCHREGEX 5
|
||||||
|
|
||||||
|
/* What a star or a capturing group stood for. Element 0 is the whole
|
||||||
|
subject, so a template writes it as $0 and the groups as $1 upwards. */
|
||||||
|
#define MAXCAPTURES 10
|
||||||
|
struct capture {
|
||||||
|
int start;
|
||||||
|
int len;
|
||||||
|
};
|
||||||
|
|
||||||
struct hostname {
|
struct hostname {
|
||||||
struct hostname *next;
|
struct hostname *next;
|
||||||
unsigned char * name;
|
unsigned char * name;
|
||||||
int matchtype;
|
int matchtype;
|
||||||
|
void * re; /* compiled regular expression, MATCHREGEX only */
|
||||||
};
|
};
|
||||||
|
|
||||||
/* A request handed to an http operation. */
|
/* A request handed to an http operation. */
|
||||||
struct httpreq {
|
struct httpreq {
|
||||||
|
struct capture caps[MAXCAPTURES];
|
||||||
|
int ncaps;
|
||||||
|
struct capture hostcaps[MAXCAPTURES];
|
||||||
|
int nhostcaps;
|
||||||
|
const char *ctype;
|
||||||
|
const char *hdrs;
|
||||||
|
int maxage;
|
||||||
|
int code;
|
||||||
|
time_t ims; /* what If-Modified-Since asked about, or 0 */
|
||||||
|
int version; /* 0 for HTTP/1.0, 1 for HTTP/1.1 */
|
||||||
|
int keepalive; /* whether the connection carries another request */
|
||||||
|
int first; /* the first request on this connection */
|
||||||
|
int chunkedreq; /* a body this server does not know how to read */
|
||||||
|
int proxy; /* the client asked the way it asks a proxy */
|
||||||
|
int connect; /* and asked for a tunnel */
|
||||||
|
int mayproxy; /* an access rule sent this to the local proxy */
|
||||||
|
unsigned char *raw; /* the request as it arrived, for handing on */
|
||||||
|
int rawlen, rawsize;
|
||||||
|
int drained; /* the body has been read and thrown away */
|
||||||
|
char *lasthost; /* where the last request on this connection went */
|
||||||
|
void *handoff; /* a child which takes the connection over */
|
||||||
struct clientparam *param;
|
struct clientparam *param;
|
||||||
char method[16];
|
char method[16];
|
||||||
char path[256];
|
char path[256];
|
||||||
char query[512];
|
char query[512];
|
||||||
char host[256];
|
char host[256];
|
||||||
unsigned long contentlen;
|
uint64_t contentlen;
|
||||||
int globstart, globlen;
|
int globstart, globlen;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -377,6 +422,10 @@ struct httprule {
|
|||||||
struct hostname url;
|
struct hostname url;
|
||||||
int op;
|
int op;
|
||||||
unsigned char *params;
|
unsigned char *params;
|
||||||
|
unsigned char *ctype; /* type named by the rule, or NULL to work it out */
|
||||||
|
unsigned char *hdrs; /* headers the rule adds, already CRLF separated */
|
||||||
|
int maxage; /* seconds to allow caching for, or -1 to say nothing */
|
||||||
|
int code; /* status the rule answers with, or 0 for the usual */
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ace {
|
struct ace {
|
||||||
@ -483,6 +532,14 @@ struct filterp {
|
|||||||
|
|
||||||
#define MAX_FILTERS 16
|
#define MAX_FILTERS 16
|
||||||
|
|
||||||
|
/* Socket options for one kind of socket, -oc and the like: which of
|
||||||
|
sockopts[] are given, by their index there, and the value of each. */
|
||||||
|
#define MAXSOCKOPTS 32
|
||||||
|
struct sockoptset {
|
||||||
|
unsigned set;
|
||||||
|
int value[MAXSOCKOPTS];
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
struct sockfuncs {
|
struct sockfuncs {
|
||||||
void * state;
|
void * state;
|
||||||
@ -554,6 +611,8 @@ struct srvparam {
|
|||||||
AUTHFUNC authfunc;
|
AUTHFUNC authfunc;
|
||||||
PROXYFUNC pf;
|
PROXYFUNC pf;
|
||||||
SOCKET srvsock, cbsock;
|
SOCKET srvsock, cbsock;
|
||||||
|
/* the file of a Unix socket bound to a path, to remove only it */
|
||||||
|
uint64_t undev, unino;
|
||||||
int childcount;
|
int childcount;
|
||||||
int maxchild;
|
int maxchild;
|
||||||
int backlog;
|
int backlog;
|
||||||
@ -567,12 +626,13 @@ struct srvparam {
|
|||||||
int stacksize;
|
int stacksize;
|
||||||
int noforce;
|
int noforce;
|
||||||
int anonymous;
|
int anonymous;
|
||||||
int clisockopts, srvsockopts, lissockopts, cbcsockopts, cbssockopts;
|
struct sockoptset clisockopts, srvsockopts, lissockopts, cbcsockopts, cbssockopts;
|
||||||
int gracetraf, gracenum, gracedelay;
|
int gracetraf, gracenum, gracedelay;
|
||||||
int requirecert;
|
int requirecert;
|
||||||
int haproxy;
|
int haproxy;
|
||||||
int nostarttls;
|
int nostarttls;
|
||||||
PROXYSERVICE srvstarttls;
|
PROXYSERVICE srvstarttls;
|
||||||
|
int allowftp; /* the http proxy fetches ftp:// for this service */
|
||||||
uint32_t fakeip;
|
uint32_t fakeip;
|
||||||
unsigned char fakeip6[16];
|
unsigned char fakeip6[16];
|
||||||
#ifdef WITHSPLICE
|
#ifdef WITHSPLICE
|
||||||
@ -589,6 +649,7 @@ struct srvparam {
|
|||||||
#endif
|
#endif
|
||||||
PROXYSOCKADDRTYPE extsa;
|
PROXYSOCKADDRTYPE extsa;
|
||||||
_3proxy_mutex_t counter_mutex;
|
_3proxy_mutex_t counter_mutex;
|
||||||
|
_3proxy_sem_t childslots; /* maxchild of them, one held by each child */
|
||||||
struct pollfd fds;
|
struct pollfd fds;
|
||||||
FILE *stdlog;
|
FILE *stdlog;
|
||||||
unsigned char * target;
|
unsigned char * target;
|
||||||
@ -687,6 +748,11 @@ struct clientparam {
|
|||||||
*clibuf,
|
*clibuf,
|
||||||
*srvbuf;
|
*srvbuf;
|
||||||
|
|
||||||
|
/* splice() pipes for the client's and the server's data, -1 until
|
||||||
|
made; kept for the life of the connection */
|
||||||
|
int pipecli[2],
|
||||||
|
pipesrv[2];
|
||||||
|
|
||||||
unsigned cliinbuf,
|
unsigned cliinbuf,
|
||||||
srvinbuf,
|
srvinbuf,
|
||||||
clioffset,
|
clioffset,
|
||||||
@ -715,6 +781,21 @@ struct clientparam {
|
|||||||
int udp_nhops;
|
int udp_nhops;
|
||||||
struct ace *lastace;
|
struct ace *lastace;
|
||||||
time_t time_start;
|
time_t time_start;
|
||||||
|
/* Set by a service which read a request itself and handed it to another
|
||||||
|
child to answer: that child answers this one request and returns,
|
||||||
|
leaving the connection to the service which called it. Added last so
|
||||||
|
that a plugin built against an older header still finds the fields it
|
||||||
|
knows where they were. */
|
||||||
|
int onerequest;
|
||||||
|
/* A STARTTLS protocol to speak before the session is wrapped, set for
|
||||||
|
one connection rather than for the service, which is how a redirect
|
||||||
|
and a service name standing in for a mail proxy reach tlspr. */
|
||||||
|
PROXYSERVICE starttls;
|
||||||
|
/* Parents which failed for this connection. A retry picks another
|
||||||
|
member of the group instead of the same one again, and a zero weight
|
||||||
|
member is only reached once every weighted one is in here. */
|
||||||
|
struct chain *failedparents[MAXFAILEDPARENTS];
|
||||||
|
int nfailedparents;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct filemon {
|
struct filemon {
|
||||||
@ -725,7 +806,6 @@ struct filemon {
|
|||||||
|
|
||||||
|
|
||||||
struct extparam {
|
struct extparam {
|
||||||
_3proxy_sem_t threadinit;
|
|
||||||
int *timeouts;
|
int *timeouts;
|
||||||
struct ace * acl;
|
struct ace * acl;
|
||||||
#ifdef WITH_HTTPSRV
|
#ifdef WITH_HTTPSRV
|
||||||
|
|||||||
@ -334,7 +334,8 @@ void * tlsprchild(struct clientparam* param) {
|
|||||||
int lv=-1;
|
int lv=-1;
|
||||||
char proto[PROTOLEN]="-";
|
char proto[PROTOLEN]="-";
|
||||||
int snipos = 0;
|
int snipos = 0;
|
||||||
PROXYSERVICE stlsproto = param->clientstarttls? param->clientstarttls : param->srv->srvstarttls;
|
PROXYSERVICE stlsproto = param->clientstarttls? param->clientstarttls :
|
||||||
|
(param->starttls? param->starttls : param->srv->srvstarttls);
|
||||||
|
|
||||||
if(!param->clientstarttls && stlsproto){
|
if(!param->clientstarttls && stlsproto){
|
||||||
res = clistarttls(param, stlsproto);
|
res = clistarttls(param, stlsproto);
|
||||||
|
|||||||
240
src/transparent.c
Normal file
240
src/transparent.c
Normal file
@ -0,0 +1,240 @@
|
|||||||
|
/*
|
||||||
|
3APA3A simplest proxy server
|
||||||
|
(c) 2002-2026 by Vladimir Dubrovin <vlad@3proxy.org>
|
||||||
|
|
||||||
|
please read License Agreement
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "structures.h"
|
||||||
|
#include "proxy.h"
|
||||||
|
|
||||||
|
#ifdef WITH_TRANSPARENT
|
||||||
|
|
||||||
|
#ifdef WITH_NETFILTER
|
||||||
|
#include <sys/utsname.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <limits.h>
|
||||||
|
#include <linux/netfilter_ipv4.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef WITH_PF
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <sys/ioctl.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <net/if.h>
|
||||||
|
#include <net/pfvar.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Where the address the client was trying to reach is read from.
|
||||||
|
|
||||||
|
AUTO uses what the platform offers, which is the only thing an
|
||||||
|
installation usually needs. The rest name one mechanism, for a machine
|
||||||
|
that has more than one and redirects with a particular one.
|
||||||
|
*/
|
||||||
|
#define TRANSPARENT_AUTO 0
|
||||||
|
#define TRANSPARENT_NETFILTER 1
|
||||||
|
#define TRANSPARENT_PF 2
|
||||||
|
#define TRANSPARENT_SOCKET 3
|
||||||
|
|
||||||
|
static struct pluginlink * pl;
|
||||||
|
|
||||||
|
static int transparent_loaded = 0;
|
||||||
|
static int transparent_mode = TRANSPARENT_AUTO;
|
||||||
|
|
||||||
|
#ifdef WITH_PF
|
||||||
|
static int pf_device = -1;
|
||||||
|
|
||||||
|
/* Ask the packet filter what the connection was addressed to before it was
|
||||||
|
redirected. pf keeps that in its state table rather than on the socket,
|
||||||
|
so it has to be looked up with the addresses of both ends.
|
||||||
|
*/
|
||||||
|
static int transparent_pf(struct clientparam *param)
|
||||||
|
{
|
||||||
|
struct pfioc_natlook nl;
|
||||||
|
|
||||||
|
if(pf_device < 0){
|
||||||
|
pf_device = open("/dev/pf", O_RDONLY);
|
||||||
|
if(pf_device < 0) return 1;
|
||||||
|
}
|
||||||
|
memset(&nl, 0, sizeof(nl));
|
||||||
|
nl.proto = IPPROTO_TCP;
|
||||||
|
nl.direction = PF_OUT;
|
||||||
|
#ifndef NOIPV6
|
||||||
|
if(*SAFAMILY(¶m->sincr) == AF_INET6){
|
||||||
|
nl.af = AF_INET6;
|
||||||
|
memcpy(&nl.saddr.v6, SAADDR(¶m->sincr), 16);
|
||||||
|
memcpy(&nl.daddr.v6, SAADDR(¶m->sincl), 16);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
nl.af = AF_INET;
|
||||||
|
memcpy(&nl.saddr.v4, SAADDR(¶m->sincr), 4);
|
||||||
|
memcpy(&nl.daddr.v4, SAADDR(¶m->sincl), 4);
|
||||||
|
}
|
||||||
|
nl.sport = *SAPORT(¶m->sincr);
|
||||||
|
nl.dport = *SAPORT(¶m->sincl);
|
||||||
|
|
||||||
|
if(ioctl(pf_device, DIOCNATLOOK, &nl)) return 1;
|
||||||
|
|
||||||
|
memset(¶m->req, 0, sizeof(param->req));
|
||||||
|
*SAFAMILY(¶m->req) = nl.af;
|
||||||
|
#ifndef NOIPV6
|
||||||
|
if(nl.af == AF_INET6) memcpy(SAADDR(¶m->req), &nl.rdaddr.v6, 16);
|
||||||
|
else
|
||||||
|
#endif
|
||||||
|
memcpy(SAADDR(¶m->req), &nl.rdaddr.v4, 4);
|
||||||
|
*SAPORT(¶m->req) = nl.rdport;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef WITH_NETFILTER
|
||||||
|
/* Linux keeps the original address for the connection it redirected. */
|
||||||
|
static int transparent_netfilter(struct clientparam *param)
|
||||||
|
{
|
||||||
|
socklen_t len = sizeof(param->req);
|
||||||
|
|
||||||
|
#ifdef SO_ORIGINAL_DST
|
||||||
|
if(getsockopt(param->clisock,
|
||||||
|
#ifndef NOIPV6
|
||||||
|
#ifdef SOL_IPV6
|
||||||
|
*SAFAMILY(¶m->sincr) == AF_INET6?SOL_IPV6:
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
SOL_IP, SO_ORIGINAL_DST, (struct sockaddr *) ¶m->req, &len)
|
||||||
|
|| !memcmp((char *)SAADDR(¶m->req), "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", SAADDRLEN(¶m->req))){
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
#else
|
||||||
|
#error No SO_ORIGINAL_DST defined
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Some redirections leave the original address on the socket itself, so the
|
||||||
|
local address of the accepted connection is what the client asked for.
|
||||||
|
|
||||||
|
A connection which was not redirected at all arrives at the address the
|
||||||
|
service listens on, and taking that as the destination would send the
|
||||||
|
service to itself. Refuse instead of making the connection.
|
||||||
|
*/
|
||||||
|
static int transparent_socket(struct clientparam *param)
|
||||||
|
{
|
||||||
|
if(*SAFAMILY(¶m->sincl) != AF_INET && *SAFAMILY(¶m->sincl) != AF_INET6)
|
||||||
|
return 1;
|
||||||
|
if(*SAPORT(¶m->sincl) == *SAPORT(¶m->srv->intsa)
|
||||||
|
&& (SAISNULL(¶m->srv->intsa)
|
||||||
|
|| !memcmp(SAADDR(¶m->sincl), SAADDR(¶m->srv->intsa), SAADDRLEN(¶m->sincl))))
|
||||||
|
return 2;
|
||||||
|
param->req = param->sincl;
|
||||||
|
param->sincl = param->srv->intsa;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void* transparent_filter_open(void * idata, struct srvparam * param){
|
||||||
|
return idata;
|
||||||
|
}
|
||||||
|
|
||||||
|
static FILTER_ACTION transparent_filter_client(void *fo, struct clientparam * param, void** fc){
|
||||||
|
|
||||||
|
char addrbuf[64];
|
||||||
|
int res = 1;
|
||||||
|
|
||||||
|
#ifdef WITH_NETFILTER
|
||||||
|
if(transparent_mode == TRANSPARENT_AUTO || transparent_mode == TRANSPARENT_NETFILTER)
|
||||||
|
res = transparent_netfilter(param);
|
||||||
|
#endif
|
||||||
|
#ifdef WITH_PF
|
||||||
|
if(res && (transparent_mode == TRANSPARENT_AUTO || transparent_mode == TRANSPARENT_PF))
|
||||||
|
res = transparent_pf(param);
|
||||||
|
#endif
|
||||||
|
if(res && (transparent_mode == TRANSPARENT_AUTO || transparent_mode == TRANSPARENT_SOCKET)){
|
||||||
|
res = transparent_socket(param);
|
||||||
|
if(res == 2){
|
||||||
|
param->srv->logfunc(param, (unsigned char *)"transparent: connection was not redirected");
|
||||||
|
return REJECT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* Nothing knows where this was going: leave the request alone, so the
|
||||||
|
service decides as it would without the command. */
|
||||||
|
if(res) return PASS;
|
||||||
|
|
||||||
|
pl->myinet_ntop(*SAFAMILY(¶m->req), SAADDR(¶m->req), (char *)addrbuf, sizeof(addrbuf));
|
||||||
|
if(param->hostname) pl->freefunc(param->hostname);
|
||||||
|
param->hostname = (unsigned char *)pl->strdupfunc(addrbuf);
|
||||||
|
param->sinsr = param->req;
|
||||||
|
return PASS;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void transparent_filter_clear(void *fo){
|
||||||
|
}
|
||||||
|
|
||||||
|
static void transparent_filter_close(void *fo){
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct filter transparent_filter = {
|
||||||
|
NULL,
|
||||||
|
"Transparent filter",
|
||||||
|
"Transparent filter",
|
||||||
|
transparent_filter_open,
|
||||||
|
transparent_filter_client,
|
||||||
|
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
|
||||||
|
transparent_filter_clear,
|
||||||
|
transparent_filter_close
|
||||||
|
};
|
||||||
|
|
||||||
|
int h_transparent(int argc, unsigned char **argv){
|
||||||
|
transparent_mode = TRANSPARENT_AUTO;
|
||||||
|
if(argc > 1){
|
||||||
|
if(!strcmp((char *)argv[1], "auto")) transparent_mode = TRANSPARENT_AUTO;
|
||||||
|
else if(!strcmp((char *)argv[1], "netfilter")){
|
||||||
|
#ifndef WITH_NETFILTER
|
||||||
|
fprintf(stderr, "transparent: netfilter is not available in this build\n");
|
||||||
|
return 1;
|
||||||
|
#else
|
||||||
|
transparent_mode = TRANSPARENT_NETFILTER;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
else if(!strcmp((char *)argv[1], "pf")){
|
||||||
|
#ifndef WITH_PF
|
||||||
|
fprintf(stderr, "transparent: pf is not available in this build\n");
|
||||||
|
return 1;
|
||||||
|
#else
|
||||||
|
transparent_mode = TRANSPARENT_PF;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
else if(!strcmp((char *)argv[1], "socket")) transparent_mode = TRANSPARENT_SOCKET;
|
||||||
|
else {
|
||||||
|
fprintf(stderr, "transparent: unknown mode %s, expected auto, netfilter, pf or socket\n", argv[1]);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
transparent_filter.filter_open = transparent_filter_open;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int h_notransparent(int argc, unsigned char **argv){
|
||||||
|
transparent_filter.filter_open = NULL;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void transparent_install(void){
|
||||||
|
pl = &pluginlink;
|
||||||
|
/* A reload runs this again: the filter is a single static entry, so it
|
||||||
|
is only linked in once, and the commands decide whether it acts. */
|
||||||
|
if(!transparent_loaded){
|
||||||
|
transparent_loaded = 1;
|
||||||
|
transparent_filter.next = pl->conf->filters;
|
||||||
|
pl->conf->filters = &transparent_filter;
|
||||||
|
}
|
||||||
|
transparent_filter.filter_open = NULL;
|
||||||
|
transparent_mode = TRANSPARENT_AUTO;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@ -121,6 +121,11 @@ 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 (extbindtodevice(param, param->remsock)) {
|
||||||
|
param->srv->so._closesocket(param->sostate, param->remsock);
|
||||||
|
param->remsock = INVALID_SOCKET;
|
||||||
|
return 12;
|
||||||
|
}
|
||||||
if (bindwithrange(param, param->remsock, ¶m->sinsl, param->extport)) {
|
if (bindwithrange(param, param->remsock, ¶m->sinsl, param->extport)) {
|
||||||
if (param->extport) {
|
if (param->extport) {
|
||||||
param->srv->so._closesocket(param->sostate, param->remsock);
|
param->srv->so._closesocket(param->sostate, param->remsock);
|
||||||
@ -135,7 +140,7 @@ int udpbind(struct clientparam *param)
|
|||||||
return 12;
|
return 12;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sasize = SASIZE(¶m->sinsl);
|
sasize = sizeof(param->sinsl);
|
||||||
param->srv->so._getsockname(param->sostate, param->remsock,
|
param->srv->so._getsockname(param->sostate, param->remsock,
|
||||||
(struct sockaddr *)¶m->sinsl, &sasize);
|
(struct sockaddr *)¶m->sinsl, &sasize);
|
||||||
return 0;
|
return 0;
|
||||||
@ -288,6 +293,12 @@ int udpsockmap(struct clientparam *param, int timeo)
|
|||||||
}
|
}
|
||||||
default: return 997;
|
default: return 997;
|
||||||
}
|
}
|
||||||
|
/* As for TCP requests, a literal address is the host name,
|
||||||
|
otherwise an ACL with host names only matches any address */
|
||||||
|
if (!dstname) {
|
||||||
|
myinet_ntop(*SAFAMILY(&dst), SAADDR(&dst), dstnamebuf, sizeof(dstnamebuf));
|
||||||
|
dstname = dstnamebuf;
|
||||||
|
}
|
||||||
memcpy(SAPORT(&dst), base + i, 2);
|
memcpy(SAPORT(&dst), base + i, 2);
|
||||||
i += 2;
|
i += 2;
|
||||||
|
|
||||||
|
|||||||
@ -1,14 +1,14 @@
|
|||||||
#ifndef VERSION
|
#ifndef VERSION
|
||||||
#define VERSION "3proxy-1.0.0"
|
#define VERSION "3proxy-1.0.2"
|
||||||
#endif
|
#endif
|
||||||
#ifndef BUILDDATE
|
#ifndef BUILDDATE
|
||||||
#define BUILDDATE "260822121300"
|
#define BUILDDATE "261024112140"
|
||||||
#endif
|
#endif
|
||||||
#define MAJOR3PROXY 1
|
#define MAJOR3PROXY 1
|
||||||
#define SUBMAJOR3PROXY 0
|
#define SUBMAJOR3PROXY 0
|
||||||
#define MINOR3PROXY 0
|
#define MINOR3PROXY 2
|
||||||
#define SUBMINOR3PROXY 0
|
#define SUBMINOR3PROXY 0
|
||||||
#define RELEASE3PROXY "3proxy-1.0.0(" BUILDDATE ")\0"
|
#define RELEASE3PROXY "3proxy-1.0.2(" BUILDDATE ")\0"
|
||||||
#ifndef YEAR3PROXY
|
#ifndef YEAR3PROXY
|
||||||
#define YEAR3PROXY "2026"
|
#define YEAR3PROXY "2026"
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -13,6 +13,14 @@ skips itself when that is missing or the build has no TLS support. With no
|
|||||||
`--bin` it looks in `bin/`, then `build/bin/`, then the per-configuration
|
`--bin` it looks in `bin/`, then `build/bin/`, then the per-configuration
|
||||||
directories a multi-configuration CMake generator uses.
|
directories a multi-configuration CMake generator uses.
|
||||||
|
|
||||||
|
The binary reports what it was built with in `3proxy --help` (a `built
|
||||||
|
with:` list), and the runner reads that before the first case: a case names
|
||||||
|
the features it needs with `if not t.skip_unless("ssl"): return`, and
|
||||||
|
skips on a build without them instead of failing. A binary too old to
|
||||||
|
report its features skips every case that needs one, and the cases with no
|
||||||
|
feature needs still run; some cases also probe at runtime, as a build can
|
||||||
|
leave a feature compiled in but unusable.
|
||||||
|
|
||||||
The proxy under test is also the origin server the tests talk to: the `http`
|
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,
|
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
|
query, host, and the source port it came from - and `data` generates a body
|
||||||
@ -69,7 +77,7 @@ means to stand on its own should start with one - otherwise an earlier
|
|||||||
|
|
||||||
## What is not covered yet
|
## What is not covered yet
|
||||||
|
|
||||||
41 of the 112 configuration commands appear in a test, and the count says
|
42 of the 112 configuration commands appear in a test, and the count says
|
||||||
nothing about service options: the IPv6 case, for instance, exercises -4,
|
nothing about service options: the IPv6 case, for instance, exercises -4,
|
||||||
-6, -46, -64 and -i without adding a command to it. What follows is
|
-6, -46, -64 and -i without adding a command to it. What follows is
|
||||||
roughly the order worth working through: how much of the product a gap
|
roughly the order worth working through: how much of the product a gap
|
||||||
@ -78,12 +86,13 @@ covers, and how much of a fixture it needs.
|
|||||||
### Traffic limits and accounting
|
### Traffic limits and accounting
|
||||||
|
|
||||||
`bandlimin` `bandlimout` `nobandlimin` `nobandlimout` `connlim` `noconnlim`
|
`bandlimin` `bandlimout` `nobandlimin` `nobandlimout` `connlim` `noconnlim`
|
||||||
`countin` `countout` `countall` and the `no*` forms, `maxconn`.
|
`countin` `countout` `countall` and the `no*` forms.
|
||||||
|
|
||||||
Cheap and worth doing first: `data?size=` and a stopwatch measure a
|
Cheap and worth doing first: `data?size=` and a stopwatch measure a
|
||||||
bandwidth limit, and the admin counters page already shows what a counter
|
bandwidth limit, and the admin counters page already shows what a counter
|
||||||
holds. `countin` appears in a configuration today but nothing checks that it
|
holds. `countin` appears in a configuration today but nothing checks that it
|
||||||
counts. `connlim` and `maxconn` need concurrent connections.
|
counts. `connlim` needs concurrent connections, held open the way the
|
||||||
|
`maxconn` case holds them.
|
||||||
|
|
||||||
### The mail proxies
|
### The mail proxies
|
||||||
|
|
||||||
|
|||||||
@ -1,10 +1,14 @@
|
|||||||
"""The admin interface, now a set of handlers on the HTTP server."""
|
"""The admin interface, now a set of handlers on the HTTP server."""
|
||||||
|
|
||||||
|
import time
|
||||||
|
|
||||||
|
|
||||||
def run(t):
|
def run(t):
|
||||||
|
if not t.skip_unless("httpsrv"):
|
||||||
|
return
|
||||||
adm = t.free_port()
|
adm = t.free_port()
|
||||||
lim = t.free_port()
|
lim = t.free_port()
|
||||||
t.start("admin", f"""
|
server = t.start("admin", f"""
|
||||||
log
|
log
|
||||||
auth iponly
|
auth iponly
|
||||||
allow *
|
allow *
|
||||||
@ -20,17 +24,40 @@ def run(t):
|
|||||||
|
|
||||||
url = f"http://127.0.0.1:{adm}"
|
url = f"http://127.0.0.1:{adm}"
|
||||||
|
|
||||||
|
def reload(path):
|
||||||
|
"""Ask for a reload and wait for it to be done. It happens on the next
|
||||||
|
tick of the main loop, at most a second later; both old services stop
|
||||||
|
and the new ones start, and a request in between is refused (Windows
|
||||||
|
cannot bind a port still listening) or reset."""
|
||||||
|
before = server.output()
|
||||||
|
accepting = before.count("Accepting connections")
|
||||||
|
exiting = before.count("Exiting thread")
|
||||||
|
reply = t.http(url + path)
|
||||||
|
deadline = time.time() + 10
|
||||||
|
while time.time() < deadline:
|
||||||
|
now = server.output()
|
||||||
|
if (now.count("Accepting connections") >= accepting + 2
|
||||||
|
and now.count("Exiting thread") >= exiting + 2):
|
||||||
|
break
|
||||||
|
time.sleep(0.05)
|
||||||
|
return reply
|
||||||
|
|
||||||
# --- the predefined pages -----------------------------------------
|
# --- the predefined pages -----------------------------------------
|
||||||
t.eq(200, t.http(url + "/").status, "the main page")
|
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 + "/C").status, "the counters page")
|
||||||
t.eq(200, t.http(url + "/R").status, "the reload page")
|
t.eq(200, reload("/R").status, "the reload page")
|
||||||
t.eq(200, t.http(url + "/S").status, "the services page")
|
t.eq(200, t.http(url + "/S").status, "the services page")
|
||||||
|
|
||||||
counters = t.http(url + "/C")
|
counters = t.http(url + "/C")
|
||||||
t.contains(counters, "countin", "the counters page names the counter type")
|
t.contains(counters, "countin", "the counters page names the counter type")
|
||||||
t.contains(counters, "<tr>", "the counters page renders a table")
|
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(reload("/R"), "Reload", "the reload page confirms the request")
|
||||||
t.contains(t.http(url + "/S"), "<", "the services page returns markup")
|
t.contains(t.http(url + "/S"), "<", "the services page returns markup")
|
||||||
|
# admin and httpsrv services are of type HTTPSRV, named as such, not as
|
||||||
|
# the zombie a stopped service is
|
||||||
|
services = t.http(url + "/S")
|
||||||
|
t.contains(services, "CDATA[HTTPSRV]", "the services page names the admin service HTTPSRV")
|
||||||
|
t.not_contains(services, "CDATA[ZOMBIE]", "and no running service a zombie")
|
||||||
|
|
||||||
# --- the menu no longer offers the removed config editor -----------
|
# --- the menu no longer offers the removed config editor -----------
|
||||||
main = t.http(url + "/")
|
main = t.http(url + "/")
|
||||||
|
|||||||
@ -7,7 +7,9 @@ handshake and the service supplies the port.
|
|||||||
|
|
||||||
|
|
||||||
def run(t):
|
def run(t):
|
||||||
certs = t.certs()
|
if not t.skip_unless("httpsrv"):
|
||||||
|
return
|
||||||
|
certs = t.certs() if t.has("ssl") else None
|
||||||
plain = t.free_port()
|
plain = t.free_port()
|
||||||
port = t.free_port()
|
port = t.free_port()
|
||||||
secure = t.free_port() if certs else None
|
secure = t.free_port() if certs else None
|
||||||
@ -21,7 +23,7 @@ def run(t):
|
|||||||
ssl_serv
|
ssl_serv
|
||||||
auth iponly
|
auth iponly
|
||||||
allow *
|
allow *
|
||||||
http * /echo* echo
|
http echo * /echo**
|
||||||
httpsrv -p{secure}
|
httpsrv -p{secure}
|
||||||
ssl_noserv"""
|
ssl_noserv"""
|
||||||
|
|
||||||
@ -30,7 +32,7 @@ def run(t):
|
|||||||
log
|
log
|
||||||
auth iponly
|
auth iponly
|
||||||
allow *
|
allow *
|
||||||
http * /echo* echo
|
http echo * /echo**
|
||||||
httpsrv -p{plain}
|
httpsrv -p{plain}
|
||||||
{tls_origin}
|
{tls_origin}
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
"""dnspr: a caching DNS proxy, answering from what it has been told."""
|
"""dnspr: a caching DNS proxy, answering from what it has been told."""
|
||||||
|
|
||||||
|
import time
|
||||||
|
|
||||||
|
|
||||||
def run(t):
|
def run(t):
|
||||||
port = t.free_port()
|
port = t.free_port()
|
||||||
@ -15,8 +17,11 @@ def run(t):
|
|||||||
allow *
|
allow *
|
||||||
dnspr -p{port}
|
dnspr -p{port}
|
||||||
""")
|
""")
|
||||||
# wait for the service: a datagram sent too early is simply lost
|
# Wait for the service: a datagram sent too early is simply lost. Bound
|
||||||
for _ in range(100):
|
# by the clock, not by a number of attempts, so a server that answers
|
||||||
|
# nothing costs seconds rather than minutes.
|
||||||
|
deadline = time.time() + 5
|
||||||
|
while time.time() < deadline:
|
||||||
if t.dns_query(port, "host.test"):
|
if t.dns_query(port, "host.test"):
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|||||||
108
tests/cases/ftppr.py
Normal file
108
tests/cases/ftppr.py
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
"""The FTP proxy logging in to its server: whatever the server answers the
|
||||||
|
login with reaches the client, which waits for the answer to its PASS."""
|
||||||
|
|
||||||
|
import socket
|
||||||
|
import threading
|
||||||
|
|
||||||
|
# what the server answers USER with, and PASS if it asks for one
|
||||||
|
LOGINS = {
|
||||||
|
"230 to USER": ([b"230 Anonymous user logged in"], None),
|
||||||
|
"multiline 230 to USER": ([b"230-Welcome", b"230-to the server", b"230 Login successful."], None),
|
||||||
|
"331 to USER, 230 to PASS": ([b"331 Please specify the password."], [b"230 Login successful."]),
|
||||||
|
"530 to USER": ([b"530 Not allowed here"], None),
|
||||||
|
"331 to USER, 530 to PASS": ([b"331 Please specify the password."], [b"530 Login incorrect."]),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def ftp_server(listener, user, password):
|
||||||
|
"""Greets, answers the login as told, then NOOP and QUIT."""
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
c, _ = listener.accept()
|
||||||
|
except OSError:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
f = c.makefile("rb")
|
||||||
|
c.sendall(b"220 Fake FTP\r\n")
|
||||||
|
if not f.readline().upper().startswith(b"USER "):
|
||||||
|
continue
|
||||||
|
c.sendall(b"".join(line + b"\r\n" for line in user))
|
||||||
|
if password is not None:
|
||||||
|
if not f.readline().upper().startswith(b"PASS "):
|
||||||
|
continue
|
||||||
|
c.sendall(b"".join(line + b"\r\n" for line in password))
|
||||||
|
for line in f:
|
||||||
|
if line.upper().startswith(b"NOOP"):
|
||||||
|
c.sendall(b"200 NOOP ok\r\n")
|
||||||
|
elif line.upper().startswith(b"QUIT"):
|
||||||
|
c.sendall(b"221 Bye\r\n")
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
c.sendall(b"502 Not implemented\r\n")
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
finally:
|
||||||
|
c.close()
|
||||||
|
|
||||||
|
|
||||||
|
def reply(f):
|
||||||
|
"""The last line of a reply, multiline or not; empty at the end or on a
|
||||||
|
time out."""
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
line = f.readline()
|
||||||
|
except OSError:
|
||||||
|
return b""
|
||||||
|
if not line:
|
||||||
|
return b""
|
||||||
|
if len(line) < 4 or line[3:4] != b"-":
|
||||||
|
return line.rstrip()
|
||||||
|
|
||||||
|
|
||||||
|
def run(t):
|
||||||
|
if not t.skip_unless("ftppr"):
|
||||||
|
return
|
||||||
|
servers = {}
|
||||||
|
for name, (user, password) in LOGINS.items():
|
||||||
|
listener = socket.socket()
|
||||||
|
listener.bind(("127.0.0.1", 0))
|
||||||
|
listener.listen(5)
|
||||||
|
threading.Thread(target=ftp_server, args=(listener, user, password), daemon=True).start()
|
||||||
|
servers[name] = listener
|
||||||
|
|
||||||
|
port = t.free_port()
|
||||||
|
t.start("ftppr", f"""
|
||||||
|
log
|
||||||
|
auth none
|
||||||
|
ftppr -p{port}
|
||||||
|
""", ports=[port])
|
||||||
|
|
||||||
|
# without WITH_FTP the service closes the connection at once
|
||||||
|
s = socket.create_connection(("127.0.0.1", port), timeout=5)
|
||||||
|
built = reply(s.makefile("rb"))
|
||||||
|
s.close()
|
||||||
|
if not built:
|
||||||
|
t.skip("the FTP proxy (not built in this configuration)")
|
||||||
|
for listener in servers.values():
|
||||||
|
listener.close()
|
||||||
|
t.stop_all()
|
||||||
|
return
|
||||||
|
|
||||||
|
for name, listener in servers.items():
|
||||||
|
s = socket.create_connection(("127.0.0.1", port), timeout=5)
|
||||||
|
f = s.makefile("rb")
|
||||||
|
reply(f)
|
||||||
|
s.sendall(b"USER anonymous@127.0.0.1:%d\r\n" % listener.getsockname()[1])
|
||||||
|
reply(f)
|
||||||
|
s.sendall(b"PASS test@example.com\r\n")
|
||||||
|
got = reply(f)
|
||||||
|
ok = LOGINS[name][1] if LOGINS[name][1] is not None else LOGINS[name][0]
|
||||||
|
t.eq(ok[-1][:3], got[:3], f"{name}: the client gets the server's answer")
|
||||||
|
if got.startswith(b"230"):
|
||||||
|
s.sendall(b"NOOP\r\n")
|
||||||
|
t.eq(b"200", reply(f)[:3], f"{name}: and the session goes on")
|
||||||
|
s.close()
|
||||||
|
|
||||||
|
for listener in servers.values():
|
||||||
|
listener.close()
|
||||||
|
t.stop_all()
|
||||||
@ -2,18 +2,20 @@
|
|||||||
|
|
||||||
|
|
||||||
def run(t):
|
def run(t):
|
||||||
|
if not t.skip_unless("httpsrv"):
|
||||||
|
return
|
||||||
srv = t.free_port()
|
srv = t.free_port()
|
||||||
openport = t.free_port()
|
openport = t.free_port()
|
||||||
t.start("httpsrv_auth", f"""
|
t.start("httpsrv_auth", f"""
|
||||||
log
|
log
|
||||||
http * /echo echo
|
http echo * /echo
|
||||||
auth strong
|
auth strong
|
||||||
users alice:CL:secret bob:CL:hunter2
|
users alice:CL:secret bob:CL:hunter2
|
||||||
allow alice
|
allow alice
|
||||||
httpsrv -p{srv}
|
httpsrv -p{srv}
|
||||||
|
|
||||||
flush
|
flush
|
||||||
http * /echo echo
|
http echo * /echo
|
||||||
auth iponly
|
auth iponly
|
||||||
allow *
|
allow *
|
||||||
httpsrv -p{openport}
|
httpsrv -p{openport}
|
||||||
|
|||||||
197
tests/cases/httpsrv_files.py
Normal file
197
tests/cases/httpsrv_files.py
Normal file
@ -0,0 +1,197 @@
|
|||||||
|
"""The operations that serve a filesystem: file, cache, redir and rewrite.
|
||||||
|
|
||||||
|
A rule maps a request onto a path with a template, where $1 upwards stand for
|
||||||
|
what the stars or the groups of a regular expression matched.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
|
def run(t):
|
||||||
|
if not t.skip_unless("httpsrv"):
|
||||||
|
return
|
||||||
|
root = os.path.join(t.tmpdir, "web")
|
||||||
|
os.makedirs(os.path.join(root, "picts", "set"), exist_ok=True)
|
||||||
|
with open(os.path.join(root, "a.html"), "w") as fp:
|
||||||
|
fp.write("<h1>hello</h1>")
|
||||||
|
with open(os.path.join(root, "big.bin"), "wb") as fp:
|
||||||
|
fp.write(b"x" * 300000) # past a single send, and past the cache limit
|
||||||
|
with open(os.path.join(root, "picts", "set", "dog.gif"), "wb") as fp:
|
||||||
|
fp.write(b"GIF89a-pretend")
|
||||||
|
|
||||||
|
with open(os.path.join(root, "b.webp"), "wb") as fp:
|
||||||
|
fp.write(b"RIFF-pretend")
|
||||||
|
|
||||||
|
port = t.free_port()
|
||||||
|
t.start("httpsrv_files", f"""
|
||||||
|
log
|
||||||
|
auth iponly
|
||||||
|
allow *
|
||||||
|
http rewrite * /alias/** "/w/$1"
|
||||||
|
http file * /w/*.html "{root}/$1.html"
|
||||||
|
http file * /big {root}/big.bin
|
||||||
|
http cache * /c/*.html "{root}/$1.html"
|
||||||
|
http cache * "pcre:^/(.*)/pic/(.*)\\.(gif|jpeg)$" "{root}/picts/$1/$2.$3"
|
||||||
|
http redir * /old/** 301 "https://example.org/$1"
|
||||||
|
http redir * /moved /w/a.html
|
||||||
|
http file * /rel/*.html "web/$1.html"
|
||||||
|
http echo * /echo
|
||||||
|
|
||||||
|
http_content_type .webp image/webp
|
||||||
|
http_content_type dat application/x-mydata
|
||||||
|
http file * /ct/*.webp "{root}/$1.webp"
|
||||||
|
http file * /named/*.html "{root}/$1.html" text/x-named
|
||||||
|
http file * /star/*.html "{root}/$1.html" *
|
||||||
|
http cache * /ctc/*.webp "{root}/$1.webp"
|
||||||
|
|
||||||
|
http file * /aged/*.html "{root}/$1.html" * 3600
|
||||||
|
http cache * /aged2/*.html "{root}/$1.html" * 60
|
||||||
|
http file * /extra/*.html "{root}/$1.html" * * "X-One: 1\\nX-Two: two words"
|
||||||
|
http file * /err/*.html "{root}/$1.html" * * "X-Served: static" 404
|
||||||
|
http reply * /ok**
|
||||||
|
http reply * /nobody** 204
|
||||||
|
http reply * /down** 503 "Retry-After: 30"
|
||||||
|
http cache * /held/*.html "{root}/$1.html" * 30
|
||||||
|
httpsrv -p{port}
|
||||||
|
""", ports=[port])
|
||||||
|
|
||||||
|
url = f"http://127.0.0.1:{port}"
|
||||||
|
|
||||||
|
# --- file -------------------------------------------------------------
|
||||||
|
r = t.http(url + "/w/a.html")
|
||||||
|
t.eq(200, r.status, "a file is served")
|
||||||
|
t.contains(r, "<h1>hello</h1>", "with its content")
|
||||||
|
t.eq("text/html", r.header("Content-Type"), "and a type taken from the name")
|
||||||
|
t.eq(str(len("<h1>hello</h1>")), r.header("Content-Length"), "and its length")
|
||||||
|
|
||||||
|
t.eq(404, t.http(url + "/w/nosuch.html").status, "a missing file is not found")
|
||||||
|
big = t.http(url + "/big")
|
||||||
|
t.eq(300000, big.length, "a large file arrives whole")
|
||||||
|
t.eq("300000", big.header("Content-Length"), "and is announced by its length")
|
||||||
|
t.eq(None, big.header("Transfer-Encoding"),
|
||||||
|
"a file is never sent chunked")
|
||||||
|
t.eq("300000", t.http(url + "/big", method="HEAD").header("Content-Length"),
|
||||||
|
"HEAD gives the length without the body")
|
||||||
|
t.eq(200, t.http(url + "/w/a.html", method="HEAD").status, "HEAD is answered")
|
||||||
|
t.eq(0, t.http(url + "/w/a.html", method="HEAD").length, "HEAD carries no body")
|
||||||
|
|
||||||
|
# --- cache ------------------------------------------------------------
|
||||||
|
first = t.http(url + "/c/a.html")
|
||||||
|
second = t.http(url + "/c/a.html")
|
||||||
|
t.eq(200, first.status, "a cached file is served")
|
||||||
|
t.eq(first.text, second.text, "and the same on the next request")
|
||||||
|
t.contains(second, "<h1>hello</h1>", "from memory this time")
|
||||||
|
|
||||||
|
# a file changed on disk is noticed rather than served from before
|
||||||
|
with open(os.path.join(root, "a.html"), "w") as fp:
|
||||||
|
fp.write("<h1>changed</h1>")
|
||||||
|
t.contains(t.http(url + "/c/a.html"), "changed",
|
||||||
|
"a file replaced on disk is read again")
|
||||||
|
|
||||||
|
# --- what the stars stand for -----------------------------------------
|
||||||
|
r = t.http(url + "/set/pic/dog.gif")
|
||||||
|
t.eq(200, r.status, "a regular expression maps a request onto a path")
|
||||||
|
t.contains(r, "GIF89a", "and the file is served")
|
||||||
|
t.eq("image/gif", r.header("Content-Type"), "with the type of that name")
|
||||||
|
|
||||||
|
# --- redir ------------------------------------------------------------
|
||||||
|
r = t.http(url + "/old/thing")
|
||||||
|
t.eq(301, r.status, "a redirect uses the status it was given")
|
||||||
|
t.eq("https://example.org/thing", r.header("Location"),
|
||||||
|
"and a location built from the request")
|
||||||
|
t.eq(302, t.http(url + "/moved").status, "without a status it is 302")
|
||||||
|
|
||||||
|
# --- rewrite ----------------------------------------------------------
|
||||||
|
r = t.http(url + "/alias/a.html")
|
||||||
|
t.eq(200, r.status, "a rewritten request reaches the rule after it")
|
||||||
|
t.contains(r, "changed", "and is served from the path it was rewritten to")
|
||||||
|
|
||||||
|
# --- the type a reply carries -------------------------------------------
|
||||||
|
# Worked out from the name, using what the configuration has registered
|
||||||
|
# on top of what is built in, unless the rule says otherwise.
|
||||||
|
t.eq("image/webp", t.http(url + "/ct/b.webp").header("Content-Type"),
|
||||||
|
"a registered extension names the type")
|
||||||
|
t.eq("image/webp", t.http(url + "/ctc/b.webp").header("Content-Type"),
|
||||||
|
"and a cached file is answered the same way")
|
||||||
|
t.eq("text/x-named", t.http(url + "/named/a.html").header("Content-Type"),
|
||||||
|
"a rule may name the type itself")
|
||||||
|
t.eq("text/html", t.http(url + "/star/a.html").header("Content-Type"),
|
||||||
|
"and a star there leaves it to the name of the file")
|
||||||
|
|
||||||
|
# --- what a rule adds to the answer ------------------------------------
|
||||||
|
r = t.http(url + "/aged/a.html")
|
||||||
|
t.eq("max-age=3600", r.header("Cache-Control"), "a rule may describe caching")
|
||||||
|
t.eq("max-age=60", t.http(url + "/aged2/a.html").header("Cache-Control"),
|
||||||
|
"a cached file is answered the same way")
|
||||||
|
t.eq(None, t.http(url + "/w/a.html").header("Cache-Control"),
|
||||||
|
"and a rule which says nothing sends nothing")
|
||||||
|
|
||||||
|
r = t.http(url + "/extra/a.html")
|
||||||
|
t.eq("1", r.header("X-One"), "a rule may add headers")
|
||||||
|
t.eq("two words", r.header("X-Two"),
|
||||||
|
"the second of them arrives whole, spaces and all")
|
||||||
|
|
||||||
|
r = t.http(url + "/err/a.html")
|
||||||
|
t.eq(404, r.status, "a rule may answer with the status it names")
|
||||||
|
t.contains(r, "<h1>", "and the file is still the body")
|
||||||
|
t.eq("static", r.header("X-Served"),
|
||||||
|
"what the rule adds goes with the status the rule asked for")
|
||||||
|
|
||||||
|
# a refusal the server decided on is its own answer
|
||||||
|
r = t.http(url + "/err/nosuch.html")
|
||||||
|
t.eq(404, r.status, "a missing file is still not found")
|
||||||
|
t.eq(None, r.header("X-Served"), "and carries none of the rule's headers")
|
||||||
|
t.eq(None, t.http(url + "/aged/nosuch.html").header("Cache-Control"),
|
||||||
|
"nor what it said about caching")
|
||||||
|
|
||||||
|
# --- reply --------------------------------------------------------------
|
||||||
|
r = t.http(url + "/ok")
|
||||||
|
t.eq(200, r.status, "reply answers with 200 by default")
|
||||||
|
t.eq("0", r.header("Content-Length"), "with a length of zero")
|
||||||
|
t.eq(0, r.length, "and no body")
|
||||||
|
|
||||||
|
r = t.http(url + "/nobody")
|
||||||
|
t.eq(204, r.status, "reply answers with the status it was given")
|
||||||
|
t.eq(None, r.header("Content-Length"),
|
||||||
|
"and a status carrying no body is sent without a length")
|
||||||
|
|
||||||
|
r = t.http(url + "/down")
|
||||||
|
t.eq(503, r.status, "reply serves a refusal the configuration decided on")
|
||||||
|
t.eq("30", r.header("Retry-After"), "with the headers that go with it")
|
||||||
|
|
||||||
|
# --- a client which has the file already --------------------------------
|
||||||
|
r = t.http(url + "/w/a.html")
|
||||||
|
stamp = r.header("Last-Modified")
|
||||||
|
t.ne(None, stamp, "a file is answered with the time it was last changed")
|
||||||
|
|
||||||
|
r = t.http(url + "/w/a.html", headers={"If-Modified-Since": stamp})
|
||||||
|
t.eq(304, r.status, "and an unchanged file is answered 304")
|
||||||
|
t.eq(0, r.length, "which carries no body")
|
||||||
|
t.eq(None, r.header("Content-Length"), "and no length")
|
||||||
|
t.eq(stamp, r.header("Last-Modified"), "but still says when the file changed")
|
||||||
|
|
||||||
|
t.eq(200, t.http(url + "/w/a.html",
|
||||||
|
headers={"If-Modified-Since": "Sun, 06 Nov 1994 08:49:37 GMT"}).status,
|
||||||
|
"an older date is answered with the file")
|
||||||
|
t.eq(200, t.http(url + "/w/a.html",
|
||||||
|
headers={"If-Modified-Since": "not a date at all"}).status,
|
||||||
|
"and a date which cannot be read is treated as none")
|
||||||
|
t.eq(304, t.http(url + "/c/a.html", headers={"If-Modified-Since": stamp}).status,
|
||||||
|
"a file answered from memory is conditional in the same way")
|
||||||
|
t.eq(404, t.http(url + "/err/a.html", headers={"If-Modified-Since": stamp}).status,
|
||||||
|
"a rule with a status of its own is not turned into a 304")
|
||||||
|
|
||||||
|
# --- a rule which says how long its copy may be held --------------------
|
||||||
|
t.contains(t.http(url + "/held/a.html"), "changed", "a held file is served")
|
||||||
|
with open(os.path.join(root, "a.html"), "w") as fp:
|
||||||
|
fp.write("<h1>replaced</h1>")
|
||||||
|
t.not_contains(t.http(url + "/held/a.html"), "replaced",
|
||||||
|
"and within its max-age the disk is not looked at again")
|
||||||
|
t.contains(t.http(url + "/c/a.html"), "replaced",
|
||||||
|
"while a rule without one notices the change at once")
|
||||||
|
|
||||||
|
# --- the paths a rule may not build ------------------------------------
|
||||||
|
t.eq(403, t.http(url + "/rel/a.html").status,
|
||||||
|
"a relative target is refused")
|
||||||
|
t.ne(200, t.http(url + "/w/../etc/passwd").status,
|
||||||
|
"a request climbing out of the tree is refused")
|
||||||
111
tests/cases/httpsrv_keepalive.py
Normal file
111
tests/cases/httpsrv_keepalive.py
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
"""Keep-alive: which answers may be followed by another request.
|
||||||
|
|
||||||
|
The next request begins where the last answer ended, so a connection is only
|
||||||
|
kept when the length of what was sent is known exactly and the body of the
|
||||||
|
request was read to its end. Everything else closes, which is the safe way to
|
||||||
|
be wrong.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
|
def run(t):
|
||||||
|
if not t.skip_unless("httpsrv"):
|
||||||
|
return
|
||||||
|
root = os.path.join(t.tmpdir, "ka")
|
||||||
|
os.makedirs(root, exist_ok=True)
|
||||||
|
with open(os.path.join(root, "a.html"), "w") as fp:
|
||||||
|
fp.write("<h1>hello</h1>")
|
||||||
|
|
||||||
|
port = t.free_port()
|
||||||
|
t.start("httpsrv_keepalive", f"""
|
||||||
|
log
|
||||||
|
auth iponly
|
||||||
|
allow *
|
||||||
|
http file * /w/*.html "{root}/$1.html"
|
||||||
|
http reply * /ok** 200
|
||||||
|
http echo * /echo**
|
||||||
|
http data * /chunked** size=100&chunked=1
|
||||||
|
httpsrv -p{port}
|
||||||
|
""", ports=[port])
|
||||||
|
|
||||||
|
def req(path, version="1.1", extra="", body=""):
|
||||||
|
head = (f"GET {path} HTTP/{version}\r\nHost: t\r\n{extra}\r\n")
|
||||||
|
if body:
|
||||||
|
head = head.replace("GET", "POST", 1)
|
||||||
|
return head + body
|
||||||
|
|
||||||
|
def session(*requests, quiet=0.5):
|
||||||
|
text, closed = t.raw_session(port, "".join(requests), quiet=quiet)
|
||||||
|
return text, closed, text.count("HTTP/1.")
|
||||||
|
|
||||||
|
# --- what keeps the connection ---------------------------------------
|
||||||
|
text, closed, n = session(req("/w/a.html"), req("/ok"),
|
||||||
|
req("/w/a.html", extra="Connection: close\r\n"))
|
||||||
|
t.eq(3, n, "three 1.1 requests are answered on one connection")
|
||||||
|
t.eq(True, closed, "and the one asking to close ends it")
|
||||||
|
t.contains(text, "Connection: keep-alive", "the answers say the connection is kept")
|
||||||
|
t.eq(2, text.count("<h1>hello</h1>"), "each file arrives whole")
|
||||||
|
|
||||||
|
text, closed, n = session(req("/w/a.html", version="1.0"), req("/ok", version="1.0"))
|
||||||
|
t.eq(1, n, "a 1.0 request without the header is answered once")
|
||||||
|
t.eq(True, closed, "and the connection ends")
|
||||||
|
t.contains(text, "Connection: close", "which the answer says")
|
||||||
|
|
||||||
|
text, closed, n = session(req("/w/a.html", version="1.0",
|
||||||
|
extra="Connection: keep-alive\r\n"),
|
||||||
|
req("/ok", version="1.0",
|
||||||
|
extra="Connection: close\r\n"))
|
||||||
|
t.eq(2, n, "a 1.0 client asking for keep-alive gets it")
|
||||||
|
|
||||||
|
# a request carrying a body: the next one begins after it
|
||||||
|
text, closed, n = session(req("/echo", extra="Content-Length: 5\r\n", body="hello"),
|
||||||
|
req("/ok", extra="Connection: close\r\n"))
|
||||||
|
t.eq(2, n, "a body which was read to its end leaves the stream in place")
|
||||||
|
t.contains(text, "content.length=5", "and the body was seen")
|
||||||
|
|
||||||
|
# --- what ends it -----------------------------------------------------
|
||||||
|
text, closed, n = session(req("/echo", extra="Transfer-Encoding: chunked\r\n"),
|
||||||
|
req("/ok"))
|
||||||
|
t.eq(1, n, "a request body this server cannot frame ends the connection")
|
||||||
|
t.eq(True, closed, "the connection is closed rather than left mid-body")
|
||||||
|
|
||||||
|
# A body longer than the server is willing to read leaves the rest of it
|
||||||
|
# in the stream, so the connection cannot carry another request. The send
|
||||||
|
# may not even finish - the server answers and closes part way through -
|
||||||
|
# which is the same answer from the other side.
|
||||||
|
big = "x" * 1500000
|
||||||
|
text, closed, n = session(req("/echo", extra="Content-Length: 1500000\r\n", body=big),
|
||||||
|
quiet=2)
|
||||||
|
t.eq(1, n, "a body past what the server will read is answered once")
|
||||||
|
t.contains(text, "Connection: close",
|
||||||
|
"and the answer ends the connection rather than leaving the rest to be read")
|
||||||
|
|
||||||
|
# --- answers of other shapes -----------------------------------------
|
||||||
|
text, closed, n = session(req("/chunked"), req("/ok", extra="Connection: close\r\n"))
|
||||||
|
t.eq(2, n, "a chunked answer may be followed by another request")
|
||||||
|
|
||||||
|
text, closed, n = session(req("/chunked", version="1.0"), req("/ok", version="1.0"))
|
||||||
|
t.eq(1, n, "but not for a client which has no chunked encoding to read")
|
||||||
|
|
||||||
|
stamp = t.http(f"http://127.0.0.1:{port}/w/a.html").header("Last-Modified")
|
||||||
|
text, closed, n = session(req("/w/a.html", extra=f"If-Modified-Since: {stamp}\r\n"),
|
||||||
|
req("/ok", extra="Connection: close\r\n"))
|
||||||
|
t.eq(2, n, "a 304 carries no body and the next request follows it")
|
||||||
|
t.contains(text, "304", "and it is a 304")
|
||||||
|
|
||||||
|
# --- the administration pages always close ---------------------------
|
||||||
|
aport = t.free_port()
|
||||||
|
t.start("httpsrv_keepalive_admin", f"""
|
||||||
|
log
|
||||||
|
auth iponly
|
||||||
|
allow *
|
||||||
|
http file * /w/*.html "{root}/$1.html"
|
||||||
|
admin -p{aport}
|
||||||
|
""", ports=[aport])
|
||||||
|
|
||||||
|
text, closed = t.raw_session(aport,
|
||||||
|
f"GET /w/a.html HTTP/1.1\r\nHost: t\r\n\r\nGET /C HTTP/1.1\r\nHost: t\r\n\r\n"
|
||||||
|
f"GET /w/a.html HTTP/1.1\r\nHost: t\r\n\r\n")
|
||||||
|
t.eq(2, text.count("HTTP/1."), "an administration page is the last thing on a connection")
|
||||||
|
t.eq(True, closed, "which the server closes, since the page states no length")
|
||||||
@ -4,14 +4,16 @@ import time
|
|||||||
|
|
||||||
|
|
||||||
def run(t):
|
def run(t):
|
||||||
|
if not t.skip_unless("httpsrv"):
|
||||||
|
return
|
||||||
srv = t.free_port()
|
srv = t.free_port()
|
||||||
t.start("httpsrv_ops", f"""
|
t.start("httpsrv_ops", f"""
|
||||||
log
|
log
|
||||||
auth iponly
|
auth iponly
|
||||||
allow *
|
allow *
|
||||||
http * /echo* echo
|
http echo * /echo**
|
||||||
http * /data data
|
http data * /data
|
||||||
http * /small data size=64
|
http data * /small size=64
|
||||||
httpsrv -p{srv}
|
httpsrv -p{srv}
|
||||||
""", ports=[srv])
|
""", ports=[srv])
|
||||||
|
|
||||||
|
|||||||
@ -6,13 +6,15 @@ most of them away before they ever reached the server.
|
|||||||
|
|
||||||
|
|
||||||
def run(t):
|
def run(t):
|
||||||
|
if not t.skip_unless("httpsrv"):
|
||||||
|
return
|
||||||
srv = t.free_port()
|
srv = t.free_port()
|
||||||
t.start("httpsrv_parsing", f"""
|
t.start("httpsrv_parsing", f"""
|
||||||
log
|
log
|
||||||
auth iponly
|
auth iponly
|
||||||
allow *
|
allow *
|
||||||
http * /echo* echo
|
http echo * /echo**
|
||||||
http * /safe/* echo
|
http echo * /safe/**
|
||||||
httpsrv -p{srv}
|
httpsrv -p{srv}
|
||||||
""", ports=[srv])
|
""", ports=[srv])
|
||||||
|
|
||||||
@ -62,3 +64,28 @@ def run(t):
|
|||||||
headers={"Content-Type": "application/x-www-form-urlencoded"})
|
headers={"Content-Type": "application/x-www-form-urlencoded"})
|
||||||
t.contains(r, "method=POST", "POST reaches the handler")
|
t.contains(r, "method=POST", "POST reaches the handler")
|
||||||
t.contains(r, "content.length=9", "the POST content length is parsed")
|
t.contains(r, "content.length=9", "the POST content length is parsed")
|
||||||
|
|
||||||
|
# --- dollars in the configuration ------------------------------------
|
||||||
|
# Outside quotes a dollar begins the name of a file to include, so an
|
||||||
|
# argument holding one is quoted. Two dollars stand for one, which is how
|
||||||
|
# a dollar reaches a rule as text.
|
||||||
|
dsrv = t.free_port()
|
||||||
|
t.start("httpsrv_dollar", f"""
|
||||||
|
log
|
||||||
|
auth iponly
|
||||||
|
allow *
|
||||||
|
http redir * /old** 301 "http://example.org/x$$y/$1"
|
||||||
|
http redir * "pcre:^/re/([a-z]+)$" 302 "http://example.org/re/$1"
|
||||||
|
http echo * /**
|
||||||
|
httpsrv -p{dsrv}
|
||||||
|
""", ports=[dsrv])
|
||||||
|
|
||||||
|
durl = f"http://127.0.0.1:{dsrv}"
|
||||||
|
r = t.http(durl + "/old/a")
|
||||||
|
t.eq(301, r.status, "a rule holding a doubled dollar loads")
|
||||||
|
t.eq("http://example.org/x$y//a", r.header("Location"),
|
||||||
|
"and two dollars reach the location as one")
|
||||||
|
t.eq(302, t.http(durl + "/re/abc").status,
|
||||||
|
"a quoted regular expression keeps its anchor")
|
||||||
|
t.eq(200, t.http(durl + "/re/ab9").status,
|
||||||
|
"and the anchor is real: what it excludes falls through")
|
||||||
|
|||||||
205
tests/cases/httpsrv_proxypass.py
Normal file
205
tests/cases/httpsrv_proxypass.py
Normal file
@ -0,0 +1,205 @@
|
|||||||
|
"""A service which is both a site and a proxy.
|
||||||
|
|
||||||
|
The rules answer what they have; anything else is handed to the proxy code,
|
||||||
|
which authenticates as a proxy and fetches it. The same connection carries
|
||||||
|
both kinds of request.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
|
def run(t):
|
||||||
|
if not t.skip_unless("httpsrv"):
|
||||||
|
return
|
||||||
|
root = os.path.join(t.tmpdir, "pp")
|
||||||
|
os.makedirs(root, exist_ok=True)
|
||||||
|
with open(os.path.join(root, "a.html"), "w") as fp:
|
||||||
|
fp.write("<h1>local</h1>")
|
||||||
|
|
||||||
|
# two origins, so a change of destination is visible
|
||||||
|
one = t.free_port()
|
||||||
|
two = t.free_port()
|
||||||
|
t.start("httpsrv_proxypass_origins", f"""
|
||||||
|
auth iponly
|
||||||
|
allow *
|
||||||
|
http echo * /**
|
||||||
|
httpsrv -p{one}
|
||||||
|
|
||||||
|
flush
|
||||||
|
auth iponly
|
||||||
|
allow *
|
||||||
|
http echo * /**
|
||||||
|
httpsrv -p{two}
|
||||||
|
""", ports=[one, two])
|
||||||
|
|
||||||
|
# --- the rule which hands a request on ---------------------------------
|
||||||
|
srv = t.free_port()
|
||||||
|
t.start("httpsrv_proxypass", f"""
|
||||||
|
log
|
||||||
|
auth iponly
|
||||||
|
allow *
|
||||||
|
http file * /local/*.html "{root}/$1.html"
|
||||||
|
http reply * /health** 200
|
||||||
|
http proxypass * /**
|
||||||
|
httpsrv -p{srv}
|
||||||
|
""", ports=[srv])
|
||||||
|
|
||||||
|
url = f"http://127.0.0.1:{srv}"
|
||||||
|
t.contains(t.http(url + "/local/a.html"), "<h1>local</h1>",
|
||||||
|
"a rule of its own is still answered here")
|
||||||
|
t.eq(200, t.http(url + "/health").status, "and so is another")
|
||||||
|
|
||||||
|
r = t.http(f"http://127.0.0.1:{one}/echo", proxy=f"127.0.0.1:{srv}")
|
||||||
|
t.eq(200, r.status, "a request the rules do not answer is proxied")
|
||||||
|
t.contains(r, "path=/echo", "and the origin sees it")
|
||||||
|
|
||||||
|
# a client which sends an origin-form request with a Host header reaches
|
||||||
|
# the same place: what decides is which rule matches, not the form
|
||||||
|
r = t.http(url + "/echo", headers={"Host": f"127.0.0.1:{one}"})
|
||||||
|
t.contains(r, "path=/echo", "an origin-form request is proxied the same way")
|
||||||
|
|
||||||
|
# --- an access rule which sends the rest to the proxy -------------------
|
||||||
|
# allow, with a chain to the local proxy, then a second rule for the pass
|
||||||
|
# the proxy itself makes
|
||||||
|
rsrv = t.free_port()
|
||||||
|
t.start("httpsrv_proxypass_acl", f"""
|
||||||
|
log
|
||||||
|
auth iponly
|
||||||
|
allow *
|
||||||
|
parent 1000 http 0.0.0.0 0
|
||||||
|
allow *
|
||||||
|
http file * /local/*.html "{root}/$1.html"
|
||||||
|
httpsrv -p{rsrv}
|
||||||
|
""", ports=[rsrv])
|
||||||
|
|
||||||
|
rurl = f"http://127.0.0.1:{rsrv}"
|
||||||
|
t.contains(t.http(rurl + "/local/a.html"), "<h1>local</h1>",
|
||||||
|
"a rule still wins over the redirect")
|
||||||
|
r = t.http(f"http://127.0.0.1:{one}/echo", proxy=f"127.0.0.1:{rsrv}")
|
||||||
|
t.eq(200, r.status, "and what no rule matches goes to the proxy the rule named")
|
||||||
|
|
||||||
|
# --- rules after the chain decide what the proxy may fetch -------------
|
||||||
|
# The service answers for itself on the first pass, so an address or a
|
||||||
|
# port there is the one the client connected to; on the pass the proxy
|
||||||
|
# makes, it is the one the request names.
|
||||||
|
gsrv = t.free_port()
|
||||||
|
t.start("httpsrv_proxypass_gate", f"""
|
||||||
|
log
|
||||||
|
auth iponly
|
||||||
|
allow *
|
||||||
|
parent 1000 http 0.0.0.0 0
|
||||||
|
allow * * 127.0.0.1/32 {one}
|
||||||
|
deny *
|
||||||
|
httpsrv -p{gsrv}
|
||||||
|
""", ports=[gsrv])
|
||||||
|
|
||||||
|
t.eq(200, t.http(f"http://127.0.0.1:{one}/echo", proxy=f"127.0.0.1:{gsrv}").status,
|
||||||
|
"a destination a later rule allows is fetched")
|
||||||
|
t.eq(403, t.http(f"http://127.0.0.1:{two}/echo", proxy=f"127.0.0.1:{gsrv}").status,
|
||||||
|
"and one no rule allows is refused")
|
||||||
|
|
||||||
|
# a deny written before the rule carrying the chain applies as well
|
||||||
|
bsrv = t.free_port()
|
||||||
|
t.start("httpsrv_proxypass_deny", f"""
|
||||||
|
log
|
||||||
|
auth iponly
|
||||||
|
deny * * 127.0.0.1/32 {two}
|
||||||
|
allow *
|
||||||
|
parent 1000 http 0.0.0.0 0
|
||||||
|
allow *
|
||||||
|
httpsrv -p{bsrv}
|
||||||
|
""", ports=[bsrv])
|
||||||
|
|
||||||
|
t.eq(200, t.http(f"http://127.0.0.1:{one}/echo", proxy=f"127.0.0.1:{bsrv}").status,
|
||||||
|
"what the deny does not name is still fetched")
|
||||||
|
t.eq(403, t.http(f"http://127.0.0.1:{two}/echo", proxy=f"127.0.0.1:{bsrv}").status,
|
||||||
|
"a deny before the chain stops the request too")
|
||||||
|
|
||||||
|
# --- one connection, both kinds of request -----------------------------
|
||||||
|
text, closed = t.raw_session(srv,
|
||||||
|
f"GET /local/a.html HTTP/1.1\r\nHost: t\r\n\r\n"
|
||||||
|
f"GET http://127.0.0.1:{one}/echo HTTP/1.1\r\nHost: 127.0.0.1:{one}\r\n\r\n"
|
||||||
|
f"GET http://127.0.0.1:{two}/echo HTTP/1.1\r\nHost: 127.0.0.1:{two}\r\n\r\n"
|
||||||
|
f"GET /local/a.html HTTP/1.1\r\nHost: t\r\nConnection: close\r\n\r\n",
|
||||||
|
quiet=2)
|
||||||
|
t.eq(4, text.count("HTTP/1."), "four requests are answered on one connection")
|
||||||
|
t.eq(2, text.count("<h1>local</h1>"), "two of them here")
|
||||||
|
t.eq(2, text.count("peer.addr="), "and two by the origins")
|
||||||
|
t.eq(True, closed, "the last one ends it")
|
||||||
|
|
||||||
|
# --- every kind of rule on the same connection --------------------------
|
||||||
|
with open(os.path.join(root, "f.html"), "w") as fp:
|
||||||
|
fp.write("FILEBODY")
|
||||||
|
with open(os.path.join(root, "c.html"), "w") as fp:
|
||||||
|
fp.write("CACHEBODY")
|
||||||
|
|
||||||
|
msrv = t.free_port()
|
||||||
|
t.start("httpsrv_proxypass_mix", f"""
|
||||||
|
log
|
||||||
|
auth iponly
|
||||||
|
allow *
|
||||||
|
http file * /f/*.html "{root}/$1.html"
|
||||||
|
http cache * /c/*.html "{root}/$1.html"
|
||||||
|
http proxypass * /**
|
||||||
|
httpsrv -p{msrv}
|
||||||
|
""", ports=[msrv])
|
||||||
|
|
||||||
|
proxied = f"GET http://127.0.0.1:{one}/echo HTTP/1.1\r\nHost: 127.0.0.1:{one}\r\n\r\n"
|
||||||
|
text, closed = t.raw_session(msrv,
|
||||||
|
"GET /f/f.html HTTP/1.1\r\nHost: t\r\n\r\n"
|
||||||
|
"GET /c/c.html HTTP/1.1\r\nHost: t\r\n\r\n"
|
||||||
|
+ proxied +
|
||||||
|
"GET /c/c.html HTTP/1.1\r\nHost: t\r\n\r\n"
|
||||||
|
+ proxied +
|
||||||
|
"GET /f/f.html HTTP/1.1\r\nHost: t\r\nConnection: close\r\n\r\n",
|
||||||
|
quiet=2)
|
||||||
|
t.eq(6, text.count("HTTP/1."), "file, cache and proxypass share one connection")
|
||||||
|
t.eq(2, text.count("FILEBODY"), "both files arrive")
|
||||||
|
t.eq(2, text.count("CACHEBODY"), "both cached files arrive")
|
||||||
|
t.eq(2, text.count("peer.addr="), "and both proxied requests arrive")
|
||||||
|
t.eq(True, closed, "the request asking to close ends it")
|
||||||
|
|
||||||
|
# --- a proxied answer of unstated length ends the connection ------------
|
||||||
|
# Its body is delimited by the close, so nothing can follow it here
|
||||||
|
# either: the client has to ask again on a new connection.
|
||||||
|
closer = t.free_port()
|
||||||
|
stop = t.raw_server(closer,
|
||||||
|
b"HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\nCLOSEDELIMITED",
|
||||||
|
close_after=True)
|
||||||
|
try:
|
||||||
|
text, closed = t.raw_session(msrv,
|
||||||
|
f"GET http://127.0.0.1:{closer}/x HTTP/1.1\r\nHost: 127.0.0.1:{closer}\r\n\r\n"
|
||||||
|
"GET /f/f.html HTTP/1.1\r\nHost: t\r\n\r\n", quiet=2)
|
||||||
|
t.eq(1, text.count("HTTP/1."), "the answer of unstated length is the last one")
|
||||||
|
t.contains(text, "CLOSEDELIMITED", "and its body still arrives whole")
|
||||||
|
t.eq(True, closed, "the connection ends with it")
|
||||||
|
finally:
|
||||||
|
stop()
|
||||||
|
|
||||||
|
# --- credentials go where a proxy expects them --------------------------
|
||||||
|
asrv = t.free_port()
|
||||||
|
t.start("httpsrv_proxypass_auth", f"""
|
||||||
|
log
|
||||||
|
users u:CL:p
|
||||||
|
auth strong
|
||||||
|
allow u
|
||||||
|
http file * /local/*.html "{root}/$1.html"
|
||||||
|
http proxypass * /**
|
||||||
|
httpsrv -p{asrv}
|
||||||
|
""", ports=[asrv])
|
||||||
|
|
||||||
|
aurl = f"http://127.0.0.1:{asrv}"
|
||||||
|
r = t.http(f"http://127.0.0.1:{one}/echo", proxy=f"127.0.0.1:{asrv}")
|
||||||
|
t.eq(407, r.status, "a proxy-style request with no credentials is asked for them")
|
||||||
|
t.contains(r.header("Proxy-Authenticate") or "", "Basic",
|
||||||
|
"with the header a proxy client reads")
|
||||||
|
|
||||||
|
r = t.http(f"http://127.0.0.1:{one}/echo", proxy=f"127.0.0.1:{asrv}",
|
||||||
|
proxy_auth=("u", "p"))
|
||||||
|
t.eq(200, r.status, "and is served once they are given")
|
||||||
|
|
||||||
|
r = t.http(aurl + "/local/a.html")
|
||||||
|
t.eq(401, r.status, "a request to the site itself is asked the site's way")
|
||||||
|
t.contains(r.header("WWW-Authenticate") or "", "Basic", "with its own header")
|
||||||
|
t.contains(t.http(aurl + "/local/a.html", auth=("u", "p")), "<h1>local</h1>",
|
||||||
|
"and answered once they are given")
|
||||||
@ -2,25 +2,33 @@
|
|||||||
|
|
||||||
|
|
||||||
def run(t):
|
def run(t):
|
||||||
|
if not t.skip_unless("httpsrv"):
|
||||||
|
return
|
||||||
srv = t.free_port()
|
srv = t.free_port()
|
||||||
srv2 = t.free_port()
|
srv2 = t.free_port()
|
||||||
t.start("httpsrv_rules", f"""
|
t.start("httpsrv_rules", f"""
|
||||||
log
|
log
|
||||||
auth iponly
|
auth iponly
|
||||||
allow *
|
allow *
|
||||||
http * /exact echo
|
http echo * /exact
|
||||||
http * /pre* echo
|
http echo * /pre*
|
||||||
http * *.suffix echo
|
http echo * /deep/**
|
||||||
http * *mid* echo
|
http echo * **.suffix
|
||||||
http host.example.com /byhost echo
|
http echo * **mid**
|
||||||
http *.wild.example.com /bywild echo
|
http echo host.example.com /byhost
|
||||||
http * /only-first echo
|
http echo *.wild.example.com /bywild
|
||||||
|
http echo * /only-first
|
||||||
|
|
||||||
|
http rewrite_host *.old.example ** "$1.new.example"
|
||||||
|
http rewrite_host "pcre:^legacy-(.*)$" ** "$1.new.example"
|
||||||
|
http rewrite_host * /badhost** "not a host name"
|
||||||
|
http echo one.new.example /**
|
||||||
httpsrv -p{srv}
|
httpsrv -p{srv}
|
||||||
|
|
||||||
flush
|
flush
|
||||||
auth iponly
|
auth iponly
|
||||||
allow *
|
allow *
|
||||||
http * /only-second echo
|
http echo * /only-second
|
||||||
httpsrv -p{srv2}
|
httpsrv -p{srv2}
|
||||||
""", ports=[srv, srv2])
|
""", ports=[srv, srv2])
|
||||||
|
|
||||||
@ -31,8 +39,15 @@ def run(t):
|
|||||||
t.eq(404, t.http(url + "/exactly").status,
|
t.eq(404, t.http(url + "/exactly").status,
|
||||||
"an exact URL does not match a longer path")
|
"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 + "/pre").status, "a prefix matches the bare prefix")
|
||||||
t.eq(200, t.http(url + "/pretty/deep").status,
|
t.eq(200, t.http(url + "/pretty").status,
|
||||||
"a prefix matches a longer path")
|
"a prefix matches a longer name in the same path element")
|
||||||
|
|
||||||
|
# a single star stays inside one element of the path, which is what keeps
|
||||||
|
# a rule from reaching into directories it did not name
|
||||||
|
t.eq(404, t.http(url + "/pretty/deep").status,
|
||||||
|
"a prefix does not cross a slash")
|
||||||
|
t.eq(200, t.http(url + "/deep/a/b/c").status,
|
||||||
|
"a double star does cross one")
|
||||||
t.eq(200, t.http(url + "/any.suffix").status, "a suffix matches")
|
t.eq(200, t.http(url + "/any.suffix").status, "a suffix matches")
|
||||||
t.eq(404, t.http(url + "/any.suffixx").status,
|
t.eq(404, t.http(url + "/any.suffixx").status,
|
||||||
"a suffix is anchored at the end")
|
"a suffix is anchored at the end")
|
||||||
@ -67,3 +82,19 @@ def run(t):
|
|||||||
"the second service has its own rules")
|
"the second service has its own rules")
|
||||||
t.eq(404, t.http(f"http://127.0.0.1:{srv2}/only-first").status,
|
t.eq(404, t.http(f"http://127.0.0.1:{srv2}/only-first").status,
|
||||||
"the second service does not have the earlier rules")
|
"the second service does not have the earlier rules")
|
||||||
|
|
||||||
|
# --- a rule which changes the host --------------------------------
|
||||||
|
# The stars of the host pattern are what $1 upwards stand for here, the
|
||||||
|
# way the stars of the URL stand for themselves in a rewrite.
|
||||||
|
r = t.http(url + "/anything", headers={"Host": "one.old.example"})
|
||||||
|
t.eq(200, r.status, "a rewritten host reaches the rules after it")
|
||||||
|
t.contains(r, "host=one.new.example", "and the request carries the new name")
|
||||||
|
|
||||||
|
t.eq(200, t.http(url + "/anything", headers={"Host": "legacy-one"}).status,
|
||||||
|
"a regular expression names the part to keep")
|
||||||
|
|
||||||
|
t.eq(404, t.http(url + "/anything", headers={"Host": "other.example"}).status,
|
||||||
|
"a host no rule rewrites is left as it was")
|
||||||
|
|
||||||
|
t.eq(403, t.http(url + "/badhost", headers={"Host": "x"}).status,
|
||||||
|
"a rule may not build something which is not a host name")
|
||||||
|
|||||||
@ -7,6 +7,8 @@ nscache6: nscache holds the IPv4 side and nothing else.
|
|||||||
|
|
||||||
|
|
||||||
def run(t):
|
def run(t):
|
||||||
|
if not t.skip_unless("httpsrv", "ipv6"):
|
||||||
|
return
|
||||||
if not t.has_ipv6():
|
if not t.has_ipv6():
|
||||||
t.skip("IPv6 (this machine has no IPv6 loopback)")
|
t.skip("IPv6 (this machine has no IPv6 loopback)")
|
||||||
return
|
return
|
||||||
@ -21,8 +23,8 @@ def run(t):
|
|||||||
log
|
log
|
||||||
auth iponly
|
auth iponly
|
||||||
allow *
|
allow *
|
||||||
http * /echo* echo
|
http echo * /echo**
|
||||||
http * /data data
|
http data * /data
|
||||||
httpsrv -p{origin} -i::1
|
httpsrv -p{origin} -i::1
|
||||||
|
|
||||||
# reached over IPv6, and allowed to reach IPv6
|
# reached over IPv6, and allowed to reach IPv6
|
||||||
@ -102,7 +104,7 @@ def run(t):
|
|||||||
log
|
log
|
||||||
auth iponly
|
auth iponly
|
||||||
allow *
|
allow *
|
||||||
http * /echo* echo
|
http echo * /echo**
|
||||||
httpsrv -p{v4origin}
|
httpsrv -p{v4origin}
|
||||||
{"".join(sections)}
|
{"".join(sections)}
|
||||||
""", ports=[v4origin] + list(family_ports.values()))
|
""", ports=[v4origin] + list(family_ports.values()))
|
||||||
@ -190,15 +192,23 @@ def run(t):
|
|||||||
|
|
||||||
t.ne(200, t.http(v4url, proxy=f"127.0.0.1:{partial}").status,
|
t.ne(200, t.http(v4url, proxy=f"127.0.0.1:{partial}").status,
|
||||||
"denying 127.0.0.1 denies the address as written")
|
"denying 127.0.0.1 denies the address as written")
|
||||||
t.eq(200, t.http(mapped, proxy=f"127.0.0.1:{partial}").status,
|
|
||||||
"but the same host asked for as ::ffff:127.0.0.1 is still reached")
|
# Whether the mapped form reaches the same host is up to the stack: it
|
||||||
|
# does where a mapped address is routed to IPv4, and that is the hazard
|
||||||
|
# the security notes describe. Where it does not, there is nothing to
|
||||||
|
# assert, but the rule that names every spelling still has to hold.
|
||||||
|
if t.http(mapped, proxy=f"127.0.0.1:{partial}").status == 200:
|
||||||
|
t.ok("the same host asked for as ::ffff:127.0.0.1 is still reached")
|
||||||
|
else:
|
||||||
|
t.skip("the mapped form (this stack does not route it to IPv4)")
|
||||||
|
|
||||||
t.eq(200, t.http(url, proxy=f"127.0.0.1:{partial}").status,
|
t.eq(200, t.http(url, proxy=f"127.0.0.1:{partial}").status,
|
||||||
"and so is ::1, which the rule never mentioned")
|
"and ::1 is reached, which the rule never mentioned")
|
||||||
|
|
||||||
t.ne(200, t.http(v4url, proxy=f"127.0.0.1:{complete}").status,
|
t.ne(200, t.http(v4url, proxy=f"127.0.0.1:{complete}").status,
|
||||||
"naming every spelling denies the plain address")
|
"naming every spelling denies the plain address")
|
||||||
t.ne(200, t.http(mapped, proxy=f"127.0.0.1:{complete}").status,
|
t.ne(200, t.http(mapped, proxy=f"127.0.0.1:{complete}").status,
|
||||||
"and the mapped one")
|
"and the mapped one, whether or not it would have been reachable")
|
||||||
t.ne(200, t.http(url, proxy=f"127.0.0.1:{complete}").status,
|
t.ne(200, t.http(url, proxy=f"127.0.0.1:{complete}").status,
|
||||||
"and the IPv6 loopback")
|
"and the IPv6 loopback")
|
||||||
|
|
||||||
|
|||||||
109
tests/cases/maxconn.py
Normal file
109
tests/cases/maxconn.py
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
"""maxconn: a service serves at most that many connections at once, and a
|
||||||
|
connection over the limit waits for one of them to finish."""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import signal
|
||||||
|
import socket
|
||||||
|
import time
|
||||||
|
|
||||||
|
|
||||||
|
def run(t):
|
||||||
|
if not t.skip_unless("httpsrv"):
|
||||||
|
return
|
||||||
|
# The origin runs in a process of its own, so the limit only applies to
|
||||||
|
# the mapper in front of it.
|
||||||
|
origin = t.free_port()
|
||||||
|
t.start("maxconn_origin", f"""
|
||||||
|
auth iponly
|
||||||
|
allow *
|
||||||
|
http echo * /echo**
|
||||||
|
httpsrv -p{origin}
|
||||||
|
""", ports=[origin])
|
||||||
|
|
||||||
|
mapped = t.free_port()
|
||||||
|
server = t.start("maxconn", f"""
|
||||||
|
log
|
||||||
|
maxconn 2
|
||||||
|
auth iponly
|
||||||
|
allow *
|
||||||
|
tcppm {mapped} 127.0.0.1 {origin}
|
||||||
|
""", ports=[mapped])
|
||||||
|
|
||||||
|
def connect():
|
||||||
|
sock = socket.create_connection(("127.0.0.1", mapped), 5)
|
||||||
|
opened.append(sock)
|
||||||
|
return sock
|
||||||
|
|
||||||
|
def served(sock, timeout):
|
||||||
|
"""Ask over a kept alive connection; True once the whole reply is in,
|
||||||
|
so the connection is ready for the next request."""
|
||||||
|
sock.sendall(b"GET /echo HTTP/1.1\r\nHost: 127.0.0.1\r\n\r\n")
|
||||||
|
sock.settimeout(timeout)
|
||||||
|
data = b""
|
||||||
|
try:
|
||||||
|
while b"\r\n\r\n" not in data:
|
||||||
|
chunk = sock.recv(4096)
|
||||||
|
if not chunk:
|
||||||
|
return False
|
||||||
|
data += chunk
|
||||||
|
head, body = data.split(b"\r\n\r\n", 1)
|
||||||
|
length = 0
|
||||||
|
for line in head.split(b"\r\n")[1:]:
|
||||||
|
name, _, value = line.partition(b":")
|
||||||
|
if name.strip().lower() == b"content-length":
|
||||||
|
length = int(value)
|
||||||
|
while len(body) < length:
|
||||||
|
chunk = sock.recv(4096)
|
||||||
|
if not chunk:
|
||||||
|
return False
|
||||||
|
body += chunk
|
||||||
|
return head.startswith(b"HTTP/1.1 200")
|
||||||
|
except OSError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
opened = []
|
||||||
|
try:
|
||||||
|
# the probe that saw the port open holds a slot until its session
|
||||||
|
# ends, so wait for both slots to be free before taking them
|
||||||
|
deadline = time.time() + 5
|
||||||
|
while True:
|
||||||
|
first, second = connect(), connect()
|
||||||
|
both = served(first, 1) and served(second, 1)
|
||||||
|
if both or time.time() > deadline:
|
||||||
|
break
|
||||||
|
first.close()
|
||||||
|
second.close()
|
||||||
|
t.eq(True, both, "two connections are served under maxconn 2")
|
||||||
|
|
||||||
|
third = connect()
|
||||||
|
t.eq(False, served(third, 1.5),
|
||||||
|
"a third connection waits while both slots are held")
|
||||||
|
t.contains(t.wait_output(server, "too many connected clients"),
|
||||||
|
"too many connected clients",
|
||||||
|
"a service at its limit says so in the log")
|
||||||
|
|
||||||
|
first.close()
|
||||||
|
t.eq(True, served(third, 5),
|
||||||
|
"the waiting connection is served once a slot is freed")
|
||||||
|
t.eq(True, served(second, 5),
|
||||||
|
"a connection holding a slot keeps being served")
|
||||||
|
|
||||||
|
if os.name == "nt":
|
||||||
|
t.skip("reload while at the limit (no SIGUSR1 on Windows)")
|
||||||
|
else:
|
||||||
|
waiting = connect()
|
||||||
|
t.eq(False, served(waiting, 1),
|
||||||
|
"the limit holds again with two connections open")
|
||||||
|
server.proc.send_signal(signal.SIGUSR1)
|
||||||
|
# A connection made the instant the signal arrives can land on
|
||||||
|
# the listener being replaced and be reset; give the new service
|
||||||
|
# the moment it needs.
|
||||||
|
time.sleep(1.5)
|
||||||
|
fresh = connect()
|
||||||
|
t.eq(True, served(fresh, 5),
|
||||||
|
"a reload while the limit is reached starts a service with "
|
||||||
|
"its slots free")
|
||||||
|
t.eq(None, server.proc.poll(), "the process survives the reload")
|
||||||
|
finally:
|
||||||
|
for sock in opened:
|
||||||
|
sock.close()
|
||||||
162
tests/cases/parent_failover.py
Normal file
162
tests/cases/parent_failover.py
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
"""parent: what happens to a group when one of its members is down.
|
||||||
|
|
||||||
|
A parent which fails is taken out of the random choice for the rest of the
|
||||||
|
connection, so a retry reaches another member of the group instead of the same
|
||||||
|
dead one again. A parent of weight 0 is the fallback of its group: it is only
|
||||||
|
used once every weighted member has failed.
|
||||||
|
|
||||||
|
The number of attempts is bounded by parentretries, two by default, so each
|
||||||
|
case here needs at most one parent to fail before a working one is reached.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import time
|
||||||
|
|
||||||
|
|
||||||
|
def served(server, needle, since=0):
|
||||||
|
"""How many log lines carrying needle a proxy wrote past offset since."""
|
||||||
|
return sum(1 for line in server.output()[since:].splitlines() if needle in line)
|
||||||
|
|
||||||
|
|
||||||
|
def wait_served(server, needle, count, since=0, timeout=5.0):
|
||||||
|
"""Wait for count such lines: a session is logged once it is over, which
|
||||||
|
is a moment after the client has its answer."""
|
||||||
|
deadline = time.time() + timeout
|
||||||
|
while time.time() < deadline:
|
||||||
|
seen = served(server, needle, since)
|
||||||
|
if seen >= count:
|
||||||
|
return seen
|
||||||
|
time.sleep(0.05)
|
||||||
|
return served(server, needle, since)
|
||||||
|
|
||||||
|
|
||||||
|
def run(t):
|
||||||
|
if not t.skip_unless("httpsrv"):
|
||||||
|
return
|
||||||
|
srv = t.free_port()
|
||||||
|
good = t.free_port()
|
||||||
|
spare = t.free_port()
|
||||||
|
# nothing is ever started here, so connecting to it is refused at once
|
||||||
|
dead = t.free_port()
|
||||||
|
|
||||||
|
origin = t.start("failover_origin", f"""
|
||||||
|
log
|
||||||
|
auth iponly
|
||||||
|
allow *
|
||||||
|
http echo * /echo**
|
||||||
|
httpsrv -p{srv}
|
||||||
|
""", ports=[srv])
|
||||||
|
|
||||||
|
goodp = t.start("failover_good", f"""
|
||||||
|
log
|
||||||
|
auth iponly
|
||||||
|
allow *
|
||||||
|
proxy -p{good}
|
||||||
|
""", ports=[good])
|
||||||
|
|
||||||
|
sparep = t.start("failover_spare", f"""
|
||||||
|
log
|
||||||
|
auth iponly
|
||||||
|
allow *
|
||||||
|
proxy -p{spare}
|
||||||
|
""", ports=[spare])
|
||||||
|
|
||||||
|
fallback = t.free_port()
|
||||||
|
idle = t.free_port()
|
||||||
|
group = t.free_port()
|
||||||
|
allgone = t.free_port()
|
||||||
|
lone = t.free_port()
|
||||||
|
|
||||||
|
t.start("failover_client", f"""
|
||||||
|
log
|
||||||
|
auth iponly
|
||||||
|
|
||||||
|
# the only weighted parent is dead, the fallback has to take over
|
||||||
|
flush
|
||||||
|
allow *
|
||||||
|
parent 1000 connect 127.0.0.1 {dead}
|
||||||
|
parent 0 connect 127.0.0.1 {spare}
|
||||||
|
proxy -p{fallback}
|
||||||
|
|
||||||
|
# the weighted parent works, so the fallback stays untouched
|
||||||
|
flush
|
||||||
|
allow *
|
||||||
|
parent 1000 connect 127.0.0.1 {good}
|
||||||
|
parent 0 connect 127.0.0.1 {spare}
|
||||||
|
proxy -p{idle}
|
||||||
|
|
||||||
|
# one member of a group of two is dead: a retry must not pick it again
|
||||||
|
flush
|
||||||
|
allow *
|
||||||
|
parent 500 connect 127.0.0.1 {dead}
|
||||||
|
parent 500 connect 127.0.0.1 {good}
|
||||||
|
proxy -p{group}
|
||||||
|
|
||||||
|
# nothing left to fall back to
|
||||||
|
flush
|
||||||
|
allow *
|
||||||
|
parent 1000 connect 127.0.0.1 {dead}
|
||||||
|
proxy -p{allgone}
|
||||||
|
|
||||||
|
# a parent of weight 0 on its own is simply the parent to use
|
||||||
|
flush
|
||||||
|
allow *
|
||||||
|
parent 0 connect 127.0.0.1 {good}
|
||||||
|
proxy -p{lone}
|
||||||
|
""", ports=[fallback, idle, group, allgone, lone])
|
||||||
|
|
||||||
|
url = f"http://127.0.0.1:{srv}/echo"
|
||||||
|
needle = f"CONNECT 127.0.0.1:{srv}"
|
||||||
|
|
||||||
|
# --- the fallback takes over --------------------------------------------
|
||||||
|
mark = len(sparep.output())
|
||||||
|
r = t.http(url, proxy=f"127.0.0.1:{fallback}")
|
||||||
|
t.eq(200, r.status, "a dead weighted parent falls back to the parent of weight 0")
|
||||||
|
t.eq(1, wait_served(sparep, needle, 1, mark),
|
||||||
|
"the fallback parent carried the request")
|
||||||
|
|
||||||
|
# --- and only then ------------------------------------------------------
|
||||||
|
mark = len(sparep.output())
|
||||||
|
gmark = len(goodp.output())
|
||||||
|
for _ in range(4):
|
||||||
|
t.eq(200, t.http(url, proxy=f"127.0.0.1:{idle}").status,
|
||||||
|
"a working weighted parent serves the request")
|
||||||
|
t.eq(4, wait_served(goodp, needle, 4, gmark),
|
||||||
|
"every request went through the weighted parent")
|
||||||
|
t.eq(0, served(sparep, needle, mark),
|
||||||
|
"the fallback is left alone while the weighted parent works")
|
||||||
|
|
||||||
|
# --- a dead member of a weighted group ----------------------------------
|
||||||
|
# Whichever of the two the first attempt picks, the request has to end up
|
||||||
|
# at the one that is up: the dead one is not offered to the retry again.
|
||||||
|
gmark = len(goodp.output())
|
||||||
|
statuses = [t.http(url, proxy=f"127.0.0.1:{group}").status for _ in range(8)]
|
||||||
|
t.eq([200] * 8, statuses,
|
||||||
|
"a dead member of a group never fails a request twice over")
|
||||||
|
t.eq(8, wait_served(goodp, needle, 8, gmark),
|
||||||
|
"all of them were carried by the member which is up")
|
||||||
|
|
||||||
|
# --- nothing left -------------------------------------------------------
|
||||||
|
# With every parent of the group gone the request has to fail. Connecting
|
||||||
|
# direct instead would be a way around the rule that asked for a parent.
|
||||||
|
omark = len(origin.output())
|
||||||
|
r = t.http(url, proxy=f"127.0.0.1:{allgone}")
|
||||||
|
t.ne(200, r.status, "a request fails when every parent of the group is down")
|
||||||
|
time.sleep(0.5)
|
||||||
|
t.eq(0, served(origin, "/echo", omark),
|
||||||
|
"and it is not sent direct to the origin instead")
|
||||||
|
|
||||||
|
# --- weight 0 on its own ------------------------------------------------
|
||||||
|
gmark = len(goodp.output())
|
||||||
|
t.eq(200, t.http(url, proxy=f"127.0.0.1:{lone}").status,
|
||||||
|
"a parent of weight 0 alone is used like any other")
|
||||||
|
t.eq(1, wait_served(goodp, needle, 1, gmark),
|
||||||
|
"through the parent it names")
|
||||||
|
|
||||||
|
# --- what the parser still rejects --------------------------------------
|
||||||
|
out = t.run_config("failover_badweight", f"""
|
||||||
|
auth iponly
|
||||||
|
allow *
|
||||||
|
parent 1001 connect 127.0.0.1 {good}
|
||||||
|
proxy -p{t.free_port()}
|
||||||
|
""")
|
||||||
|
t.contains(out, "bad chain weight", "a weight above 1000 is still refused")
|
||||||
@ -14,17 +14,29 @@ def _windows():
|
|||||||
net.ipv4.ip_local_port_range; a window outside it is ignored and an
|
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
|
ordinary ephemeral port is used, so a fixed low window would be
|
||||||
measuring the kernel's own choice rather than the setting.
|
measuring the kernel's own choice rather than the setting.
|
||||||
|
|
||||||
|
Everywhere else the range is honoured by binding a port out of it at
|
||||||
|
random, ten times before giving up and letting the system choose. A port
|
||||||
|
which carried a connection a moment ago cannot be bound again while it
|
||||||
|
waits out its close - four minutes of it on Windows - so the window has
|
||||||
|
to be wide enough that ten tries do not all land on one. The window the
|
||||||
|
kernel picks from on Linux needs no such room, since it skips them.
|
||||||
|
|
||||||
|
Each service gets a window of its own. A port bound a second time towards
|
||||||
|
a destination it has just been used for makes a pair which is still
|
||||||
|
closing, which Windows refuses at the connect, and services sharing one
|
||||||
|
window all talk to the same origin here.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
with open("/proc/sys/net/ipv4/ip_local_port_range") as fp:
|
with open("/proc/sys/net/ipv4/ip_local_port_range") as fp:
|
||||||
low, high = (int(part) for part in fp.read().split()[:2])
|
low, high = (int(part) for part in fp.read().split()[:2])
|
||||||
except (OSError, ValueError):
|
except (OSError, ValueError):
|
||||||
return (21400, 21449), (21500, 21549)
|
return [(21400 + i * 600, 21899 + i * 600) for i in range(4)]
|
||||||
base = low + 1000 if low + 1150 <= high else low
|
base = low + 1000 if low + 1400 <= high else low
|
||||||
return (base, base + 49), (base + 100, base + 149)
|
return [(base + i * 100, base + i * 100 + 49) for i in range(4)]
|
||||||
|
|
||||||
|
|
||||||
(LOW, HIGH), (ILOW, IHIGH) = _windows()
|
(LOW, HIGH), (SLOW, SHIGH), (MLOW, MHIGH), (ILOW, IHIGH) = _windows()
|
||||||
|
|
||||||
# Privileged ports: the kernel ignores such a range on Linux, since it is
|
# Privileged ports: the kernel ignores such a range on Linux, since it is
|
||||||
# outside net.ipv4.ip_local_port_range, and binding them fails outright
|
# outside net.ipv4.ip_local_port_range, and binding them fails outright
|
||||||
@ -34,6 +46,8 @@ UNHONOURED = (1, 99)
|
|||||||
|
|
||||||
|
|
||||||
def run(t):
|
def run(t):
|
||||||
|
if not t.skip_unless("httpsrv"):
|
||||||
|
return
|
||||||
srv = t.free_port()
|
srv = t.free_port()
|
||||||
prx = t.free_port()
|
prx = t.free_port()
|
||||||
sks = t.free_port()
|
sks = t.free_port()
|
||||||
@ -43,7 +57,7 @@ def run(t):
|
|||||||
log
|
log
|
||||||
auth iponly
|
auth iponly
|
||||||
allow *
|
allow *
|
||||||
http * /echo* echo
|
http echo * /echo**
|
||||||
httpsrv -p{srv}
|
httpsrv -p{srv}
|
||||||
|
|
||||||
# every outgoing connection binds inside the range
|
# every outgoing connection binds inside the range
|
||||||
@ -58,7 +72,7 @@ def run(t):
|
|||||||
flush
|
flush
|
||||||
auth iponly
|
auth iponly
|
||||||
allow * * * * HTTP_CONNECT
|
allow * * * * HTTP_CONNECT
|
||||||
parent 1000 extport 0.0.0.0 {LOW}-{HIGH}
|
parent 1000 extport 0.0.0.0 {MLOW}-{MHIGH}
|
||||||
allow *
|
allow *
|
||||||
proxy -p{meth}
|
proxy -p{meth}
|
||||||
|
|
||||||
@ -66,7 +80,7 @@ def run(t):
|
|||||||
flush
|
flush
|
||||||
auth iponly
|
auth iponly
|
||||||
allow *
|
allow *
|
||||||
parent 1000 extport 0.0.0.0 {LOW}-{HIGH}
|
parent 1000 extport 0.0.0.0 {SLOW}-{SHIGH}
|
||||||
socks -p{sks}
|
socks -p{sks}
|
||||||
""", ports=[srv, prx, sks, meth])
|
""", ports=[srv, prx, sks, meth])
|
||||||
|
|
||||||
@ -86,19 +100,25 @@ def run(t):
|
|||||||
|
|
||||||
port = int_field(t.socks_http(f"127.0.0.1:{sks}", origin + "/echo"),
|
port = int_field(t.socks_http(f"127.0.0.1:{sks}", origin + "/echo"),
|
||||||
"peer.port")
|
"peer.port")
|
||||||
t.in_range(port, LOW, HIGH,
|
t.in_range(port, SLOW, SHIGH,
|
||||||
"socks binds the outgoing connection inside the range")
|
"socks binds the outgoing connection inside the range")
|
||||||
|
|
||||||
# --- per-method scoping ------------------------------------------------
|
# --- per-method scoping ------------------------------------------------
|
||||||
method_proxy = f"127.0.0.1:{meth}"
|
method_proxy = f"127.0.0.1:{meth}"
|
||||||
port = int_field(t.http(origin + "/echo", proxy=method_proxy, tunnel=True),
|
port = int_field(t.http(origin + "/echo", proxy=method_proxy, tunnel=True),
|
||||||
"peer.port")
|
"peer.port")
|
||||||
t.in_range(port, LOW, HIGH, "CONNECT uses the range its rule sets")
|
t.in_range(port, MLOW, MHIGH, "CONNECT uses the range its rule sets")
|
||||||
|
|
||||||
# a plain GET matches the later rule, which sets no range
|
# a plain GET matches the later rule, which sets no range. On Linux the
|
||||||
port = int_field(t.http(origin + "/echo", proxy=method_proxy), "peer.port")
|
# window lies within the ephemeral range, so an ephemeral port falls in
|
||||||
t.not_in_range(port, LOW, HIGH,
|
# it now and then by chance: bound to the window every port would, so
|
||||||
"a method outside that rule keeps an ephemeral port")
|
# one of a few outside it tells them apart.
|
||||||
|
ports = [int_field(t.http(origin + "/echo", proxy=method_proxy), "peer.port")
|
||||||
|
for _ in range(5)]
|
||||||
|
outside = [p for p in ports if isinstance(p, int) and not MLOW <= p <= MHIGH]
|
||||||
|
t.ok(f"a method outside that rule keeps an ephemeral port ({ports})") if outside else \
|
||||||
|
t.fail("a method outside that rule keeps an ephemeral port",
|
||||||
|
f"some outside {MLOW}-{MHIGH}", ports)
|
||||||
|
|
||||||
# --- a range the platform cannot honour --------------------------------
|
# --- a range the platform cannot honour --------------------------------
|
||||||
# Linux ignores a range outside net.ipv4.ip_local_port_range, and any
|
# Linux ignores a range outside net.ipv4.ip_local_port_range, and any
|
||||||
|
|||||||
212
tests/cases/parent_weights.py
Normal file
212
tests/cases/parent_weights.py
Normal file
@ -0,0 +1,212 @@
|
|||||||
|
"""parent weights: the fraction notation, and what a group adds up to.
|
||||||
|
|
||||||
|
A weight is scanned as an integer into a share of 1000000000. A weight which
|
||||||
|
starts with 0 or . is a fraction of one, .333 being a third; anything else is
|
||||||
|
the old notation, thousandths, which may now carry further digits after a dot,
|
||||||
|
so 123.456 means the same as .123456. 1.0 is the exception, read as it looks,
|
||||||
|
and a trailing % makes a weight a percentage: 50.5% is .505 is 505.
|
||||||
|
|
||||||
|
The values are read back from the admin interface, which dumps the parsed
|
||||||
|
configuration, so what is checked is the number 3proxy holds rather than the
|
||||||
|
behaviour it happens to produce.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import re
|
||||||
|
import time
|
||||||
|
|
||||||
|
CHAIN_WEIGHT = re.compile(
|
||||||
|
r"parent weight[^<]*</description><value><!\[CDATA\[([0-9]+)")
|
||||||
|
|
||||||
|
|
||||||
|
def weights(t, adm):
|
||||||
|
return [int(v) for v in CHAIN_WEIGHT.findall(t.http(f"http://127.0.0.1:{adm}/S").text)]
|
||||||
|
|
||||||
|
|
||||||
|
def served(server, needle, since=0):
|
||||||
|
return sum(1 for line in server.output()[since:].splitlines() if needle in line)
|
||||||
|
|
||||||
|
|
||||||
|
def wait_served(server, needle, count, since=0, timeout=5.0):
|
||||||
|
deadline = time.time() + timeout
|
||||||
|
while time.time() < deadline:
|
||||||
|
seen = served(server, needle, since)
|
||||||
|
if seen >= count:
|
||||||
|
return seen
|
||||||
|
time.sleep(0.05)
|
||||||
|
return served(server, needle, since)
|
||||||
|
|
||||||
|
|
||||||
|
def run(t):
|
||||||
|
if not t.skip_unless("httpsrv"):
|
||||||
|
return
|
||||||
|
adm = t.free_port()
|
||||||
|
prx = t.free_port()
|
||||||
|
dummy = t.free_port()
|
||||||
|
|
||||||
|
t.start("weights_parse", f"""
|
||||||
|
auth iponly
|
||||||
|
allow *
|
||||||
|
admin -p{adm}
|
||||||
|
|
||||||
|
flush
|
||||||
|
auth iponly
|
||||||
|
allow *
|
||||||
|
parent 1000 connect 127.0.0.1 {dummy}
|
||||||
|
parent 1.0 connect 127.0.0.1 {dummy}
|
||||||
|
parent 1.00000000000000 connect 127.0.0.1 {dummy}
|
||||||
|
parent 500 connect 127.0.0.1 {dummy}
|
||||||
|
parent 1 connect 127.0.0.1 {dummy}
|
||||||
|
parent 1.5 connect 127.0.0.1 {dummy}
|
||||||
|
parent 123.456 connect 127.0.0.1 {dummy}
|
||||||
|
parent 12.34 connect 127.0.0.1 {dummy}
|
||||||
|
parent 1.000001 connect 127.0.0.1 {dummy}
|
||||||
|
parent .333 connect 127.0.0.1 {dummy}
|
||||||
|
parent 0.333 connect 127.0.0.1 {dummy}
|
||||||
|
parent .5 connect 127.0.0.1 {dummy}
|
||||||
|
parent .333333333 connect 127.0.0.1 {dummy}
|
||||||
|
parent 0.000000001 connect 127.0.0.1 {dummy}
|
||||||
|
parent 100% connect 127.0.0.1 {dummy}
|
||||||
|
parent 50.5% connect 127.0.0.1 {dummy}
|
||||||
|
parent 50% connect 127.0.0.1 {dummy}
|
||||||
|
parent 33.3333333% connect 127.0.0.1 {dummy}
|
||||||
|
parent 1.0% connect 127.0.0.1 {dummy}
|
||||||
|
parent 0.0000001% connect 127.0.0.1 {dummy}
|
||||||
|
parent 0 connect 127.0.0.1 {dummy}
|
||||||
|
proxy -p{prx}
|
||||||
|
""", ports=[adm, prx])
|
||||||
|
|
||||||
|
t.eq([
|
||||||
|
1000000000, # 1000, the old notation for the whole share
|
||||||
|
1000000000, # 1.0, the same share written as a fraction
|
||||||
|
1000000000, # 1.00000000000000, zeroes past the point change nothing
|
||||||
|
500000000, # 500
|
||||||
|
1000000, # 1, a thousandth
|
||||||
|
1500000, # 1.5, one thousandth and a half of one
|
||||||
|
123456000, # 123.456, the old notation carried further
|
||||||
|
12340000, # 12.34
|
||||||
|
1000001, # 1.000001, six digits past the thousandths
|
||||||
|
333000000, # .333
|
||||||
|
333000000, # 0.333, the same thing written out
|
||||||
|
500000000, # .5
|
||||||
|
333333333, # .333333333, the finest the resolution goes
|
||||||
|
1, # 0.000000001, one part of the whole
|
||||||
|
1000000000, # 100%
|
||||||
|
505000000, # 50.5%, the same as .505 and as 505
|
||||||
|
500000000, # 50%
|
||||||
|
333333333, # 33.3333333%, seven digits past the point
|
||||||
|
10000000, # 1.0%, a percent rather than the whole share
|
||||||
|
1, # 0.0000001%, one part again
|
||||||
|
0, # the fallback weight
|
||||||
|
], weights(t, adm), "every notation is scanned into its share")
|
||||||
|
|
||||||
|
# --- what the parser refuses ------------------------------------------
|
||||||
|
for bad in ("1001", "1000.1", "1.0000001", ".1234567890", "01", "abc",
|
||||||
|
"1.2.3", "-1", "1e9", "101%", "50.55555555%", "%", "5%%",
|
||||||
|
"%5"):
|
||||||
|
out = t.run_config("weights_bad", f"""
|
||||||
|
auth iponly
|
||||||
|
allow *
|
||||||
|
parent {bad} connect 127.0.0.1 {dummy}
|
||||||
|
proxy -p{t.free_port()}
|
||||||
|
""")
|
||||||
|
t.contains(out, "bad chain weight", f"{bad} is refused as a weight")
|
||||||
|
|
||||||
|
# --- a group that all but adds up ---------------------------------------
|
||||||
|
# .999999999 is one part short of the whole share. It still closes its
|
||||||
|
# group, so the parent after it is the next hop of a chain rather than
|
||||||
|
# another member of the same group.
|
||||||
|
srv = t.free_port()
|
||||||
|
first = t.free_port()
|
||||||
|
second = t.free_port()
|
||||||
|
chained = t.free_port()
|
||||||
|
grouped = t.free_port()
|
||||||
|
thirds = t.free_port()
|
||||||
|
|
||||||
|
t.start("weights_origin", f"""
|
||||||
|
log
|
||||||
|
auth iponly
|
||||||
|
allow *
|
||||||
|
http echo * /echo**
|
||||||
|
httpsrv -p{srv}
|
||||||
|
""", ports=[srv])
|
||||||
|
|
||||||
|
firstp = t.start("weights_first", f"""
|
||||||
|
log
|
||||||
|
auth iponly
|
||||||
|
allow *
|
||||||
|
proxy -p{first}
|
||||||
|
""", ports=[first])
|
||||||
|
|
||||||
|
secondp = t.start("weights_second", f"""
|
||||||
|
log
|
||||||
|
auth iponly
|
||||||
|
allow *
|
||||||
|
proxy -p{second}
|
||||||
|
""", ports=[second])
|
||||||
|
|
||||||
|
t.start("weights_client", f"""
|
||||||
|
log
|
||||||
|
auth iponly
|
||||||
|
|
||||||
|
# one part short of the whole share still ends the group
|
||||||
|
flush
|
||||||
|
allow *
|
||||||
|
parent .999999999 connect 127.0.0.1 {first}
|
||||||
|
parent 1000 connect 127.0.0.1 {second}
|
||||||
|
proxy -p{chained}
|
||||||
|
|
||||||
|
# two halves, one written each way, are one group and one hop
|
||||||
|
flush
|
||||||
|
allow *
|
||||||
|
parent 500 connect 127.0.0.1 {first}
|
||||||
|
parent .5 connect 127.0.0.1 {second}
|
||||||
|
proxy -p{grouped}
|
||||||
|
|
||||||
|
# three thirds are a thousandth short of the whole share and still
|
||||||
|
# make a group, so the parent after them is the next hop
|
||||||
|
flush
|
||||||
|
allow *
|
||||||
|
parent 333 connect 127.0.0.1 {first}
|
||||||
|
parent 333 connect 127.0.0.1 {first}
|
||||||
|
parent 333 connect 127.0.0.1 {first}
|
||||||
|
parent 1000 connect 127.0.0.1 {second}
|
||||||
|
proxy -p{thirds}
|
||||||
|
""", ports=[chained, grouped, thirds])
|
||||||
|
|
||||||
|
url = f"http://127.0.0.1:{srv}/echo"
|
||||||
|
toorigin = f"CONNECT 127.0.0.1:{srv}"
|
||||||
|
tosecond = f"CONNECT 127.0.0.1:{second}"
|
||||||
|
|
||||||
|
fmark, smark = len(firstp.output()), len(secondp.output())
|
||||||
|
t.eq(200, t.http(url, proxy=f"127.0.0.1:{chained}").status,
|
||||||
|
"a chain of two groups carries the request")
|
||||||
|
t.eq(1, wait_served(firstp, tosecond, 1, fmark),
|
||||||
|
"the first group's parent was asked for the second one")
|
||||||
|
t.eq(1, wait_served(secondp, toorigin, 1, smark),
|
||||||
|
"and the second group's parent reached the origin")
|
||||||
|
t.eq(0, served(firstp, toorigin, fmark),
|
||||||
|
"the first parent never went to the origin itself")
|
||||||
|
|
||||||
|
# both members of one group talk to the origin, never to each other
|
||||||
|
fmark, smark = len(firstp.output()), len(secondp.output())
|
||||||
|
for _ in range(8):
|
||||||
|
t.eq(200, t.http(url, proxy=f"127.0.0.1:{grouped}").status,
|
||||||
|
"a group of two halves carries the request")
|
||||||
|
t.eq(8, wait_served(firstp, toorigin, 8, fmark, timeout=0.5) +
|
||||||
|
wait_served(secondp, toorigin, 8, smark, timeout=0.5),
|
||||||
|
"each request took one hop, through either half")
|
||||||
|
t.eq(0, served(firstp, tosecond, fmark),
|
||||||
|
"the halves are one group, not a chain")
|
||||||
|
|
||||||
|
# --- three thirds -------------------------------------------------------
|
||||||
|
# 999000000 is within a thousandth of the whole share, so the group closes
|
||||||
|
# there. Were it left open the parent of weight 1000 would join it and
|
||||||
|
# carry about half the requests on its own, without the first hop.
|
||||||
|
fmark, smark = len(firstp.output()), len(secondp.output())
|
||||||
|
for _ in range(8):
|
||||||
|
t.eq(200, t.http(url, proxy=f"127.0.0.1:{thirds}").status,
|
||||||
|
"a group of three thirds carries the request")
|
||||||
|
t.eq(8, wait_served(firstp, tosecond, 8, fmark),
|
||||||
|
"every request took a third as its first hop")
|
||||||
|
t.eq(8, wait_served(secondp, toorigin, 8, smark),
|
||||||
|
"and the parent after them as its second")
|
||||||
@ -20,6 +20,8 @@ def _has_pcre(t):
|
|||||||
|
|
||||||
|
|
||||||
def run(t):
|
def run(t):
|
||||||
|
if not t.skip_unless("httpsrv", "pcre"):
|
||||||
|
return
|
||||||
if not _has_pcre(t):
|
if not _has_pcre(t):
|
||||||
t.skip("PCRE (this build has no PCRE support)")
|
t.skip("PCRE (this build has no PCRE support)")
|
||||||
return
|
return
|
||||||
@ -29,9 +31,9 @@ def run(t):
|
|||||||
log
|
log
|
||||||
auth iponly
|
auth iponly
|
||||||
allow *
|
allow *
|
||||||
http * /echo* echo
|
http echo * /echo**
|
||||||
http * /secret* echo
|
http echo * /secret**
|
||||||
http * /data data
|
http data * /data
|
||||||
httpsrv -p{origin}
|
httpsrv -p{origin}
|
||||||
""", ports=[origin])
|
""", ports=[origin])
|
||||||
|
|
||||||
@ -88,6 +90,27 @@ def run(t):
|
|||||||
t.eq(200, t.http(url + "/echo", proxy=p).status,
|
t.eq(200, t.http(url + "/echo", proxy=p).status,
|
||||||
"an extension that matches nothing changes nothing")
|
"an extension that matches nothing changes nothing")
|
||||||
|
|
||||||
|
# --- a regular expression where a host name is expected -----------------
|
||||||
|
# The same prefix works in an access rule and in an http rule, so one
|
||||||
|
# kind of expression is understood wherever a name can be written.
|
||||||
|
named = t.free_port()
|
||||||
|
t.start("pcre_named", f"""
|
||||||
|
log
|
||||||
|
flush
|
||||||
|
nserver 127.0.0.1
|
||||||
|
nscache 1024
|
||||||
|
nsrecord host1.test 127.0.0.1
|
||||||
|
nsrecord other.test 127.0.0.1
|
||||||
|
auth iponly
|
||||||
|
allow * * "pcre:^host[0-9]+\\.test$"
|
||||||
|
proxy -p{named}
|
||||||
|
""", ports=[named])
|
||||||
|
|
||||||
|
t.eq(200, t.http(f"http://host1.test:{origin}/echo", proxy=f"127.0.0.1:{named}").status,
|
||||||
|
"a destination matching the expression is allowed")
|
||||||
|
t.ne(200, t.http(f"http://other.test:{origin}/echo", proxy=f"127.0.0.1:{named}").status,
|
||||||
|
"one that does not match is refused")
|
||||||
|
|
||||||
# --- rewriting the reply ------------------------------------------------
|
# --- rewriting the reply ------------------------------------------------
|
||||||
p = proxy_with("rewrite_srv",
|
p = proxy_with("rewrite_srv",
|
||||||
'pcre_rewrite srvheader dunno "text/plain" "text/rewritten"',
|
'pcre_rewrite srvheader dunno "text/plain" "text/rewritten"',
|
||||||
@ -143,7 +166,7 @@ def run(t):
|
|||||||
flush
|
flush
|
||||||
auth iponly
|
auth iponly
|
||||||
allow *
|
allow *
|
||||||
http * /echo* echo
|
http echo * /echo**
|
||||||
httpsrv -p{elsewhere}
|
httpsrv -p{elsewhere}
|
||||||
""", ports=[elsewhere])
|
""", ports=[elsewhere])
|
||||||
|
|
||||||
@ -172,3 +195,43 @@ def run(t):
|
|||||||
r = t.http(url + "/echo/old", proxy=p)
|
r = t.http(url + "/echo/old", proxy=p)
|
||||||
t.eq(200, r.status, "a rewritten request through a parent arrives")
|
t.eq(200, r.status, "a rewritten request through a parent arrives")
|
||||||
t.contains(r, "path=/echo/new", "the origin sees the rewritten path through a parent")
|
t.contains(r, "path=/echo/new", "the origin sees the rewritten path through a parent")
|
||||||
|
|
||||||
|
# --- a rewrite which grows the headers ----------------------------------
|
||||||
|
# GHSA-h845-prxq-ww3q: a rewrite that doubles the client headers used to
|
||||||
|
# leave a buffer holding exactly what it produced, and the Content-Length
|
||||||
|
# the data filter regenerates was then written past the end of it.
|
||||||
|
# The origin here reads whatever it is sent and answers the same way every
|
||||||
|
# time: what is being tested is the proxy in the middle, not what a server
|
||||||
|
# is willing to accept in one request.
|
||||||
|
grown = t.free_port()
|
||||||
|
stop = t.raw_server(grown, b"HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nok",
|
||||||
|
drain=True)
|
||||||
|
try:
|
||||||
|
p = proxy_with("rewrite_grow",
|
||||||
|
'pcre_rewrite cliheader dunno "(?s).*" "$0$0"',
|
||||||
|
'pcre clidata dunno *')
|
||||||
|
big = "".join("X-%d: %s\r\n" % (i, chr(65 + i) * 20000) for i in range(5))
|
||||||
|
reply = t.raw_proxy_request(p, f"http://127.0.0.1:{grown}/x",
|
||||||
|
extra=big, body="z")
|
||||||
|
t.contains(reply, "200", "a doubled header block with a body is answered")
|
||||||
|
t.contains(t.raw_proxy_request(p, f"http://127.0.0.1:{grown}/x"), "200",
|
||||||
|
"and the proxy is still there afterwards")
|
||||||
|
finally:
|
||||||
|
stop()
|
||||||
|
|
||||||
|
# A reference to a group the pattern does not have is dropped, and dropped
|
||||||
|
# by both the pass which measures the result and the pass which writes it.
|
||||||
|
p = proxy_with("rewrite_nogroup",
|
||||||
|
'pcre_rewrite cliheader dunno "(?s)Host:" "$9$9$9$9$9$9$9$9"')
|
||||||
|
r = t.http(url + "/echo", proxy=p, headers={"X-Pad": "P" * 2000})
|
||||||
|
t.eq(200, r.status, "a reference to a group which did not match is left out")
|
||||||
|
t.contains(t.http(url + "/echo", proxy=p), "path=/echo",
|
||||||
|
"and that proxy is still there too")
|
||||||
|
|
||||||
|
# an optional group which took part on one request and not on the next
|
||||||
|
p = proxy_with("rewrite_optgroup",
|
||||||
|
'pcre_rewrite cliheader dunno "X-Mark: (a)?(b)" "[$1][$2]"')
|
||||||
|
t.eq(200, t.http(url + "/echo", proxy=p, headers={"X-Mark": "ab"}).status,
|
||||||
|
"a group which matched is put in")
|
||||||
|
t.eq(200, t.http(url + "/echo", proxy=p, headers={"X-Mark": "b"}).status,
|
||||||
|
"and one which did not is left out")
|
||||||
|
|||||||
@ -1,7 +1,33 @@
|
|||||||
"""The port mappers: tcppm forwards a TCP port, udppm a UDP one."""
|
"""The port mappers: tcppm forwards a TCP port, udppm a UDP one."""
|
||||||
|
|
||||||
|
import socket
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
|
||||||
|
HALFCLOSE_SIZE = 20000000
|
||||||
|
|
||||||
|
|
||||||
|
def halfclose_origin(listener):
|
||||||
|
"""Answers each connection which asks for something with HALFCLOSE_SIZE
|
||||||
|
bytes, then waits for the client's end of the stream and closes. The
|
||||||
|
connections waiting for the service to start ask nothing."""
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
c, _ = listener.accept()
|
||||||
|
except OSError:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
if c.recv(1000):
|
||||||
|
c.sendall(b"x" * HALFCLOSE_SIZE)
|
||||||
|
c.recv(10)
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
c.close()
|
||||||
|
|
||||||
|
|
||||||
def run(t):
|
def run(t):
|
||||||
|
if not t.skip_unless("httpsrv"):
|
||||||
|
return
|
||||||
# --- tcppm ---------------------------------------------------------
|
# --- tcppm ---------------------------------------------------------
|
||||||
origin = t.free_port()
|
origin = t.free_port()
|
||||||
mapped = t.free_port()
|
mapped = t.free_port()
|
||||||
@ -11,8 +37,8 @@ def run(t):
|
|||||||
log
|
log
|
||||||
auth iponly
|
auth iponly
|
||||||
allow *
|
allow *
|
||||||
http * /echo* echo
|
http echo * /echo**
|
||||||
http * /data data
|
http data * /data
|
||||||
httpsrv -p{origin}
|
httpsrv -p{origin}
|
||||||
|
|
||||||
flush
|
flush
|
||||||
@ -40,6 +66,41 @@ def run(t):
|
|||||||
|
|
||||||
t.stop_all()
|
t.stop_all()
|
||||||
|
|
||||||
|
# A client which shuts down its sending side still gets the whole answer,
|
||||||
|
# even when it reads slowly: the server has finished and closed by then,
|
||||||
|
# and the rest of the answer waits in the proxy's buffer and the kernel's.
|
||||||
|
listener = socket.socket()
|
||||||
|
listener.bind(("127.0.0.1", 0))
|
||||||
|
listener.listen(1)
|
||||||
|
threading.Thread(target=halfclose_origin, args=(listener,), daemon=True).start()
|
||||||
|
halfclosed = t.free_port()
|
||||||
|
t.start("portmap_halfclose", f"""
|
||||||
|
log
|
||||||
|
auth none
|
||||||
|
tcppm {halfclosed} 127.0.0.1 {listener.getsockname()[1]}
|
||||||
|
""", ports=[halfclosed])
|
||||||
|
s = socket.socket()
|
||||||
|
s.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 32768)
|
||||||
|
s.settimeout(30)
|
||||||
|
got = 0
|
||||||
|
try:
|
||||||
|
s.connect(("127.0.0.1", halfclosed))
|
||||||
|
s.sendall(b"request")
|
||||||
|
s.shutdown(socket.SHUT_WR)
|
||||||
|
time.sleep(1)
|
||||||
|
while True:
|
||||||
|
d = s.recv(65536)
|
||||||
|
if not d:
|
||||||
|
break
|
||||||
|
got += len(d)
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
s.close()
|
||||||
|
listener.close()
|
||||||
|
t.eq(HALFCLOSE_SIZE, got, "a half-closed client reading slowly gets the whole answer")
|
||||||
|
|
||||||
|
t.stop_all()
|
||||||
|
|
||||||
# --- udppm ---------------------------------------------------------
|
# --- udppm ---------------------------------------------------------
|
||||||
# something has to be listening for the mapped datagrams to go anywhere
|
# something has to be listening for the mapped datagrams to go anywhere
|
||||||
echo = t.udp_echo()
|
echo = t.udp_echo()
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user