blob: e62c8489c616cc5bba647a29139b4f24ed2e71f5 [file] [log] [blame]
/*
* stunnel TLS offloading and load-balancing proxy
* Copyright (C) 1998-2015 Michal Trojnara <Michal.Trojnara@mirt.net>
*
* 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_server(CLI *, SERVICE_OPTIONS *, const PHASE);
NOEXPORT void socks4_server(CLI *);
NOEXPORT void socks5_server_method(CLI *);
NOEXPORT void socks5_server(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 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 *);
NOEXPORT void crypt_DES(DES_cblock, DES_cblock, DES_cblock);
#endif
NOEXPORT char *base64(int, 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 ?
"The 'socks' protocol is not supported in client mode" :
socks_server(c, opt, phase);
if(!strcasecmp(opt->protocol, "proxy"))
return opt->option.client ?
"The 'proxy' protocol is not supported in 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 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 SSL (SOCKS protocol itself is also encrypted) */
/* FIXME: connect() failures are not currently reported with SOCKS protocol */
NOEXPORT char *socks_server(CLI *c, SERVICE_OPTIONS *opt, const PHASE phase) {
uint8_t version;
(void)opt; /* skip warning about unused parameter */
if(phase!=PROTOCOL_MIDDLE)
return NULL;
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 %u", version);
longjmp(c->err, 1);
}
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);
socks.cd=90;
close_connection=0;
} else {
s_log(LOG_ERR, "SOCKS4a failed to resolve \"%s\"", host_name);
socks.cd=91;
}
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");
socks.cd=90;
close_connection=0;
}
} 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;
} else {
s_log(LOG_ERR, "SOCKS4a/TOR failed to resolve \"%s\"", host_name);
socks.cd=91;
}
str_free(host_name);
} else {
s_log(LOG_ERR, "Unsupported SOCKS4/SOCKS4a command %u", socks.cd);
socks.cd=91;
}
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) {
union {
struct {
uint8_t ver, cmd, rsv, atyp;
} req;
struct {
uint8_t ver, cmd, rsv, atyp, addr[4], port[2];
} v4;
struct {
uint8_t ver, cmd, rsv, atyp, addr[16], port[2];
} v6;
struct {
uint8_t ver, rep, rsv, atyp;
} resp;
} 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 %u", 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");
socks.resp.rep=0x00; /* succeeded */
close_connection=0;
} 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);
socks.resp.rep=0x00; /* succeeded */
close_connection=0;
} 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");
socks.resp.rep=0x00; /* succeeded */
close_connection=0;
#endif
} else {
s_log(LOG_ERR, "Unsupported SOCKS5 address type %u", socks.req.atyp);
socks.resp.rep=0x07; /* Address type not supported */
}
} else if(socks.req.cmd==0xf0) { /* RESOLVE (a TOR extension) */
host_name=ssl_getstring(c);
if(hostport2addr(&addr, host_name, "0", 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 family 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);
} else {
s_log(LOG_ERR, "Unsupported SOCKS5 command %u", 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 */
}
/**************************************** 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 SSL 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; /* skip warning about unused parameter */
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; /* skip warning about unused parameter */
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 SSL */
s_log(LOG_ERR, "Remote server does not require SSL");
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; /* skip warning about unused parameter */
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; /* skip warning about unused parameter */
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-SSL preferred */
if(buffer[0]!='S') {
s_log(LOG_ERR, "PostgreSQL server rejected SSL");
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; /* skip warning about unused parameter */
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 SSL, 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) {
char *line;
(void)opt; /* skip warning about unused parameter */
if(phase!=PROTOCOL_MIDDLE)
return NULL;
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);
return NULL;
}
NOEXPORT char *smtp_server(CLI *c, SERVICE_OPTIONS *opt, const PHASE phase) {
char *line, *domain, *greeting;
(void)opt; /* skip warning about unused parameter */
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; /* skip warning about unused parameter */
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;
(void)opt; /* skip warning about unused parameter */
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; /* skip warning about unused parameter */
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;
(void)opt; /* skip warning about unused parameter */
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 capabilites */
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; /* skip warning about unused parameter */
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; /* skip warning about unused parameter */
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(*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_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[16];
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 */
return base64(1, phase1, sizeof phase1); /* encode */
}
NOEXPORT char *ntlm3(char *username, char *password, char *phase2) {
MD4_CTX md4;
uint8_t *decoded; /* decoded reply from proxy */
uint8_t phase3[146];
uint8_t md4_hash[21];
size_t userlen=strlen(username);
size_t phase3len=s_min(88+userlen, sizeof phase3);
/* setup phase3 structure */
memset(phase3, 0, sizeof phase3);
strcpy((char *)phase3, "NTLMSSP");
phase3[8]=3; /* type: 3 */
phase3[16]=(uint8_t)phase3len; /* LM-resp off */
phase3[20]=24; /* NT-resp len */
phase3[22]=24; /* NT-Resp len */
phase3[24]=64; /* NT-resp off */
phase3[32]=(uint8_t)phase3len; /* domain offset */
phase3[36]=(uint8_t)userlen; /* user length */
phase3[38]=(uint8_t)userlen; /* user length */
phase3[40]=88; /* user offset */
phase3[48]=(uint8_t)phase3len; /* host offset */
phase3[56]=(uint8_t)phase3len; /* message len */
phase3[60]=2; /* flag: negotiate OEM */
phase3[61]=2; /* flag: negotiate NTLM */
/* calculate MD4 of 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 challenge and calculate response */
decoded=(uint8_t *)base64(0, phase2, (int)strlen(phase2)); /* decode */
if(!decoded)
return NULL;
crypt_DES(phase3+64, decoded+24, md4_hash);
crypt_DES(phase3+72, decoded+24, md4_hash+7);
crypt_DES(phase3+80, decoded+24, md4_hash+14);
str_free(decoded);
strncpy((char *)phase3+88, username, sizeof phase3-88);
return base64(1, (char *)phase3, (int)phase3len); /* encode */
}
NOEXPORT void crypt_DES(DES_cblock dst, const_DES_cblock src, DES_cblock hash) {
DES_cblock key;
DES_key_schedule sched;
/* convert key from 56 to 64 bits */
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, 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 */