Compare commits

..

No commits in common. "master" and "1.0.2" have entirely different histories.

45 changed files with 69 additions and 509 deletions

View File

@ -8,8 +8,7 @@
- **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 1.1 (development, don't use it in production) - **Devel** branch - 3proxy 2.0-devel (don't use it)
- **Experimental** branch - 3proxy 2.0 (experimental, don't use it)
### Binaries (deb / rpm / Windows zip) ### Binaries (deb / rpm / Windows zip)

View File

@ -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>-B</code> option on the listening service: <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:
</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 -B -i127.0.0.1 -p1443 proxy -s -i127.0.0.1 -ocTCP_NODELAY -osTCP_NODELAY -p1443
</pre> </pre>
<p> <p>
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. TCP_NODELAY is required to prevent the kernel from merging the split packets.
</p> </p>
<p> <p>
<b>Configuration examples:</b> <b>Configuration examples:</b>

View File

@ -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>-B</code> на слушающем сервисе: <br>Для включения SNI break используйте <code>parent ... tls 0.0.0.0 0</code> и опцию <code>-s</code> на слушающем сервисе с TCP_NODELAY:
</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 -B -i127.0.0.1 -p1443 proxy -s -i127.0.0.1 -ocTCP_NODELAY -osTCP_NODELAY -p1443
</pre> </pre>
<p> <p>
TCP_NODELAY, установленный по умолчанию, необходим, чтобы ядро не объединяло разделенные пакеты: не отключайте его опциями -ocTCP_NODELAY=0 или -osTCP_NODELAY=0. TCP_NODELAY необходим, чтобы ядро не объединяло разделенные пакеты.
</p> </p>
<p> <p>
<b>Примеры конфигурации:</b> <b>Примеры конфигурации:</b>

View File

@ -245,14 +245,6 @@ 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>

View File

@ -230,16 +230,15 @@ a certificate (not compatible with TLS 1.3)</p></td></tr>
<td width="4%"> <td width="4%">
<p><b>-B</b></p></td> <p><b>-s</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 two TCP <p>Split the TLS Client HELLO packet across multiple TCP
segments, in the middle of the SNI, to make SNI-based DPI segments to make SNI-based DPI detection harder. An optional
detection harder. In builds without splice() <b>-s</b> does numeric value can follow (e.g. <b>-s1</b>) to control the
the same; with splice() (Linux) <b>-s</b> is splice, see splitting behaviour.</p></td></tr>
<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 |

View File

@ -198,13 +198,6 @@ 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.

View File

@ -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 -B .B -s
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). 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.
.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

View File

@ -382,21 +382,6 @@ 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[]) {
@ -439,10 +424,6 @@ 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")) {
@ -555,7 +536,16 @@ int WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int
} }
if(argc > 2 || !(fp)) { if(argc > 2 || !(fp)) {
printusage(&conf, argc, 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, "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;
} }

View File

@ -198,20 +198,12 @@ 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);
/* hash the reply, then the secret: nothing is written past the reply */ memcpy(packet + len, secret, secretlen);
if(!(ctx = mdh_init(MDH_MD5, 16))) return 2; md5_calc(calc_digest, (u_char *)packet, len + secretlen);
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.
@ -306,7 +298,6 @@ 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;
@ -577,25 +568,15 @@ int radsend(struct clientparam * param, int auth, int stop){
continue; continue;
} }
salen = sizeof(safrom); salen = sizeof(saremote);
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 *)&safrom, &salen); 0, (struct sockaddr *)&saremote, &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 ){

View File

@ -634,26 +634,6 @@ 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
@ -780,22 +760,6 @@ 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(&param->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(&param->sinsl) != AF_UNIX) if(*SAFAMILY(&param->sinsl) != AF_UNIX)
#endif #endif

View File

@ -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) {
if(innbuf)*innbuf = i; *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,9 +77,6 @@ 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;
} }

View File

@ -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=param->srv->so._socket(param->sostate, SASOCK(&param->sincl), SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET) {RETURN(821);} if ((clidatasock=socket(SASOCK(&param->sincl), SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET) {RETURN(821);}
if(intbindtodevice(param, clidatasock)){RETURN(822);} if(intbindtodevice(param, clidatasock)){RETURN(822);}
*SAPORT(&param->sincl) = 0; *SAPORT(&param->sincl) = 0;
if(bindwithrange(param, clidatasock, &param->sincl, param->intport)){RETURN(822);} if(bindwithrange(param, clidatasock, &param->sincl, param->intport)){RETURN(822);}

View File

@ -220,14 +220,6 @@ 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--;
@ -261,30 +253,16 @@ 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;
if(!rule->acl) return CONTINUE; #define pcrefd ((struct pcre_filter_data *)fo)
if(!pcrefd->acl) return CONTINUE;
memset (&tmpace, 0, sizeof(struct ace)); memset (&tmpace, 0, sizeof(struct ace));
tmpace.src = rule->acl->src; tmpace.src = pcrefd->acl->src;
res = pl->ACLMatches(&tmpace, param); res = pl->ACLMatches(&tmpace, param);
if(!res) return PASS; #undef pcrefd
/* the connection goes on with a match data of its own */ return (res)? CONTINUE:PASS;
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. */
@ -299,8 +277,7 @@ 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;
struct pcre_client_data *pcd = (struct pcre_client_data *)fc; #define pcrefd ((struct pcre_filter_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)){
@ -313,9 +290,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, pcd->match_data, pcrefd->match_context); count = pcre2_match(pcrefd->re, (PCRE2_SPTR)*buf_p, *length_p, offset, 0, pcrefd->match_data, pcrefd->match_context);
if(count <= 0) break; if(count <= 0) break;
ovector = pcre2_get_ovector_pointer(pcd->match_data); ovector = pcre2_get_ovector_pointer(pcrefd->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;
@ -399,13 +376,7 @@ static FILTER_ACTION pcre_filter_buffer(void *fc, struct clientparam *param, uns
#undef pcrefd #undef pcrefd
} }
static void pcre_filter_clear(void *fc){ static void pcre_filter_clear(void *fo){
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){

View File

@ -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(NULL, fps->fpd.cp->clisock, (unsigned char *)fp_stringtable[1], (int)strlen((char *)fp_stringtable[1]), pl->conf->timeouts[STRING_S]); 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]);
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(NULL, fps->fpd.cp->clisock, (unsigned char *)fp_stringtable[0], (int)strlen((char *)fp_stringtable[0]), pl->conf->timeouts[STRING_S]); 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]);
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(NULL, fps->fpd.cp->ctrlsock, (unsigned char *)fp_stringtable[1], (int)strlen((char *)fp_stringtable[1]), pl->conf->timeouts[STRING_S]); 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]);
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(NULL, fps->fpd.cp->clisock, (unsigned char *)fps->buf, sendchunk, pl->conf->timeouts[STRING_S]) != sendchunk){ if(pl->socksend(fps->fpd.cp->sostate, 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(NULL, sock, (unsigned char *)fps->buf, res, pl->conf->timeouts[STRING_S]) != res) { if(pl->socksend(fps->fpd.cp->sostate, 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(NULL, sock, (unsigned char *)"\r\n", 2, pl->conf->timeouts[STRING_S]) != 2) if(pl->socksend(fps->fpd.cp->sostate, sock, (unsigned char *)"\r\n", 2, pl->conf->timeouts[STRING_S]) != 2)
return -4; return -4;
} }
fps->state = state; fps->state = state;

View File

@ -61,9 +61,14 @@ char **load_string(FILE *f,int max_count_str, int *countloadstr,
if (strstr(tmpbuf1,"[end]")==NULL) if (strstr(tmpbuf1,"[end]")==NULL)
{ {
/* the line's end cut off: what the file says with \n is what is /* find and replace \n \r*/
sent */ rstr = tmpbuf1;
tmpbuf1[strcspn(tmpbuf1, "\r\n")] = '\0'; while (*rstr!='\0')
{
if (*rstr=='\r' || *rstr=='\n' )
{ *rstr='\0'; rstr++; }
rstr++;
}
while ((rstr=strstr(tmpbuf1,"\\n"))) while ((rstr=strstr(tmpbuf1,"\\n")))
{ {

View File

@ -191,8 +191,7 @@ size_t threadstacksize(int extra);
#ifndef strncasecmp #ifndef strncasecmp
#define strncasecmp strnicmp #define strncasecmp strnicmp
#endif #endif
/* errno is WSAGetLastError() here */ #define seterrno3(x) _set_errno(x)
#define seterrno3(x) WSASetLastError(x)
#else #else
#define seterrno3(x) (errno = x) #define seterrno3(x) (errno = x)
#endif #endif
@ -406,8 +405,6 @@ unsigned char * dologname (unsigned char *buf, unsigned char *name, const unsign
int readconfig(FILE * fp); int readconfig(FILE * fp);
void initcommands(void); void initcommands(void);
int connectwithpoll(struct clientparam *param, SOCKET sock, struct sockaddr *sa, SASIZETYPE size, int to); int connectwithpoll(struct clientparam *param, SOCKET sock, struct sockaddr *sa, SASIZETYPE size, int to);
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);
@ -512,7 +509,6 @@ 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);

View File

@ -63,8 +63,6 @@ void * threadfunc (void *p) {
} }
param->remsock = param->srv->so._accept(param->sostate, param->srv->cbsock, (struct sockaddr*)&param->sinsr, &size); param->remsock = param->srv->so._accept(param->sostate, param->srv->cbsock, (struct sockaddr*)&param->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;
} }
@ -226,69 +224,6 @@ 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
@ -429,7 +364,6 @@ 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"
@ -697,13 +631,6 @@ 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 */
@ -937,8 +864,6 @@ 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");

View File

@ -167,7 +167,6 @@ 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) {
@ -196,14 +195,12 @@ 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 */
} }
s1 = buf + k + 12 + flen; /* the end of this record's data, inside the answer */ for (s2 = buf + k + 12; s2 < (buf + k + 12 + len) && *s2; ){
for (s2 = buf + k + 12; s2 < s1 && *s2 && *s2 < 64; ){ s1 = s2 + ((unsigned)*s2) + 1;
unsigned char *next = s2 + ((unsigned)*s2) + 1;
*s2 = '.'; *s2 = '.';
s2 = next; s2 = s1;
} }
if(s2 >= s1 || *s2 || s2 == buf + k + 12) *s2 = 0;
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);

View File

@ -74,9 +74,6 @@ 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 {
@ -105,12 +102,10 @@ 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;
} }
} }
@ -141,14 +136,12 @@ 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 || err == SSL_ERROR_WANT_READ){ if (err == SSL_ERROR_WANT_WRITE){
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;
} }
@ -167,14 +160,12 @@ static ssize_t ssl_sendto(void *state, SOCKET s, const void *msg, size_t len, in
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 || err == SSL_ERROR_WANT_READ){ if (err == SSL_ERROR_WANT_WRITE){
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;
} }
@ -192,14 +183,12 @@ 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 || err == SSL_ERROR_WANT_WRITE) { if (err == SSL_ERROR_WANT_READ) {
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);
@ -216,14 +205,12 @@ 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 || err == SSL_ERROR_WANT_WRITE) { if (err == SSL_ERROR_WANT_READ) {
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;
} }
@ -248,8 +235,6 @@ 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;
@ -258,29 +243,8 @@ 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;
} }

View File

@ -13,14 +13,6 @@ 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

View File

@ -4,8 +4,6 @@ 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"""

View File

@ -7,9 +7,7 @@ handshake and the service supplies the port.
def run(t): def run(t):
if not t.skip_unless("httpsrv"): certs = t.certs()
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

View File

@ -1,108 +0,0 @@
"""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()

View File

@ -2,8 +2,6 @@
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"""

View File

@ -8,8 +8,6 @@ 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:

View File

@ -10,8 +10,6 @@ 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:

View File

@ -4,8 +4,6 @@ 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

View File

@ -6,8 +6,6 @@ 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

View File

@ -9,8 +9,6 @@ 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:

View File

@ -2,8 +2,6 @@
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"""

View File

@ -7,8 +7,6 @@ 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

View File

@ -8,8 +8,6 @@ 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()

View File

@ -30,8 +30,6 @@ 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()

View File

@ -46,8 +46,6 @@ 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()

View File

@ -37,8 +37,6 @@ 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()

View File

@ -20,8 +20,6 @@ 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

View File

@ -26,8 +26,6 @@ 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()

View File

@ -6,8 +6,6 @@ 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()

View File

@ -2,8 +2,6 @@
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()

View File

@ -11,8 +11,6 @@ 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)")

View File

@ -2,8 +2,6 @@
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)")

View File

@ -39,8 +39,6 @@ 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")

View File

@ -36,8 +36,6 @@ 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

View File

@ -122,11 +122,10 @@ 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, features=None): def __init__(self, binary, tmpdir, case):
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
@ -136,25 +135,6 @@ 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):

View File

@ -47,37 +47,6 @@ 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="",
@ -106,9 +75,7 @@ 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
@ -118,7 +85,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, features) tester = Tester(binary, tmpdir, name)
error = None error = None
try: try:
module.run(tester) module.run(tester)