blob: 7edc4f39880607b510f992626f88cbd30d848fda [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"
/**************************************** prototypes */
#if defined(USE_WIN32) && !defined(_WIN32_WCE)
NOEXPORT int get_ipv6(LPTSTR);
#endif
NOEXPORT void addrlist2addr(SOCKADDR_UNION *, SOCKADDR_LIST *);
NOEXPORT void addrlist_reset(SOCKADDR_LIST *);
#ifndef HAVE_GETADDRINFO
#ifndef EAI_MEMORY
#define EAI_MEMORY 1
#endif
#ifndef EAI_NONAME
#define EAI_NONAME 2
#endif
#ifndef EAI_SERVICE
#define EAI_SERVICE 8
#endif
/* rename some potentially locally shadowed declarations */
#define getaddrinfo local_getaddrinfo
#define freeaddrinfo local_freeaddrinfo
#ifndef HAVE_STRUCT_ADDRINFO
struct addrinfo {
int ai_flags;
int ai_family;
int ai_socktype;
int ai_protocol;
int ai_addrlen;
struct sockaddr *ai_addr;
char *ai_canonname;
struct addrinfo *ai_next;
};
#endif
#ifndef AI_PASSIVE
#define AI_PASSIVE 1
#endif
NOEXPORT int getaddrinfo(const char *, const char *,
const struct addrinfo *, struct addrinfo **);
NOEXPORT int alloc_addresses(struct hostent *, const struct addrinfo *,
u_short port, struct addrinfo **, struct addrinfo **);
NOEXPORT void freeaddrinfo(struct addrinfo *);
#endif /* !defined HAVE_GETADDRINFO */
/**************************************** resolver initialization */
#if defined(USE_WIN32) && !defined(_WIN32_WCE)
GETADDRINFO s_getaddrinfo;
FREEADDRINFO s_freeaddrinfo;
GETNAMEINFO s_getnameinfo;
#endif
void resolver_init() {
#if defined(USE_WIN32) && !defined(_WIN32_WCE)
if(get_ipv6(TEXT("ws2_32.dll"))) /* IPv6 in Windows XP or higher */
return;
if(get_ipv6(TEXT("wship6.dll"))) /* experimental IPv6 for Windows 2000 */
return;
/* fall back to the built-in emulation */
#endif
}
#if defined(USE_WIN32) && !defined(_WIN32_WCE)
NOEXPORT int get_ipv6(LPTSTR file) {
HINSTANCE handle;
handle=LoadLibrary(file);
if(!handle)
return 0;
s_getaddrinfo=(GETADDRINFO)GetProcAddress(handle, "getaddrinfo");
s_freeaddrinfo=(FREEADDRINFO)GetProcAddress(handle, "freeaddrinfo");
s_getnameinfo=(GETNAMEINFO)GetProcAddress(handle, "getnameinfo");
if(!s_getaddrinfo || !s_freeaddrinfo || !s_getnameinfo) {
s_getaddrinfo=NULL;
s_freeaddrinfo=NULL;
s_getnameinfo=NULL;
FreeLibrary(handle);
return 0;
}
return 1; /* IPv6 detected -> OK */
}
#endif
/**************************************** stunnel resolver API */
unsigned name2addr(SOCKADDR_UNION *addr, char *name, int passive) {
SOCKADDR_LIST *addr_list;
unsigned retval;
addr_list=str_alloc(sizeof(SOCKADDR_LIST));
addrlist_clear(addr_list, passive);
retval=name2addrlist(addr_list, name);
if(retval)
addrlist2addr(addr, addr_list);
str_free(addr_list->addr);
str_free(addr_list);
return retval;
}
unsigned hostport2addr(SOCKADDR_UNION *addr,
char *host_name, char *port_name, int passive) {
SOCKADDR_LIST *addr_list;
unsigned retval;
addr_list=str_alloc(sizeof(SOCKADDR_LIST));
addrlist_clear(addr_list, passive);
retval=hostport2addrlist(addr_list, host_name, port_name);
if(retval)
addrlist2addr(addr, addr_list);
str_free(addr_list->addr);
str_free(addr_list);
return retval;
}
NOEXPORT void addrlist2addr(SOCKADDR_UNION *addr, SOCKADDR_LIST *addr_list) {
unsigned i;
for(i=0; i<addr_list->num; ++i) { /* find the first IPv4 address */
if(addr_list->addr[i].in.sin_family==AF_INET) {
memcpy(addr, &addr_list->addr[i], sizeof(SOCKADDR_UNION));
return;
}
}
#ifdef USE_IPv6
for(i=0; i<addr_list->num; ++i) { /* find the first IPv6 address */
if(addr_list->addr[i].in.sin_family==AF_INET6) {
memcpy(addr, &addr_list->addr[i], sizeof(SOCKADDR_UNION));
return;
}
}
#endif
/* copy the first address resolved (curently AF_UNIX) */
memcpy(addr, &addr_list->addr[0], sizeof(SOCKADDR_UNION));
}
unsigned name2addrlist(SOCKADDR_LIST *addr_list, char *name) {
char *tmp, *host_name, *port_name;
unsigned retval;
/* first check if this is a UNIX socket */
#ifdef HAVE_STRUCT_SOCKADDR_UN
if(*name=='/') {
if(offsetof(struct sockaddr_un, sun_path)+strlen(name)+1
> sizeof(struct sockaddr_un)) {
s_log(LOG_ERR, "Unix socket path is too long");
return 0; /* no results */
}
addr_list->addr=str_realloc(addr_list->addr,
(addr_list->num+1)*sizeof(SOCKADDR_UNION));
addr_list->addr[addr_list->num].un.sun_family=AF_UNIX;
strcpy(addr_list->addr[addr_list->num].un.sun_path, name);
return ++(addr_list->num); /* ok - return the number of addresses */
}
#endif
/* setup host_name and port_name */
tmp=str_dup(name);
port_name=strrchr(tmp, ':');
if(port_name) {
host_name=tmp;
*port_name++='\0';
} else { /* no ':' - use default host IP */
host_name=NULL;
port_name=tmp;
}
/* fill addr_list structure */
retval=hostport2addrlist(addr_list, host_name, port_name);
str_free(tmp);
return retval;
}
unsigned hostport2addrlist(SOCKADDR_LIST *addr_list,
char *host_name, char *port_name) {
struct addrinfo hints, *res=NULL, *cur;
int err, retry=0;
memset(&hints, 0, sizeof hints);
#if defined(USE_IPv6) || defined(USE_WIN32)
hints.ai_family=AF_UNSPEC;
#else
hints.ai_family=AF_INET;
#endif
hints.ai_socktype=SOCK_STREAM;
hints.ai_protocol=IPPROTO_TCP;
hints.ai_flags=0;
if(addr_list->passive) {
hints.ai_family=AF_INET; /* first try IPv4 for passive requests */
hints.ai_flags|=AI_PASSIVE;
}
for(;;) {
err=getaddrinfo(host_name, port_name, &hints, &res);
if(!err)
break;
if(res)
freeaddrinfo(res);
if(err==EAI_AGAIN && ++retry<=3) {
s_log(LOG_DEBUG, "getaddrinfo: EAI_AGAIN received: retrying");
sleep(1);
continue;
}
#if defined(USE_IPv6) || defined(USE_WIN32)
if(hints.ai_family==AF_INET) {
hints.ai_family=AF_UNSPEC;
continue; /* retry for non-IPv4 addresses */
}
#endif
break;
}
if(err==EAI_SERVICE) {
s_log(LOG_ERR, "Unknown TCP service \"%s\"", port_name);
return 0; /* error */
}
if(err) {
s_log(LOG_ERR, "Error resolving \"%s\": %s",
host_name ? host_name :
(addr_list->passive ? DEFAULT_ANY : DEFAULT_LOOPBACK),
s_gai_strerror(err));
return 0; /* error */
}
/* copy the list of addresses */
for(cur=res; cur; cur=cur->ai_next) {
if(cur->ai_addrlen>(int)sizeof(SOCKADDR_UNION)) {
s_log(LOG_ERR, "INTERNAL ERROR: ai_addrlen value too big");
freeaddrinfo(res);
return 0; /* no results */
}
addr_list->addr=str_realloc(addr_list->addr,
(addr_list->num+1)*sizeof(SOCKADDR_UNION));
memcpy(&addr_list->addr[addr_list->num], cur->ai_addr,
(size_t)cur->ai_addrlen);
++(addr_list->num);
}
freeaddrinfo(res);
return addr_list->num; /* ok - return the number of addresses */
}
/* initialize the structure */
void addrlist_clear(SOCKADDR_LIST *addr_list, int passive) {
addrlist_reset(addr_list);
addr_list->names=NULL;
addr_list->passive=passive;
}
/* prepare the structure to resolve new hosts */
NOEXPORT void addrlist_reset(SOCKADDR_LIST *addr_list) {
addr_list->num=0;
addr_list->addr=NULL;
addr_list->rr_val=0; /* reset the round-robin counter */
/* allow structures created with sockaddr_dup() to modify
* the original rr_val rather than its local copy */
addr_list->rr_ptr=&addr_list->rr_val;
}
unsigned addrlist_dup(SOCKADDR_LIST *dst, const SOCKADDR_LIST *src) {
memcpy(dst, src, sizeof(SOCKADDR_LIST));
if(src->num) { /* already resolved */
dst->addr=str_alloc(src->num*sizeof(SOCKADDR_UNION));
memcpy(dst->addr, src->addr, src->num*sizeof(SOCKADDR_UNION));
} else { /* delayed resolver */
addrlist_resolve(dst);
}
return dst->num;
}
unsigned addrlist_resolve(SOCKADDR_LIST *addr_list) {
unsigned num=0, rnd;
NAME_LIST *host;
addrlist_reset(addr_list);
for(host=addr_list->names; host; host=host->next)
num+=name2addrlist(addr_list, host->name);
switch(num) {
case 0:
case 1:
addr_list->rr_val=0;
break;
default:
/* randomize the initial value of round-robin counter */
/* ignore the error value and the distribution bias */
RAND_bytes((unsigned char *)&rnd, sizeof rnd);
addr_list->rr_val=rnd%num;
}
return num;
}
char *s_ntop(SOCKADDR_UNION *addr, socklen_t addrlen) {
int err;
char *host, *port, *retval;
if(addrlen==sizeof(u_short)) /* see UNIX(7) manual for details */
return str_dup("unnamed socket");
host=str_alloc(256);
port=str_alloc(256); /* needs to be long enough for AF_UNIX path */
err=getnameinfo(&addr->sa, addrlen,
host, 256, port, 256, NI_NUMERICHOST|NI_NUMERICSERV);
if(err) {
s_log(LOG_ERR, "getnameinfo: %s", s_gai_strerror(err));
retval=str_dup("unresolvable address");
} else
retval=str_printf("%s:%s", host, port);
str_free(host);
str_free(port);
return retval;
}
socklen_t addr_len(const SOCKADDR_UNION *addr) {
if(addr->sa.sa_family==AF_INET)
return sizeof(struct sockaddr_in);
#ifdef USE_IPv6
if(addr->sa.sa_family==AF_INET6)
return sizeof(struct sockaddr_in6);
#endif
#ifdef HAVE_STRUCT_SOCKADDR_UN
if(addr->sa.sa_family==AF_UNIX)
return sizeof(struct sockaddr_un);
#endif
s_log(LOG_ERR, "INTERNAL ERROR: Unknown sa_family: %d",
addr->sa.sa_family);
return sizeof(SOCKADDR_UNION);
}
/**************************************** my getaddrinfo() */
/* implementation is limited to functionality needed by stunnel */
#ifndef HAVE_GETADDRINFO
NOEXPORT int getaddrinfo(const char *node, const char *service,
const struct addrinfo *hints, struct addrinfo **res) {
struct hostent *h;
#ifndef _WIN32_WCE
struct servent *p;
#endif
u_short port;
struct addrinfo *ai;
int retval;
char *tmpstr;
if(!node)
node=hints->ai_flags&AI_PASSIVE ? DEFAULT_ANY : DEFAULT_LOOPBACK;
#if defined(USE_WIN32) && !defined(_WIN32_WCE)
if(s_getaddrinfo)
return s_getaddrinfo(node, service, hints, res);
#endif
/* decode service name */
port=htons((u_short)strtol(service, &tmpstr, 10));
if(tmpstr==service || *tmpstr) { /* not a number */
#ifdef _WIN32_WCE
return EAI_NONAME;
#else /* defined(_WIN32_WCE) */
p=getservbyname(service, "tcp");
if(!p)
return EAI_NONAME;
port=(u_short)p->s_port;
#endif /* defined(_WIN32_WCE) */
}
/* allocate addrlist structure */
ai=str_alloc(sizeof(struct addrinfo));
if(hints)
memcpy(ai, hints, sizeof(struct addrinfo));
/* try to decode numerical address */
#if defined(USE_IPv6) && !defined(USE_WIN32)
ai->ai_family=AF_INET6;
ai->ai_addrlen=sizeof(struct sockaddr_in6);
ai->ai_addr=str_alloc((size_t)ai->ai_addrlen);
ai->ai_addr->sa_family=AF_INET6;
if(inet_pton(AF_INET6, node,
&((struct sockaddr_in6 *)ai->ai_addr)->sin6_addr)>0) {
#else
ai->ai_family=AF_INET;
ai->ai_addrlen=sizeof(struct sockaddr_in);
ai->ai_addr=str_alloc(ai->ai_addrlen);
ai->ai_addr->sa_family=AF_INET;
((struct sockaddr_in *)ai->ai_addr)->sin_addr.s_addr=inet_addr(node);
if(((struct sockaddr_in *)ai->ai_addr)->sin_addr.s_addr+1) {
/* (signed)((struct sockaddr_in *)ai->ai_addr)->sin_addr.s_addr!=-1 */
#endif
((struct sockaddr_in *)ai->ai_addr)->sin_port=port;
*res=ai;
return 0; /* numerical address resolved */
}
str_free(ai->ai_addr);
str_free(ai);
/* not numerical: need to call resolver library */
*res=NULL;
ai=NULL;
enter_critical_section(CRIT_INET);
#ifdef HAVE_GETHOSTBYNAME2
h=gethostbyname2(node, AF_INET6);
if(h) /* some IPv6 addresses found */
alloc_addresses(h, hints, port, res, &ai); /* ignore the error */
#endif
h=gethostbyname(node); /* get list of addresses */
if(h)
retval=ai ?
alloc_addresses(h, hints, port, &ai->ai_next, &ai) :
alloc_addresses(h, hints, port, res, &ai);
else if(!*res)
retval=EAI_NONAME; /* no results */
else
retval=0;
#ifdef HAVE_ENDHOSTENT
endhostent();
#endif
leave_critical_section(CRIT_INET);
if(retval) { /* error: free allocated memory */
freeaddrinfo(*res);
*res=NULL;
}
return retval;
}
NOEXPORT int alloc_addresses(struct hostent *h, const struct addrinfo *hints,
u_short port, struct addrinfo **head, struct addrinfo **tail) {
int i;
struct addrinfo *ai;
/* copy addresses */
for(i=0; h->h_addr_list[i]; i++) {
ai=str_alloc(sizeof(struct addrinfo));
if(hints)
memcpy(ai, hints, sizeof(struct addrinfo));
ai->ai_next=NULL; /* just in case */
if(*tail) { /* list not empty: add a node */
(*tail)->ai_next=ai;
*tail=ai;
} else { /* list empty: create it */
*head=ai;
*tail=ai;
}
ai->ai_family=h->h_addrtype;
#if defined(USE_IPv6)
if(h->h_addrtype==AF_INET6) {
ai->ai_addrlen=sizeof(struct sockaddr_in6);
ai->ai_addr=str_alloc((size_t)ai->ai_addrlen);
memcpy(&((struct sockaddr_in6 *)ai->ai_addr)->sin6_addr,
h->h_addr_list[i], (size_t)h->h_length);
} else
#endif
{
ai->ai_addrlen=sizeof(struct sockaddr_in);
ai->ai_addr=str_alloc((size_t)ai->ai_addrlen);
memcpy(&((struct sockaddr_in *)ai->ai_addr)->sin_addr,
h->h_addr_list[i], (size_t)h->h_length);
}
ai->ai_addr->sa_family=(u_short)h->h_addrtype;
/* offsets of sin_port and sin6_port should be the same */
((struct sockaddr_in *)ai->ai_addr)->sin_port=port;
}
return 0; /* success */
}
NOEXPORT void freeaddrinfo(struct addrinfo *current) {
struct addrinfo *next;
#if defined(USE_WIN32) && !defined(_WIN32_WCE)
if(s_freeaddrinfo) {
s_freeaddrinfo(current);
return;
}
#endif
while(current) {
str_free(current->ai_addr);
str_free(current->ai_canonname);
next=current->ai_next;
str_free(current);
current=next;
}
}
#endif /* !defined HAVE_GETADDRINFO */
/* due to a problem with Mingw32 I decided to define my own gai_strerror() */
const char *s_gai_strerror(int err) {
switch(err) {
#ifdef EAI_BADFLAGS
case EAI_BADFLAGS:
return "Invalid value for ai_flags (EAI_BADFLAGS)";
#endif
case EAI_NONAME:
return "Neither nodename nor servname known (EAI_NONAME)";
#ifdef EAI_AGAIN
case EAI_AGAIN:
return "Temporary failure in name resolution (EAI_AGAIN)";
#endif
#ifdef EAI_FAIL
case EAI_FAIL:
return "Non-recoverable failure in name resolution (EAI_FAIL)";
#endif
#ifdef EAI_NODATA
#if EAI_NODATA!=EAI_NONAME
case EAI_NODATA:
return "No address associated with nodename (EAI_NODATA)";
#endif /* EAI_NODATA!=EAI_NONAME */
#endif /* defined EAI_NODATA */
#ifdef EAI_FAMILY
case EAI_FAMILY:
return "ai_family not supported (EAI_FAMILY)";
#endif
#ifdef EAI_SOCKTYPE
case EAI_SOCKTYPE:
return "ai_socktype not supported (EAI_SOCKTYPE)";
#endif
#ifdef EAI_SERVICE
case EAI_SERVICE:
return "servname is not supported for ai_socktype (EAI_SERVICE)";
#endif
#ifdef EAI_ADDRFAMILY
case EAI_ADDRFAMILY:
return "Address family for nodename not supported (EAI_ADDRFAMILY)";
#endif /* EAI_ADDRFAMILY */
case EAI_MEMORY:
return "Memory allocation failure (EAI_MEMORY)";
#ifdef EAI_SYSTEM
case EAI_SYSTEM:
return "System error returned in errno (EAI_SYSTEM)";
#endif /* EAI_SYSTEM */
default:
return "Unknown error";
}
}
/**************************************** my getnameinfo() */
/* implementation is limited to functionality needed by stunnel */
#ifndef HAVE_GETNAMEINFO
int getnameinfo(const struct sockaddr *sa, socklen_t salen,
char *host, size_t hostlen, char *serv, size_t servlen, int flags) {
#if defined(USE_WIN32) && !defined(_WIN32_WCE)
if(s_getnameinfo)
return s_getnameinfo(sa, salen, host, hostlen, serv, servlen, flags);
#endif
if(host && hostlen) {
#if defined(USE_IPv6) && !defined(USE_WIN32)
inet_ntop(sa->sa_family, sa->sa_family==AF_INET6 ?
(void *)&((struct sockaddr_in6 *)sa)->sin6_addr :
(void *)&((struct sockaddr_in *)sa)->sin_addr,
host, hostlen);
#else /* USE_IPv6 */
enter_critical_section(CRIT_INET); /* inet_ntoa is not mt-safe */
strncpy(host, inet_ntoa(((struct sockaddr_in *)sa)->sin_addr),
hostlen);
leave_critical_section(CRIT_INET);
host[hostlen-1]='\0';
#endif /* USE_IPv6 */
}
if(serv && servlen)
sprintf(serv, "%u", ntohs(((struct sockaddr_in *)sa)->sin_port));
/* sin_port is in the same place both in sockaddr_in and sockaddr_in6 */
/* ignore servlen since it's long enough in stunnel code */
return 0;
}
#endif
/* end of resolver.c */