Compare commits

...

4 Commits

Author SHA1 Message Date
Pаramtamtām
2626e67984
Merge ccdecfd832 into 5f1ed7363b 2026-07-31 14:32:22 +03:00
Vladimir Dubrovin
5f1ed7363b Fix: race conditions / stack overflow on FreeBSD / minor bugs 2026-07-31 13:27:33 +03:00
Vladimir Dubrovin
c370635fc4 fix wolfSSL, switch minimal/busybox to musl+wolfssl 2026-07-31 11:27:10 +03:00
Pаramtamtām
ccdecfd832
Update README with alternative Docker image information
Added a tip about an alternative Docker image with Helm chart support.
2026-06-02 18:32:47 +04:00
23 changed files with 438 additions and 95 deletions

73
.dockerignore Normal file
View File

@ -0,0 +1,73 @@
# Exclude top-level dirs not needed for build
/bin/
/bin64/
/build/
/cfg/
/cmake/
/CMakeFiles/
/debian/
/doc/
/man/
# Build artifacts
**/*.o
**/*.obj
**/*.so
**/*.dll
**/*.exe
**/*.exp
**/*.lib
**/*.pdb
**/*.ilk
**/*.idb
**/*.err
**/*.ld.so
**/*.dSYM
**/*.var
build*/
buildlinux.sh
tmp/
# macOS
.DS_Store
# Editor / IDE
**/*.swp
**/*.swo
**/*~
**/*.bak
**/*.tmp
**/*.log
.project
.cproject
.classpath
.settings/
.vscode/
.idea/
# Git
.git
.gitignore
# Claude / docs
CLAUDE.md
.claude/
# Generated version files
version
version.c
version.sh
verfile.sh
# Local config / keys
**/*.key
**/*.pem
**/*.pfx
3proxy.cfg
3proxy-ssl.cfg
3proxy-pcre.cfg
# Misc
res
3proxy.res
copytgz.sh

35
.github/workflows/docker-test.yml vendored Normal file
View File

@ -0,0 +1,35 @@
name: Build Docker images (test, no publish)
on:
workflow_dispatch:
permissions:
contents: read
jobs:
build:
name: Build ${{ matrix.file }} (${{ matrix.platform }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
file: [Dockerfile.minimal, Dockerfile.busybox, Dockerfile.full]
platform: [linux/amd64, linux/arm64]
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
- name: Build image
uses: docker/build-push-action@v6
with:
context: .
file: ${{ matrix.file }}
platforms: ${{ matrix.platform }}
push: false
load: ${{ matrix.platform == 'linux/amd64' }}

3
.gitignore vendored
View File

@ -270,4 +270,5 @@ bin/3proxy_socks
bin/3proxy_tcppm
bin/3proxy_tlspr
bin/3proxy_udppm
build*/*
build*/*
testcerts/

View File

@ -1,4 +1,5 @@
# 3proxy.full is fully functional 3proxy build based on busybox:glibc
# 3proxy.busybox is fully functional 3proxy build based on busybox:musl
# built against wolfSSL and PCRE2.
#
# Examples are for podman, for docker change 'podman' to 'docker'
#
@ -19,11 +20,27 @@
# configuration is supported for compatibility only.
FROM docker.io/gcc AS buildenv
FROM docker.io/alpine:latest AS wolfssl
RUN apk add --no-cache build-base curl autoconf automake libtool
RUN TAG=$(curl -s https://api.github.com/repos/wolfSSL/wolfssl/releases/latest \
| sed -n 's/.*"tag_name": *"\([^"]*\)".*/\1/p') && \
echo "wolfssl tag: $TAG" && \
curl -sL https://api.github.com/repos/wolfSSL/wolfssl/tarball/$TAG -o /tmp/w.tar.gz && \
mkdir /src && tar -xzf /tmp/w.tar.gz -C /src --strip-components=1 && \
cd /src && \
autoreconf -i && \
./configure --enable-opensslextra --enable-opensslall --enable-certgen \
--enable-tls13 --enable-sni --enable-session-ticket \
--enable-static --disable-shared --prefix=/usr/local && \
make -j$(nproc) && make install
FROM docker.io/alpine:latest AS buildenv
RUN apk add --no-cache gcc make musl-dev pcre2-dev
COPY --from=wolfssl /usr/local /usr/local
COPY . 3proxy
RUN cd 3proxy &&\
apt --assume-yes update && apt --assume-yes install libssl-dev libpcre2-dev &&\
make -f Makefile.Linux LIBSTATIC=true PAM_CHECK=false &&\
RUN cd 3proxy && mkdir -p bin &&\
make -f Makefile.Linux LIBSTATIC=true PAM_CHECK=false WOLFSSL_CHECK=true \
EXTRA_LDFLAGS="-L/usr/local/lib" &&\
strip bin/3proxy &&\
strip bin/*so &&\
mkdir /dist &&\
@ -42,11 +59,9 @@ RUN cd /dist &&\
ln -s /lib lib64 &&\
ln -s /lib usr/lib &&\
ln -s /lib usr/lib64
RUN cp /lib/ld-*.so.* /dist/usr/local/3proxy/libexec || true
RUN cp /lib64/ld-*.so.* /dist/usr/local/3proxy/libexec || true
RUN cp "/lib/`gcc -dumpmachine`"/libdl.so.* /dist/usr/local/3proxy/libexec
RUN cp /lib/ld-musl-*.so.* /dist/usr/local/3proxy/libexec || true
FROM docker.io/busybox:glibc
FROM docker.io/busybox:musl
COPY --from=buildenv /dist /
RUN ln -sf /usr/local/3proxy/libexec/* /lib/ && cd /usr/local/3proxy/ && ln -s libexec lib && ln -s libexec lib64 && mkdir usr && ln -s libexec usr/lib && ln -s libexec usr//lib64
RUN mkdir -p /lib /lib64 && ln -sf /usr/local/3proxy/libexec/* /lib/ && cd /usr/local/3proxy/ && ln -s libexec lib && ln -s libexec lib64 && mkdir usr && ln -s libexec usr/lib && ln -s libexec usr/lib64
CMD ["/bin/3proxy", "/etc/3proxy/3proxy.cfg"]

View File

@ -23,6 +23,7 @@ FROM docker.io/gcc AS buildenv
COPY . 3proxy
RUN cd 3proxy &&\
apt --assume-yes update && apt --assume-yes install libssl-dev libpcre2-dev &&\
mkdir -p bin &&\
make -f Makefile.Linux LIBSTATIC=true PAM_CHECK=false &&\
strip bin/3proxy &&\
mkdir /dist &&\

View File

@ -3,7 +3,8 @@
#
# Examples are for podman. For docker change 'podman' to 'docker'.
#
# This is busybox based docker with only 3proxy static executable.
# This is a scratch-based docker with statically linked 3proxy executable
# built against musl and wolfSSL.
#
# Limitations for minimal version:
# no support for plugins and system resolver.
@ -15,7 +16,7 @@
#
# Run example:
#
# podman run --read-only -i -p 3129:3129 --name 3proxy 3proxy.minimal
# podman run --read-only -i -p 3129:3129 --name 3proxy 3proxy.minimal
#or
# podman start -ai 3proxy
#<nserver 8.8.8.8
@ -25,12 +26,29 @@
#<end
#
FROM docker.io/gcc AS buildenv
FROM docker.io/alpine:latest AS wolfssl
RUN apk add --no-cache build-base curl autoconf automake libtool
RUN TAG=$(curl -s https://api.github.com/repos/wolfSSL/wolfssl/releases/latest \
| sed -n 's/.*"tag_name": *"\([^"]*\)".*/\1/p') && \
echo "wolfssl tag: $TAG" && \
curl -sL https://api.github.com/repos/wolfSSL/wolfssl/tarball/$TAG -o /tmp/w.tar.gz && \
mkdir /src && tar -xzf /tmp/w.tar.gz -C /src --strip-components=1 && \
cd /src && \
autoreconf -i && \
./configure --enable-opensslextra --enable-opensslall --enable-certgen \
--enable-tls13 --enable-sni --enable-session-ticket \
--enable-static --disable-shared --prefix=/usr/local && \
make -j$(nproc) && make install
FROM docker.io/alpine:latest AS buildenv
RUN apk add --no-cache gcc make musl-dev
COPY --from=wolfssl /usr/local /usr/local
COPY . 3proxy
RUN cd 3proxy &&\
make -f Makefile.Linux STATIC=true &&\
RUN cd 3proxy && mkdir -p bin &&\
make -f Makefile.Linux STATIC=true WOLFSSL_CHECK=true \
EXTRA_LDFLAGS="-L/usr/local/lib" &&\
strip bin/3proxy
FROM scratch
COPY --from=buildenv 3proxy/bin/3proxy /bin/3proxy
CMD ["/bin/3proxy"]

View File

@ -15,7 +15,12 @@ CFLAGS += -c -fno-strict-aliasing -DFD_SETSIZE=4096 -DWITH_POLL -DWITH_UN
COUT = -o
LN ?= ${CC}
LDFLAGS ?= -O3 -flto
LDFLAGS += -pthread -fno-strict-aliasing
LDFLAGS += -pthread -fno-strict-aliasing
# Use EXTRA_CFLAGS/EXTRA_LDFLAGS to add flags from the make command line.
# Setting CFLAGS or LDFLAGS there instead overrides every assignment in this
# makefile, including the += above and the STATIC/LIBSTATIC handling below.
CFLAGS += $(EXTRA_CFLAGS)
LDFLAGS += $(EXTRA_LDFLAGS)
# -lpthreads may be reuiured on some platforms instead of -pthreads
# -ldl or -lld may be required for some platforms
DCFLAGS ?= -fPIC

View File

@ -16,6 +16,11 @@ LN ?= ${CC}
DCFLAGS ?= -fPIC
LDFLAGS ?= -O3 -flto
LDFLAGS += -fno-strict-aliasing -pthread
# Use EXTRA_CFLAGS/EXTRA_LDFLAGS to add flags from the make command line.
# Setting CFLAGS or LDFLAGS there instead overrides every assignment in this
# makefile, including the += above and the STATIC/LIBSTATIC handling below.
CFLAGS += $(EXTRA_CFLAGS)
LDFLAGS += $(EXTRA_LDFLAGS)
DLFLAGS ?= -shared
DLSUFFICS = .ld.so
# -lpthreads may be reuqired on some platforms instead of -pthreads

View File

@ -18,6 +18,11 @@ COUT = -o
LN ?= $(CC)
LDFLAGS ?= -O3 -flto
LDFLAGS += -pthread -fno-strict-aliasing
# Use EXTRA_CFLAGS/EXTRA_LDFLAGS to add flags from the make command line.
# Setting CFLAGS or LDFLAGS there instead overrides every assignment in this
# makefile, including the += above and the STATIC/LIBSTATIC handling below.
CFLAGS += $(EXTRA_CFLAGS)
LDFLAGS += $(EXTRA_LDFLAGS)
# -lpthreads may be reuqired on some platforms instead of -pthreads
# -ldl or -lld may be required for some platforms
DCFLAGS ?= -fPIC

View File

@ -15,6 +15,11 @@ COUT = -o
LN ?= $(CC)
LDFLAGS ?= -O3 -flto
LDFLAGS += -fno-strict-aliasing -mthreads
# Use EXTRA_CFLAGS/EXTRA_LDFLAGS to add flags from the make command line.
# Setting CFLAGS or LDFLAGS there instead overrides every assignment in this
# makefile, including the += above and the STATIC/LIBSTATIC handling below.
CFLAGS += $(EXTRA_CFLAGS)
LDFLAGS += $(EXTRA_LDFLAGS)
DLFLAGS ?= -shared
DLSUFFICS = .dll
LIBS += -lws2_32 -lodbc32 -ladvapi32 -luser32 -lbcrypt

View File

@ -18,6 +18,9 @@ https://github.com/3proxy/3proxy/releases
https://hub.docker.com/r/3proxy/3proxy
https://github.com/3proxy/3proxy/pkgs/container/3proxy
> [!TIP]
> An alternative Docker image (community-maintained) with support for configuration through environment variables and a ready-to-use Helm chart for Kubernetes can be found here: [tarampampam/3proxy-docker](https://github.com/tarampampam/3proxy-docker).
### Archive of old versions
https://github.com/z3APA3A/3proxy-archive

View File

@ -1178,6 +1178,19 @@ crash on request processing, try to set some positive value. You may start with
stacksize 65536
and then find the minimal value for the service to work. If you experience
memory shortage, you can try to experiment with negative values.
.br
With SQL logging (log &ODBC_string) the value is automatically raised to
32768 if it is smaller, because ODBC drivers require more stack. A
.BR stacksize
command placed after the
.BR log
command overrides this.
.br
The base stack size the value is added to is 49152. On FreeBSD, NetBSD,
OpenBSD and DragonFly it is 65536, because libc functions such as vfprintf()
called by syslog() use significantly more stack there. The result is never
lowered below PTHREAD_STACK_MIN, so a large negative value can not disable
the thread stack.
.SH PLUGINS

View File

@ -519,15 +519,10 @@ int WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int
#ifndef NORADIUS
_3proxy_mutex_init(&rad_mutex);
#endif
#ifdef _WIN32
conf.threadinit = CreateSemaphore(NULL, 1, 1, NULL);
if(!conf.threadinit){
if(_3proxy_sem_init(conf.threadinit, 1, 1)){
fprintf(stderr, "semaphore init failed\n");
return 1;
}
#else
_3proxy_mutex_init(&conf.threadinit);
#endif
#ifdef WITH_SSL
ssl_install();

View File

@ -134,12 +134,47 @@ int timeouts[12] = {
0
};
struct extparam conf = {
#ifdef _WIN32
.threadinit = NULL,
#else
.threadinit = 0,
#ifndef _WIN32
/* PTHREAD_STACK_MIN is 128K on glibc/aarch64 and glibc/powerpc and may be
a sysconf() call with _GNU_SOURCE. pthread_attr_setstacksize() fails with
EINVAL below it and the thread silently gets the 8M system default stack.
*/
size_t threadstacksize(int extra){
long size = BASESTACKSIZE + extra;
if(size < (long)PTHREAD_STACK_MIN) size = (long)PTHREAD_STACK_MIN;
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
struct extparam conf = {
.timeouts = timeouts,
.acl = NULL,
.conffile = NULL,

View File

@ -156,7 +156,7 @@ int start_proxy_thread(struct child * chp){
if(h)CloseHandle(h);
#else
pthread_attr_init(&pa);
pthread_attr_setstacksize(&pa,PTHREAD_STACK_MIN + (32768+conf.stacksize));
pthread_attr_setstacksize(&pa,threadstacksize(conf.stacksize));
pthread_attr_setdetachstate(&pa,PTHREAD_CREATE_DETACHED);
pthread_create(&thread, &pa, startsrv, (void *)chp);
pthread_attr_destroy(&pa);
@ -301,7 +301,13 @@ static int h_external(int argc, unsigned char ** argv){
}
static int h_log(int argc, unsigned char ** argv){
/* ODBC drivers require noticeably more stack than the file logger, raise
the client thread stack size unless a larger one is configured
explicitly. An explicit stacksize placed after the log command still wins.
*/
#define LOGSTACKSIZE 32768
static int h_log(int argc, unsigned char ** argv){
unsigned char tmpbuf[8192];
int notchanged = 0;
@ -330,6 +336,7 @@ static int h_log(int argc, unsigned char ** argv){
#ifdef WITH_ODBC
else if(*argv[1]=='&'){
conf.logfunc = logsql;
if(conf.stacksize < LOGSTACKSIZE) conf.stacksize = LOGSTACKSIZE;
if(notchanged) return 0;
_3proxy_mutex_lock(&log_mutex);
close_sql();

View File

@ -48,6 +48,13 @@ typedef struct {
#endif
} MD4_CTX;
/* Remap public symbols to a 3proxy-private namespace so the bundled
implementation does not collide with OpenSSL/libcrypto's MD4 symbols
when statically linked. Callers keep using the MD4_* names. */
#define MD4_Init _3proxy_MD4_Init
#define MD4_Update _3proxy_MD4_Update
#define MD4_Final _3proxy_MD4_Final
extern void MD4_Init(MD4_CTX *ctx);
extern void MD4_Update(MD4_CTX *ctx, const void *data, size_t size);
extern void MD4_Final(unsigned char *result, MD4_CTX *ctx);

View File

@ -44,6 +44,13 @@ typedef struct {
MD5_u32plus block[16];
} MD5_CTX;
/* Remap public symbols to a 3proxy-private namespace so the bundled
implementation does not collide with OpenSSL/libcrypto's MD5 symbols
when statically linked. Callers keep using the MD5_* names. */
#define MD5_Init _3proxy_MD5_Init
#define MD5_Update _3proxy_MD5_Update
#define MD5_Final _3proxy_MD5_Final
extern void MD5_Init(MD5_CTX *ctx);
extern void MD5_Update(MD5_CTX *ctx, const void *data, unsigned long size);
extern void MD5_Final(unsigned char *result, MD5_CTX *ctx);

View File

@ -112,6 +112,21 @@ void daemonize(void);
#endif
#endif
/* Thread stack size, stacksize command value is added to it. BSD libc uses
significantly more stack, e.g. in vfprintf() called by syslog().
*/
#ifndef BASESTACKSIZE
#if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__DragonFly__)
#define BASESTACKSIZE 65536
#else
#define BASESTACKSIZE 49152
#endif
#endif
#ifndef _WIN32
size_t threadstacksize(int extra);
#endif
#ifdef WITH_ODBC
#ifndef _WIN32
#include <sqltypes.h>
@ -248,14 +263,18 @@ unsigned char* en64 (const unsigned char *in, unsigned char *out, int inlen);
void tohex(unsigned char *in, unsigned char *out, int len);
void fromhex(unsigned char *in, unsigned char *out, int len);
extern _3proxy_sem_t udpinit;
#ifdef _WIN32
extern HANDLE udpinit;
#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_unlock(x) ReleaseSemaphore(x, 1, NULL)
#else
extern _3proxy_mutex_t udpinit;
#define _3proxy_sem_lock(x) pthread_mutex_lock(&x)
#define _3proxy_sem_unlock(x) pthread_mutex_unlock(&x)
int _3proxy_sem_init_f(_3proxy_sem_t *sem, unsigned count, unsigned maxcount);
void _3proxy_sem_lock_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))
#define _3proxy_sem_lock(x) _3proxy_sem_lock_f(&x)
#define _3proxy_sem_unlock(x) _3proxy_sem_unlock_f(&x)
#endif

View File

@ -11,6 +11,18 @@
#include <sched.h>
#endif
/* Child functions do not call each other, a child requesting redirection to
another child returns it instead of calling it, to keep the stack flat.
NULL is returned by the child which completed the request, it has already
released param and it must not be accessed after the call.
*/
void * childfunc(struct clientparam * param){
PROXYFUNC pf = param->srv->pf;
while(pf) pf = (PROXYFUNC)(*pf)(param);
return NULL;
}
#define param ((struct clientparam *) p)
#ifdef _WIN32
DWORD WINAPI threadfunc(LPVOID p) {
@ -367,6 +379,7 @@ int MODULEMAINFUNC (int argc, char** argv){
if(!srv.udpbuf || !srv.udpbuf2) {
#ifndef STDMAIN
haveerror = 2;
_3proxy_sem_unlock(conf.threadinit);
#endif
return 11;
}
@ -608,7 +621,6 @@ int MODULEMAINFUNC (int argc, char** argv){
if (error || i!=argc) {
#ifndef STDMAIN
haveerror = 1;
_3proxy_sem_unlock(conf.threadinit);
#endif
fprintf(stderr, "%s of %s\n"
"Usage: %s options\n"
@ -628,6 +640,9 @@ int MODULEMAINFUNC (int argc, char** argv){
""
#endif
);
#ifndef STDMAIN
_3proxy_sem_unlock(conf.threadinit);
#endif
return (1);
}
@ -640,7 +655,6 @@ int MODULEMAINFUNC (int argc, char** argv){
if (error || argc != i+3 || *argv[i]=='-'|| (*SAPORT(&srv.intsa) = htons((uint16_t)atoi(argv[i])))==0 || (srv.targetport = htons((uint16_t)atoi(argv[i+2])))==0) {
#ifndef STDMAIN
haveerror = 1;
_3proxy_sem_unlock(conf.threadinit);
#endif
fprintf(stderr, "%s of %s\n"
"Usage: %s options"
@ -661,6 +675,9 @@ int MODULEMAINFUNC (int argc, char** argv){
""
#endif
);
#ifndef STDMAIN
_3proxy_sem_unlock(conf.threadinit);
#endif
return (1);
}
srv.target = (unsigned char *)strdup(argv[i+1]);
@ -917,7 +934,7 @@ int MODULEMAINFUNC (int argc, char** argv){
#ifndef _WIN32
pthread_attr_init(&pa);
pthread_attr_setstacksize(&pa,PTHREAD_STACK_MIN + (32768 + srv.stacksize));
pthread_attr_setstacksize(&pa,threadstacksize(srv.stacksize));
pthread_attr_setdetachstate(&pa,PTHREAD_CREATE_DETACHED);
#endif
@ -1051,9 +1068,12 @@ int MODULEMAINFUNC (int argc, char** argv){
else {
struct clientparam *toparam;
srv.udplen = sockrecvfrom(NULL, srv.srvsock, (struct sockaddr *)&defparam.sincr, srv.udpbuf, UDPBUFSIZE, 0);
if(srv.udplen <= 0) continue;
_3proxy_sem_lock(udpinit);
srv.udplen = sockrecvfrom(NULL, srv.srvsock, (struct sockaddr *)&defparam.sincr, srv.udpbuf, UDPBUFSIZE, 0);
if(srv.udplen <= 0) {
_3proxy_sem_unlock(udpinit);
continue;
}
if(hashresolv(&udp_table, &defparam, &toparam, NULL)) {
int i, len=0;
@ -1172,12 +1192,7 @@ int MODULEMAINFUNC (int argc, char** argv){
#ifndef NOUDPMAIN
int udpinited = 0;
#ifdef _WIN32
HANDLE udpinit;
#else
_3proxy_mutex_t udpinit;
#endif
_3proxy_sem_t udpinit;
#endif
void srvinit(struct srvparam * srv, struct clientparam *param){
@ -1216,11 +1231,7 @@ void srvinit(struct srvparam * srv, struct clientparam *param){
_3proxy_mutex_init(&srv->counter_mutex);
#ifndef NOUDPMAIN
if(!udpinited){
#ifdef _WIN32
udpinit = CreateSemaphore(NULL, 1, 1, NULL);
#else
_3proxy_mutex_init(&udpinit);
#endif
(void)_3proxy_sem_init(udpinit, 1, 1);
}
udpinited = 1;
#endif

View File

@ -490,6 +490,24 @@ static int verify_callback(int preverify_ok, X509_STORE_CTX *ctx){
return preverify_ok;
}
#ifdef WITH_WOLFSSL
/* wolfSSL's SSL_CTX_use_PrivateKey(EVP_PKEY*) compat is unreliable: it
* silently fails (returns 0, no error queued) for keys loaded via
* PEM_read_bio_PrivateKey. Export the key to DER and load via the
* native buffer API instead. */
static int ssl_ctx_use_pkey(SSL_CTX *ctx, EVP_PKEY *key){
unsigned char *der = NULL;
int len, rc;
len = i2d_PrivateKey(key, &der);
if(len <= 0 || !der) return 0;
rc = wolfSSL_CTX_use_PrivateKey_buffer(ctx, der, (long)len, SSL_FILETYPE_ASN1);
OPENSSL_free(der);
return rc;
}
#else
#define ssl_ctx_use_pkey(ctx, key) SSL_CTX_use_PrivateKey((ctx), (key))
#endif
SSL_CTX * ssl_cli_ctx(SSL_CONFIG *config, X509 *server_cert, EVP_PKEY *server_key, char** errSSL){
SSL_CTX *ctx;
@ -516,11 +534,18 @@ SSL_CTX * ssl_cli_ctx(SSL_CONFIG *config, X509 *server_cert, EVP_PKEY *server_ke
}
}
err = SSL_CTX_use_PrivateKey(ctx, server_key);
if ( err <= 0 ) {
*errSSL = getSSLErr();
SSL_CTX_free(ctx);
return NULL;
/* Load the private key only when a cert is already loaded into ctx.
* wolfSSL validates the key against the cert and requires the cert
* to be present first; OpenSSL tolerates either order but is happy
* with cert-first too. When server_cert is NULL (serv path), the
* caller loads the cert chain and the key itself after this call. */
if(server_cert) {
err = ssl_ctx_use_pkey(ctx, server_key);
if ( err <= 0 ) {
*errSSL = getSSLErr();
SSL_CTX_free(ctx);
return NULL;
}
}
SSL_CTX_set_session_id_context(ctx, (const unsigned char *)"3proxy", 6);
if(config->server_min_proto_version)SSL_CTX_set_min_proto_version(ctx, config->server_min_proto_version);
@ -655,6 +680,10 @@ static void* ssl_filter_open(void * idata, struct srvparam * srv){
fprintf(stderr, "failed to read server cert: %s\n", srvcert);
return sc;
}
if(ssl_ctx_use_pkey(sc->cli_ctx, sc->server_key) <= 0){
fprintf(stderr, "failed to use server key\n");
return sc;
}
sc->serv = 1;
}
if(mitm || cli || serv){
@ -678,7 +707,7 @@ static void* ssl_filter_open(void * idata, struct srvparam * srv){
}
if(sc->client_cert){
SSL_CTX_use_certificate(sc->srv_ctx, (X509 *) sc->client_cert);
SSL_CTX_use_PrivateKey(sc->srv_ctx, sc->client_key);
ssl_ctx_use_pkey(sc->srv_ctx, sc->client_key);
}
if(sc->client_min_proto_version)SSL_CTX_set_min_proto_version(sc->srv_ctx, sc->client_min_proto_version);
if(sc->client_max_proto_version)SSL_CTX_set_max_proto_version(sc->srv_ctx, sc->client_max_proto_version);
@ -705,6 +734,15 @@ static void* ssl_filter_open(void * idata, struct srvparam * srv){
SSL_CTX_set_default_verify_paths(sc->srv_ctx);
SSL_CTX_set_verify(sc->srv_ctx, SSL_VERIFY_PEER|SSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL);
}
#ifdef WITH_WOLFSSL
else {
/* wolfSSL defaults to peer verification; OpenSSL defaults to
* SSL_VERIFY_NONE. Make the no-verify intent explicit so the
* upstream handshake (ssl_cli / ssl_mitm) succeeds without a
* trusted CA store. */
SSL_CTX_set_verify(sc->srv_ctx, SSL_VERIFY_NONE, NULL);
}
#endif
}
#ifdef WITHSPLICE
srv->usesplice = 0;

View File

@ -45,8 +45,6 @@ static char hexMap[] = {
'8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
};
static BIO *bio_err=NULL;
char * getSSLErr(){
@ -74,10 +72,11 @@ static size_t bin2hex (const unsigned char* bin, size_t bin_length, char* str, s
return p - str;
}
static int add_ext(X509 *cert, int nid, char *value)
static int add_ext(X509 *cert, int nid, const char *value)
{
X509_EXTENSION *ex;
X509V3_CTX ctx;
int err;
/* This sets the 'context' of the extensions. */
/* No configuration database */
X509V3_set_ctx_nodb(&ctx);
@ -85,35 +84,24 @@ static int add_ext(X509 *cert, int nid, char *value)
* no request and no CRL
*/
X509V3_set_ctx(&ctx, cert, cert, NULL, NULL, 0);
ex = X509V3_EXT_conf_nid(NULL, &ctx, nid, value);
/* value is char * prior to OpenSSL 1.1.0 */
ex = X509V3_EXT_conf_nid(NULL, &ctx, nid, (char *)value);
if (!ex)
return 0;
X509_add_ext(cert,ex,-1);
err = X509_add_ext(cert,ex,-1);
X509_EXTENSION_free(ex);
return 1;
}
void del_ext(X509 *dst_cert, int nid, int where){
int ex;
ex = X509_get_ext_by_NID(dst_cert, nid, where);
if(ex>=0){
X509_EXTENSION *ext;
if((ext = X509_delete_ext(dst_cert, ex))) X509_EXTENSION_free(ext);
}
return err > 0;
}
SSL_CERT ssl_copy_cert(SSL_CERT cert, SSL_CONFIG *config)
{
int err = -1;
int san_idx;
BIO *fcache;
X509 *src_cert = (X509 *) cert;
X509 *dst_cert = NULL;
EVP_PKEY *pk = NULL;
unsigned char hash_sha256[32];
char hash_name_sha256[(16*2) + 1];
char cache_name[256];
@ -142,25 +130,56 @@ SSL_CERT ssl_copy_cert(SSL_CERT cert, SSL_CONFIG *config)
}
}
}
/* proceed if certificate is not cached */
dst_cert = X509_dup(src_cert);
/* Build a fresh certificate instead of duplicating the source: only
* the fields required for a usable server cert are copied (version,
* serial, subject, validity, SAN). This avoids inheriting upstream
* extensions (AKI, CRL dist points, certificate policies, ...) that
* break chain validation, and works around wolfSSL's no-op
* X509_delete_ext compat shim. */
dst_cert = X509_new();
if ( dst_cert == NULL ) {
return NULL;
}
del_ext(dst_cert, NID_crl_distribution_points, -1);
del_ext(dst_cert, NID_info_access, -1);
del_ext(dst_cert, NID_authority_key_identifier, -1);
del_ext(dst_cert, NID_certificate_policies, 0);
/* v3 is required, extensions are added below */
X509_set_version(dst_cert, 2);
if(!X509_set_serialNumber(dst_cert, X509_get_serialNumber(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_free(dst_cert);
return NULL;
}
err = X509_set_pubkey(dst_cert, config->server_key?config->server_key:config->CA_key);
if ( err == 0 ) {
X509_free(dst_cert);
return NULL;
}
err = X509_set_issuer_name(dst_cert, X509_get_subject_name(config->CA_cert));
if(!err){
#if !defined(WITH_WOLFSSL) && OPENSSL_VERSION_NUMBER < 0x10100000L
if(!X509_set_notBefore(dst_cert, X509_get_notBefore(src_cert))
|| !X509_set_notAfter(dst_cert, X509_get_notAfter(src_cert))){
#else
if(!X509_set1_notBefore(dst_cert, X509_get0_notBefore(src_cert))
|| !X509_set1_notAfter(dst_cert, X509_get0_notAfter(src_cert))){
#endif
X509_free(dst_cert);
return NULL;
}
san_idx = X509_get_ext_by_NID(src_cert, NID_subject_alt_name, -1);
if(san_idx >= 0){
X509_EXTENSION *san;
san = X509_get_ext(src_cert, san_idx);
if(san && !X509_add_ext(dst_cert, san, -1)){
X509_free(dst_cert);
return NULL;
}
}
/* Extensions required from an end entity certificate. Without EKU
* serverAuth Apple's TLS stack (and Chrome on macOS/iOS, which uses it)
* rejects the certificate. keyUsage is intentionally not set: it depends
* on the type of the key being reused for every generated certificate,
* and an absent keyUsage places no restriction.
*/
if(!add_ext(dst_cert, NID_basic_constraints, "critical,CA:FALSE")
|| !add_ext(dst_cert, NID_ext_key_usage, "serverAuth")){
X509_free(dst_cert);
return NULL;
}
@ -229,6 +248,14 @@ void _ssl_cert_free(SSL_CERT cert)
/* OpenSSL before 1.1.0 requires the application to install threading
callbacks; OpenSSL >= 1.1.0 and wolfSSL handle locking internally. */
#if !defined(WITH_WOLFSSL) && defined(OPENSSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER < 0x10100000L
#define LEGACY_SSL_THREADING 1
#else
#define LEGACY_SSL_THREADING 0
#endif
/* This array will store all of the mutexes available to OpenSSL. */
static _3proxy_mutex_t *mutex_buf= NULL;
@ -252,6 +279,7 @@ static unsigned long id_function(void)
int thread_setup(void)
{
#if LEGACY_SSL_THREADING
int i;
mutex_buf = malloc(CRYPTO_num_locks( ) * sizeof(_3proxy_mutex_t));
@ -262,10 +290,14 @@ int thread_setup(void)
CRYPTO_set_id_callback(id_function);
CRYPTO_set_locking_callback(locking_function);
return 1;
#else
return 1;
#endif
}
int thread_cleanup(void)
{
#if LEGACY_SSL_THREADING
int i;
if (!mutex_buf)
@ -277,6 +309,9 @@ int thread_cleanup(void)
free(mutex_buf);
mutex_buf = NULL;
return 1;
#else
return 1;
#endif
}
@ -291,9 +326,14 @@ void ssl_init()
ssl_init_done = 1;
thread_setup();
#ifdef WITH_WOLFSSL
wolfSSL_Init();
#elif defined(OPENSSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER >= 0x10100000L
OPENSSL_init_ssl(OPENSSL_INIT_LOAD_SSL_STRINGS, NULL);
#else
SSLeay_add_ssl_algorithms();
SSL_load_error_strings();
#endif
_3proxy_mutex_init(&ssl_file_mutex);
bio_err=BIO_new_fp(stderr,BIO_NOCLOSE);
}
}

View File

@ -42,6 +42,12 @@ extern "C" {
#define _3proxy_mutex_destroy pthread_mutex_destroy
#define _3proxy_mutex_lock pthread_mutex_lock
#define _3proxy_mutex_unlock pthread_mutex_unlock
typedef struct _3proxy_sem_s {
pthread_mutex_t mutex;
pthread_cond_t cond;
unsigned count;
unsigned maxcount;
} _3proxy_sem_t;
#else
#include <winsock2.h>
#include <ws2tcpip.h>
@ -50,6 +56,7 @@ extern "C" {
#define _3proxy_mutex_lock(x) EnterCriticalSection(x)
#define _3proxy_mutex_unlock(x) LeaveCriticalSection(x)
#define _3proxy_mutex_destroy(x) DeleteCriticalSection(x)
#define _3proxy_sem_t HANDLE
#ifdef MSVC
#pragma warning (disable : 4996)
#endif
@ -682,11 +689,7 @@ struct filemon {
struct extparam {
#ifdef _WIN32
HANDLE threadinit;
#else
_3proxy_mutex_t threadinit;
#endif
_3proxy_sem_t threadinit;
int *timeouts;
struct ace * acl;
char * conffile;

View File

@ -190,7 +190,7 @@ int clistarttls(struct clientparam *param, PROXYSERVICE proto){
return 0;
}
if(!strncasecmp((char *)buf, "EHLO ", 5)){
socksend(param, param->clisock, (unsigned char *)"250-Proxy\r\n250 STARTTLS\r\n", 24, conf.timeouts[STRING_S]);
socksend(param, param->clisock, (unsigned char *)"250-Proxy\r\n250 STARTTLS\r\n", 25, conf.timeouts[STRING_S]);
continue;
}
if(!strncasecmp((char *)buf, "HELO ", 5)){
@ -201,8 +201,10 @@ int clistarttls(struct clientparam *param, PROXYSERVICE proto){
socksend(param, param->clisock, (unsigned char *)"221 Proxy\r\n", 11, conf.timeouts[STRING_S]);
return -1;
}
socksend(param, param->clisock, (unsigned char *)"530 5.7.0 Must issue a STARTTLS command first\r\n", 45, conf.timeouts[STRING_S]);
socksend(param, param->clisock, (unsigned char *)"530 5.7.0 Must issue a STARTTLS command first\r\n", 47, conf.timeouts[STRING_S]);
}
default:
break;
}
return 1;
}