1444 lines
50 KiB
C
1444 lines
50 KiB
C
/*
|
|
* stunnel TLS offloading and load-balancing proxy
|
|
* Copyright (C) 1998-2017 Michal Trojnara <Michal.Trojnara@stunnel.org>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License as published by the
|
|
* Free Software Foundation; either version 2 of the License, or (at your
|
|
* option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
* See the GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with this program; if not, see <http://www.gnu.org/licenses>.
|
|
*
|
|
* Linking stunnel statically or dynamically with other modules is making
|
|
* a combined work based on stunnel. Thus, the terms and conditions of
|
|
* the GNU General Public License cover the whole combination.
|
|
*
|
|
* In addition, as a special exception, the copyright holder of stunnel
|
|
* gives you permission to combine stunnel with free software programs or
|
|
* libraries that are released under the GNU LGPL and with code included
|
|
* in the standard release of OpenSSL under the OpenSSL License (or
|
|
* modified versions of such code, with unchanged license). You may copy
|
|
* and distribute such a system following the terms of the GNU GPL for
|
|
* stunnel and the licenses of the other code concerned.
|
|
*
|
|
* Note that people who make modified versions of stunnel are not obligated
|
|
* to grant this special exception for their modified versions; it is their
|
|
* choice whether to do so. The GNU General Public License gives permission
|
|
* to release a modified version without this exception; this exception
|
|
* also makes it possible to release a modified version which carries
|
|
* forward this exception.
|
|
*/
|
|
|
|
#include "common.h"
|
|
#include "prototypes.h"
|
|
|
|
#define is_prefix(a, b) (strncasecmp((a), (b), strlen(b))==0)
|
|
|
|
/* protocol-specific function prototypes */
|
|
NOEXPORT char *socks_client(CLI *, SERVICE_OPTIONS *, const PHASE);
|
|
NOEXPORT void socks5_client_method(CLI *);
|
|
NOEXPORT void socks5_client_address(CLI *);
|
|
NOEXPORT char *socks_server(CLI *, SERVICE_OPTIONS *, const PHASE);
|
|
NOEXPORT void socks4_server(CLI *);
|
|
NOEXPORT void socks5_server_method(CLI *);
|
|
NOEXPORT void socks5_server(CLI *);
|
|
NOEXPORT int validate(CLI *);
|
|
NOEXPORT char *proxy_server(CLI *, SERVICE_OPTIONS *, const PHASE);
|
|
NOEXPORT char *cifs_client(CLI *, SERVICE_OPTIONS *, const PHASE);
|
|
NOEXPORT char *cifs_server(CLI *, SERVICE_OPTIONS *, const PHASE);
|
|
NOEXPORT char *pgsql_client(CLI *, SERVICE_OPTIONS *, const PHASE);
|
|
NOEXPORT char *pgsql_server(CLI *, SERVICE_OPTIONS *, const PHASE);
|
|
NOEXPORT char *smtp_client(CLI *, SERVICE_OPTIONS *, const PHASE);
|
|
NOEXPORT void smtp_client_negotiate(CLI *);
|
|
NOEXPORT void smtp_client_plain(CLI *, const char *, const char *);
|
|
NOEXPORT void smtp_client_login(CLI *, const char *, const char *);
|
|
NOEXPORT char *smtp_server(CLI *, SERVICE_OPTIONS *, const PHASE);
|
|
NOEXPORT char *pop3_client(CLI *, SERVICE_OPTIONS *, const PHASE);
|
|
NOEXPORT char *pop3_server(CLI *, SERVICE_OPTIONS *, const PHASE);
|
|
NOEXPORT char *imap_client(CLI *, SERVICE_OPTIONS *, const PHASE);
|
|
NOEXPORT char *imap_server(CLI *, SERVICE_OPTIONS *, const PHASE);
|
|
NOEXPORT char *nntp_client(CLI *, SERVICE_OPTIONS *, const PHASE);
|
|
NOEXPORT char *connect_server(CLI *, SERVICE_OPTIONS *, const PHASE);
|
|
NOEXPORT char *connect_client(CLI *, SERVICE_OPTIONS *, const PHASE);
|
|
#ifndef OPENSSL_NO_MD4
|
|
NOEXPORT void ntlm(CLI *, SERVICE_OPTIONS *);
|
|
NOEXPORT char *ntlm1();
|
|
NOEXPORT char *ntlm3(char *, char *, char *, char *);
|
|
NOEXPORT void crypt_DES(DES_cblock, DES_cblock, DES_cblock);
|
|
#endif
|
|
NOEXPORT char *base64(int, const char *, int);
|
|
|
|
/**************************************** framework */
|
|
|
|
char *protocol(CLI *c, SERVICE_OPTIONS *opt, const PHASE phase) {
|
|
if(phase==PROTOCOL_CHECK) /* default to be overridden by protocols */
|
|
opt->option.connect_before_ssl=opt->option.client;
|
|
if(!opt->protocol) /* no protocol specified */
|
|
return NULL; /* skip further actions */
|
|
if(!strcasecmp(opt->protocol, "socks"))
|
|
return opt->option.client ?
|
|
socks_client(c, opt, phase) :
|
|
socks_server(c, opt, phase);
|
|
if(!strcasecmp(opt->protocol, "proxy"))
|
|
return opt->option.client ?
|
|
"The 'proxy' protocol is not supported in the client mode" :
|
|
proxy_server(c, opt, phase);
|
|
if(!strcasecmp(opt->protocol, "cifs"))
|
|
return opt->option.client ?
|
|
cifs_client(c, opt, phase) :
|
|
cifs_server(c, opt, phase);
|
|
if(!strcasecmp(opt->protocol, "pgsql"))
|
|
return opt->option.client ?
|
|
pgsql_client(c, opt, phase) :
|
|
pgsql_server(c, opt, phase);
|
|
if(!strcasecmp(opt->protocol, "smtp"))
|
|
return opt->option.client ?
|
|
smtp_client(c, opt, phase) :
|
|
smtp_server(c, opt, phase);
|
|
if(!strcasecmp(opt->protocol, "pop3"))
|
|
return opt->option.client ?
|
|
pop3_client(c, opt, phase) :
|
|
pop3_server(c, opt, phase);
|
|
if(!strcasecmp(opt->protocol, "imap"))
|
|
return opt->option.client ?
|
|
imap_client(c, opt, phase) :
|
|
imap_server(c, opt, phase);
|
|
if(!strcasecmp(opt->protocol, "nntp"))
|
|
return opt->option.client ?
|
|
nntp_client(c, opt, phase) :
|
|
"The 'nntp' protocol is not supported in the server mode";
|
|
if(!strcasecmp(opt->protocol, "connect"))
|
|
return opt->option.client ?
|
|
connect_client(c, opt, phase) :
|
|
connect_server(c, opt, phase);
|
|
return "Protocol not supported";
|
|
}
|
|
|
|
/**************************************** socks */
|
|
|
|
/* SOCKS over TLS (SOCKS protocol itself is also encrypted) */
|
|
|
|
typedef union {
|
|
struct {
|
|
uint8_t ver, cmd, rsv, atyp;
|
|
} req;
|
|
struct {
|
|
uint8_t ver, rep, rsv, atyp;
|
|
} resp;
|
|
struct {
|
|
uint8_t ver, code, rsv, atyp, addr[4], port[2];
|
|
} v4;
|
|
struct {
|
|
uint8_t ver, code, rsv, atyp, addr[16], port[2];
|
|
} v6;
|
|
} SOCKS5_UNION;
|
|
|
|
NOEXPORT char *socks_client(CLI *c, SERVICE_OPTIONS *opt, const PHASE phase) {
|
|
(void)opt; /* squash the unused parameter warning */
|
|
if(phase!=PROTOCOL_LATE)
|
|
return NULL;
|
|
socks5_client_method(c);
|
|
socks5_client_address(c);
|
|
return NULL;
|
|
}
|
|
|
|
NOEXPORT void socks5_client_method(CLI *c) {
|
|
const struct {
|
|
uint8_t ver, nmethods, method;
|
|
} req={5, 1, 0x00}; /* NO AUTHENTICATION REQUIRED */
|
|
struct {
|
|
uint8_t ver, method;
|
|
} resp;
|
|
|
|
s_ssl_write(c, &req, sizeof req);
|
|
|
|
s_ssl_read(c, &resp, sizeof resp);
|
|
if(resp.ver!=5) {
|
|
s_log(LOG_ERR, "Invalid SOCKS5 message version 0x%02x", resp.ver);
|
|
longjmp(c->err, 2); /* don't reset */
|
|
}
|
|
/* TODO: add USERNAME/PASSWORD authentication */
|
|
if(resp.method!=0x00) {
|
|
s_log(LOG_ERR, "No supported SOCKS5 authentication method received");
|
|
longjmp(c->err, 2); /* don't reset */
|
|
}
|
|
}
|
|
|
|
NOEXPORT void socks5_client_address(CLI *c) {
|
|
SOCKADDR_UNION addr;
|
|
SOCKS5_UNION socks;
|
|
|
|
if(original_dst(c->local_rfd.fd, &addr))
|
|
longjmp(c->err, 2); /* don't reset */
|
|
memset(&socks, 0, sizeof socks);
|
|
socks.req.ver=5; /* SOCKS5 */
|
|
socks.req.cmd=0x01; /* CONNECT */
|
|
switch(addr.sa.sa_family) {
|
|
case AF_INET:
|
|
socks.req.atyp=0x01; /* IP v4 address */
|
|
memcpy(&socks.v4.addr, &addr.in.sin_addr, 4);
|
|
memcpy(&socks.v4.port, &addr.in.sin_port, 2);
|
|
s_log(LOG_INFO, "Sending SOCKS5 IPv4 address");
|
|
s_ssl_write(c, &socks, sizeof socks.v4);
|
|
break;
|
|
#ifdef USE_IPv6
|
|
case AF_INET6:
|
|
socks.req.atyp=0x04; /* IP v6 address */
|
|
memcpy(&socks.v6.addr, &addr.in6.sin6_addr, 16);
|
|
memcpy(&socks.v6.port, &addr.in6.sin6_port, 2);
|
|
s_log(LOG_INFO, "Sending SOCKS5 IPv6 address");
|
|
s_ssl_write(c, &socks, sizeof socks.v6);
|
|
break;
|
|
#endif
|
|
default:
|
|
s_log(LOG_ERR, "Unsupported address type 0x%02x", addr.sa.sa_family);
|
|
longjmp(c->err, 2); /* don't reset */
|
|
}
|
|
|
|
s_ssl_read(c, &socks, sizeof socks.resp);
|
|
if(socks.resp.atyp==0x04) /* IP V6 address */
|
|
s_ssl_read(c, &socks.v6.addr, 16+2);
|
|
else
|
|
s_ssl_read(c, &socks.v4.addr, 4+2);
|
|
if(socks.resp.ver!=5) {
|
|
s_log(LOG_ERR, "Invalid SOCKS5 message version 0x%02x", socks.req.ver);
|
|
longjmp(c->err, 2); /* don't reset */
|
|
}
|
|
switch(socks.resp.rep) {
|
|
case 0x00:
|
|
s_log(LOG_INFO,
|
|
"SOCKS5 request succeeded");
|
|
return; /* SUCCESS */
|
|
case 0x01:
|
|
s_log(LOG_ERR,
|
|
"SOCKS5 request failed: General SOCKS server failure");
|
|
break;
|
|
case 0x02:
|
|
s_log(LOG_ERR,
|
|
"SOCKS5 request failed: Connection not allowed by ruleset");
|
|
break;
|
|
case 0x03:
|
|
s_log(LOG_ERR,
|
|
"SOCKS5 request failed: Network unreachable");
|
|
break;
|
|
case 0x04:
|
|
s_log(LOG_ERR,
|
|
"SOCKS5 request failed: Host unreachable");
|
|
break;
|
|
case 0x05:
|
|
s_log(LOG_ERR,
|
|
"SOCKS5 request failed: Connection refused");
|
|
break;
|
|
case 0x06:
|
|
s_log(LOG_ERR,
|
|
"SOCKS5 request failed: TTL expired");
|
|
break;
|
|
case 0x07:
|
|
s_log(LOG_ERR,
|
|
"SOCKS5 request failed: Command not supported");
|
|
break;
|
|
case 0x08:
|
|
s_log(LOG_ERR,
|
|
"SOCKS5 request failed: Address type not supported");
|
|
break;
|
|
default:
|
|
s_log(LOG_ERR,
|
|
"SOCKS5 request failed: Unknown error 0x%02x", socks.resp.rep);
|
|
}
|
|
longjmp(c->err, 2); /* don't reset */
|
|
}
|
|
|
|
NOEXPORT char *socks_server(CLI *c, SERVICE_OPTIONS *opt, const PHASE phase) {
|
|
uint8_t version;
|
|
|
|
switch(phase) {
|
|
case PROTOCOL_CHECK:
|
|
opt->option.protocol_endpoint=1;
|
|
break;
|
|
case PROTOCOL_MIDDLE:
|
|
s_log(LOG_DEBUG, "Waiting for the SOCKS request");
|
|
s_ssl_read(c, &version, sizeof version);
|
|
switch(version) {
|
|
case 4:
|
|
socks4_server(c);
|
|
break;
|
|
case 5:
|
|
socks5_server_method(c);
|
|
socks5_server(c);
|
|
break;
|
|
default:
|
|
s_log(LOG_ERR, "Unsupported SOCKS version 0x%02x", version);
|
|
longjmp(c->err, 1);
|
|
}
|
|
break;
|
|
case PROTOCOL_LATE:
|
|
/* TODO: send the SOCKS reply *after* the target is connected */
|
|
/* FIXME: the SOCKS replies do not report CONNECT failures */
|
|
/* FIXME: the SOCKS replies do not contain the bound IP address */
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/* SOCKS4 or SOCKS4a */
|
|
/* BIND command is not supported */
|
|
/* USERID parameter is ignored */
|
|
|
|
NOEXPORT void socks4_server(CLI *c) {
|
|
struct {
|
|
uint8_t vn, cd;
|
|
u_short sin_port;
|
|
struct in_addr sin_addr;
|
|
} socks;
|
|
char *user_name, *host_name, *port_name;
|
|
SOCKADDR_UNION addr;
|
|
int close_connection=1;
|
|
|
|
s_ssl_read(c, &socks.cd, sizeof socks-sizeof socks.vn);
|
|
socks.vn=0; /* response version 0 */
|
|
user_name=ssl_getstring(c); /* ignore the username */
|
|
str_free(user_name);
|
|
|
|
if(socks.cd==0x01) { /* CONNECT */
|
|
if(ntohl(socks.sin_addr.s_addr)>0 &&
|
|
ntohl(socks.sin_addr.s_addr)<256) { /* 0.0.0.x */
|
|
host_name=ssl_getstring(c);
|
|
port_name=str_printf("%u", ntohs(socks.sin_port));
|
|
hostport2addrlist(&c->connect_addr, host_name, port_name);
|
|
str_free(port_name);
|
|
if(c->connect_addr.num) {
|
|
s_log(LOG_INFO, "SOCKS4a resolved \"%s\" to %u host(s)",
|
|
host_name, c->connect_addr.num);
|
|
if(validate(c)) {
|
|
socks.cd=90; /* access granted */
|
|
close_connection=0;
|
|
} else {
|
|
socks.cd=91; /* rejected */
|
|
}
|
|
} else {
|
|
s_log(LOG_ERR, "SOCKS4a failed to resolve \"%s\"", host_name);
|
|
socks.cd=91; /* failed */
|
|
}
|
|
str_free(host_name);
|
|
} else {
|
|
c->connect_addr.num=1;
|
|
c->connect_addr.addr=str_alloc(sizeof(SOCKADDR_UNION));
|
|
c->connect_addr.addr[0].in.sin_family=AF_INET;
|
|
c->connect_addr.addr[0].in.sin_port=socks.sin_port;
|
|
c->connect_addr.addr[0].in.sin_addr.s_addr=socks.sin_addr.s_addr;
|
|
s_log(LOG_INFO, "SOCKS4 address received");
|
|
if(validate(c)) {
|
|
socks.cd=90; /* access granted */
|
|
close_connection=0;
|
|
} else {
|
|
socks.cd=91; /* rejected */
|
|
}
|
|
}
|
|
} else if(socks.cd==0xf0) { /* RESOLVE (a TOR extension) */
|
|
host_name=ssl_getstring(c);
|
|
if(hostport2addr(&addr, host_name, "0", 0) && addr.sa.sa_family==AF_INET) {
|
|
memcpy(&socks.sin_addr, &addr.in.sin_addr, 4);
|
|
s_log(LOG_INFO, "SOCKS4a/TOR resolved \"%s\"", host_name);
|
|
socks.cd=90; /* access granted */
|
|
} else {
|
|
s_log(LOG_ERR, "SOCKS4a/TOR failed to resolve \"%s\"", host_name);
|
|
socks.cd=91; /* failed */
|
|
}
|
|
str_free(host_name);
|
|
} else {
|
|
s_log(LOG_ERR, "Unsupported SOCKS4/SOCKS4a command 0x%02x", socks.cd);
|
|
socks.cd=91; /* failed */
|
|
}
|
|
s_ssl_write(c, &socks, sizeof socks);
|
|
if(close_connection)
|
|
longjmp(c->err, 2); /* don't reset */
|
|
}
|
|
|
|
NOEXPORT void socks5_server_method(CLI *c) {
|
|
uint8_t nmethods, *methods;
|
|
struct {
|
|
uint8_t ver, method;
|
|
} response;
|
|
int i;
|
|
|
|
response.ver=0x05;
|
|
response.method=0xff; /* NO ACCEPTABLE METHODS */
|
|
s_ssl_read(c, &nmethods, sizeof nmethods);
|
|
methods=str_alloc(nmethods);
|
|
s_ssl_read(c, methods, nmethods);
|
|
for(i=0; i<nmethods; ++i)
|
|
if(methods[i]==0x00) { /* NO AUTHENTICATION REQUIRED */
|
|
response.method=0x00; /* use this method */
|
|
break;
|
|
}
|
|
str_free(methods);
|
|
s_ssl_write(c, &response, sizeof response);
|
|
if(response.method) { /* request failed */
|
|
s_log(LOG_ERR, "No supported SOCKS5 authentication method received");
|
|
longjmp(c->err, 2); /* don't reset */
|
|
}
|
|
}
|
|
|
|
/* CONNECT does not return valid BND.ADDR and BND.PORT values */
|
|
|
|
NOEXPORT void socks5_server(CLI *c) {
|
|
SOCKS5_UNION socks;
|
|
uint8_t host_len;
|
|
char *host_name, *port_name;
|
|
u_short port_number;
|
|
SOCKADDR_UNION addr;
|
|
int close_connection=1;
|
|
|
|
/* parse request */
|
|
memset(&socks, 0, sizeof socks);
|
|
s_ssl_read(c, &socks, sizeof socks.req);
|
|
if(socks.req.ver!=0x05) {
|
|
s_log(LOG_ERR, "Invalid SOCKS5 message version 0x%02x", socks.req.ver);
|
|
socks.resp.ver=0x05; /* response version 5 */
|
|
socks.resp.rep=0x01; /* general SOCKS server failure */
|
|
} else if(socks.req.cmd==0x01) { /* CONNECT */
|
|
if(socks.req.atyp==0x01) { /* IP v4 address */
|
|
c->connect_addr.num=1;
|
|
c->connect_addr.addr=str_alloc(sizeof(SOCKADDR_UNION));
|
|
c->connect_addr.addr[0].in.sin_family=AF_INET;
|
|
s_ssl_read(c, &socks.v4.addr, 4+2);
|
|
memcpy(&c->connect_addr.addr[0].in.sin_addr, &socks.v4.addr, 4);
|
|
memcpy(&c->connect_addr.addr[0].in.sin_port, &socks.v4.port, 2);
|
|
s_log(LOG_INFO, "SOCKS5 IPv4 address received");
|
|
if(validate(c)) {
|
|
socks.resp.rep=0x00; /* succeeded */
|
|
close_connection=0;
|
|
} else {
|
|
socks.resp.rep=0x02; /* connection not allowed by ruleset */
|
|
}
|
|
} else if(socks.req.atyp==0x03) { /* DOMAINNAME */
|
|
s_ssl_read(c, &host_len, sizeof host_len);
|
|
host_name=str_alloc((size_t)host_len+1);
|
|
s_ssl_read(c, host_name, host_len);
|
|
host_name[host_len]='\0';
|
|
s_ssl_read(c, &port_number, 2);
|
|
port_name=str_printf("%u", ntohs(port_number));
|
|
hostport2addrlist(&c->connect_addr, host_name, port_name);
|
|
str_free(port_name);
|
|
if(c->connect_addr.num) {
|
|
s_log(LOG_INFO, "SOCKS5 resolved \"%s\" to %u host(s)",
|
|
host_name, c->connect_addr.num);
|
|
if(validate(c)) {
|
|
socks.resp.rep=0x00; /* succeeded */
|
|
close_connection=0;
|
|
} else {
|
|
socks.resp.rep=0x02; /* connection not allowed by ruleset */
|
|
}
|
|
} else {
|
|
s_log(LOG_ERR, "SOCKS5 failed to resolve \"%s\"", host_name);
|
|
socks.resp.rep=0x04; /* Host unreachable */
|
|
}
|
|
str_free(host_name);
|
|
#ifdef USE_IPv6
|
|
} else if(socks.req.atyp==0x04) { /* IP v6 address */
|
|
c->connect_addr.num=1;
|
|
c->connect_addr.addr=str_alloc(sizeof(SOCKADDR_UNION));
|
|
c->connect_addr.addr[0].in6.sin6_family=AF_INET6;
|
|
s_ssl_read(c, &socks.v6.addr, 16+2);
|
|
memcpy(&c->connect_addr.addr[0].in6.sin6_addr, &socks.v6.addr, 16);
|
|
memcpy(&c->connect_addr.addr[0].in6.sin6_port, &socks.v6.port, 2);
|
|
s_log(LOG_INFO, "SOCKS5 IPv6 address received");
|
|
if(validate(c)) {
|
|
socks.resp.rep=0x00; /* succeeded */
|
|
close_connection=0;
|
|
} else {
|
|
socks.resp.rep=0x02; /* connection not allowed by ruleset */
|
|
}
|
|
#endif
|
|
} else {
|
|
s_log(LOG_ERR,
|
|
"Unsupported SOCKS5 address type 0x%02x", socks.req.atyp);
|
|
socks.resp.rep=0x07; /* Address type not supported */
|
|
}
|
|
} else if(socks.req.cmd==0xf0) { /* RESOLVE (a TOR extension) */
|
|
s_ssl_read(c, &host_len, sizeof host_len);
|
|
host_name=str_alloc((size_t)host_len+1);
|
|
s_ssl_read(c, host_name, host_len);
|
|
host_name[host_len]='\0';
|
|
s_ssl_read(c, &port_number, 2);
|
|
port_name=str_printf("%u", ntohs(port_number));
|
|
if(hostport2addr(&addr, host_name, port_name, 0)) {
|
|
if(addr.sa.sa_family==AF_INET) {
|
|
s_log(LOG_INFO, "SOCKS5/TOR resolved \"%s\" to IPv4", host_name);
|
|
memcpy(&socks.v4.addr, &addr.in.sin_addr, 4);
|
|
socks.resp.atyp=0x01; /* IP v4 address */
|
|
socks.resp.rep=0x00; /* succeeded */
|
|
#ifdef USE_IPv6
|
|
} else if(addr.sa.sa_family==AF_INET6) {
|
|
s_log(LOG_INFO, "SOCKS5/TOR resolved \"%s\" to IPv6", host_name);
|
|
memcpy(&socks.v6.addr, &addr.in6.sin6_addr, 16);
|
|
socks.resp.atyp=0x04; /* IP v6 address */
|
|
socks.resp.rep=0x00; /* succeeded */
|
|
#endif
|
|
} else {
|
|
s_log(LOG_ERR, "SOCKS5/TOR unsupported address type for \"%s\"",
|
|
host_name);
|
|
socks.resp.rep=0x04; /* Host unreachable */
|
|
}
|
|
} else {
|
|
s_log(LOG_ERR, "SOCKS5/TOR failed to resolve \"%s\"", host_name);
|
|
socks.resp.rep=0x04; /* Host unreachable */
|
|
}
|
|
str_free(host_name);
|
|
str_free(port_name);
|
|
} else {
|
|
s_log(LOG_ERR, "Unsupported SOCKS5 command 0x%02x", socks.req.cmd);
|
|
socks.resp.rep=0x07; /* Command not supported */
|
|
}
|
|
|
|
/* send response */
|
|
/* broken clients tend to expect the same address family for response,
|
|
* so stunnel tries to preserve the address family if possible */
|
|
if(socks.resp.atyp==0x04) { /* IP V6 address */
|
|
s_ssl_write(c, &socks, sizeof socks.v6);
|
|
} else {
|
|
socks.resp.atyp=0x01; /* IP v4 address */
|
|
s_ssl_write(c, &socks, sizeof socks.v4);
|
|
}
|
|
if(close_connection) /* request failed */
|
|
longjmp(c->err, 2); /* don't reset */
|
|
}
|
|
|
|
/* validate the allocated address */
|
|
NOEXPORT int validate(CLI *c) {
|
|
#ifdef USE_IPv6
|
|
const unsigned char ipv6_loopback[16]={0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1};
|
|
#endif
|
|
unsigned i;
|
|
|
|
for(i=0; i<c->connect_addr.num; ++i) {
|
|
SOCKADDR_UNION *addr=&c->connect_addr.addr[i];
|
|
#ifdef USE_IPv6
|
|
if(addr->sa.sa_family==AF_INET6) {
|
|
if(!memcmp(&addr->in6.sin6_addr, ipv6_loopback, 16)) {
|
|
s_log(LOG_ERR,
|
|
"SOCKS connection to the IPv6 loopback rejected");
|
|
return 0;
|
|
}
|
|
/* TODO: implement more checks */
|
|
} else
|
|
#endif
|
|
if(addr->sa.sa_family==AF_INET) {
|
|
if((ntohl(addr->in.sin_addr.s_addr)&0xff000000)==0x7f000000) {
|
|
s_log(LOG_ERR,
|
|
"SOCKS connection to the IPv4 loopback rejected");
|
|
return 0;
|
|
}
|
|
/* TODO: implement more checks */
|
|
} else {
|
|
s_log(LOG_ERR, "Unsupported address type 0x%02x",
|
|
addr->sa.sa_family);
|
|
return 0;
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/**************************************** proxy */
|
|
|
|
/*
|
|
* PROXY protocol: http://haproxy.1wt.eu/download/1.5/doc/proxy-protocol.txt
|
|
* this is a protocol client support for stunnel acting as an TLS server
|
|
* I don't think anything else is useful, but feel free to discuss on the
|
|
* stunnel-users mailing list if you disagree
|
|
*/
|
|
|
|
/* IP address textual representation length */
|
|
/* 1234:6789:1234:6789:1234:6789:1234:6789 -> 40 chars with '\0' */
|
|
#define IP_LEN 40
|
|
#define PORT_LEN 6
|
|
|
|
NOEXPORT char *proxy_server(CLI *c, SERVICE_OPTIONS *opt, const PHASE phase) {
|
|
SOCKADDR_UNION addr;
|
|
socklen_t addrlen;
|
|
char src_host[IP_LEN], dst_host[IP_LEN];
|
|
char src_port[PORT_LEN], dst_port[PORT_LEN], *proto;
|
|
int err;
|
|
|
|
(void)opt; /* squash the unused parameter warning */
|
|
if(phase!=PROTOCOL_LATE)
|
|
return NULL;
|
|
addrlen=sizeof addr;
|
|
if(getpeername(c->local_rfd.fd, &addr.sa, &addrlen)) {
|
|
sockerror("getpeername");
|
|
longjmp(c->err, 1);
|
|
}
|
|
err=getnameinfo(&addr.sa, addr_len(&addr), src_host, IP_LEN,
|
|
src_port, PORT_LEN, NI_NUMERICHOST|NI_NUMERICSERV);
|
|
if(err) {
|
|
s_log(LOG_ERR, "getnameinfo: %s", s_gai_strerror(err));
|
|
longjmp(c->err, 1);
|
|
}
|
|
|
|
addrlen=sizeof addr;
|
|
if(getsockname(c->local_rfd.fd, &addr.sa, &addrlen)) {
|
|
sockerror("getsockname");
|
|
longjmp(c->err, 1);
|
|
}
|
|
err=getnameinfo(&addr.sa, addr_len(&addr), dst_host, IP_LEN,
|
|
dst_port, PORT_LEN, NI_NUMERICHOST|NI_NUMERICSERV);
|
|
if(err) {
|
|
s_log(LOG_ERR, "getnameinfo: %s", s_gai_strerror(err));
|
|
longjmp(c->err, 1);
|
|
}
|
|
|
|
switch(addr.sa.sa_family) {
|
|
case AF_INET:
|
|
proto="TCP4";
|
|
break;
|
|
#ifdef USE_IPv6
|
|
case AF_INET6:
|
|
proto="TCP6";
|
|
break;
|
|
#endif
|
|
default: /* AF_UNIX */
|
|
proto="UNKNOWN";
|
|
}
|
|
fd_printf(c, c->remote_fd.fd, "PROXY %s %s %s %s %s",
|
|
proto, src_host, dst_host, src_port, dst_port);
|
|
return NULL;
|
|
}
|
|
|
|
/**************************************** cifs */
|
|
|
|
NOEXPORT char *cifs_client(CLI *c, SERVICE_OPTIONS *opt, const PHASE phase) {
|
|
uint8_t buffer[5];
|
|
uint8_t request_dummy[4] = {0x81, 0, 0, 0}; /* a zero-length request */
|
|
|
|
(void)opt; /* squash the unused parameter warning */
|
|
if(phase!=PROTOCOL_MIDDLE)
|
|
return NULL;
|
|
s_write(c, c->remote_fd.fd, request_dummy, 4);
|
|
s_read(c, c->remote_fd.fd, buffer, 5);
|
|
if(buffer[0]!=0x83) { /* NB_SSN_NEGRESP */
|
|
s_log(LOG_ERR, "Negative response expected");
|
|
longjmp(c->err, 1);
|
|
}
|
|
if(buffer[2]!=0 || buffer[3]!=1) { /* length != 1 */
|
|
s_log(LOG_ERR, "Unexpected NetBIOS response size");
|
|
longjmp(c->err, 1);
|
|
}
|
|
if(buffer[4]!=0x8e) { /* use TLS */
|
|
s_log(LOG_ERR, "Remote server does not require TLS");
|
|
longjmp(c->err, 1);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
NOEXPORT char *cifs_server(CLI *c, SERVICE_OPTIONS *opt, const PHASE phase) {
|
|
uint8_t buffer[128];
|
|
uint8_t response_access_denied[5] = {0x83, 0, 0, 1, 0x81};
|
|
uint8_t response_use_ssl[5] = {0x83, 0, 0, 1, 0x8e};
|
|
uint16_t len;
|
|
|
|
(void)opt; /* squash the unused parameter warning */
|
|
if(phase!=PROTOCOL_EARLY)
|
|
return NULL;
|
|
s_read(c, c->local_rfd.fd, buffer, 4) ;/* NetBIOS header */
|
|
len=(uint16_t)(((uint16_t)(buffer[2])<<8)|buffer[3]);
|
|
if(len>sizeof buffer-4) {
|
|
s_log(LOG_ERR, "Received block too long");
|
|
longjmp(c->err, 1);
|
|
}
|
|
s_read(c, c->local_rfd.fd, buffer+4, len);
|
|
if(buffer[0]!=0x81) { /* NB_SSN_REQUEST */
|
|
s_log(LOG_ERR, "Client did not send session setup");
|
|
s_write(c, c->local_wfd.fd, response_access_denied, 5);
|
|
longjmp(c->err, 1);
|
|
}
|
|
s_write(c, c->local_wfd.fd, response_use_ssl, 5);
|
|
return NULL;
|
|
}
|
|
|
|
/**************************************** pgsql */
|
|
|
|
/* http://www.postgresql.org/docs/8.3/static/protocol-flow.html#AEN73982 */
|
|
static const uint8_t ssl_request[8]={0, 0, 0, 8, 0x04, 0xd2, 0x16, 0x2f};
|
|
|
|
NOEXPORT char *pgsql_client(CLI *c, SERVICE_OPTIONS *opt, const PHASE phase) {
|
|
uint8_t buffer[1];
|
|
|
|
(void)opt; /* squash the unused parameter warning */
|
|
if(phase!=PROTOCOL_MIDDLE)
|
|
return NULL;
|
|
s_write(c, c->remote_fd.fd, ssl_request, sizeof ssl_request);
|
|
s_read(c, c->remote_fd.fd, buffer, 1);
|
|
/* S - accepted, N - rejected, non-TLS preferred */
|
|
if(buffer[0]!='S') {
|
|
s_log(LOG_ERR, "PostgreSQL server rejected TLS");
|
|
longjmp(c->err, 1);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
NOEXPORT char *pgsql_server(CLI *c, SERVICE_OPTIONS *opt, const PHASE phase) {
|
|
uint8_t buffer[8], ssl_ok[1]={'S'};
|
|
|
|
(void)opt; /* squash the unused parameter warning */
|
|
if(phase!=PROTOCOL_EARLY)
|
|
return NULL;
|
|
memset(buffer, 0, sizeof buffer);
|
|
s_read(c, c->local_rfd.fd, buffer, sizeof buffer);
|
|
if(safe_memcmp(buffer, ssl_request, sizeof ssl_request)) {
|
|
s_log(LOG_ERR, "PostgreSQL client did not request TLS, rejecting");
|
|
/* no way to send error on startup, so just drop the client */
|
|
longjmp(c->err, 1);
|
|
}
|
|
s_write(c, c->local_wfd.fd, ssl_ok, sizeof ssl_ok);
|
|
return NULL;
|
|
}
|
|
|
|
/**************************************** smtp */
|
|
|
|
NOEXPORT char *smtp_client(CLI *c, SERVICE_OPTIONS *opt, const PHASE phase) {
|
|
(void)opt; /* squash the unused parameter warning */
|
|
switch(phase) {
|
|
case PROTOCOL_MIDDLE:
|
|
smtp_client_negotiate(c);
|
|
break;
|
|
case PROTOCOL_LATE:
|
|
if(opt->protocol_username && opt->protocol_password) {
|
|
if(!strcasecmp(c->opt->protocol_authentication, "LOGIN"))
|
|
smtp_client_login(c,
|
|
opt->protocol_username, opt->protocol_password);
|
|
else /* use PLAIN by default */
|
|
smtp_client_plain(c,
|
|
opt->protocol_username, opt->protocol_password);
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
NOEXPORT void smtp_client_negotiate(CLI *c) {
|
|
char *line;
|
|
|
|
line=str_dup("");
|
|
do { /* copy multiline greeting */
|
|
str_free(line);
|
|
line=fd_getline(c, c->remote_fd.fd);
|
|
fd_putline(c, c->local_wfd.fd, line);
|
|
} while(is_prefix(line, "220-"));
|
|
|
|
fd_putline(c, c->remote_fd.fd, "EHLO localhost");
|
|
do { /* skip multiline reply */
|
|
str_free(line);
|
|
line=fd_getline(c, c->remote_fd.fd);
|
|
} while(is_prefix(line, "250-"));
|
|
if(!is_prefix(line, "250 ")) { /* error */
|
|
s_log(LOG_ERR, "Remote server is not RFC 1425 compliant");
|
|
str_free(line);
|
|
longjmp(c->err, 1);
|
|
}
|
|
|
|
fd_putline(c, c->remote_fd.fd, "STARTTLS");
|
|
do { /* skip multiline reply */
|
|
str_free(line);
|
|
line=fd_getline(c, c->remote_fd.fd);
|
|
} while(is_prefix(line, "220-"));
|
|
if(!is_prefix(line, "220 ")) { /* error */
|
|
s_log(LOG_ERR, "Remote server is not RFC 2487 compliant");
|
|
str_free(line);
|
|
longjmp(c->err, 1);
|
|
}
|
|
str_free(line);
|
|
}
|
|
|
|
/* http://www.samlogic.net/articles/smtp-commands-reference-auth.htm */
|
|
|
|
NOEXPORT void smtp_client_plain(CLI *c, const char *user, const char *pass) {
|
|
char *line, *encoded;
|
|
|
|
line=str_printf("%c%s%c%s", '\0', user, '\0', pass);
|
|
encoded=base64(1, line, (int)strlen(user) + (int)strlen(pass) + 2);
|
|
if(!encoded) {
|
|
s_log(LOG_ERR, "Base64 encoder failed");
|
|
str_free(line);
|
|
longjmp(c->err, 1);
|
|
}
|
|
str_free(line);
|
|
line=str_printf("AUTH PLAIN %s", encoded);
|
|
str_free(encoded);
|
|
ssl_putline(c, line);
|
|
str_free(line);
|
|
|
|
line=ssl_getline(c);
|
|
if(!is_prefix(line, "235 ")) { /* not 'Authentication successful' */
|
|
s_log(LOG_ERR, "PLAIN Authentication Failed");
|
|
str_free(line);
|
|
longjmp(c->err, 1);
|
|
}
|
|
str_free(line);
|
|
}
|
|
|
|
NOEXPORT void smtp_client_login(CLI *c, const char *user, const char *pass) {
|
|
char *line, *encoded;
|
|
|
|
ssl_putline(c, "AUTH LOGIN");
|
|
line=ssl_getline(c);
|
|
if(!is_prefix(line, "334 ")) { /* not the username challenge */
|
|
s_log(LOG_ERR, "Remote server does not support LOGIN authentication");
|
|
str_free(line);
|
|
longjmp(c->err, 1);
|
|
}
|
|
str_free(line);
|
|
|
|
encoded=base64(1, user, (int)strlen(user));
|
|
if(!encoded) {
|
|
s_log(LOG_ERR, "Base64 encoder failed");
|
|
longjmp(c->err, 1);
|
|
}
|
|
ssl_putline(c, encoded);
|
|
str_free(encoded);
|
|
line=ssl_getline(c);
|
|
if(!is_prefix(line, "334 ")) { /* not the password challenge */
|
|
s_log(LOG_ERR, "LOGIN authentication failed");
|
|
str_free(line);
|
|
longjmp(c->err, 1);
|
|
}
|
|
str_free(line);
|
|
|
|
encoded=base64(1, pass, (int)strlen(pass));
|
|
if(!encoded) {
|
|
s_log(LOG_ERR, "Base64 encoder failed");
|
|
longjmp(c->err, 1);
|
|
}
|
|
ssl_putline(c, encoded);
|
|
str_free(encoded);
|
|
line=ssl_getline(c);
|
|
if(!is_prefix(line, "235 ")) { /* not 'Authentication successful' */
|
|
s_log(LOG_ERR, "LOGIN authentication failed");
|
|
str_free(line);
|
|
longjmp(c->err, 1);
|
|
}
|
|
str_free(line);
|
|
}
|
|
|
|
NOEXPORT char *smtp_server(CLI *c, SERVICE_OPTIONS *opt, const PHASE phase) {
|
|
char *line, *domain, *greeting;
|
|
|
|
if(phase==PROTOCOL_CHECK)
|
|
opt->option.connect_before_ssl=1; /* c->remote_fd needed */
|
|
if(phase!=PROTOCOL_MIDDLE)
|
|
return NULL;
|
|
|
|
/* detect RFC 2487 */
|
|
s_poll_init(c->fds);
|
|
s_poll_add(c->fds, c->local_rfd.fd, 1, 0);
|
|
switch(s_poll_wait(c->fds, 0, 200)) { /* wait up to 200ms */
|
|
case 0: /* fd not ready to read */
|
|
s_log(LOG_DEBUG, "RFC 2487 detected");
|
|
break;
|
|
case 1: /* fd ready to read */
|
|
s_log(LOG_DEBUG, "RFC 2487 not detected");
|
|
return NULL; /* return if RFC 2487 is not used */
|
|
default: /* -1 */
|
|
sockerror("RFC2487 (s_poll_wait)");
|
|
longjmp(c->err, 1);
|
|
}
|
|
|
|
/* process server's greeting */
|
|
line=fd_getline(c, c->remote_fd.fd);
|
|
if(!(is_prefix(line, "220 ") || is_prefix(line, "220-"))) {
|
|
s_log(LOG_ERR, "Unknown server welcome");
|
|
str_free(line);
|
|
longjmp(c->err, 1);
|
|
}
|
|
domain=str_dup(line+4); /* skip "220" and the separator */
|
|
line[4]='\0'; /* only leave "220" and the separator */
|
|
greeting=strchr(domain, ' ');
|
|
if(greeting) {
|
|
*greeting++='\0'; /* truncate anything after the domain name */
|
|
fd_printf(c, c->local_wfd.fd, "%s%s stunnel for %s",
|
|
line, domain, greeting);
|
|
} else {
|
|
fd_printf(c, c->local_wfd.fd, "%s%s stunnel", line, domain);
|
|
}
|
|
while(is_prefix(line, "220-")) { /* copy multiline greeting */
|
|
str_free(line);
|
|
line=fd_getline(c, c->remote_fd.fd);
|
|
fd_putline(c, c->local_wfd.fd, line);
|
|
}
|
|
str_free(line);
|
|
|
|
/* process client's EHLO */
|
|
line=fd_getline(c, c->local_rfd.fd);
|
|
if(!is_prefix(line, "EHLO ")) {
|
|
s_log(LOG_ERR, "Unknown client EHLO");
|
|
str_free(line);
|
|
str_free(domain);
|
|
longjmp(c->err, 1);
|
|
}
|
|
str_free(line);
|
|
fd_printf(c, c->local_wfd.fd, "250-%s", domain);
|
|
str_free(domain);
|
|
fd_putline(c, c->local_wfd.fd, "250 STARTTLS");
|
|
|
|
/* process client's STARTTLS */
|
|
line=fd_getline(c, c->local_rfd.fd);
|
|
if(!is_prefix(line, "STARTTLS")) {
|
|
s_log(LOG_ERR, "STARTTLS expected");
|
|
str_free(line);
|
|
longjmp(c->err, 1);
|
|
}
|
|
fd_putline(c, c->local_wfd.fd, "220 Go ahead");
|
|
str_free(line);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/**************************************** pop3 */
|
|
|
|
NOEXPORT char *pop3_client(CLI *c, SERVICE_OPTIONS *opt, const PHASE phase) {
|
|
char *line;
|
|
|
|
(void)opt; /* squash the unused parameter warning */
|
|
if(phase!=PROTOCOL_MIDDLE)
|
|
return NULL;
|
|
line=fd_getline(c, c->remote_fd.fd);
|
|
if(!is_prefix(line, "+OK ")) {
|
|
s_log(LOG_ERR, "Unknown server welcome");
|
|
str_free(line);
|
|
longjmp(c->err, 1);
|
|
}
|
|
fd_putline(c, c->local_wfd.fd, line);
|
|
fd_putline(c, c->remote_fd.fd, "STLS");
|
|
str_free(line);
|
|
line=fd_getline(c, c->remote_fd.fd);
|
|
if(!is_prefix(line, "+OK ")) {
|
|
s_log(LOG_ERR, "Server does not support TLS");
|
|
str_free(line);
|
|
longjmp(c->err, 1);
|
|
}
|
|
str_free(line);
|
|
return NULL;
|
|
}
|
|
|
|
NOEXPORT char *pop3_server(CLI *c, SERVICE_OPTIONS *opt, const PHASE phase) {
|
|
char *line;
|
|
|
|
if(phase==PROTOCOL_CHECK)
|
|
opt->option.connect_before_ssl=1; /* c->remote_fd needed */
|
|
if(phase!=PROTOCOL_MIDDLE)
|
|
return NULL;
|
|
line=fd_getline(c, c->remote_fd.fd);
|
|
fd_printf(c, c->local_wfd.fd, "%s + stunnel", line);
|
|
str_free(line);
|
|
line=fd_getline(c, c->local_rfd.fd);
|
|
if(is_prefix(line, "CAPA")) { /* client wants RFC 2449 extensions */
|
|
fd_putline(c, c->local_wfd.fd, "+OK Stunnel capability list follows");
|
|
fd_putline(c, c->local_wfd.fd, "STLS");
|
|
fd_putline(c, c->local_wfd.fd, ".");
|
|
str_free(line);
|
|
line=fd_getline(c, c->local_rfd.fd);
|
|
}
|
|
if(!is_prefix(line, "STLS")) {
|
|
s_log(LOG_ERR, "Client does not want TLS");
|
|
str_free(line);
|
|
longjmp(c->err, 1);
|
|
}
|
|
str_free(line);
|
|
fd_putline(c, c->local_wfd.fd, "+OK Stunnel starts TLS negotiation");
|
|
return NULL;
|
|
}
|
|
|
|
/**************************************** imap */
|
|
|
|
NOEXPORT char *imap_client(CLI *c, SERVICE_OPTIONS *opt, const PHASE phase) {
|
|
char *line;
|
|
|
|
(void)opt; /* squash the unused parameter warning */
|
|
if(phase!=PROTOCOL_MIDDLE)
|
|
return NULL;
|
|
line=fd_getline(c, c->remote_fd.fd);
|
|
if(!is_prefix(line, "* OK")) {
|
|
s_log(LOG_ERR, "Unknown server welcome");
|
|
str_free(line);
|
|
longjmp(c->err, 1);
|
|
}
|
|
fd_putline(c, c->local_wfd.fd, line);
|
|
fd_putline(c, c->remote_fd.fd, "stunnel STARTTLS");
|
|
str_free(line);
|
|
line=fd_getline(c, c->remote_fd.fd);
|
|
if(!is_prefix(line, "stunnel OK")) {
|
|
fd_putline(c, c->local_wfd.fd,
|
|
"* BYE stunnel: Server does not support TLS");
|
|
s_log(LOG_ERR, "Server does not support TLS");
|
|
str_free(line);
|
|
longjmp(c->err, 2); /* don't reset */
|
|
}
|
|
str_free(line);
|
|
return NULL;
|
|
}
|
|
|
|
NOEXPORT char *imap_server(CLI *c, SERVICE_OPTIONS *opt, const PHASE phase) {
|
|
char *line, *id, *tail, *capa;
|
|
|
|
if(phase==PROTOCOL_CHECK)
|
|
opt->option.connect_before_ssl=1; /* c->remote_fd needed */
|
|
if(phase!=PROTOCOL_MIDDLE)
|
|
return NULL;
|
|
s_poll_init(c->fds);
|
|
s_poll_add(c->fds, c->local_rfd.fd, 1, 0);
|
|
switch(s_poll_wait(c->fds, 0, 200)) {
|
|
case 0: /* fd not ready to read */
|
|
s_log(LOG_DEBUG, "RFC 2595 detected");
|
|
break;
|
|
case 1: /* fd ready to read */
|
|
s_log(LOG_DEBUG, "RFC 2595 not detected");
|
|
return NULL; /* return if RFC 2595 is not used */
|
|
default: /* -1 */
|
|
sockerror("RFC2595 (s_poll_wait)");
|
|
longjmp(c->err, 1);
|
|
}
|
|
|
|
/* process server welcome and send it to client */
|
|
line=fd_getline(c, c->remote_fd.fd);
|
|
if(!is_prefix(line, "* OK")) {
|
|
s_log(LOG_ERR, "Unknown server welcome");
|
|
str_free(line);
|
|
longjmp(c->err, 1);
|
|
}
|
|
capa=strstr(line, "CAPABILITY");
|
|
if(!capa)
|
|
capa=strstr(line, "capability");
|
|
if(capa)
|
|
*capa='K'; /* disable CAPABILITY within greeting */
|
|
fd_printf(c, c->local_wfd.fd, "%s (stunnel)", line);
|
|
|
|
id=str_dup("");
|
|
while(1) { /* process client commands */
|
|
str_free(line);
|
|
line=fd_getline(c, c->local_rfd.fd);
|
|
/* split line into id and tail */
|
|
str_free(id);
|
|
id=str_dup(line);
|
|
tail=strchr(id, ' ');
|
|
if(!tail)
|
|
break;
|
|
*tail++='\0';
|
|
|
|
if(is_prefix(tail, "STARTTLS")) {
|
|
fd_printf(c, c->local_wfd.fd,
|
|
"%s OK Begin TLS negotiation now", id);
|
|
str_free(line);
|
|
str_free(id);
|
|
return NULL; /* success */
|
|
} else if(is_prefix(tail, "CAPABILITY")) {
|
|
fd_putline(c, c->remote_fd.fd, line); /* send it to server */
|
|
str_free(line);
|
|
line=fd_getline(c, c->remote_fd.fd); /* get the capabilities */
|
|
if(*line=='*') {
|
|
/*
|
|
* append STARTTLS
|
|
* should also add LOGINDISABLED, but can't because
|
|
* of Mozilla bug #324138/#312009
|
|
* LOGIN would fail as "unexpected command", anyway
|
|
*/
|
|
fd_printf(c, c->local_wfd.fd, "%s STARTTLS", line);
|
|
str_free(line);
|
|
line=fd_getline(c, c->remote_fd.fd); /* next line */
|
|
}
|
|
fd_putline(c, c->local_wfd.fd, line); /* forward to the client */
|
|
tail=strchr(line, ' ');
|
|
if(!tail || !is_prefix(tail+1, "OK")) { /* not OK? */
|
|
fd_putline(c, c->local_wfd.fd,
|
|
"* BYE unexpected server response");
|
|
s_log(LOG_ERR, "Unexpected server response: %s", line);
|
|
break;
|
|
}
|
|
} else if(is_prefix(tail, "LOGOUT")) {
|
|
fd_putline(c, c->local_wfd.fd, "* BYE server terminating");
|
|
fd_printf(c, c->local_wfd.fd, "%s OK LOGOUT completed", id);
|
|
break;
|
|
} else {
|
|
fd_putline(c, c->local_wfd.fd, "* BYE stunnel: unexpected command");
|
|
fd_printf(c, c->local_wfd.fd, "%s BAD %s unexpected", id, tail);
|
|
s_log(LOG_ERR, "Unexpected client command %s", tail);
|
|
break;
|
|
}
|
|
}
|
|
/* clean server shutdown */
|
|
str_free(id);
|
|
fd_putline(c, c->remote_fd.fd, "stunnel LOGOUT");
|
|
str_free(line);
|
|
line=fd_getline(c, c->remote_fd.fd);
|
|
if(*line=='*') {
|
|
str_free(line);
|
|
line=fd_getline(c, c->remote_fd.fd);
|
|
}
|
|
str_free(line);
|
|
longjmp(c->err, 2); /* don't reset */
|
|
return NULL; /* some C compilers require a return value */
|
|
}
|
|
|
|
/**************************************** nntp */
|
|
|
|
NOEXPORT char *nntp_client(CLI *c, SERVICE_OPTIONS *opt, const PHASE phase) {
|
|
char *line;
|
|
|
|
(void)opt; /* squash the unused parameter warning */
|
|
if(phase!=PROTOCOL_MIDDLE)
|
|
return NULL;
|
|
line=fd_getline(c, c->remote_fd.fd);
|
|
if(!is_prefix(line, "200 ") && !is_prefix(line, "201 ")) {
|
|
s_log(LOG_ERR, "Unknown server welcome");
|
|
str_free(line);
|
|
longjmp(c->err, 1);
|
|
}
|
|
fd_putline(c, c->local_wfd.fd, line);
|
|
fd_putline(c, c->remote_fd.fd, "STARTTLS");
|
|
str_free(line);
|
|
line=fd_getline(c, c->remote_fd.fd);
|
|
if(!is_prefix(line, "382 ")) {
|
|
s_log(LOG_ERR, "Server does not support TLS");
|
|
str_free(line);
|
|
longjmp(c->err, 1);
|
|
}
|
|
str_free(line);
|
|
return NULL;
|
|
}
|
|
|
|
/**************************************** connect */
|
|
|
|
NOEXPORT char *connect_server(CLI *c, SERVICE_OPTIONS *opt, const PHASE phase) {
|
|
char *request, *proto, *header;
|
|
|
|
(void)opt; /* squash the unused parameter warning */
|
|
if(phase!=PROTOCOL_EARLY)
|
|
return NULL;
|
|
request=fd_getline(c, c->local_rfd.fd);
|
|
if(!is_prefix(request, "CONNECT ")) {
|
|
fd_putline(c, c->local_wfd.fd, "HTTP/1.0 400 Bad Request Method");
|
|
fd_putline(c, c->local_wfd.fd, "Server: stunnel/" STUNNEL_VERSION);
|
|
fd_putline(c, c->local_wfd.fd, "");
|
|
str_free(request);
|
|
longjmp(c->err, 1);
|
|
}
|
|
proto=strchr(request+8, ' ');
|
|
if(!proto || !is_prefix(proto, " HTTP/")) {
|
|
fd_putline(c, c->local_wfd.fd, "HTTP/1.0 400 Bad Request Protocol");
|
|
fd_putline(c, c->local_wfd.fd, "Server: stunnel/" STUNNEL_VERSION);
|
|
fd_putline(c, c->local_wfd.fd, "");
|
|
str_free(request);
|
|
longjmp(c->err, 1);
|
|
}
|
|
*proto='\0';
|
|
|
|
header=str_dup("");
|
|
do { /* ignore any headers */
|
|
str_free(header);
|
|
header=fd_getline(c, c->local_rfd.fd);
|
|
} while(*header); /* not empty */
|
|
str_free(header);
|
|
|
|
if(!name2addrlist(&c->connect_addr, request+8)) {
|
|
fd_putline(c, c->local_wfd.fd, "HTTP/1.0 404 Not Found");
|
|
fd_putline(c, c->local_wfd.fd, "Server: stunnel/" STUNNEL_VERSION);
|
|
fd_putline(c, c->local_wfd.fd, "");
|
|
str_free(request);
|
|
longjmp(c->err, 1);
|
|
}
|
|
str_free(request);
|
|
fd_putline(c, c->local_wfd.fd, "HTTP/1.0 200 OK");
|
|
fd_putline(c, c->local_wfd.fd, "Server: stunnel/" STUNNEL_VERSION);
|
|
fd_putline(c, c->local_wfd.fd, "");
|
|
return NULL;
|
|
}
|
|
|
|
NOEXPORT char *connect_client(CLI *c, SERVICE_OPTIONS *opt, const PHASE phase) {
|
|
char *line, *encoded;
|
|
|
|
if(phase!=PROTOCOL_MIDDLE)
|
|
return NULL;
|
|
if(!opt->protocol_host) {
|
|
s_log(LOG_ERR, "protocolHost not specified");
|
|
longjmp(c->err, 1);
|
|
}
|
|
fd_printf(c, c->remote_fd.fd, "CONNECT %s HTTP/1.1",
|
|
opt->protocol_host);
|
|
fd_printf(c, c->remote_fd.fd, "Host: %s", opt->protocol_host);
|
|
if(opt->protocol_username && opt->protocol_password) {
|
|
if(!strcasecmp(opt->protocol_authentication, "ntlm")) {
|
|
#ifndef OPENSSL_NO_MD4
|
|
ntlm(c, opt);
|
|
#else
|
|
s_log(LOG_ERR, "NTLM authentication is not available");
|
|
longjmp(c->err, 1);
|
|
#endif
|
|
} else { /* basic authentication */
|
|
line=str_printf("%s:%s",
|
|
opt->protocol_username, opt->protocol_password);
|
|
encoded=base64(1, line, (int)strlen(line));
|
|
str_free(line);
|
|
if(!encoded) {
|
|
s_log(LOG_ERR, "Base64 encoder failed");
|
|
longjmp(c->err, 1);
|
|
}
|
|
fd_printf(c, c->remote_fd.fd, "Proxy-Authorization: basic %s",
|
|
encoded);
|
|
str_free(encoded);
|
|
}
|
|
}
|
|
fd_putline(c, c->remote_fd.fd, ""); /* empty line */
|
|
line=fd_getline(c, c->remote_fd.fd);
|
|
if(!is_prefix(line, "HTTP/1.0 2") && !is_prefix(line, "HTTP/1.1 2")) {
|
|
/* not "HTTP/1.x 2xx Connection established" */
|
|
s_log(LOG_ERR, "CONNECT request rejected");
|
|
do { /* read all headers */
|
|
str_free(line);
|
|
line=fd_getline(c, c->remote_fd.fd);
|
|
} while(*line);
|
|
str_free(line);
|
|
longjmp(c->err, 1);
|
|
}
|
|
s_log(LOG_INFO, "CONNECT request accepted");
|
|
do {
|
|
str_free(line);
|
|
line=fd_getline(c, c->remote_fd.fd); /* read all headers */
|
|
} while(*line);
|
|
str_free(line);
|
|
return NULL;
|
|
}
|
|
|
|
#ifndef OPENSSL_NO_MD4
|
|
|
|
/*
|
|
* NTLM code is based on the following documentation:
|
|
* http://davenport.sourceforge.net/ntlm.html
|
|
* http://www.innovation.ch/personal/ronald/ntlm.html
|
|
*/
|
|
|
|
#define s_min(a, b) ((a)>(b)?(b):(a))
|
|
|
|
NOEXPORT void ntlm(CLI *c, SERVICE_OPTIONS *opt) {
|
|
char *line, buf[BUFSIZ], *ntlm1_txt, *ntlm2_txt, *ntlm3_txt, *tmpstr;
|
|
long content_length=0; /* no HTTP content */
|
|
|
|
/* send Proxy-Authorization (phase 1) */
|
|
fd_printf(c, c->remote_fd.fd, "Proxy-Connection: keep-alive");
|
|
ntlm1_txt=ntlm1();
|
|
if(!ntlm1_txt) {
|
|
s_log(LOG_ERR, "Proxy-Authenticate: Failed to build NTLM request");
|
|
longjmp(c->err, 1);
|
|
}
|
|
fd_printf(c, c->remote_fd.fd, "Proxy-Authorization: NTLM %s", ntlm1_txt);
|
|
str_free(ntlm1_txt);
|
|
fd_putline(c, c->remote_fd.fd, ""); /* empty line */
|
|
line=fd_getline(c, c->remote_fd.fd);
|
|
|
|
/* receive Proxy-Authenticate (phase 2) */
|
|
if(!is_prefix(line, "HTTP/1.0 407") && !is_prefix(line, "HTTP/1.1 407")) {
|
|
s_log(LOG_ERR, "Proxy-Authenticate: NTLM authorization request rejected");
|
|
do { /* read all headers */
|
|
str_free(line);
|
|
line=fd_getline(c, c->remote_fd.fd);
|
|
} while(*line);
|
|
str_free(line);
|
|
longjmp(c->err, 1);
|
|
}
|
|
ntlm2_txt=NULL;
|
|
do { /* read all headers */
|
|
str_free(line);
|
|
line=fd_getline(c, c->remote_fd.fd);
|
|
if(is_prefix(line, "Proxy-Authenticate: NTLM "))
|
|
ntlm2_txt=str_dup(line+25);
|
|
else if(is_prefix(line, "Content-Length: ")) {
|
|
content_length=strtol(line+16, &tmpstr, 10);
|
|
if(tmpstr>line+16) /* found some digits */
|
|
while(*tmpstr && isspace((int)*tmpstr))
|
|
++tmpstr;
|
|
if(tmpstr==line+16 || *tmpstr || content_length<0) {
|
|
s_log(LOG_ERR, "Proxy-Authenticate: Invalid Content-Length");
|
|
str_free(line);
|
|
longjmp(c->err, 1);
|
|
}
|
|
}
|
|
} while(*line);
|
|
if(!ntlm2_txt) { /* no Proxy-Authenticate: NTLM header */
|
|
s_log(LOG_ERR, "Proxy-Authenticate: NTLM header not found");
|
|
str_free(line);
|
|
longjmp(c->err, 1);
|
|
}
|
|
|
|
/* read and ignore HTTP content (if any) */
|
|
while(content_length>0) {
|
|
size_t n=s_min((size_t)content_length, BUFSIZ);
|
|
s_read(c, c->remote_fd.fd, buf, n);
|
|
content_length-=(long)n;
|
|
}
|
|
|
|
/* send Proxy-Authorization (phase 3) */
|
|
fd_printf(c, c->remote_fd.fd, "CONNECT %s HTTP/1.1", opt->protocol_host);
|
|
fd_printf(c, c->remote_fd.fd, "Host: %s", opt->protocol_host);
|
|
ntlm3_txt=ntlm3(opt->protocol_domain,
|
|
opt->protocol_username, opt->protocol_password, ntlm2_txt);
|
|
str_free(ntlm2_txt);
|
|
if(!ntlm3_txt) {
|
|
s_log(LOG_ERR, "Proxy-Authenticate: Failed to build NTLM response");
|
|
longjmp(c->err, 1);
|
|
}
|
|
fd_printf(c, c->remote_fd.fd, "Proxy-Authorization: NTLM %s", ntlm3_txt);
|
|
str_free(ntlm3_txt);
|
|
}
|
|
|
|
NOEXPORT char *ntlm1() {
|
|
char phase1[32];
|
|
|
|
memset(phase1, 0, sizeof phase1);
|
|
strcpy(phase1, "NTLMSSP");
|
|
phase1[8]=1; /* type: 1 */
|
|
phase1[12]=2; /* flag: negotiate OEM */
|
|
phase1[13]=2; /* flag: negotiate NTLM */
|
|
/* bytes 16-23: supplied domain security buffer */
|
|
/* bytes 24-31: supplied workstation security buffer */
|
|
return base64(1, phase1, sizeof phase1); /* encode */
|
|
}
|
|
|
|
NOEXPORT char *ntlm3(char *domain,
|
|
char *user, char *password, char *phase2) {
|
|
MD4_CTX md4;
|
|
uint8_t *decoded; /* decoded reply from proxy */
|
|
uint8_t phase3[146];
|
|
uint8_t md4_hash[21];
|
|
const size_t ntlm_len=24; /* length of the NTLM hash response */
|
|
const size_t domain_len=strlen(domain);
|
|
const size_t user_len=strlen(user);
|
|
const size_t ntlm_off=64; /* start of the data block in version 2 */
|
|
const size_t domain_off=ntlm_off+ntlm_len;
|
|
const size_t user_off=domain_off+domain_len;
|
|
const size_t end_off=user_off+user_len;
|
|
|
|
/* setup the phase3 structure */
|
|
if(end_off>sizeof phase3)
|
|
return NULL;
|
|
memset(phase3, 0, sizeof phase3);
|
|
/* bytes 0-7: null-terminated NTLMSSP signature */
|
|
strcpy((char *)phase3, "NTLMSSP");
|
|
/* bytes 8-11: NTLM message type */
|
|
phase3[8]=3; /* type: 3 */
|
|
/* bytes 12-19: LM/LMv2 response */
|
|
phase3[16]=(uint8_t)end_off; /* LM response offset */
|
|
/* bytes 20-27: NTLM/NTLMv2 response */
|
|
phase3[20]=(uint8_t)ntlm_len; /* NTLM response length */
|
|
phase3[22]=(uint8_t)ntlm_len; /* NTLM response length */
|
|
phase3[24]=(uint8_t)ntlm_off; /* NTLM response offset */
|
|
/* bytes 28-35: target (domain/server) name */
|
|
phase3[28]=(uint8_t)domain_len; /* domain length */
|
|
phase3[30]=(uint8_t)domain_len; /* domain length */
|
|
phase3[32]=(uint8_t)domain_off; /* domain offset */
|
|
/* bytes 36-43: user name */
|
|
phase3[36]=(uint8_t)user_len; /* user length */
|
|
phase3[38]=(uint8_t)user_len; /* user length */
|
|
phase3[40]=(uint8_t)user_off; /* user offset */
|
|
/* bytes 44-51: workstation name */
|
|
phase3[48]=(uint8_t)end_off; /* host offset */
|
|
/* bytes 52-59: session key */
|
|
phase3[56]=(uint8_t)end_off; /* session key offset */
|
|
/* bytes 60-63: flags */
|
|
phase3[60]=2; /* flag: negotiate OEM */
|
|
phase3[61]=2; /* flag: negotiate NTLM */
|
|
|
|
/* calculate MD4 of the UTF-16 encoded password */
|
|
MD4_Init(&md4);
|
|
while(*password) {
|
|
MD4_Update(&md4, password++, 1);
|
|
MD4_Update(&md4, "", 1); /* UTF-16 */
|
|
}
|
|
MD4_Final(md4_hash, &md4);
|
|
memset(md4_hash+16, 0, 5); /* pad to 21 bytes */
|
|
|
|
/* decode the challenge and calculate the response */
|
|
decoded=(uint8_t *)base64(0, phase2, (int)strlen(phase2)); /* decode */
|
|
if(!decoded)
|
|
return NULL;
|
|
crypt_DES(phase3+ntlm_off, decoded+24, md4_hash);
|
|
crypt_DES(phase3+ntlm_off+8, decoded+24, md4_hash+7);
|
|
crypt_DES(phase3+ntlm_off+16, decoded+24, md4_hash+14);
|
|
str_free(decoded);
|
|
|
|
strncpy((char *)phase3+domain_off, domain, domain_len);
|
|
strncpy((char *)phase3+user_off, user, user_len);
|
|
|
|
return base64(1, (char *)phase3, (int)end_off); /* encode */
|
|
}
|
|
|
|
NOEXPORT void crypt_DES(DES_cblock dst, const_DES_cblock src,
|
|
unsigned char hash[7]) {
|
|
DES_cblock key;
|
|
DES_key_schedule sched;
|
|
|
|
/* convert 56-bit hash to 64-bit DES key */
|
|
key[0]=hash[0];
|
|
key[1]=(unsigned char)(((hash[0]&1)<<7)|(hash[1]>>1));
|
|
key[2]=(unsigned char)(((hash[1]&3)<<6)|(hash[2]>>2));
|
|
key[3]=(unsigned char)(((hash[2]&7)<<5)|(hash[3]>>3));
|
|
key[4]=(unsigned char)(((hash[3]&15)<<4)|(hash[4]>>4));
|
|
key[5]=(unsigned char)(((hash[4]&31)<<3)|(hash[5]>>5));
|
|
key[6]=(unsigned char)(((hash[5]&63)<<2)|(hash[6]>>6));
|
|
key[7]=(unsigned char)(((hash[6]&127)<<1));
|
|
DES_set_odd_parity(&key);
|
|
|
|
/* encrypt */
|
|
DES_set_key_unchecked(&key, &sched);
|
|
DES_ecb_encrypt((const_DES_cblock *)src,
|
|
(DES_cblock *)dst, &sched, DES_ENCRYPT);
|
|
}
|
|
|
|
#endif
|
|
|
|
NOEXPORT char *base64(int encode, const char *in, int len) {
|
|
BIO *bio, *b64;
|
|
char *out;
|
|
int n;
|
|
|
|
b64=BIO_new(BIO_f_base64());
|
|
if(!b64)
|
|
return NULL;
|
|
BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
|
|
bio=BIO_new(BIO_s_mem());
|
|
if(!bio) {
|
|
str_free(b64);
|
|
return NULL;
|
|
}
|
|
if(encode)
|
|
bio=BIO_push(b64, bio);
|
|
BIO_write(bio, in, len);
|
|
(void)BIO_flush(bio); /* ignore the error if any */
|
|
if(encode) {
|
|
bio=BIO_pop(bio);
|
|
BIO_free(b64);
|
|
} else {
|
|
bio=BIO_push(b64, bio);
|
|
}
|
|
n=BIO_pending(bio);
|
|
/* 32 bytes as a safety precaution for passing decoded data to crypt_DES */
|
|
/* n+1 to get null-terminated string on encode */
|
|
out=str_alloc(n<32?32:(size_t)n+1);
|
|
n=BIO_read(bio, out, n);
|
|
if(n<0) {
|
|
BIO_free_all(bio);
|
|
str_free(out);
|
|
return NULL;
|
|
}
|
|
BIO_free_all(bio);
|
|
return out;
|
|
}
|
|
|
|
/* end of protocol.c */
|