mirror of
https://github.com/3proxy/3proxy.git
synced 2026-11-07 02:05:49 +08:00
Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
14e87dbd78 | ||
|
|
629a978c00 | ||
|
|
a75b488aaf | ||
|
|
e9714fa4df | ||
|
|
9be415b731 | ||
|
|
371e78691d | ||
|
|
a5129f85c7 | ||
|
|
33dce70177 | ||
|
|
d770c51761 | ||
|
|
aaa36f1dd1 | ||
|
|
b56e5aff2b | ||
|
|
0c80c9f526 | ||
|
|
62fd5e3a8d | ||
|
|
50bc4b4e27 | ||
|
|
b0b796823d |
@ -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)
|
||||||
|
|
||||||
|
|||||||
@ -648,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>
|
||||||
|
|||||||
@ -656,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>
|
||||||
|
|||||||
@ -245,6 +245,14 @@ 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>
|
||||||
|
|||||||
@ -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 |
|
||||||
|
|||||||
@ -198,6 +198,13 @@ 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.
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
32
src/3proxy.c
32
src/3proxy.c
@ -382,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[]) {
|
||||||
@ -424,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")) {
|
||||||
@ -536,18 +555,9 @@ 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
|
#ifndef _WIN32
|
||||||
/* before readconfig() starts the services: a client gone while it is
|
/* before readconfig() starts the services: a client gone while it is
|
||||||
|
|||||||
@ -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 ){
|
||||||
|
|||||||
36
src/common.c
36
src/common.c
@ -634,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
|
||||||
@ -760,6 +780,22 @@ int doconnect(struct clientparam * param){
|
|||||||
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));
|
||||||
#endif
|
#endif
|
||||||
if(extbindtodevice(param, param->remsock)) return 12;
|
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
|
||||||
#ifdef WITH_UN
|
#ifdef WITH_UN
|
||||||
if(*SAFAMILY(¶m->sinsl) != AF_UNIX)
|
if(*SAFAMILY(¶m->sinsl) != AF_UNIX)
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -38,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");
|
||||||
@ -77,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;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -132,7 +132,7 @@ 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);}
|
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);}
|
||||||
|
|||||||
47
src/pcre.c
47
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,16 +261,30 @@ 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. */
|
/* What a rewritten buffer keeps free for its caller to append to. */
|
||||||
@ -277,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)){
|
||||||
@ -290,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;
|
||||||
|
|
||||||
@ -376,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){
|
||||||
|
|||||||
@ -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")))
|
||||||
{
|
{
|
||||||
|
|||||||
@ -191,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
|
||||||
@ -405,6 +406,8 @@ 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 bindtodevice(struct sockfuncs *sf, void *state, SOCKET sock, const char *device, int family);
|
||||||
int extbindtodevice(struct clientparam *param, SOCKET sock);
|
int extbindtodevice(struct clientparam *param, SOCKET sock);
|
||||||
@ -509,6 +512,7 @@ extern struct socketoptions {
|
|||||||
int getopts(const char *s, struct sockoptset *opts);
|
int getopts(const char *s, struct sockoptset *opts);
|
||||||
void setopts(SOCKET s, const 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);
|
||||||
|
|||||||
@ -63,6 +63,8 @@ void * threadfunc (void *p) {
|
|||||||
}
|
}
|
||||||
param->remsock = param->srv->so._accept(param->sostate, param->srv->cbsock, (struct sockaddr*)¶m->sinsr, &size);
|
param->remsock = param->srv->so._accept(param->sostate, param->srv->cbsock, (struct sockaddr*)¶m->sinsr, &size);
|
||||||
if(param->remsock == INVALID_SOCKET) {
|
if(param->remsock == INVALID_SOCKET) {
|
||||||
|
/* another client of the service took it: not a try */
|
||||||
|
if(acceptagain()) { i++; continue; }
|
||||||
dolog(param, (unsigned char *)"Connect back accept() failed");
|
dolog(param, (unsigned char *)"Connect back accept() failed");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -224,6 +226,69 @@ char * printopts(char *sep){
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
char featsbuf[1024];
|
||||||
|
|
||||||
|
/* The features of this build, by the names the manual and the test suite
|
||||||
|
know them, so a user can see what a binary was made with. */
|
||||||
|
char * printfeatures(char *sep){
|
||||||
|
int pos=0;
|
||||||
|
|
||||||
|
#define ADDFEATURE(x) pos += sprintf(featsbuf+pos,"%s%s",pos?sep:"",x)
|
||||||
|
#ifdef WITH_SSL
|
||||||
|
ADDFEATURE("ssl");
|
||||||
|
#ifdef WITH_WOLFSSL
|
||||||
|
ADDFEATURE("wolfssl");
|
||||||
|
#else
|
||||||
|
ADDFEATURE("openssl");
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
#ifndef NORADIUS
|
||||||
|
ADDFEATURE("radius");
|
||||||
|
#endif
|
||||||
|
#ifdef WITH_PCRE
|
||||||
|
ADDFEATURE("pcre");
|
||||||
|
#endif
|
||||||
|
#ifndef NOIPV6
|
||||||
|
ADDFEATURE("ipv6");
|
||||||
|
#endif
|
||||||
|
#ifdef WITH_UN
|
||||||
|
ADDFEATURE("unix");
|
||||||
|
#endif
|
||||||
|
#ifdef WITH_HTTPSRV
|
||||||
|
ADDFEATURE("httpsrv");
|
||||||
|
#endif
|
||||||
|
#ifdef WITH_TRANSPARENT
|
||||||
|
ADDFEATURE("transparent");
|
||||||
|
#endif
|
||||||
|
#ifdef WITH_NETFILTER
|
||||||
|
ADDFEATURE("netfilter");
|
||||||
|
#endif
|
||||||
|
#ifdef WITH_PF
|
||||||
|
ADDFEATURE("pf");
|
||||||
|
#endif
|
||||||
|
#ifdef WITH_ODBC
|
||||||
|
ADDFEATURE("odbc");
|
||||||
|
#endif
|
||||||
|
#ifdef WITH_FTP
|
||||||
|
ADDFEATURE("ftppr");
|
||||||
|
#endif
|
||||||
|
#ifdef WITH_POP3P
|
||||||
|
ADDFEATURE("pop3p");
|
||||||
|
#endif
|
||||||
|
#ifdef WITH_SMTPP
|
||||||
|
ADDFEATURE("smtpp");
|
||||||
|
#endif
|
||||||
|
#ifdef WITH_IMAPP
|
||||||
|
ADDFEATURE("imapp");
|
||||||
|
#endif
|
||||||
|
#ifndef NOPLUGINS
|
||||||
|
ADDFEATURE("plugins");
|
||||||
|
#endif
|
||||||
|
#undef ADDFEATURE
|
||||||
|
return featsbuf;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* A comma separated list of options, each NAME or NAME=VALUE; a name alone
|
/* A comma separated list of options, each NAME or NAME=VALUE; a name alone
|
||||||
stands for 1, or for TCP_MAXSEG for the value of maxseg. Options are added
|
stands for 1, or for TCP_MAXSEG for the value of maxseg. Options are added
|
||||||
to what is already set, so a default is changed only by naming it, and
|
to what is already set, so a default is changed only by naming it, and
|
||||||
@ -364,6 +429,7 @@ static int srvprepare(struct srvstart *s, int argc, char **argv){
|
|||||||
" association: 1 - log, 2 - authorize, 3 - both (default), 0 (same as -U) -\n"
|
" association: 1 - log, 2 - authorize, 3 - both (default), 0 (same as -U) -\n"
|
||||||
" neither. The first destination is always authorized and logged\n"
|
" neither. The first destination is always authorized and logged\n"
|
||||||
#endif
|
#endif
|
||||||
|
" -B split the TLS ClientHello passed through tlspr at the SNI (DPI bypass)\n"
|
||||||
"-g(GRACE_TRAFF,GRACE_NUM,GRACE_DELAY) - delay GRACE_DELAY milliseconds before polling if average polling size below GRACE_TRAFF bytes and GRACE_NUM read operations in single directions are detected within 1 second to minimize polling\n"
|
"-g(GRACE_TRAFF,GRACE_NUM,GRACE_DELAY) - delay GRACE_DELAY milliseconds before polling if average polling size below GRACE_TRAFF bytes and GRACE_NUM read operations in single directions are detected within 1 second to minimize polling\n"
|
||||||
" -fFORMAT logging format (see documentation)\n"
|
" -fFORMAT logging format (see documentation)\n"
|
||||||
" -l log to stderr\n"
|
" -l log to stderr\n"
|
||||||
@ -631,6 +697,13 @@ static int srvprepare(struct srvstart *s, int argc, char **argv){
|
|||||||
case 'U':
|
case 'U':
|
||||||
srv->udpauth = *(argv[i]+2)? atoi(argv[i]+2) : 0;
|
srv->udpauth = *(argv[i]+2)? atoi(argv[i]+2) : 0;
|
||||||
break;
|
break;
|
||||||
|
case 'B':
|
||||||
|
/* the TLS ClientHello split at the SNI (tlspr), which -s is
|
||||||
|
where -s is not splice; s_option means something else
|
||||||
|
for these */
|
||||||
|
if(!s->isudp && srv->service != S_ADMIN && srv->service != S_HTTPSRV)
|
||||||
|
srv->s_option = 1 + atoi(argv[i]+2);
|
||||||
|
break;
|
||||||
case 's':
|
case 's':
|
||||||
#ifdef WITHSPLICE
|
#ifdef WITHSPLICE
|
||||||
/* admin is an httpsrv, where -s is the limited mode */
|
/* admin is an httpsrv, where -s is the limited mode */
|
||||||
@ -864,6 +937,8 @@ static int cblisten(struct srvstart *s){
|
|||||||
return -6;
|
return -6;
|
||||||
}
|
}
|
||||||
setopts(srv->cbsock, &srv->cbssockopts);
|
setopts(srv->cbsock, &srv->cbssockopts);
|
||||||
|
/* the clients of the service all wait for it: one accepts what comes */
|
||||||
|
nonblock(srv->cbsock);
|
||||||
|
|
||||||
if(srv->so._bind(srv->so.state, srv->cbsock, (struct sockaddr*)&cbsa, SASIZE(&cbsa))==-1) {
|
if(srv->so._bind(srv->so.state, srv->cbsock, (struct sockaddr*)&cbsa, SASIZE(&cbsa))==-1) {
|
||||||
dolog(defparam, (unsigned char *)"Failed to bind connect back socket");
|
dolog(defparam, (unsigned char *)"Failed to bind connect back socket");
|
||||||
|
|||||||
@ -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,12 +196,14 @@ 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);
|
||||||
|
|
||||||
|
|||||||
46
src/ssl.c
46
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -4,6 +4,8 @@ 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()
|
||||||
server = t.start("admin", f"""
|
server = t.start("admin", f"""
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
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,6 +2,8 @@
|
|||||||
|
|
||||||
|
|
||||||
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"""
|
||||||
|
|||||||
@ -8,6 +8,8 @@ import os
|
|||||||
|
|
||||||
|
|
||||||
def run(t):
|
def run(t):
|
||||||
|
if not t.skip_unless("httpsrv"):
|
||||||
|
return
|
||||||
root = os.path.join(t.tmpdir, "web")
|
root = os.path.join(t.tmpdir, "web")
|
||||||
os.makedirs(os.path.join(root, "picts", "set"), exist_ok=True)
|
os.makedirs(os.path.join(root, "picts", "set"), exist_ok=True)
|
||||||
with open(os.path.join(root, "a.html"), "w") as fp:
|
with open(os.path.join(root, "a.html"), "w") as fp:
|
||||||
|
|||||||
@ -10,6 +10,8 @@ import os
|
|||||||
|
|
||||||
|
|
||||||
def run(t):
|
def run(t):
|
||||||
|
if not t.skip_unless("httpsrv"):
|
||||||
|
return
|
||||||
root = os.path.join(t.tmpdir, "ka")
|
root = os.path.join(t.tmpdir, "ka")
|
||||||
os.makedirs(root, exist_ok=True)
|
os.makedirs(root, exist_ok=True)
|
||||||
with open(os.path.join(root, "a.html"), "w") as fp:
|
with open(os.path.join(root, "a.html"), "w") as fp:
|
||||||
|
|||||||
@ -4,6 +4,8 @@ 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
|
||||||
|
|||||||
@ -6,6 +6,8 @@ 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
|
||||||
|
|||||||
@ -9,6 +9,8 @@ import os
|
|||||||
|
|
||||||
|
|
||||||
def run(t):
|
def run(t):
|
||||||
|
if not t.skip_unless("httpsrv"):
|
||||||
|
return
|
||||||
root = os.path.join(t.tmpdir, "pp")
|
root = os.path.join(t.tmpdir, "pp")
|
||||||
os.makedirs(root, exist_ok=True)
|
os.makedirs(root, exist_ok=True)
|
||||||
with open(os.path.join(root, "a.html"), "w") as fp:
|
with open(os.path.join(root, "a.html"), "w") as fp:
|
||||||
|
|||||||
@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
|
|
||||||
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"""
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -8,6 +8,8 @@ import time
|
|||||||
|
|
||||||
|
|
||||||
def run(t):
|
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 origin runs in a process of its own, so the limit only applies to
|
||||||
# the mapper in front of it.
|
# the mapper in front of it.
|
||||||
origin = t.free_port()
|
origin = t.free_port()
|
||||||
|
|||||||
@ -30,6 +30,8 @@ def wait_served(server, needle, count, since=0, timeout=5.0):
|
|||||||
|
|
||||||
|
|
||||||
def run(t):
|
def run(t):
|
||||||
|
if not t.skip_unless("httpsrv"):
|
||||||
|
return
|
||||||
srv = t.free_port()
|
srv = t.free_port()
|
||||||
good = t.free_port()
|
good = t.free_port()
|
||||||
spare = t.free_port()
|
spare = t.free_port()
|
||||||
|
|||||||
@ -46,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()
|
||||||
|
|||||||
@ -37,6 +37,8 @@ def wait_served(server, needle, count, since=0, timeout=5.0):
|
|||||||
|
|
||||||
|
|
||||||
def run(t):
|
def run(t):
|
||||||
|
if not t.skip_unless("httpsrv"):
|
||||||
|
return
|
||||||
adm = t.free_port()
|
adm = t.free_port()
|
||||||
prx = t.free_port()
|
prx = t.free_port()
|
||||||
dummy = t.free_port()
|
dummy = t.free_port()
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -26,6 +26,8 @@ def halfclose_origin(listener):
|
|||||||
|
|
||||||
|
|
||||||
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()
|
||||||
|
|||||||
@ -6,6 +6,8 @@ from a clean list.
|
|||||||
|
|
||||||
|
|
||||||
def run(t):
|
def run(t):
|
||||||
|
if not t.skip_unless("httpsrv"):
|
||||||
|
return
|
||||||
srv = t.free_port()
|
srv = t.free_port()
|
||||||
other = t.free_port()
|
other = t.free_port()
|
||||||
prx = t.free_port()
|
prx = t.free_port()
|
||||||
|
|||||||
@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
|
|
||||||
def run(t):
|
def run(t):
|
||||||
|
if not t.skip_unless("httpsrv"):
|
||||||
|
return
|
||||||
srv = t.free_port()
|
srv = t.free_port()
|
||||||
sks = t.free_port()
|
sks = t.free_port()
|
||||||
sauth = t.free_port()
|
sauth = t.free_port()
|
||||||
|
|||||||
@ -11,6 +11,8 @@ def _no_tls(t, server):
|
|||||||
|
|
||||||
|
|
||||||
def run(t):
|
def run(t):
|
||||||
|
if not t.skip_unless("httpsrv", "ssl"):
|
||||||
|
return
|
||||||
certs = t.certs()
|
certs = t.certs()
|
||||||
if not certs:
|
if not certs:
|
||||||
t.skip("TLS (openssl is not available to generate certificates)")
|
t.skip("TLS (openssl is not available to generate certificates)")
|
||||||
|
|||||||
@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
|
|
||||||
def run(t):
|
def run(t):
|
||||||
|
if not t.skip_unless("httpsrv", "ssl"):
|
||||||
|
return
|
||||||
certs = t.certs()
|
certs = t.certs()
|
||||||
if not certs:
|
if not certs:
|
||||||
t.skip("tlspr (openssl is not available to generate certificates)")
|
t.skip("tlspr (openssl is not available to generate certificates)")
|
||||||
|
|||||||
@ -39,6 +39,8 @@ def _config(t, name, body):
|
|||||||
|
|
||||||
|
|
||||||
def run(t):
|
def run(t):
|
||||||
|
if not t.skip_unless("httpsrv"):
|
||||||
|
return
|
||||||
# --- the command and its modes ------------------------------------
|
# --- the command and its modes ------------------------------------
|
||||||
# These need no redirection, so they run wherever the feature is built.
|
# These need no redirection, so they run wherever the feature is built.
|
||||||
out = _config(t, "transparent_probe", "log\ntransparent")
|
out = _config(t, "transparent_probe", "log\ntransparent")
|
||||||
|
|||||||
@ -36,6 +36,8 @@ def unix_get(path, url, timeout=10):
|
|||||||
|
|
||||||
|
|
||||||
def run(t):
|
def run(t):
|
||||||
|
if not t.skip_unless("httpsrv", "unix"):
|
||||||
|
return
|
||||||
if not hasattr(socket, "AF_UNIX") or sys.platform.startswith("win"):
|
if not hasattr(socket, "AF_UNIX") or sys.platform.startswith("win"):
|
||||||
t.skip("Unix domain sockets (not on this platform)")
|
t.skip("Unix domain sockets (not on this platform)")
|
||||||
return
|
return
|
||||||
|
|||||||
@ -122,10 +122,11 @@ class Failure(Exception):
|
|||||||
class Tester:
|
class Tester:
|
||||||
"""The API a case runs against: start servers, make requests, assert."""
|
"""The API a case runs against: start servers, make requests, assert."""
|
||||||
|
|
||||||
def __init__(self, binary, tmpdir, case):
|
def __init__(self, binary, tmpdir, case, features=None):
|
||||||
self.binary = binary
|
self.binary = binary
|
||||||
self.tmpdir = tmpdir
|
self.tmpdir = tmpdir
|
||||||
self.case = case
|
self.case = case
|
||||||
|
self.features = features if features is not None else frozenset()
|
||||||
self.servers = []
|
self.servers = []
|
||||||
self.checks = []
|
self.checks = []
|
||||||
self.timeout = 10
|
self.timeout = 10
|
||||||
@ -135,6 +136,25 @@ class Tester:
|
|||||||
self.logs = []
|
self.logs = []
|
||||||
self.udp_servers = []
|
self.udp_servers = []
|
||||||
|
|
||||||
|
# ---- build features ----------------------------------------------
|
||||||
|
|
||||||
|
def has(self, *names):
|
||||||
|
"""Whether the binary reports every named feature."""
|
||||||
|
return all(name in self.features for name in names)
|
||||||
|
|
||||||
|
def skip_unless(self, *names):
|
||||||
|
"""Skip what needs a feature the binary was built without.
|
||||||
|
|
||||||
|
Returns False after recording the skip, so a case says
|
||||||
|
`if not t.skip_unless("ssl"): return` and keeps its checks out of
|
||||||
|
the failed count on a build that cannot pass them.
|
||||||
|
"""
|
||||||
|
missing = [name for name in names if name not in self.features]
|
||||||
|
if not missing:
|
||||||
|
return True
|
||||||
|
self.skip("built without: " + ", ".join(missing))
|
||||||
|
return False
|
||||||
|
|
||||||
# ---- servers -----------------------------------------------------
|
# ---- servers -----------------------------------------------------
|
||||||
|
|
||||||
def has_ipv6(self):
|
def has_ipv6(self):
|
||||||
|
|||||||
35
tests/run.py
35
tests/run.py
@ -47,6 +47,37 @@ def load_case(path):
|
|||||||
return name, module
|
return name, module
|
||||||
|
|
||||||
|
|
||||||
|
def built_features(binary):
|
||||||
|
"""The features the binary reports in `3proxy --help`, as a set.
|
||||||
|
|
||||||
|
A binary too old to have the list, or one that cannot be run at all,
|
||||||
|
reports nothing: every case that needs a feature then skips, and the
|
||||||
|
ones that need none still run.
|
||||||
|
"""
|
||||||
|
import subprocess
|
||||||
|
try:
|
||||||
|
done = subprocess.run([binary, "--help"], stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.STDOUT, timeout=15)
|
||||||
|
text = done.stdout.decode("utf-8", "replace")
|
||||||
|
except (OSError, subprocess.SubprocessError):
|
||||||
|
return frozenset()
|
||||||
|
features = set()
|
||||||
|
collecting = False
|
||||||
|
for line in text.splitlines():
|
||||||
|
stripped = line.strip()
|
||||||
|
if line.startswith("built with:"):
|
||||||
|
collecting = True
|
||||||
|
rest = stripped[len("built with:"):].strip()
|
||||||
|
if rest:
|
||||||
|
features.update(rest.split())
|
||||||
|
continue
|
||||||
|
if collecting:
|
||||||
|
if not line[:1].isspace() or not stripped:
|
||||||
|
break
|
||||||
|
features.update(stripped.split())
|
||||||
|
return frozenset(features)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
parser.add_argument("pattern", nargs="?", default="",
|
parser.add_argument("pattern", nargs="?", default="",
|
||||||
@ -75,7 +106,9 @@ def main():
|
|||||||
return 2
|
return 2
|
||||||
|
|
||||||
tmpdir = tempfile.mkdtemp(prefix="3proxy-tests.")
|
tmpdir = tempfile.mkdtemp(prefix="3proxy-tests.")
|
||||||
|
features = built_features(binary)
|
||||||
print(f"3proxy tests: {binary}")
|
print(f"3proxy tests: {binary}")
|
||||||
|
print(f"built with: {' '.join(sorted(features)) or '(nothing reported)'}")
|
||||||
print(f"working in: {tmpdir}\n")
|
print(f"working in: {tmpdir}\n")
|
||||||
|
|
||||||
passed = failed = skipped = 0
|
passed = failed = skipped = 0
|
||||||
@ -85,7 +118,7 @@ def main():
|
|||||||
for path in paths:
|
for path in paths:
|
||||||
name, module = load_case(path)
|
name, module = load_case(path)
|
||||||
print(f" {name}")
|
print(f" {name}")
|
||||||
tester = Tester(binary, tmpdir, name)
|
tester = Tester(binary, tmpdir, name, features)
|
||||||
error = None
|
error = None
|
||||||
try:
|
try:
|
||||||
module.run(tester)
|
module.run(tester)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user