stunnel4/src/protocol.c

748 lines
25 KiB
C

/*
* stunnel Universal SSL tunnel
* Copyright (C) 1998-2012 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 isprefix(a, b) (strncasecmp((a), (b), strlen(b))==0)
/* protocol-specific function prototypes */
static void proxy_server(CLI *c);
static void cifs_client(CLI *);
static void cifs_server(CLI *);
static void pgsql_client(CLI *);
static void pgsql_server(CLI *);
static void smtp_client(CLI *);
static void smtp_server(CLI *);
static void pop3_client(CLI *);
static void pop3_server(CLI *);
static void imap_client(CLI *);
static void imap_server(CLI *);
static void nntp_client(CLI *);
static void connect_server(CLI *);
static void connect_client(CLI *);
#if !defined(OPENSSL_NO_MD4) && OPENSSL_VERSION_NUMBER>=0x0090700fL
static void ntlm(CLI *);
static char *ntlm1();
static char *ntlm3(char *, char *, char *);
static void crypt_DES(DES_cblock, DES_cblock, DES_cblock);
#endif
static char *base64(int, char *, int);
/**************************************** framework */
typedef void (*FUNCTION)(CLI *);
static const struct {
char *name;
struct {
PROTOCOL_TYPE type;
FUNCTION func;
} handlers[2];
} protocols[]={
{"proxy", {{PROTOCOL_PRE_SSL, proxy_server}, {PROTOCOL_PRE_SSL, NULL}}},
{"cifs", {{PROTOCOL_PRE_CONNECT, cifs_server}, {PROTOCOL_PRE_SSL, cifs_client}}},
{"pgsql", {{PROTOCOL_PRE_CONNECT, pgsql_server}, {PROTOCOL_PRE_SSL, pgsql_client}}},
{"smtp", {{PROTOCOL_PRE_SSL, smtp_server}, {PROTOCOL_PRE_SSL, smtp_client}}},
{"pop3", {{PROTOCOL_PRE_SSL, pop3_server}, {PROTOCOL_PRE_SSL, pop3_client}}},
{"imap", {{PROTOCOL_PRE_SSL, imap_server}, {PROTOCOL_PRE_SSL, imap_client}}},
{"nntp", {{PROTOCOL_NONE, NULL}, {PROTOCOL_PRE_SSL, nntp_client}}},
{"connect", {{PROTOCOL_PRE_CONNECT, connect_server}, {PROTOCOL_PRE_SSL, connect_client}}},
{NULL, {{PROTOCOL_NONE, NULL}, {PROTOCOL_NONE, NULL}}}
};
int find_protocol_id(const char *name) {
int id;
for(id=0; protocols[id].name; ++id)
if(!strcmp(name, protocols[id].name))
return id;
return -1;
}
void protocol(CLI *c, const PROTOCOL_TYPE type) {
const int id=c->opt->protocol, mode=(unsigned int)c->opt->option.client;
if(id<0 || type!=protocols[id].handlers[mode].type ||
!protocols[id].handlers[mode].func)
return;
s_log(LOG_INFO, "%s-mode %s protocol negotiations started",
mode ? "Client" : "Server", protocols[id].name);
protocols[id].handlers[mode].func(c);
s_log(LOG_INFO, "%s-mode %s protocol negotiations succeeded",
mode ? "Client" : "Server", protocols[id].name);
}
/**************************************** 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
static void proxy_server(CLI *c) {
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;
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;
case AF_INET6:
proto="TCP6";
break;
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);
}
/**************************************** cifs */
static void cifs_client(CLI *c) {
u8 buffer[5];
u8 request_dummy[4] = {0x81, 0, 0, 0}; /* a zero-length request */
write_blocking(c, c->remote_fd.fd, request_dummy, 4);
read_blocking(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);
}
}
static void cifs_server(CLI *c) {
u8 buffer[128];
u8 response_access_denied[5] = {0x83, 0, 0, 1, 0x81};
u8 response_use_ssl[5] = {0x83, 0, 0, 1, 0x8e};
u16 len;
read_blocking(c, c->local_rfd.fd, buffer, 4) ;/* NetBIOS header */
len=buffer[3];
len|=(u16)(buffer[2]) << 8;
if(len>sizeof buffer-4) {
s_log(LOG_ERR, "Received block too long");
longjmp(c->err, 1);
}
read_blocking(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");
write_blocking(c, c->local_wfd.fd, response_access_denied, 5);
longjmp(c->err, 1);
}
write_blocking(c, c->local_wfd.fd, response_use_ssl, 5);
}
/**************************************** pgsql */
/* http://www.postgresql.org/docs/8.3/static/protocol-flow.html#AEN73982 */
u8 ssl_request[8]={0, 0, 0, 8, 0x04, 0xd2, 0x16, 0x2f};
static void pgsql_client(CLI *c) {
u8 buffer[1];
write_blocking(c, c->remote_fd.fd, ssl_request, sizeof ssl_request);
read_blocking(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);
}
}
static void pgsql_server(CLI *c) {
u8 buffer[8], ssl_ok[1]={'S'};
memset(buffer, 0, sizeof buffer);
read_blocking(c, c->local_rfd.fd, buffer, sizeof buffer);
if(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);
}
write_blocking(c, c->local_wfd.fd, ssl_ok, sizeof ssl_ok);
}
/**************************************** smtp */
static void smtp_client(CLI *c) {
char *line;
do { /* copy multiline greeting */
line=fd_getline(c, c->remote_fd.fd);
fd_putline(c, c->local_wfd.fd, line);
} while(isprefix(line, "220-"));
fd_putline(c, c->remote_fd.fd, "EHLO localhost");
do { /* skip multiline reply */
line=fd_getline(c, c->remote_fd.fd);
} while(isprefix(line, "250-"));
if(!isprefix(line, "250 ")) { /* error */
s_log(LOG_ERR, "Remote server is not RFC 1425 compliant");
longjmp(c->err, 1);
}
fd_putline(c, c->remote_fd.fd, "STARTTLS");
do { /* skip multiline reply */
line=fd_getline(c, c->remote_fd.fd);
} while(isprefix(line, "220-"));
if(!isprefix(line, "220 ")) { /* error */
s_log(LOG_ERR, "Remote server is not RFC 2487 compliant");
longjmp(c->err, 1);
}
}
static void smtp_server(CLI *c) {
char *line;
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; /* return if RFC 2487 is not used */
default: /* -1 */
sockerror("RFC2487 (s_poll_wait)");
longjmp(c->err, 1);
}
line=fd_getline(c, c->remote_fd.fd);
if(!isprefix(line, "220")) {
s_log(LOG_ERR, "Unknown server welcome");
longjmp(c->err, 1);
}
fd_printf(c, c->local_wfd.fd, "%s + stunnel", line);
line=fd_getline(c, c->local_rfd.fd);
if(!isprefix(line, "EHLO ")) {
s_log(LOG_ERR, "Unknown client EHLO");
longjmp(c->err, 1);
}
fd_printf(c, c->local_wfd.fd, "250-%s Welcome", line);
fd_putline(c, c->local_wfd.fd, "250 STARTTLS");
line=fd_getline(c, c->local_rfd.fd);
if(!isprefix(line, "STARTTLS")) {
s_log(LOG_ERR, "STARTTLS expected");
longjmp(c->err, 1);
}
fd_putline(c, c->local_wfd.fd, "220 Go ahead");
}
/**************************************** pop3 */
static void pop3_client(CLI *c) {
char *line;
line=fd_getline(c, c->remote_fd.fd);
if(!isprefix(line, "+OK ")) {
s_log(LOG_ERR, "Unknown server welcome");
longjmp(c->err, 1);
}
fd_putline(c, c->local_wfd.fd, line);
fd_putline(c, c->remote_fd.fd, "STLS");
line=fd_getline(c, c->remote_fd.fd);
if(!isprefix(line, "+OK ")) {
s_log(LOG_ERR, "Server does not support TLS");
longjmp(c->err, 1);
}
}
static void pop3_server(CLI *c) {
char *line;
line=fd_getline(c, c->remote_fd.fd);
fd_printf(c, c->local_wfd.fd, "%s + stunnel", line);
line=fd_getline(c, c->local_rfd.fd);
if(isprefix(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, ".");
line=fd_getline(c, c->local_rfd.fd);
}
if(!isprefix(line, "STLS")) {
s_log(LOG_ERR, "Client does not want TLS");
longjmp(c->err, 1);
}
fd_putline(c, c->local_wfd.fd, "+OK Stunnel starts TLS negotiation");
}
/**************************************** imap */
static void imap_client(CLI *c) {
char *line;
line=fd_getline(c, c->remote_fd.fd);
if(!isprefix(line, "* OK")) {
s_log(LOG_ERR, "Unknown server welcome");
longjmp(c->err, 1);
}
fd_putline(c, c->local_wfd.fd, line);
fd_putline(c, c->remote_fd.fd, "stunnel STARTTLS");
line=fd_getline(c, c->remote_fd.fd);
if(!isprefix(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");
longjmp(c->err, 2); /* don't reset */
}
}
static void imap_server(CLI *c) {
char *line, *id, *tail, *capa;
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; /* 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(!isprefix(line, "* OK")) {
s_log(LOG_ERR, "Unknown server welcome");
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);
while(1) { /* process client commands */
line=fd_getline(c, c->local_rfd.fd);
/* split line into id and tail */
id=str_dup(line);
tail=strchr(id, ' ');
if(!tail)
break;
*tail++='\0';
if(isprefix(tail, "STARTTLS")) {
fd_printf(c, c->local_wfd.fd,
"%s OK Begin TLS negotiation now", id);
return; /* success */
} else if(isprefix(tail, "CAPABILITY")) {
fd_putline(c, c->remote_fd.fd, line); /* send it to server */
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);
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 || !isprefix(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(isprefix(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 */
fd_putline(c, c->remote_fd.fd, "stunnel LOGOUT");
line=fd_getline(c, c->remote_fd.fd);
if(*line=='*')
line=fd_getline(c, c->remote_fd.fd);
longjmp(c->err, 2); /* don't reset */
}
/**************************************** nntp */
static void nntp_client(CLI *c) {
char *line;
line=fd_getline(c, c->remote_fd.fd);
if(!isprefix(line, "200 ") && !isprefix(line, "201 ")) {
s_log(LOG_ERR, "Unknown server welcome");
longjmp(c->err, 1);
}
fd_putline(c, c->local_wfd.fd, line);
fd_putline(c, c->remote_fd.fd, "STARTTLS");
line=fd_getline(c, c->remote_fd.fd);
if(!isprefix(line, "382 ")) {
s_log(LOG_ERR, "Server does not support TLS");
longjmp(c->err, 1);
}
}
/**************************************** connect */
static void connect_server(CLI *c) {
char *request, *proto, *header;
int not_empty;
request=fd_getline(c, c->local_rfd.fd);
if(!isprefix(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, "");
longjmp(c->err, 1);
}
proto=strchr(request+8, ' ');
if(!proto || !isprefix(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, "");
longjmp(c->err, 1);
}
*proto='\0';
do { /* ignore any headers*/
header=fd_getline(c, c->local_rfd.fd);
not_empty=*header;
str_free(header);
} while(not_empty);
if(!name2addrlist(&c->connect_addr, request+8, DEFAULT_LOOPBACK)) {
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, "");
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, "");
}
static void connect_client(CLI *c) {
char *line, *encoded;
if(!c->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",
c->opt->protocol_host);
fd_printf(c, c->remote_fd.fd, "Host: %s", c->opt->protocol_host);
if(c->opt->protocol_username && c->opt->protocol_password) {
if(!strcasecmp(c->opt->protocol_authentication, "NTLM")) {
#if !defined(OPENSSL_NO_MD4) && OPENSSL_VERSION_NUMBER>=0x0090700fL
ntlm(c);
#else
s_log(LOG_ERR, "NTLM authentication is not available");
longjmp(c->err, 1);
#endif
} else { /* basic authentication */
line=str_printf("%s:%s",
c->opt->protocol_username, c->opt->protocol_password);
encoded=base64(1, line, 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(strlen(line)<12 || line[9]!='2') {
/* not "HTTP/1.0 200 Connection established" */
s_log(LOG_ERR, "CONNECT request rejected");
do { /* read all headers */
line=fd_getline(c, c->remote_fd.fd);
} while(*line);
longjmp(c->err, 1);
}
s_log(LOG_INFO, "CONNECT request accepted");
do {
line=fd_getline(c, c->remote_fd.fd); /* read all headers */
} while(*line);
}
#if !defined(OPENSSL_NO_MD4) && OPENSSL_VERSION_NUMBER>=0x0090700fL
/*
* 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))
static void ntlm(CLI *c) {
char *line, buf[BUFSIZ], *ntlm1_txt, *ntlm2_txt, *ntlm3_txt;
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(line[9]!='4' || line[10]!='0' || line[11]!='7') { /* code 407 */
s_log(LOG_ERR, "NTLM authorization request rejected");
do { /* read all headers */
line=fd_getline(c, c->remote_fd.fd);
} while(*line);
longjmp(c->err, 1);
}
ntlm2_txt=NULL;
do { /* read all headers */
line=fd_getline(c, c->remote_fd.fd);
if(isprefix(line, "Proxy-Authenticate: NTLM "))
ntlm2_txt=str_dup(line+25);
else if(isprefix(line, "Content-Length: "))
content_length=atol(line+16);
} while(*line);
if(!ntlm2_txt) { /* no Proxy-Authenticate: NTLM header */
s_log(LOG_ERR, "Proxy-Authenticate: NTLM header not found");
longjmp(c->err, 1);
}
/* read and ignore HTTP content (if any) */
while(content_length) {
read_blocking(c, c->remote_fd.fd, buf, s_min(content_length, BUFSIZ));
content_length-=s_min(content_length, BUFSIZ);
}
/* send Proxy-Authorization (phase 3) */
fd_printf(c, c->remote_fd.fd, "CONNECT %s HTTP/1.1", c->opt->protocol_host);
fd_printf(c, c->remote_fd.fd, "Host: %s", c->opt->protocol_host);
ntlm3_txt=ntlm3(c->opt->protocol_username, c->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);
}
static 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 */
}
static char *ntlm3(char *username, char *password, char *phase2) {
MD4_CTX md4;
char *decoded; /* decoded reply from proxy */
char phase3[146];
unsigned char md4_hash[21];
unsigned int userlen=strlen(username);
unsigned int phase3len=s_min(88+userlen, sizeof phase3);
/* setup phase3 structure */
memset(phase3, 0, sizeof phase3);
strcpy(phase3, "NTLMSSP");
phase3[8]=3; /* type: 3 */
phase3[16]=phase3len; /* LM-resp off */
phase3[20]=24; /* NT-resp len */
phase3[22]=24; /* NT-Resp len */
phase3[24]=64; /* NT-resp off */
phase3[32]=phase3len; /* domain offset */
phase3[36]=userlen; /* user length */
phase3[38]=userlen; /* user length */
phase3[40]=88; /* user offset */
phase3[48]=phase3len; /* host offset */
phase3[56]=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=base64(0, phase2, strlen(phase2)); /* decode */
if(!decoded)
return NULL;
crypt_DES((unsigned char *)phase3+64,
(unsigned char *)decoded+24, md4_hash);
crypt_DES((unsigned char *)phase3+72,
(unsigned char *)decoded+24, md4_hash+7);
crypt_DES((unsigned char *)phase3+80,
(unsigned char *)decoded+24, md4_hash+14);
str_free(decoded);
strncpy(phase3+88, username, sizeof phase3-88);
return base64(1, phase3, phase3len); /* encode */
}
static 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]=((hash[0]&1)<<7)|(hash[1]>>1);
key[2]=((hash[1]&3)<<6)|(hash[2]>>2);
key[3]=((hash[2]&7)<<5)|(hash[3]>>3);
key[4]=((hash[3]&15)<<4)|(hash[4]>>4);
key[5]=((hash[4]&31)<<3)|(hash[5]>>5);
key[6]=((hash[5]&63)<<2)|(hash[6]>>6);
key[7]=((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
static 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: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 */