mirror of
https://github.com/3proxy/3proxy.git
synced 2026-11-07 02:05:49 +08:00
Compare commits
No commits in common. "master" and "1.0.2" have entirely different histories.
@ -8,8 +8,7 @@
|
||||
|
||||
- **Master** (current/latest) branch - 3proxy 1.0
|
||||
- **0.9** (LTS) branch - 3proxy 0.9.9.x
|
||||
- **Devel** branch - 3proxy 1.1 (development, don't use it in production)
|
||||
- **Experimental** branch - 3proxy 2.0 (experimental, don't use it)
|
||||
- **Devel** branch - 3proxy 2.0-devel (don't use it)
|
||||
|
||||
### 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).
|
||||
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.
|
||||
<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>
|
||||
auth iponly
|
||||
allow *
|
||||
parent 1000 tls 0.0.0.0 0
|
||||
allow *
|
||||
proxy -B -i127.0.0.1 -p1443
|
||||
proxy -s -i127.0.0.1 -ocTCP_NODELAY -osTCP_NODELAY -p1443
|
||||
</pre>
|
||||
<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>
|
||||
<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).
|
||||
Клиент отправляет первую часть TLS ClientHello, tlspr разбивает его на расширении SNI и отправляет двумя TCP-пакетами,
|
||||
что позволяет обойти некоторые 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>
|
||||
auth iponly
|
||||
allow *
|
||||
parent 1000 tls 0.0.0.0 0
|
||||
allow *
|
||||
proxy -B -i127.0.0.1 -p1443
|
||||
proxy -s -i127.0.0.1 -ocTCP_NODELAY -osTCP_NODELAY -p1443
|
||||
</pre>
|
||||
<p>
|
||||
TCP_NODELAY, установленный по умолчанию, необходим, чтобы ядро не объединяло разделенные пакеты: не отключайте его опциями -ocTCP_NODELAY=0 или -osTCP_NODELAY=0.
|
||||
TCP_NODELAY необходим, чтобы ядро не объединяло разделенные пакеты.
|
||||
</p>
|
||||
<p>
|
||||
<b>Примеры конфигурации:</b>
|
||||
|
||||
@ -245,14 +245,6 @@ 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 <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>
|
||||
(for admin) secure, allow only secure operations, currently
|
||||
only traffic counters view without ability to reset. <br>
|
||||
|
||||
@ -230,16 +230,15 @@ a certificate (not compatible with TLS 1.3)</p></td></tr>
|
||||
<td width="4%">
|
||||
|
||||
|
||||
<p><b>-B</b></p></td>
|
||||
<p><b>-s</b></p></td>
|
||||
<td width="5%"></td>
|
||||
<td width="82%">
|
||||
|
||||
|
||||
<p>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() <b>-s</b> does
|
||||
the same; with splice() (Linux) <b>-s</b> is splice, see
|
||||
<b>3proxy.cfg</b>(5).</p> </td></tr>
|
||||
<p>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. <b>-s1</b>) to control the
|
||||
splitting behaviour.</p></td></tr>
|
||||
</table>
|
||||
|
||||
<p style="margin-left:9%;"><b>-Ximap | -Xpop3 |
|
||||
|
||||
@ -198,13 +198,6 @@ any protocol-specific data.
|
||||
.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
|
||||
.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
|
||||
(for admin) secure, allow only secure operations, currently only traffic counters
|
||||
view without ability to reset.
|
||||
|
||||
@ -76,8 +76,8 @@ destination_port. Port to establish outgoing connections. Required unless the Tr
|
||||
.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)
|
||||
.TP
|
||||
.B -B
|
||||
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).
|
||||
.B -s
|
||||
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
|
||||
.B -Ximap | -Xpop3 | -Xsmtp
|
||||
STARTTLS mode for the given protocol. The proxy speaks the plaintext protocol
|
||||
|
||||
30
src/3proxy.c
30
src/3proxy.c
@ -382,21 +382,6 @@ void cyclestep(void){
|
||||
#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
|
||||
int main(int argc, char * argv[]) {
|
||||
@ -439,10 +424,6 @@ int WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int
|
||||
#endif
|
||||
|
||||
conf.stringtable = strings;
|
||||
if(argc == 2 && (!strcmp((char *)argv[1], "--help") || !strcmp((char *)argv[1], "-h"))) {
|
||||
printusage(&conf, argc, argv);
|
||||
return 0;
|
||||
}
|
||||
#ifdef _WIN32
|
||||
#ifndef _WINCE
|
||||
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)) {
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@ -198,20 +198,12 @@ static int calc_replydigest(char *packet, char *original, const char *secret, in
|
||||
int secretlen;
|
||||
uint8_t calc_digest[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(packet + 4, original, AUTH_VECTOR_LEN);
|
||||
secretlen = strlen(secret);
|
||||
/* hash the reply, then the secret: nothing is written past the reply */
|
||||
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);
|
||||
memcpy(packet + len, secret, secretlen);
|
||||
md5_calc(calc_digest, (u_char *)packet, len + secretlen);
|
||||
|
||||
/*
|
||||
* 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 len;
|
||||
PROXYSOCKADDRTYPE saremote;
|
||||
PROXYSOCKADDRTYPE safrom;
|
||||
struct pollfd fds[1];
|
||||
char vector[AUTH_VECTOR_LEN];
|
||||
radius_packet_t packet, rpacket;
|
||||
@ -577,25 +568,15 @@ int radsend(struct clientparam * param, int auth, int stop){
|
||||
continue;
|
||||
}
|
||||
|
||||
salen = sizeof(safrom);
|
||||
salen = sizeof(saremote);
|
||||
|
||||
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) {
|
||||
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 &&
|
||||
rpacket.code != PW_AUTHENTICATION_REJECT ){
|
||||
|
||||
36
src/common.c
36
src/common.c
@ -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){
|
||||
struct pollfd fds[1];
|
||||
#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));
|
||||
#endif
|
||||
if(extbindtodevice(param, param->remsock)) return 12;
|
||||
#ifdef IP_BIND_ADDRESS_NO_PORT
|
||||
/* A port the kernel chooses is chosen by connect(), for the pair with
|
||||
the destination: one in use towards another destination, or closing
|
||||
from an earlier connection, does as well. bind() would take one of
|
||||
its own, and with many connections find none left, or search long
|
||||
for it. */
|
||||
if((param->operation >= 256 || (param->operation & CONNECT) || param->redirected)
|
||||
# ifdef WITH_UN
|
||||
&& *SAFAMILY(¶m->sinsl) != AF_UNIX
|
||||
# endif
|
||||
){
|
||||
int one = 1;
|
||||
|
||||
param->srv->so._setsockopt(param->sostate, param->remsock, IPPROTO_IP, IP_BIND_ADDRESS_NO_PORT, (char *)&one, sizeof(one));
|
||||
}
|
||||
#endif
|
||||
#ifdef WITH_UN
|
||||
if(*SAFAMILY(¶m->sinsl) != AF_UNIX)
|
||||
#endif
|
||||
|
||||
@ -38,7 +38,7 @@ int ftplogin(struct clientparam *param, char *nbuf, int *innbuf) {
|
||||
if(i < 3) return 706;
|
||||
buf[i] = 0;
|
||||
if(atoi(buf)/100 != 2) {
|
||||
if(innbuf)*innbuf = i;
|
||||
*innbuf = i;
|
||||
return 702;
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
/* 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) {
|
||||
return 700;
|
||||
}
|
||||
|
||||
@ -132,7 +132,7 @@ void * ftpprchild(struct clientparam* param) {
|
||||
param->srv->so._closesocket(param->sostate, clidatasock);
|
||||
clidatasock = INVALID_SOCKET;
|
||||
}
|
||||
if ((clidatasock=param->srv->so._socket(param->sostate, SASOCK(¶m->sincl), SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET) {RETURN(821);}
|
||||
if ((clidatasock=socket(SASOCK(¶m->sincl), SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET) {RETURN(821);}
|
||||
if(intbindtodevice(param, clidatasock)){RETURN(822);}
|
||||
*SAPORT(¶m->sincl) = 0;
|
||||
if(bindwithrange(param, clidatasock, ¶m->sincl, param->intport)){RETURN(822);}
|
||||
|
||||
47
src/pcre.c
47
src/pcre.c
@ -220,14 +220,6 @@ struct pcre_filter_data {
|
||||
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){
|
||||
_3proxy_mutex_lock(&pcre_mutex);
|
||||
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){
|
||||
int res;
|
||||
struct ace tmpace;
|
||||
struct pcre_filter_data *rule = (struct pcre_filter_data *)fo;
|
||||
struct pcre_client_data *pcd;
|
||||
|
||||
*fc = fo;
|
||||
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));
|
||||
tmpace.src = rule->acl->src;
|
||||
tmpace.src = pcrefd->acl->src;
|
||||
res = pl->ACLMatches(&tmpace, param);
|
||||
if(!res) return 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;
|
||||
#undef pcrefd
|
||||
return (res)? CONTINUE:PASS;
|
||||
}
|
||||
|
||||
/* 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 * tmpbuf, *target, *newbuf;
|
||||
int nreplaces=0;
|
||||
struct pcre_client_data *pcd = (struct pcre_client_data *)fc;
|
||||
#define pcrefd (pcd->pcrefd)
|
||||
#define pcrefd ((struct pcre_filter_data *)fc)
|
||||
|
||||
for(acl = pcrefd->acl; acl; acl=acl->next){
|
||||
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++){
|
||||
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;
|
||||
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(!(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
|
||||
}
|
||||
|
||||
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_clear(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_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;
|
||||
break;
|
||||
case GOT_HTTP_REQUEST:
|
||||
@ -298,7 +298,7 @@ void processcallbacks(struct fp_stream *fps, int what, char *msg, int size){
|
||||
case GOT_HTTP_SRVDATA:
|
||||
if(!fps->serversent){
|
||||
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;
|
||||
}
|
||||
break;
|
||||
@ -306,7 +306,7 @@ void processcallbacks(struct fp_stream *fps, int what, char *msg, int size){
|
||||
case GOT_FTP_REQ:
|
||||
case GOT_FTP_SRVDATA:
|
||||
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;
|
||||
break;
|
||||
default:
|
||||
@ -358,7 +358,7 @@ static int copyfdtosock(struct fp_stream * fps, DIRECTION which, long len){
|
||||
if(fps->serversent >= fps->srvhdrwritten){
|
||||
sprintf(fps->buf, "%lx\r\n", len);
|
||||
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;
|
||||
}
|
||||
}
|
||||
@ -397,13 +397,13 @@ static int copyfdtosock(struct fp_stream * fps, DIRECTION which, long len){
|
||||
#endif
|
||||
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;
|
||||
}
|
||||
len -= res;
|
||||
}
|
||||
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;
|
||||
}
|
||||
fps->state = state;
|
||||
|
||||
@ -61,9 +61,14 @@ char **load_string(FILE *f,int max_count_str, int *countloadstr,
|
||||
|
||||
if (strstr(tmpbuf1,"[end]")==NULL)
|
||||
{
|
||||
/* the line's end cut off: what the file says with \n is what is
|
||||
sent */
|
||||
tmpbuf1[strcspn(tmpbuf1, "\r\n")] = '\0';
|
||||
/* find and replace \n \r*/
|
||||
rstr = tmpbuf1;
|
||||
while (*rstr!='\0')
|
||||
{
|
||||
if (*rstr=='\r' || *rstr=='\n' )
|
||||
{ *rstr='\0'; rstr++; }
|
||||
rstr++;
|
||||
}
|
||||
|
||||
while ((rstr=strstr(tmpbuf1,"\\n")))
|
||||
{
|
||||
|
||||
@ -191,8 +191,7 @@ size_t threadstacksize(int extra);
|
||||
#ifndef strncasecmp
|
||||
#define strncasecmp strnicmp
|
||||
#endif
|
||||
/* errno is WSAGetLastError() here */
|
||||
#define seterrno3(x) WSASetLastError(x)
|
||||
#define seterrno3(x) _set_errno(x)
|
||||
#else
|
||||
#define seterrno3(x) (errno = x)
|
||||
#endif
|
||||
@ -406,8 +405,6 @@ unsigned char * dologname (unsigned char *buf, unsigned char *name, const unsign
|
||||
int readconfig(FILE * fp);
|
||||
void initcommands(void);
|
||||
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 bindtodevice(struct sockfuncs *sf, void *state, SOCKET sock, const char *device, int family);
|
||||
int extbindtodevice(struct clientparam *param, SOCKET sock);
|
||||
@ -512,7 +509,6 @@ extern struct socketoptions {
|
||||
int getopts(const char *s, struct sockoptset *opts);
|
||||
void setopts(SOCKET s, const struct sockoptset *opts);
|
||||
char * printopts(char *sep);
|
||||
char * printfeatures(char *sep);
|
||||
|
||||
#ifdef _WINCE
|
||||
char * CEToUnicode (const char *str);
|
||||
|
||||
@ -63,8 +63,6 @@ void * threadfunc (void *p) {
|
||||
}
|
||||
param->remsock = param->srv->so._accept(param->sostate, param->srv->cbsock, (struct sockaddr*)¶m->sinsr, &size);
|
||||
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");
|
||||
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
|
||||
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
|
||||
@ -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"
|
||||
" neither. The first destination is always authorized and logged\n"
|
||||
#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"
|
||||
" -fFORMAT logging format (see documentation)\n"
|
||||
" -l log to stderr\n"
|
||||
@ -697,13 +631,6 @@ static int srvprepare(struct srvstart *s, int argc, char **argv){
|
||||
case 'U':
|
||||
srv->udpauth = *(argv[i]+2)? atoi(argv[i]+2) : 0;
|
||||
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':
|
||||
#ifdef WITHSPLICE
|
||||
/* admin is an httpsrv, where -s is the limited mode */
|
||||
@ -937,8 +864,6 @@ static int cblisten(struct srvstart *s){
|
||||
return -6;
|
||||
}
|
||||
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) {
|
||||
dolog(defparam, (unsigned char *)"Failed to bind connect back socket");
|
||||
|
||||
@ -167,7 +167,6 @@ uint32_t udpresolve(int af, unsigned char * name, unsigned char * value, uint32_
|
||||
k += 4;
|
||||
if(na > 255) na = 255; /* somebody is very evil */
|
||||
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);
|
||||
if(!buf[k]) k--;
|
||||
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);
|
||||
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 < s1 && *s2 && *s2 < 64; ){
|
||||
unsigned char *next = s2 + ((unsigned)*s2) + 1;
|
||||
for (s2 = buf + k + 12; s2 < (buf + k + 12 + len) && *s2; ){
|
||||
s1 = s2 + ((unsigned)*s2) + 1;
|
||||
*s2 = '.';
|
||||
s2 = next;
|
||||
s2 = s1;
|
||||
}
|
||||
if(s2 >= s1 || *s2 || s2 == buf + k + 12)
|
||||
break; /* the name runs past its data, is compressed or empty */
|
||||
*s2 = 0;
|
||||
if(param->username)free(param->username);
|
||||
param->username = (unsigned char *)strdup ((char *)buf + k + 13);
|
||||
|
||||
|
||||
46
src/ssl.c
46
src/ssl.c
@ -74,9 +74,6 @@ static int client_mode = 0;
|
||||
struct SSLsock {
|
||||
SOCKET s;
|
||||
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 {
|
||||
@ -105,12 +102,10 @@ static void addSSL(
|
||||
if (cli_s != INVALID_SOCKET){
|
||||
SOSTATE->cli.s = cli_s;
|
||||
SOSTATE->cli.conn = cli_conn;
|
||||
SOSTATE->cli.rwant = SOSTATE->cli.wwant = 0;
|
||||
}
|
||||
if (srv_s != INVALID_SOCKET){
|
||||
SOSTATE->srv.s = srv_s;
|
||||
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;
|
||||
if((res = ssl_write(sslq->conn, (void *)msg, len)) <= 0){
|
||||
err = SSL_get_error((SSL *)((ssl_conn*)sslq->conn)->ssl, res);
|
||||
if (err == SSL_ERROR_WANT_WRITE || err == SSL_ERROR_WANT_READ){
|
||||
sslq->wwant = (err == SSL_ERROR_WANT_READ)? POLLIN : POLLOUT;
|
||||
if (err == SSL_ERROR_WANT_WRITE){
|
||||
seterrno3(EAGAIN);
|
||||
return -1;
|
||||
}
|
||||
else seterrno3(err);
|
||||
}
|
||||
else sslq->wwant = 0;
|
||||
return res;
|
||||
}
|
||||
|
||||
@ -165,16 +158,14 @@ static ssize_t ssl_sendto(void *state, SOCKET s, const void *msg, size_t len, in
|
||||
|
||||
if ((sslq = searchSSL(state, s))){
|
||||
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);
|
||||
if (err == SSL_ERROR_WANT_WRITE || err == SSL_ERROR_WANT_READ){
|
||||
sslq->wwant = (err == SSL_ERROR_WANT_READ)? POLLIN : POLLOUT;
|
||||
if (err == SSL_ERROR_WANT_WRITE){
|
||||
seterrno3(EAGAIN);
|
||||
return -1;
|
||||
}
|
||||
else seterrno3(err);
|
||||
}
|
||||
else sslq->wwant = 0;
|
||||
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;
|
||||
if((res = ssl_read(sslq->conn, (void *)msg, len)) <= 0) {
|
||||
err = SSL_get_error((SSL *)((ssl_conn*)sslq->conn)->ssl, res);
|
||||
if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) {
|
||||
sslq->rwant = (err == SSL_ERROR_WANT_WRITE)? POLLOUT : POLLIN;
|
||||
if (err == SSL_ERROR_WANT_READ) {
|
||||
seterrno3(EAGAIN);
|
||||
return -1;
|
||||
}
|
||||
else seterrno3(err);
|
||||
}
|
||||
else sslq->rwant = 0;
|
||||
return res;
|
||||
}
|
||||
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;
|
||||
if((res = ssl_read(sslq->conn, (void *)msg, len)) <= 0) {
|
||||
err = SSL_get_error((SSL *)((ssl_conn*)sslq->conn)->ssl, res);
|
||||
if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) {
|
||||
sslq->rwant = (err == SSL_ERROR_WANT_WRITE)? POLLOUT : POLLIN;
|
||||
if (err == SSL_ERROR_WANT_READ) {
|
||||
seterrno3(EAGAIN);
|
||||
return -1;
|
||||
}
|
||||
else seterrno3(err);
|
||||
}
|
||||
else sslq->rwant = 0;
|
||||
return res;
|
||||
}
|
||||
|
||||
@ -248,8 +235,6 @@ static int ssl_poll(void *state, struct pollfd *fds, nfds_t nfds, int timeout){
|
||||
struct SSLsock *sslq = NULL;
|
||||
unsigned int i;
|
||||
int ret = 0;
|
||||
short own[64], *events = own, ev;
|
||||
|
||||
for(i = 0; i < nfds; i++){
|
||||
if((fds[i].events & POLLIN) && (sslq = searchSSL(state, fds[i].fd)) && ssl_pending(sslq->conn)){
|
||||
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;
|
||||
}
|
||||
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);
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
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`
|
||||
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
|
||||
|
||||
@ -4,8 +4,6 @@ import time
|
||||
|
||||
|
||||
def run(t):
|
||||
if not t.skip_unless("httpsrv"):
|
||||
return
|
||||
adm = t.free_port()
|
||||
lim = t.free_port()
|
||||
server = t.start("admin", f"""
|
||||
|
||||
@ -7,9 +7,7 @@ handshake and the service supplies the port.
|
||||
|
||||
|
||||
def run(t):
|
||||
if not t.skip_unless("httpsrv"):
|
||||
return
|
||||
certs = t.certs() if t.has("ssl") else None
|
||||
certs = t.certs()
|
||||
plain = t.free_port()
|
||||
port = t.free_port()
|
||||
secure = t.free_port() if certs else None
|
||||
|
||||
@ -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()
|
||||
@ -2,8 +2,6 @@
|
||||
|
||||
|
||||
def run(t):
|
||||
if not t.skip_unless("httpsrv"):
|
||||
return
|
||||
srv = t.free_port()
|
||||
openport = t.free_port()
|
||||
t.start("httpsrv_auth", f"""
|
||||
|
||||
@ -8,8 +8,6 @@ import os
|
||||
|
||||
|
||||
def run(t):
|
||||
if not t.skip_unless("httpsrv"):
|
||||
return
|
||||
root = os.path.join(t.tmpdir, "web")
|
||||
os.makedirs(os.path.join(root, "picts", "set"), exist_ok=True)
|
||||
with open(os.path.join(root, "a.html"), "w") as fp:
|
||||
|
||||
@ -10,8 +10,6 @@ import os
|
||||
|
||||
|
||||
def run(t):
|
||||
if not t.skip_unless("httpsrv"):
|
||||
return
|
||||
root = os.path.join(t.tmpdir, "ka")
|
||||
os.makedirs(root, exist_ok=True)
|
||||
with open(os.path.join(root, "a.html"), "w") as fp:
|
||||
|
||||
@ -4,8 +4,6 @@ import time
|
||||
|
||||
|
||||
def run(t):
|
||||
if not t.skip_unless("httpsrv"):
|
||||
return
|
||||
srv = t.free_port()
|
||||
t.start("httpsrv_ops", f"""
|
||||
log
|
||||
|
||||
@ -6,8 +6,6 @@ most of them away before they ever reached the server.
|
||||
|
||||
|
||||
def run(t):
|
||||
if not t.skip_unless("httpsrv"):
|
||||
return
|
||||
srv = t.free_port()
|
||||
t.start("httpsrv_parsing", f"""
|
||||
log
|
||||
|
||||
@ -9,8 +9,6 @@ import os
|
||||
|
||||
|
||||
def run(t):
|
||||
if not t.skip_unless("httpsrv"):
|
||||
return
|
||||
root = os.path.join(t.tmpdir, "pp")
|
||||
os.makedirs(root, exist_ok=True)
|
||||
with open(os.path.join(root, "a.html"), "w") as fp:
|
||||
|
||||
@ -2,8 +2,6 @@
|
||||
|
||||
|
||||
def run(t):
|
||||
if not t.skip_unless("httpsrv"):
|
||||
return
|
||||
srv = t.free_port()
|
||||
srv2 = t.free_port()
|
||||
t.start("httpsrv_rules", f"""
|
||||
|
||||
@ -7,8 +7,6 @@ nscache6: nscache holds the IPv4 side and nothing else.
|
||||
|
||||
|
||||
def run(t):
|
||||
if not t.skip_unless("httpsrv", "ipv6"):
|
||||
return
|
||||
if not t.has_ipv6():
|
||||
t.skip("IPv6 (this machine has no IPv6 loopback)")
|
||||
return
|
||||
|
||||
@ -8,8 +8,6 @@ import time
|
||||
|
||||
|
||||
def run(t):
|
||||
if not t.skip_unless("httpsrv"):
|
||||
return
|
||||
# The origin runs in a process of its own, so the limit only applies to
|
||||
# the mapper in front of it.
|
||||
origin = t.free_port()
|
||||
|
||||
@ -30,8 +30,6 @@ def wait_served(server, needle, count, since=0, timeout=5.0):
|
||||
|
||||
|
||||
def run(t):
|
||||
if not t.skip_unless("httpsrv"):
|
||||
return
|
||||
srv = t.free_port()
|
||||
good = t.free_port()
|
||||
spare = t.free_port()
|
||||
|
||||
@ -46,8 +46,6 @@ UNHONOURED = (1, 99)
|
||||
|
||||
|
||||
def run(t):
|
||||
if not t.skip_unless("httpsrv"):
|
||||
return
|
||||
srv = t.free_port()
|
||||
prx = t.free_port()
|
||||
sks = t.free_port()
|
||||
|
||||
@ -37,8 +37,6 @@ def wait_served(server, needle, count, since=0, timeout=5.0):
|
||||
|
||||
|
||||
def run(t):
|
||||
if not t.skip_unless("httpsrv"):
|
||||
return
|
||||
adm = t.free_port()
|
||||
prx = t.free_port()
|
||||
dummy = t.free_port()
|
||||
|
||||
@ -20,8 +20,6 @@ def _has_pcre(t):
|
||||
|
||||
|
||||
def run(t):
|
||||
if not t.skip_unless("httpsrv", "pcre"):
|
||||
return
|
||||
if not _has_pcre(t):
|
||||
t.skip("PCRE (this build has no PCRE support)")
|
||||
return
|
||||
|
||||
@ -26,8 +26,6 @@ def halfclose_origin(listener):
|
||||
|
||||
|
||||
def run(t):
|
||||
if not t.skip_unless("httpsrv"):
|
||||
return
|
||||
# --- tcppm ---------------------------------------------------------
|
||||
origin = t.free_port()
|
||||
mapped = t.free_port()
|
||||
|
||||
@ -6,8 +6,6 @@ from a clean list.
|
||||
|
||||
|
||||
def run(t):
|
||||
if not t.skip_unless("httpsrv"):
|
||||
return
|
||||
srv = t.free_port()
|
||||
other = t.free_port()
|
||||
prx = t.free_port()
|
||||
|
||||
@ -2,8 +2,6 @@
|
||||
|
||||
|
||||
def run(t):
|
||||
if not t.skip_unless("httpsrv"):
|
||||
return
|
||||
srv = t.free_port()
|
||||
sks = t.free_port()
|
||||
sauth = t.free_port()
|
||||
|
||||
@ -11,8 +11,6 @@ def _no_tls(t, server):
|
||||
|
||||
|
||||
def run(t):
|
||||
if not t.skip_unless("httpsrv", "ssl"):
|
||||
return
|
||||
certs = t.certs()
|
||||
if not certs:
|
||||
t.skip("TLS (openssl is not available to generate certificates)")
|
||||
|
||||
@ -2,8 +2,6 @@
|
||||
|
||||
|
||||
def run(t):
|
||||
if not t.skip_unless("httpsrv", "ssl"):
|
||||
return
|
||||
certs = t.certs()
|
||||
if not certs:
|
||||
t.skip("tlspr (openssl is not available to generate certificates)")
|
||||
|
||||
@ -39,8 +39,6 @@ def _config(t, name, body):
|
||||
|
||||
|
||||
def run(t):
|
||||
if not t.skip_unless("httpsrv"):
|
||||
return
|
||||
# --- the command and its modes ------------------------------------
|
||||
# These need no redirection, so they run wherever the feature is built.
|
||||
out = _config(t, "transparent_probe", "log\ntransparent")
|
||||
|
||||
@ -36,8 +36,6 @@ def unix_get(path, url, timeout=10):
|
||||
|
||||
|
||||
def run(t):
|
||||
if not t.skip_unless("httpsrv", "unix"):
|
||||
return
|
||||
if not hasattr(socket, "AF_UNIX") or sys.platform.startswith("win"):
|
||||
t.skip("Unix domain sockets (not on this platform)")
|
||||
return
|
||||
|
||||
@ -122,11 +122,10 @@ class Failure(Exception):
|
||||
class Tester:
|
||||
"""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.tmpdir = tmpdir
|
||||
self.case = case
|
||||
self.features = features if features is not None else frozenset()
|
||||
self.servers = []
|
||||
self.checks = []
|
||||
self.timeout = 10
|
||||
@ -136,25 +135,6 @@ class Tester:
|
||||
self.logs = []
|
||||
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 -----------------------------------------------------
|
||||
|
||||
def has_ipv6(self):
|
||||
|
||||
35
tests/run.py
35
tests/run.py
@ -47,37 +47,6 @@ def load_case(path):
|
||||
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():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("pattern", nargs="?", default="",
|
||||
@ -106,9 +75,7 @@ def main():
|
||||
return 2
|
||||
|
||||
tmpdir = tempfile.mkdtemp(prefix="3proxy-tests.")
|
||||
features = built_features(binary)
|
||||
print(f"3proxy tests: {binary}")
|
||||
print(f"built with: {' '.join(sorted(features)) or '(nothing reported)'}")
|
||||
print(f"working in: {tmpdir}\n")
|
||||
|
||||
passed = failed = skipped = 0
|
||||
@ -118,7 +85,7 @@ def main():
|
||||
for path in paths:
|
||||
name, module = load_case(path)
|
||||
print(f" {name}")
|
||||
tester = Tester(binary, tmpdir, name, features)
|
||||
tester = Tester(binary, tmpdir, name)
|
||||
error = None
|
||||
try:
|
||||
module.run(tester)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user