Compare commits

..

6 Commits

Author SHA1 Message Date
Vladimir Dubrovin
f271a57238 Fix build
Some checks failed
C/C++ CI Linux / ${{ matrix.target }} (ubuntu-24.04-arm) (push) Has been cancelled
C/C++ CI Linux / ${{ matrix.target }} (ubuntu-latest) (push) Has been cancelled
C/C++ CI MacOS / ${{ matrix.target }} (macos-15) (push) Has been cancelled
C/C++ CI Windows / ${{ matrix.target }} (windows-2022) (push) Has been cancelled
C/C++ CI cmake / ${{ matrix.target }} (macos-15) (push) Has been cancelled
C/C++ CI cmake / ${{ matrix.target }} (ubuntu-24.04-arm) (push) Has been cancelled
C/C++ CI cmake / ${{ matrix.target }} (ubuntu-latest) (push) Has been cancelled
C/C++ CI cmake / ${{ matrix.target }} (windows-2022) (push) Has been cancelled
C/C++ CI cmake / ubuntu-latest (wolfSSL) (push) Has been cancelled
Update version files / version (push) Has been cancelled
2026-10-10 19:37:04 +03:00
github-actions[bot]
dded867ede Update version files for 1.0.1 2026-10-10 16:21:01 +00:00
Vladimir Dubrovin
fad5acea9a Commit as 1.0.1 2026-10-10 19:20:50 +03:00
Vladimir Dubrovin
a0ea1b3e7d FIx: race condition on service reload 2026-10-10 19:00:52 +03:00
Vladimir Dubrovin
5e71f7daff Fix missing device binding for few cases 2026-10-10 17:39:41 +03:00
Derrick Lyndon Pallas
36ae10ad16 Fix: wrong sasize in sockgetchar/sockrecvfrom
On receive, the struct sockaddr is zeroed and does not have an address
family until recvfrom fills it in.  SASIZE assumes that if there is no
address family, it is INET.  This means that for IPv6 UDP sockets, the
sockaddr is truncated and the reply is forwarded to the wrong address.
Instead, use sizeof(PROXYSOCKADDRTYPE).
2026-10-10 16:47:18 +03:00
20 changed files with 196 additions and 99 deletions

View File

@ -1,13 +1,25 @@
3proxy-1.0.0 Released August, 22 2026 3proxy-1.0.1 Released October, 10 2026
No changes in 3proxy code, this release only changes how packages are built, signed and published. ! 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: 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: SOCKSv5 UDP ASSOCIATE over IPv6: the source address of a datagram was truncated and the reply was sent to the wrong address (#1279)
! 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: 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: 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: 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: 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: buffer could be insufficient for the headers the HTTP proxy adds after a pcre_rewrite had grown the request
+ 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: 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
+ 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: pcre_extend did nothing, the rules it appended were never consulted; the manual described an argument the command does not take
+ 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: 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
+ 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: null pointer dereference in ftppr on PASS without USER and in smtpp on a user name without a host
- 32-bit ARM (armhf) is published as a deb package only, Enterprise Linux has no 32-bit ARM build ! 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

View File

@ -1,13 +1,25 @@
3proxy-1.0.0 Вышел 22 Августа 2026 3proxy-1.0.1 Вышел 10 Октября 2026
Изменений в коде 3proxy нет, этот выпуск меняет только сборку, подписывание и публикацию пакетов. ! Исправление: падение (segmentation fault) при перезагрузке конфигурации (SIGUSR1) на нагруженном сервере: закрывающаяся сессия могла обращаться к службе после того, как перезагрузка её освободила; гонка существовала с 0.9.7, а добавленная в 0.9.9 задержка закрытия (linger) в 5 секунд сделала её гораздо вероятнее (#1278)
! Исправление: -Di и -De (привязка к интерфейсу) не применялись к сокетам SOCKSv5 UDP ASSOCIATE и BIND, к соединению данных FTP прокси и к обратному соединению (connect back) (#1277)
! Исправление: поддержка TLS, PCRE2 и PAM молча не включалась при сборке в системах, где /bin/sh не dash, то есть во всех дистрибутивах на основе RPM и в macOS; проверки наличия библиотек формировали тестовую программу через echo с escape-последовательностями, которые раскрывает только dash, поэтому все проверки завершались неудачно и возможности отключались без каких-либо сообщений ! Исправление: SOCKSv5 UDP ASSOCIATE по IPv6: адрес отправителя датаграммы обрезался, и ответ отправлялся не на тот адрес (#1279)
! Исправление: пакеты rpm собирались в Ubuntu и требовали glibc 2.38, libssl.so.3 и libpcre2-8, которых нет ни в одном дистрибутиве на основе RPM, поэтому установить их было невозможно нигде; теперь они собираются в AlmaLinux 8, 9 и 10 и содержат метку el8, el9 или el10, что покрывает RHEL, AlmaLinux, Rocky и CentOS Stream соответствующей версии ! Исправление: SOCKSv5 UDP ASSOCIATE: правило доступа с именами хостов срабатывало для любой датаграммы, отправленной на адрес, а не на имя; теперь адрес используется как имя хоста, так же как для запросов TCP
! Исправление: пакет rpm зависел от собственного интерпретатора /bin/3proxy, взятого из первой строки устанавливаемого файла конфигурации, и не устанавливался в el9 и el10, поскольку rpm приводит эту зависимость к /usr/bin/3proxy ! Исправление: запрос, изменённый pcre_rewrite, только записывался в журнал, а при прямом соединении серверу отправлялся без изменений; теперь сервер получает изменённые путь и строку запроса
! Исправление: пакеты deb требовали glibc 2.38 и libssl3t64 и устанавливались только в Ubuntu 24.04 и новее; теперь они собираются в Ubuntu 22.04 и устанавливаются в Ubuntu 22.04 и новее, а также в Debian 12 и новее ! Исправление: буфера могло не хватить для заголовков, добавляемых HTTP прокси, после того как pcre_rewrite увеличил запрос
+ Подписанные репозитории apt и dnf публикуются на https://3proxy.org/repo/ в двух каналах: current, собираемый из ветки master, и lts, собираемый из ветки 0.9; подписываются и пакеты, и метаданные репозитория ! Исправление: переполнение буфера в куче в pcre_rewrite: ссылка $N на группу, не участвовавшую в совпадении, выводилась своими цифрами, которые не учитывались при выделении памяти под результат
+ Двоичные файлы релиза публикуются с контрольными суммами SHA256, подписью OpenPGP и подтверждением происхождения сборки GitHub (build provenance attestation); образы docker также подписываются и снабжаются подтверждением ! Исправление: pcre_extend ничего не делал, добавленные им правила никогда не проверялись; в документации был описан аргумент, который команда не принимает
+ Ключ подписи релизов — RSA-4096, опубликован в репозитории как 3proxy-release-key.asc; rpm версии 4.14 и более ранние вообще не могут импортировать ключ Ed25519, что лишило бы RHEL 8 и производные от него возможности проверки ! Исправление: сертификаты, создаваемые для TLS MitM (openssl), не содержали идентификаторов ключа субъекта и издателя и отвергались строгой проверкой, в том числе клиентами macOS, OpenSSL с x509_strict и Python 3.13
+ Пакеты воспроизводимы: повторная сборка релиза даёт побайтово идентичные файлы deb и rpm, все отметки времени берутся из даты сборки, записанной для релиза ! Исправление: обращение по нулевому указателю в ftppr при PASS без USER и в smtpp при имени пользователя без хоста
- Для 32-битной ARM (armhf) публикуется только пакет deb, в Enterprise Linux нет сборки для 32-битной ARM ! Исправление: с wolfSSL на glibc не запускалась ни одна служба: стека потока по умолчанию не хватало для thread-local памяти wolfSSL, pthread_create() завершался ошибкой, о которой не сообщалось
! Исправление: dnspr не работал в Windows; сборки для Windows снова отвечают по UDP с отдельного сокета
+ Встроенный HTTP сервер: служба httpsrv с правилами http для отдачи файлов (из кэша или через sendfile / TransmitFile), перенаправлений, перезаписи, фиксированных ответов и proxypass для передачи запроса прокси, так что один порт может обслуживать и сайт, и прокси; admin — это httpsrv со страницами администрирования
+ Имена хостов в правилах доступа могут быть регулярными выражениями с префиксом pcre: (или regex:)
+ Команды transparent / notransparent: прозрачное проксирование встроено в 3proxy (netfilter в Linux, pf в BSD, socket для divert-to и ipfw fwd), TransparentPlugin удалён, конфигурация, загружающая его, продолжает работать
+ parent: вес можно задавать дробью (.5) или в процентах (50%); вес 0 означает резервный parent, который используется только когда отказали все parent его группы с ненулевым весом; отказавший parent исключается из следующей попытки того же соединения
+ parent extport / intport: диапазон локальных портов для исходящих соединений и для сокетов на стороне клиента (UDP ASSOCIATE, данные FTP)
+ parent, заданный именем хоста, разрешается для каждого соединения, если используется nserver, а не один раз при загрузке конфигурации
+ $$ в конфигурации обозначает одиночный знак доллара, доллар в кавычках — обычный символ
+ Пакет для OpenWrt (скрипт запуска procd, конфигурация UCI) и сборки OpenWrt для mipsel_24kc, mips_24kc, arm_cortex-a7_neon-vfpv4 и aarch64_cortex-a53
- pop3p, imapp, smtpp и ftppr не входят в сборку по умолчанию: собирайте с MAILPROXY=true и FTP=true (CMake: 3PROXY_USE_MAILPROXY, 3PROXY_USE_FTP); без них почтовые прокси работают как tlspr с согласованием STARTTLS, а ftppr отказывает клиентам
- FTP через HTTP прокси (адреса ftp://) также не входит в сборку по умолчанию и должен быть включён явно: собирайте с FTP=true (CMake: 3PROXY_USE_FTP), без этого HTTP прокси не обрабатывает адреса ftp://
- Двоичные файлы для Windows больше не подписываются самоподписанным сертификатом Authenticode: доверия он не давал и вызывал ложные срабатывания антивирусов

View File

@ -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 &&\

View File

@ -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 &&\

View File

@ -135,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

View File

@ -123,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
@ -189,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

View File

@ -130,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

View File

@ -1 +1 @@
1.0.0 1.0.1

6
debian/changelog vendored
View File

@ -1,3 +1,9 @@
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
View File

@ -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
View 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

View File

@ -1,5 +1,5 @@
Name: 3proxy Name: 3proxy
Version: 1.0.0 Version: 1.0.1
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

View File

@ -683,6 +683,49 @@ int connectwithpoll(struct clientparam *param, SOCKET sock, struct sockaddr *sa,
static int bindrange(struct clientparam *param, SOCKET sock, PROXYSOCKADDRTYPE *sa, uint32_t range, int *tries); 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(&param->srv->so, param->sostate, sock, param->srv->obindtodevice, *SAFAMILY(&param->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(&param->srv->so, param->sostate, sock, param->srv->ibindtodevice, *SAFAMILY(&param->sincl));
#else
return 0;
#endif
}
int doconnect(struct clientparam * param){ int doconnect(struct clientparam * param){
SASIZETYPE size; SASIZETYPE size;
@ -732,25 +775,7 @@ int doconnect(struct clientparam * param){
setopts(param->remsock, param->srv->srvsockopts); setopts(param->remsock, param->srv->srvsockopts);
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(extbindtodevice(param, param->remsock)) return 12;
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(&param->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(&param->sinsl) == AF_INET6 && param->srv->so._setsockopt(param->sostate, param->remsock, IPPROTO_IPV6, IPV6_BOUND_IF, &idx, sizeof(idx))) return 12;
#endif
}
#endif
#ifdef WITH_UN #ifdef WITH_UN
if(*SAFAMILY(&param->sinsl) != AF_UNIX) if(*SAFAMILY(&param->sinsl) != AF_UNIX)
#endif #endif

View File

@ -129,6 +129,7 @@ void * ftpprchild(struct clientparam* param) {
clidatasock = INVALID_SOCKET; clidatasock = INVALID_SOCKET;
} }
if ((clidatasock=socket(SASOCK(&param->sincl), SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET) {RETURN(821);} if ((clidatasock=socket(SASOCK(&param->sincl), SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET) {RETURN(821);}
if(intbindtodevice(param, clidatasock)){RETURN(822);}
*SAPORT(&param->sincl) = 0; *SAPORT(&param->sincl) = 0;
if(bindwithrange(param, clidatasock, &param->sincl, param->intport)){RETURN(822);} if(bindwithrange(param, clidatasock, &param->sincl, param->intport)){RETURN(822);}
if (pasv) { if (pasv) {

View File

@ -391,6 +391,9 @@ int readconfig(FILE * fp);
void initcommands(void); void initcommands(void);
int connectwithpoll(struct clientparam *param, SOCKET sock, struct sockaddr *sa, SASIZETYPE size, int to); int connectwithpoll(struct clientparam *param, SOCKET sock, struct sockaddr *sa, SASIZETYPE size, int to);
int bindwithrange(struct clientparam *param, SOCKET sock, PROXYSOCKADDRTYPE *sa, uint32_t range); int bindwithrange(struct clientparam *param, SOCKET sock, PROXYSOCKADDRTYPE *sa, uint32_t range);
int 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 #ifdef WITH_PCRE
/* One regular expression implementation for the whole program: the pcre /* One regular expression implementation for the whole program: the pcre
commands and every pattern that carries a pcre: prefix. */ commands and every pattern that carries a pcre: prefix. */

View File

@ -856,29 +856,11 @@ int MODULEMAINFUNC (int argc, char** argv){
srv.so._setsockopt(srv.so.state, sock, SOL_SOCKET, SO_REUSEPORT, (char *)&opt, sizeof(int)); srv.so._setsockopt(srv.so.state, sock, SOL_SOCKET, SO_REUSEPORT, (char *)&opt, sizeof(int));
#endif #endif
} }
#if defined SO_BINDTODEVICE #if defined SO_BINDTODEVICE || defined IP_BOUND_IF
if(srv.ibindtodevice && srv.so._setsockopt(srv.so.state, sock, SOL_SOCKET, SO_BINDTODEVICE, srv.ibindtodevice, strlen(srv.ibindtodevice) + 1)) { if(bindtodevice(&srv.so, srv.so.state, sock, srv.ibindtodevice, *SAFAMILY(&srv.intsa))) {
dolog(&defparam, (unsigned char *)"failed to bind device"); dolog(&defparam, (unsigned char *)"failed to bind device");
return -12; return -12;
} }
#elif defined IP_BOUND_IF
if(srv.ibindtodevice){
int idx;
idx = if_nametoindex(srv.ibindtodevice);
if(!idx || (*SAFAMILY(&srv.intsa) == AF_INET && setsockopt(sock, IPPROTO_IP, IP_BOUND_IF, &idx, sizeof(idx)))) {
dolog(&defparam, (unsigned char *)"failed to bind device");
return -12;
}
#ifndef NOIPV6
#ifndef IPV6_BOUND_IF
#define IPV6_BOUND_IF 125
#endif
if((*SAFAMILY(&srv.intsa) == AF_INET6 && srv.so._setsockopt(srv.so.state, sock, IPPROTO_IPV6, IPV6_BOUND_IF, &idx, sizeof(idx)))) {
dolog(&defparam, (unsigned char *)"failed to bind device");
return -12;
}
#endif
}
#endif #endif
} }
#ifdef WITH_UN #ifdef WITH_UN
@ -1032,6 +1014,15 @@ int MODULEMAINFUNC (int argc, char** argv){
setopts(new_sock, srv.cbcsockopts); setopts(new_sock, srv.cbcsockopts);
parsehost(srv.family, cbc_string, (struct sockaddr *)&defparam.sincr); parsehost(srv.family, cbc_string, (struct sockaddr *)&defparam.sincr);
#if defined SO_BINDTODEVICE || defined IP_BOUND_IF
/* as for a listening socket, an interface that cannot be
bound to is not worth retrying */
if(bindtodevice(&so, so.state, new_sock, srv.ibindtodevice, *SAFAMILY(&defparam.sincr))) {
dolog(&defparam, (unsigned char *)"failed to bind device");
so._closesocket(so.state, new_sock);
break;
}
#endif
if(connectwithpoll(NULL, new_sock,(struct sockaddr *)&defparam.sincr,SASIZE(&defparam.sincr),CONNBACK_TO)) { if(connectwithpoll(NULL, new_sock,(struct sockaddr *)&defparam.sincr,SASIZE(&defparam.sincr),CONNBACK_TO)) {
so._closesocket(so.state, new_sock); so._closesocket(so.state, new_sock);
new_sock = INVALID_SOCKET; new_sock = INVALID_SOCKET;
@ -1342,7 +1333,16 @@ void srvfree(struct srvparam * srv){
if(srv->cbsock != INVALID_SOCKET) so._closesocket(srv->so.state, srv->cbsock); if(srv->cbsock != INVALID_SOCKET) so._closesocket(srv->so.state, srv->cbsock);
srv->cbsock = INVALID_SOCKET; srv->cbsock = INVALID_SOCKET;
srv->service = S_ZOMBIE; srv->service = S_ZOMBIE;
while(srv->child) usleep(SLEEPTIME * 100); /* under the mutex, so the last child has let go of it before it is destroyed */
for(;;){
struct clientparam *child;
_3proxy_mutex_lock(&srv->counter_mutex);
child = srv->child;
_3proxy_mutex_unlock(&srv->counter_mutex);
if(!child) break;
usleep(SLEEPTIME * 100);
}
#ifndef STDMAIN #ifndef STDMAIN
if(srv->filter){ if(srv->filter){
while(srv->nfilters){ while(srv->nfilters){
@ -1387,22 +1387,13 @@ void srvfree(struct srvparam * srv){
void freeparam(struct clientparam * param) { void freeparam(struct clientparam * param) {
if(param->srv){ if(param->srv){
if(param->srv->so.freefunc) param->srv->so.freefunc(param->sostate); if(param->srv->so.freefunc) param->srv->so.freefunc(param->sostate);
_3proxy_mutex_lock(&param->srv->counter_mutex);
#ifndef STDMAIN #ifndef STDMAIN
if(param->srv->service == S_UDPPM) hashdelete(&udp_table, param); if(param->srv->service == S_UDPPM){
#endif _3proxy_mutex_lock(&param->srv->counter_mutex);
if(param->prev || param->next || param->srv->child == param){ hashdelete(&udp_table, param);
if(param->prev){ _3proxy_mutex_unlock(&param->srv->counter_mutex);
param->prev->next = param->next;
}
else
param->srv->child = param->next;
if(param->next){
param->next->prev = param->prev;
}
(param->srv->childcount)--;
} }
_3proxy_mutex_unlock(&param->srv->counter_mutex); #endif
} }
if(param->clibuf) free(param->clibuf); if(param->clibuf) free(param->clibuf);
if(param->srvbuf) free(param->srvbuf); if(param->srvbuf) free(param->srvbuf);
@ -1442,6 +1433,24 @@ void freeparam(struct clientparam * param) {
} }
if(param->connlim) stopconnlims(param); if(param->connlim) stopconnlims(param);
#endif #endif
/* Leaving the service comes last. Once its last child is gone, srvfree()
frees the service along with its socket functions, filters and mutex,
and a close above may have lingered for seconds before getting here. */
if(param->srv){
_3proxy_mutex_lock(&param->srv->counter_mutex);
if(param->prev || param->next || param->srv->child == param){
if(param->prev){
param->prev->next = param->next;
}
else
param->srv->child = param->next;
if(param->next){
param->next->prev = param->prev;
}
(param->srv->childcount)--;
}
_3proxy_mutex_unlock(&param->srv->counter_mutex);
}
if(param->hostname) free(param->hostname); if(param->hostname) free(param->hostname);
if(param->username) free(param->username); if(param->username) free(param->username);
if(param->password) free(param->password); if(param->password) free(param->password);

View File

@ -63,7 +63,7 @@ int sockrecvfrom(struct clientparam *param, SOCKET sock, struct sockaddr * sin,
if(conf.timetoexit) return EOF; if(conf.timetoexit) return EOF;
res = param?param->srv->so._poll(param->sostate, &fds, 1, to):so._poll(so.state, &fds, 1, to); res = param?param->srv->so._poll(param->sostate, &fds, 1, to):so._poll(so.state, &fds, 1, to);
if (res<1) return 0; if (res<1) return 0;
sasize = SASIZE(sin); sasize = sizeof(PROXYSOCKADDRTYPE);
do { do {
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); 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);
} while (res < 0 && (errno == EAGAIN || errno == EINTR)); } while (res < 0 && (errno == EAGAIN || errno == EINTR));

View File

@ -185,6 +185,7 @@ void * sockschild(struct clientparam* param) {
if(command == 2){ if(command == 2){
if ((param->remsock=param->srv->so._socket(param->sostate, SASOCK(&param->req), SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET) {RETURN (11);} if ((param->remsock=param->srv->so._socket(param->sostate, SASOCK(&param->req), SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET) {RETURN (11);}
setopts(param->remsock, param->srv->lissockopts); setopts(param->remsock, param->srv->lissockopts);
if(extbindtodevice(param, param->remsock)) {RETURN (12);}
} }
break; break;
@ -244,6 +245,7 @@ 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 */

View File

@ -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, &param->sinsl, param->extport)) { if (bindwithrange(param, param->remsock, &param->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);

View File

@ -1,14 +1,14 @@
#ifndef VERSION #ifndef VERSION
#define VERSION "3proxy-1.0.0" #define VERSION "3proxy-1.0.1"
#endif #endif
#ifndef BUILDDATE #ifndef BUILDDATE
#define BUILDDATE "260822121300" #define BUILDDATE "261010162101"
#endif #endif
#define MAJOR3PROXY 1 #define MAJOR3PROXY 1
#define SUBMAJOR3PROXY 0 #define SUBMAJOR3PROXY 0
#define MINOR3PROXY 0 #define MINOR3PROXY 1
#define SUBMINOR3PROXY 0 #define SUBMINOR3PROXY 0
#define RELEASE3PROXY "3proxy-1.0.0(" BUILDDATE ")\0" #define RELEASE3PROXY "3proxy-1.0.1(" BUILDDATE ")\0"
#ifndef YEAR3PROXY #ifndef YEAR3PROXY
#define YEAR3PROXY "2026" #define YEAR3PROXY "2026"
#endif #endif