Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save jgeraerts/7d0cc62d3d2ba390b29254d4e3afa9a7 to your computer and use it in GitHub Desktop.
Save jgeraerts/7d0cc62d3d2ba390b29254d4e3afa9a7 to your computer and use it in GitHub Desktop.
/*
* afindep.c - address family independant networking functions
*
* nc6 - an advanced netcat clone
* Copyright (C) 2002-2006 Chris Leishman <chris _at_ leishman.org>
* Copyright (C) 2001-2006 Mauro Tortonesi <mauro _at_ deepspace6.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, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "system.h"
#include "afindep.h"
#include "misc.h"
#include "netsupport.h"
#include "parser.h"
#include <assert.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <limits.h>
RCSID("@(#) $Header: /ds6/cvs/nc6/src/afindep.c,v 1.4 2006/01/19 22:46:23 chris Exp $");
/* suggested size for argument to getnameinfo_ex */
static const int AI_STR_SIZE = (2 * (NI_MAXHOST + NI_MAXSERV + 2)) + 8;
static bool skip_address(const struct addrinfo *ai);
#ifdef ENABLE_IPV6
static struct addrinfo *order_ipv6_first(struct addrinfo *ai);
#endif
static void getnameinfo_ex(const struct sockaddr *sa, socklen_t len, char *str,
size_t size, bool numeric_mode);
static bool is_allowed(const struct sockaddr *sa, socklen_t salen,
const struct addrinfo *hints,
const char *address, const char *service);
int afindep_connect(const struct addrinfo *hints,
const char *remote_address, const char *remote_service,
const char *local_address, const char *local_service,
set_sockopt_handler_t set_sockopt_handler, void *hdata,
time_t timeout, int *rt_socktype)
{
int err, fd = -1;
struct addrinfo *res = NULL, *ptr;
bool connect_attempted = false;
char name_buf[AI_STR_SIZE];
/* make sure arguments are valid and preconditions are respected */
assert(hints != NULL);
assert(remote_address != NULL && strlen(remote_address) > 0);
assert(remote_service != NULL && strlen(remote_service) > 0);
assert(local_address == NULL || strlen(local_address) > 0);
assert(local_service == NULL || strlen(local_service) > 0);
/* get the address of the remote end of the connection */
err = getaddrinfo(remote_address, remote_service, hints, &res);
if (err != 0) {
warning(_("forward host lookup failed "
"for remote endpoint %s: %s"),
remote_address, gai_strerror(err));
return -1;
}
/* check the results of getaddrinfo */
assert(res != NULL);
/* try connecting to any of the addresses returned by getaddrinfo */
for (ptr = res; ptr != NULL; ptr = ptr->ai_next) {
/* only accept results we can handle */
if (skip_address(ptr) == true) continue;
/* we are going to try to connect to this address */
connect_attempted = true;
/* create the socket */
fd = socket(ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol);
if (fd < 0) {
/* ignore this address if it is not supported */
if (!unsupported_sock_error(errno))
continue;
warning("cannot create the socket: %s",
strerror(errno));
return -1;
}
#if defined(ENABLE_IPV6) && defined(IPV6_V6ONLY)
if (ptr->ai_family == PF_INET6) {
int on = 1;
/* in case of error, we will go on anyway... */
err = setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY,
&on, sizeof(on));
if (err < 0)
warning("error with sockopt IPV6_V6ONLY");
}
#endif
if (set_sockopt_handler != NULL)
set_sockopt_handler(fd, hdata);
/* setup name_buf if we're in verbose mode */
if (verbose_mode())
getnameinfo_ex(ptr->ai_addr, ptr->ai_addrlen,
name_buf, sizeof(name_buf),
(hints->ai_flags & AI_NUMERICHOST));
/* setup local source address and/or service */
if (local_address != NULL || local_service != NULL) {
struct addrinfo src_hints, *src_res = NULL, *src_ptr;
/* setup hints structure to be passed to getaddrinfo */
memset(&src_hints, 0, sizeof(src_hints));
src_hints.ai_family = ptr->ai_family;
src_hints.ai_flags = AI_PASSIVE;
src_hints.ai_socktype = ptr->ai_socktype;
src_hints.ai_protocol = ptr->ai_protocol;
src_hints.ai_flags = hints->ai_flags;
/* get the local IP address of the connection */
err = getaddrinfo(local_address, local_service,
&src_hints, &src_res);
if (err != 0) {
if (verbose_mode()) {
warning(_("bind to source addr/port "
"failed when connecting to "
"%s: %s"), name_buf,
gai_strerror(err));
}
close(fd);
fd = -1;
continue;
}
/* check the results of getaddrinfo */
assert(src_res != NULL);
/* try binding to any of the addresses */
for (src_ptr = src_res; src_ptr != NULL;
src_ptr = src_ptr->ai_next)
{
err = bind(fd, src_ptr->ai_addr,
src_ptr->ai_addrlen);
if (err == 0) break;
}
if (err != 0) {
/* make sure we have tried all addresses */
assert(src_ptr == NULL);
if (verbose_mode()) {
warning(_("bind to source addr/port "
"failed when connecting to "
"%s: %s"), name_buf,
strerror(errno));
}
freeaddrinfo(src_res);
close(fd);
fd = -1;
continue;
}
freeaddrinfo(src_res);
}
/* attempt the connection */
err = connect_with_timeout(fd,
ptr->ai_addr, ptr->ai_addrlen, timeout);
/* exit from the loop if we have a valid connection */
if (err == 0)
break;
/* check error code */
if (verbose_mode()) {
/* use different error message for timeout */
if (errno == ETIMEDOUT) {
/* connection timed out */
warning(_("timeout while connecting to %s"),
name_buf);
}
else {
/* connection failed */
warning(_("cannot connect to %s: %s"),
name_buf, strerror(errno));
}
}
close(fd);
fd = -1;
}
/* either all possibilities were exahusted, or a connection was made */
assert(ptr == NULL || fd >= 0);
/* if the connection failed, output an error message */
if (ptr == NULL) {
/* if a connection was attempted, an error has been output */
if (connect_attempted == false) {
warning(_("forward lookup returned "
"no useful socket types"));
} else {
warning(_("unable to connect "
"to address %s, service %s"),
remote_address, remote_service);
}
return -1;
}
/* let the user know the connection has been established */
if (verbose_mode())
warning(_("%s open"), name_buf);
/* return the socktype */
if (rt_socktype != NULL)
*rt_socktype = ptr->ai_socktype;
/* cleanup addrinfo structure */
freeaddrinfo(res);
return fd;
}
int afindep_listener(const struct addrinfo *hints,
const char *local_address, const char *local_service,
const char *remote_address, const char *remote_service,
set_sockopt_handler_t set_sockopt_handler, void *hdata,
listen_callback_t callback, void *cdata,
time_t timeout, int max_accept)
{
int nfd, i, fd, err, maxfd = -1;
struct addrinfo *res = NULL, *ptr;
#ifdef ENABLE_IPV6
bool set_ipv6_only = false;
bool bound_ipv6_any = false;
#endif
bound_socket_t *bound_sockets = NULL;
fd_set accept_fdset;
char name_buf[AI_STR_SIZE];
/* make sure arguments are valid and preconditions are respected */
assert(hints != NULL);
assert(remote_address == NULL || strlen(remote_address) > 0);
assert(remote_service == NULL || strlen(remote_service) > 0);
assert(local_address == NULL || strlen(local_address) > 0);
assert(local_service != NULL && strlen(local_service) > 0);
/* if max_accept is 0, just return */
if (max_accept == 0)
return 0;
/* get the IP address of the local end of the connection */
err = getaddrinfo(local_address, local_service, hints, &res);
if (err != 0) {
warning(_("forward host lookup failed "
"for local endpoint %s (%s): %s"),
local_address? local_address : _("[unspecified]"),
local_service, gai_strerror(err));
return -1;
}
/* check the results of getaddrinfo */
assert(res != NULL);
#ifdef ENABLE_IPV6
/*
* Some systems (notably Linux) with a shared stack for ipv6 and ipv4,
* have a broken bind(2) implementation. In these systems, binding an
* ipv6 socket to ipv6_addr_any (::) will also bind the socket to
* the ipv4 unspecified address (0.0.0.0).
*
* So when listening on ipv6_addr_any (::) accept(2) will return also
* ipv4 connection attemptes, as ipv4 mapped ipv6 addresses.
*
* This is a problem, since when called with AF_UNSPEC and AI_PASSIVE,
* getaddrinfo will return results for both ipv6 AND ipv4, but if we
* try to bind on the ipv4 unspecified address (0.0.0.0) AFTER we have
* bound our socket to ipv6_addr_any (::), the syscall bind(2) will
* fail and return -1, setting errno to EADDRINUSE.
*
* The following algorithm is used to work around this error to some
* degree:
*
* Ensure binds to IPv6 addresses are attempted before IPv4 addresses.
* On broken systems where IPV6_V6ONLY is not defined or the setsockopt
* fails:
*
* - Keep track of whether an IPv6 socket has been bound to
* in6_addr_any.
* - If a bind to IPv4 fails with EADDRINUSE and an IPv6 socket
* has been bound, then just ignore the error.
*/
/*
* TODO: instead of reordering the results, just loop through the
* results twice - once for ipv6 then for the rest.
*/
res = order_ipv6_first(res);
#endif
/* initialize accept_fdset */
FD_ZERO(&accept_fdset);
/* try binding to all of the addresses returned by getaddrinfo */
nfd = 0;
for (ptr = res; ptr != NULL; ptr = ptr->ai_next) {
/* only accept results we can handle */
if (skip_address(ptr) == true) continue;
/* create the socket */
fd = socket(ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol);
if (fd < 0) {
/* ignore this address if it is not supported */
if (unsupported_sock_error(errno))
continue;
warning("cannot create the socket: %s",
strerror(errno));
return -1;
}
#if defined(ENABLE_IPV6) && defined(IPV6_V6ONLY)
if (ptr->ai_family == PF_INET6) {
int on = 1;
/* in case of error, we will go on anyway... */
err = setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY,
&on, sizeof(on));
if (err < 0)
warning("error with sockopt IPV6_V6ONLY");
else
set_ipv6_only = true;
}
#endif
if (set_sockopt_handler != NULL)
set_sockopt_handler(fd, hdata);
/* get the numeric name for this source address */
getnameinfo_ex(ptr->ai_addr, ptr->ai_addrlen, name_buf,
sizeof(name_buf), true);
/* bind to the local address */
err = bind(fd, ptr->ai_addr, ptr->ai_addrlen);
if (err != 0) {
#ifdef ENABLE_IPV6
/* suppress ADDRINUSE error for systems that double
* bind ipv6 and ipv4 and pretend it succeeded */
if (errno == EADDRINUSE &&
ptr->ai_family == PF_INET &&
set_ipv6_only == false &&
bound_ipv6_any == true)
{
if (verbose_mode())
warning(_("listening on %s ..."),
name_buf);
close(fd);
continue;
}
#endif
warning(_("bind to source %s failed: %s"),
name_buf, strerror(errno));
close(fd);
continue;
}
if (ptr->ai_socktype == SOCK_STREAM) {
err = listen(fd, SOMAXCONN);
if (err != 0) {
warning(_("cannot listen on %s: %s"),
name_buf, strerror(errno));
free_bound_sockets(bound_sockets);
return -1;
}
}
if (verbose_mode())
warning(_("listening on %s ..."), name_buf);
#ifdef ENABLE_IPV6
/* check if this was an IPv6 socket bound to IN6_ADDR_ANY */
if (ptr->ai_family == PF_INET6 &&
memcmp(&((struct sockaddr_in6 *)(ptr->ai_addr))->sin6_addr,
&in6addr_any, sizeof(struct in6_addr)) == 0)
{
bound_ipv6_any = true;
}
#endif
/* add fd to bound_sockets (just add to the head of the list) */
bound_sockets = add_bound_socket(bound_sockets, fd,
ptr->ai_socktype);
/* add fd to accept_fdset */
FD_SET(fd, &accept_fdset);
maxfd = MAX(maxfd, fd);
nfd++;
}
freeaddrinfo(res);
if (nfd == 0) {
warning(_("failed to bind to any local addr/port"));
return -1;
}
/* enter into the accept loop */
for (;;) {
fd_set tmp_ap_fdset;
struct timeval tv, *tvp = NULL;
struct sockaddr_storage dest;
socklen_t destlen;
int ns, socktype;
char c_name_buf[AI_STR_SIZE];
/* make a copy of accept_fdset before passing to select */
memcpy(&tmp_ap_fdset, &accept_fdset, sizeof(fd_set));
/* setup timeout */
if (timeout > 0) {
tv.tv_sec = timeout;
tv.tv_usec = 0;
tvp = &tv;
}
/* wait for an incoming connection */
err = select(maxfd + 1, &tmp_ap_fdset, NULL, NULL, tvp);
if (err <= 0) {
if (err < 0 && errno == EINTR)
continue;
if (err == 0)
warning(_("connection timed out"));
else
warning("select error: %s", strerror(errno));
free_bound_sockets(bound_sockets);
return -1;
}
/* find the ready filedescriptor */
for (fd = 0; fd <= maxfd && !FD_ISSET(fd, &tmp_ap_fdset); ++fd)
;
/* if none were ready, loop to select again */
if (fd > maxfd)
continue;
/* find socket type in bound_sockets */
socktype = get_bound_socket_type(bound_sockets, fd);
destlen = sizeof(dest);
/* for stream sockets we accept a new connection, whereas for
* dgram sockets we use MSG_PEEK to determine the sender */
if (socktype == SOCK_STREAM) {
ns = accept(fd, (struct sockaddr *)&dest, &destlen);
if (ns < 0) {
warning("accept failed: %s", strerror(errno));
free_bound_sockets(bound_sockets);
return -1;
}
} else {
/* this is checked when binding listen sockets */
assert(socktype == SOCK_DGRAM);
err = recvfrom(fd, NULL, 0, MSG_PEEK,
(struct sockaddr *)&dest, &destlen);
if (err < 0) {
warning("recvfrom failed: %s", strerror(errno));
free_bound_sockets(bound_sockets);
return -1;
}
ns = dup(fd);
if (ns < 0) {
warning("dup failed: %s", strerror(errno));
free_bound_sockets(bound_sockets);
return -1;
}
}
/* get names for each end of the connection */
if (verbose_mode()) {
struct sockaddr_storage src;
socklen_t srclen = sizeof(src);
/* find out what address the connection was to */
err = getsockname(ns, (struct sockaddr *)&src, &srclen);
if (err < 0) {
warning("getsockname failed: %s",
strerror(errno));
free_bound_sockets(bound_sockets);
return -1;
}
/* get the numeric name for this source */
getnameinfo_ex((struct sockaddr *)&src, srclen,
name_buf, sizeof(name_buf), true);
/* get the name for this client */
getnameinfo_ex((struct sockaddr *)&dest, destlen,
c_name_buf, sizeof(c_name_buf),
(hints->ai_flags & AI_NUMERICHOST));
}
/* check if connections from this client are allowed */
if ((remote_address == NULL && remote_service == NULL) ||
(is_allowed((struct sockaddr *)&dest, destlen, hints,
remote_address, remote_service) == true))
{
if (socktype == SOCK_DGRAM) {
/* connect the socket to ensure we only talk
* with this client */
err = connect(ns, (struct sockaddr *)&dest,
destlen);
if (err != 0) {
warning(_("connect failed on "
"datagram socket: %s"),
strerror(errno));
free_bound_sockets(bound_sockets);
return -1;
}
}
if (verbose_mode()) {
warning(_("connect to %s from %s"),
name_buf, c_name_buf);
}
callback(ns, socktype, cdata);
if (max_accept > 0 && --max_accept == 0)
break;
} else {
if (socktype == SOCK_DGRAM) {
/* the connection wasn't accepted -
* remove the queued packet */
recvfrom(ns, NULL, 0, 0, NULL, 0);
}
close(ns);
if (verbose_mode()) {
warning(_("refused connect to %s from %s"),
name_buf, c_name_buf);
}
}
}
/* close the listening sockets */
for (i = 0; i <= maxfd; ++i) {
if (FD_ISSET(i, &accept_fdset) != 0) close(i);
}
/* free the bound_socket list */
free_bound_sockets(bound_sockets);
return 0;
}
static bool skip_address(const struct addrinfo *ai)
{
/* check arguments */
assert(ai != NULL);
/* only use socktypes we can handle */
if (ai->ai_socktype != SOCK_STREAM &&
ai->ai_socktype != SOCK_DGRAM)
return true;
#ifdef ENABLE_IPV6
/*
* skip IPv4 mapped addresses returned from getaddrinfo,
* for security reasons. see the documents:
*
* http://playground.iijlab.net/i-d/
* /draft-itojun-ipv6-transition-abuse-01.txt
*
* http://playground.iijlab.net/i-d/
* /draft-cmetz-v6ops-v4mapped-api-harmful-00.txt
*
* http://playground.iijlab.net/i-d/
* /draft-itojun-v6ops-v4mapped-harmful-01.txt
*/
if (is_address_ipv4_mapped(ai->ai_addr))
return true;
#else
#ifdef PF_INET6
/* skip IPv6 if disabled */
if (ai->ai_family == PF_INET6)
return true;
#endif
#endif
return false;
}
/*
* Some systems (notably Linux) will bind to both IPv6 AND IPv4 when
* listening. Connections will still be accepted from either IPv6 or
* IPv4 clients (IPv4 will be mapped into IPv6). However, this means
* that we MUST bind the IPv6 address ONLY for these hosts.
*
* To handle this, we will ensure that IPv6 sockets are bound first and then
* attempt to bind the IPv4 ones. On systems that double bind IPv6/IPv4 the
* IPv4 bind will simply fail. This function does the reordering - moving all
* IPv6 addresses to the start of the getaddrinfo results.
*/
#ifdef ENABLE_IPV6
static struct addrinfo *order_ipv6_first(struct addrinfo *ai)
{
struct addrinfo *ptr;
struct addrinfo *lastv6 = NULL;
struct addrinfo *tmp;
assert(ai != NULL);
/* Move all IPv6 addresses to the start of the list - keeping
* them in the original order. */
if (ai->ai_family == PF_INET6)
lastv6 = ai;
for (ptr = ai; ptr != NULL && ptr->ai_next != NULL; ptr = ptr->ai_next){
if (ptr->ai_next->ai_family == PF_INET6) {
tmp = ptr->ai_next;
ptr->ai_next = tmp->ai_next;
if (lastv6) {
tmp->ai_next = lastv6->ai_next;
lastv6->ai_next = tmp;
} else {
tmp->ai_next = ai;
ai = tmp;
}
lastv6 = tmp;
}
}
return ai;
}
#endif
static void getnameinfo_ex(const struct sockaddr *sa, socklen_t len, char *str,
size_t size, bool numeric_mode)
{
int err;
char hbuf_rev[NI_MAXHOST + 1];
char hbuf_num[NI_MAXHOST + 1];
char sbuf_rev[NI_MAXSERV + 1];
char sbuf_num[NI_MAXSERV + 1];
/* check arguments */
assert(sa != NULL);
assert(len > 0);
assert(str != NULL);
assert(size > 0);
/* get the numeric name for this destination as a string */
err = getnameinfo(sa, len, hbuf_num, sizeof(hbuf_num),
sbuf_num, sizeof(sbuf_num),
NI_NUMERICHOST | NI_NUMERICSERV);
/* this should never happen */
if (err != 0)
fatal("getnameinfo failed: %s", gai_strerror(err));
if (numeric_mode == false) {
/* get the real name for this destination as a string */
err = getnameinfo(sa, len, hbuf_rev, sizeof(hbuf_rev),
sbuf_rev, sizeof(sbuf_rev), 0);
if (err == 0) {
snprintf(str, size, "%s (%s) %s [%s]", hbuf_rev,
hbuf_num, sbuf_num, sbuf_rev);
} else {
warning(_("inverse lookup failed for %s: %s"),
hbuf_num, gai_strerror(err));
snprintf(str, size, "%s %s", hbuf_num, sbuf_num);
}
} else {
snprintf(str, size, "%s %s", hbuf_num, sbuf_num);
}
}
/* returns true if sa corresponds to the address/port specified in addr */
static bool is_allowed(const struct sockaddr *sa, socklen_t salen,
const struct addrinfo *hints,
const char *address, const char *service)
{
struct addrinfo *res = NULL, *ptr;
int err;
bool ret;
assert(sa != NULL);
assert(hints != NULL);
assert(address == NULL || strlen(address) > 0);
assert(service == NULL || strlen(service) > 0);
/* if no address or service is supplied, match everything */
if (address == NULL && service == NULL) return true;
err = getaddrinfo(address, service, hints, &res);
if (err != 0) {
/* some errors just indicate that the address wasn't suitable */
switch (err) {
#ifdef HAVE_GETADDRINFO_EAI_NODATA
case EAI_NODATA:
#endif
#ifdef HAVE_GETADDRINFO_EAI_ADDRFAMILY
case EAI_ADDRFAMILY:
#endif
case EAI_FAMILY:
case EAI_SERVICE:
case EAI_SOCKTYPE:
return false;
default:
fatal("getaddrinfo error: %s", gai_strerror(err));
}
}
ret = false;
for (ptr = res; ptr != NULL; ptr = ptr->ai_next) {
#ifdef ENABLE_IPV6
/* skip IPv4 mapped addresses returned from getaddrinfo */
if (is_address_ipv4_mapped(ptr->ai_addr))
continue;
#endif
if (sockaddr_compare(sa, salen,
ptr->ai_addr, ptr->ai_addrlen) == true)
{
ret = true;
break;
}
}
freeaddrinfo(res);
return ret;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment