/* * stunnel Universal SSL tunnel * Copyright (C) 1998-2012 Michal Trojnara * * 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 . * * 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 */