/* * ProFTPD - FTP server daemon * Copyright (c) 1997, 1998 Public Flood Software * Copyright (c) 1999, 2000 MacGyver aka Habeeb J. Dihu * Copyright (c) 2001-2005 The ProFTPD Project team * * 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. * * As a special exemption, Public Flood Software/MacGyver aka Habeeb J. Dihu * and other respective copyright holders give permission to link this program * with OpenSSL, and distribute the resulting executable, without including * the source code for OpenSSL in the source distribution. */ /* Inet support functions, many wrappers for netdb functions * $Id: inet.c,v 1.94 2005/11/29 02:42:19 castaglia Exp $ */ #include "conf.h" #include "privs.h" extern server_rec *main_server; /* A private work pool for all pr_inet_* functions to use. */ static pool *inet_pool = NULL; static int ip_proto = IPPROTO_IP; #ifdef PR_USE_IPV6 static int ipv6_proto = IPPROTO_IPV6; #endif /* PR_USE_IPV6 */ static int tcp_proto = IPPROTO_TCP; static int inet_errno = 0; /* Holds errno */ /* The default address family to use when creating a socket, if a pr_netaddr_t * is not given. This is mainly for the benefit of initialize_connection(). */ static int inet_family = 0; /* Called by others after running a number of pr_inet_* functions in order * to free up memory. */ void pr_inet_clear(void) { destroy_pool(inet_pool); inet_pool = NULL; } /* All inet_ interface functions take a pool as the first arg, which * is where any returned allocated memory is taken from. For purposes * of uniformity the pool is included in all calls, even those that * don't need to return allocated memory. */ int pr_inet_set_default_family(pool *p, int family) { int old_family = inet_family; inet_family = family; return old_family; } /* Find a service and return its port number. */ int pr_inet_getservport(pool *p, const char *serv, const char *proto) { struct servent *servent = getservbyname(serv, proto); /* getservbyname returns the port in network byte order. */ return (servent ? ntohs(servent->s_port) : -1); } /* Validate anything returned from the 'outside', since it's untrusted * information. */ char *pr_inet_validate(char *buf) { char *p; /* Validate anything returned from a DNS. */ for (p = buf; p && *p; p++) { /* Per RFC requirements, these are all that are valid from a DNS. */ if (!isalnum((int) *p) && *p != '.' && *p != '-' #ifdef PR_USE_IPV6 && *p != ':' #endif /* PR_USE_IPV6 */ ) { /* We set it to _ because we know that's an invalid, yet safe, option * for a DNS entry. */ *p = '_'; } } return buf; } static void conn_cleanup_cb(void *cv) { conn_t *c = (conn_t *) cv; if (c->instrm) pr_netio_close(c->instrm); if (c->outstrm && c->outstrm != c->instrm) pr_netio_close(c->outstrm); if (c->listen_fd != -1) close(c->listen_fd); if (c->rfd != -1) close(c->rfd); if (c->wfd != -1) close(c->wfd); } /* Copy a connection structure, also creates a sub pool for the new * connection. */ conn_t *pr_inet_copy_connection(pool *p, conn_t *c) { conn_t *res = NULL; pool *sub_pool = NULL; sub_pool = make_sub_pool(p); pr_pool_tag(sub_pool, "pr_inet_copy_connection() subpool"); res = (conn_t *) pcalloc(sub_pool,sizeof(conn_t)); memcpy(res, c, sizeof(conn_t)); res->pool = sub_pool; res->instrm = res->outstrm = NULL; if (c->local_addr) { res->local_addr = pr_netaddr_alloc(res->pool); pr_netaddr_set_family(res->local_addr, pr_netaddr_get_family(c->local_addr)); pr_netaddr_set_sockaddr(res->local_addr, pr_netaddr_get_sockaddr(c->local_addr)); } if (c->remote_addr) { res->remote_addr = pr_netaddr_alloc(res->pool); pr_netaddr_set_family(res->remote_addr, pr_netaddr_get_family(c->remote_addr)); pr_netaddr_set_sockaddr(res->remote_addr, pr_netaddr_get_sockaddr(c->remote_addr)); } if (c->remote_name) res->remote_name = pstrdup(res->pool, c->remote_name); register_cleanup(res->pool, (void *) res, conn_cleanup_cb, conn_cleanup_cb); return res; } /* Initialize a new connection record, also creates a new subpool just for the * new connection. */ static conn_t *inet_initialize_connection(pool *p, xaset_t *servers, int fd, pr_netaddr_t *bind_addr, int port, int retry_bind, int reporting) { pool *sub_pool = NULL; conn_t *c; pr_netaddr_t na; int addr_family; int res = 0, one = 1, hold_errno; if ((!servers || !servers->xas_list) && !main_server) { errno = EINVAL; return NULL; } if (!inet_pool) { inet_pool = make_sub_pool(permanent_pool); pr_pool_tag(inet_pool, "Inet Pool"); } /* Initialize the netaddr. */ pr_netaddr_clear(&na); sub_pool = make_sub_pool(p); pr_pool_tag(sub_pool, "inet_initialize_connection() subpool"); c = (conn_t *) pcalloc(sub_pool, sizeof(conn_t)); c->pool = sub_pool; c->local_port = port; c->rfd = c->wfd = -1; if (bind_addr) addr_family = pr_netaddr_get_family(bind_addr); else if (inet_family) addr_family = inet_family; else { /* If no default family has been set, then default to IPv6 (if IPv6 * support is enabled), otherwise use IPv4. */ #ifdef PR_USE_IPV6 addr_family = AF_INET6; #else addr_family = AF_INET; #endif /* PR_USE_IPV6 */ } /* If fd == -1, there is no currently open socket, so create one. */ if (fd == -1) { socklen_t salen; register unsigned int i = 0; /* Certain versions of Solaris apparently require us to be root * in order to create a socket inside a chroot. * * FreeBSD 2.2.6 (possibly other versions as well), has a security * "feature" which disallows SO_REUSEADDR from working if the socket * owners don't match. The easiest thing to do is simply make sure * the socket is created as root. (Note: this "feature" seems to apply * to _all_ BSDs.) */ #if defined(SOLARIS2) || defined(FREEBSD2) || defined(FREEBSD3) || \ defined(FREEBSD4) || defined(FREEBSD5) || defined(__OpenBSD__) || \ defined(__NetBSD__) || defined(DARWIN6) || defined(DARWIN7) || \ defined(DARWIN8) || \ defined(SCO3) || defined(CYGWIN) || defined(SYSV4_2MP) || \ defined(SYSV5UNIXWARE7) # ifdef SOLARIS2 if (port != INPORT_ANY && port < 1024) { # endif pr_signals_block(); PRIVS_ROOT # ifdef SOLARIS2 } # endif #endif fd = socket(addr_family, SOCK_STREAM, tcp_proto); #if defined(SOLARIS2) || defined(FREEBSD2) || defined(FREEBSD3) || \ defined(FREEBSD4) || defined(FREEBSD5) || defined(__OpenBSD__) || \ defined(__NetBSD__) || defined(DARWIN6) || defined(DARWIN7) || \ defined(DARWIN8) || \ defined(SCO3) || defined(CYGWIN) || defined(SYSV4_2MP) || \ defined(SYSV5UNIXWARE7) # ifdef SOLARIS2 if (port != INPORT_ANY && port < 1024) { # endif PRIVS_RELINQUISH pr_signals_unblock(); # ifdef SOLARIS2 } # endif #endif if (fd == -1) { /* On failure, destroy the connection and return NULL. */ inet_errno = errno; if (reporting) pr_log_pri(PR_LOG_ERR, "socket() failed in connection initialization: " "%s", strerror(inet_errno)); destroy_pool(c->pool); return NULL; } /* Allow address reuse. */ if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (void *) &one, sizeof(one)) < 0) pr_log_pri(PR_LOG_NOTICE, "error setting SO_REUSEADDR: %s", strerror(errno)); memset(&na, 0, sizeof(na)); pr_netaddr_set_family(&na, addr_family); if (bind_addr) pr_netaddr_set_sockaddr(&na, pr_netaddr_get_sockaddr(bind_addr)); else pr_netaddr_set_sockaddr_any(&na); #if defined(PR_USE_IPV6) && defined(IPV6_V6ONLY) if (addr_family == AF_INET6) { int on = 0; # ifdef SOL_IP int level = SOL_IP; # else int level = ipv6_proto; # endif /* SOL_IP */ /* If creating a wildcard socket IPv6 socket, make sure that it * will accept IPv4 connections as well. This is the default on * Linux and Solaris; BSD usually defaults to allowing only IPv6 * (depending on the net.inet6.ip6.v6only sysctl value). * * Ideally, this setsockopt() call would be configurable via the * SocketOptions directive. */ if (setsockopt(fd, level, IPV6_V6ONLY, (void *) &on, sizeof(on)) < 0) pr_log_pri(PR_LOG_NOTICE, "error setting IPV6_V6ONLY: %s", strerror(errno)); } #endif /* PR_USE_IPV6 and IPV6_V6ONLY */ pr_netaddr_set_port(&na, htons(port)); if (port != INPORT_ANY && port < 1024) { pr_signals_block(); PRIVS_ROOT } /* According to one expert, the very nature of the FTP protocol, and it's * multiple data-connections creates problems with "rapid-fire" connections * (transfering lots of files) causing an eventual "Address already in use" * error. As a result, this nasty kludge retries ten times (once per * second) if the port being bound to is INPORT_ANY. */ for (i = 10; i; i--) { res = bind(fd, pr_netaddr_get_sockaddr(&na), pr_netaddr_get_sockaddr_len(&na)); hold_errno = errno; if (res == -1 && errno == EINTR) { pr_signals_handle(); i++; continue; } if (res != -1 || errno != EADDRINUSE || (port != INPORT_ANY && !retry_bind)) break; if (port != INPORT_ANY && port < 1024) { PRIVS_RELINQUISH pr_signals_unblock(); } pr_timer_sleep(1); if (port != INPORT_ANY && port < 1024) { pr_signals_block(); PRIVS_ROOT } } if (res == -1) { if (port != INPORT_ANY && port < 1024) { PRIVS_RELINQUISH pr_signals_unblock(); } if (reporting) { pr_log_pri(PR_LOG_ERR, "Failed binding to %s, port %d: %s", pr_netaddr_get_ipstr(&na), port, strerror(hold_errno)); pr_log_pri(PR_LOG_ERR, "Check the ServerType directive to ensure " "you are configured correctly."); } inet_errno = hold_errno; destroy_pool(c->pool); close(fd); return NULL; } if (port != INPORT_ANY && port < 1024) { PRIVS_RELINQUISH pr_signals_unblock(); } /* We use getsockname here because the caller might be binding to * INPORT_ANY (0), in which case our port number will be dynamic. */ salen = pr_netaddr_get_sockaddr_len(&na); if (getsockname(fd, pr_netaddr_get_sockaddr(&na), &salen) != -1) { if (!c->local_addr) c->local_addr = pr_netaddr_alloc(c->pool); pr_netaddr_set_family(c->local_addr, pr_netaddr_get_family(&na)); pr_netaddr_set_sockaddr(c->local_addr, pr_netaddr_get_sockaddr(&na)); c->local_port = ntohs(pr_netaddr_get_port(&na)); } } c->listen_fd = fd; register_cleanup(c->pool, (void *) c, conn_cleanup_cb, conn_cleanup_cb); return c; } conn_t *pr_inet_create_connection(pool *p, xaset_t *servers, int fd, pr_netaddr_t *bind_addr, int port, int retry_bind) { conn_t *c = NULL; c = inet_initialize_connection(p, servers, fd, bind_addr, port, retry_bind, TRUE); /* This code is somewhat of a kludge, because error handling should * NOT occur in inet.c, it should be handled by the caller. */ if (!c) end_login(1); return c; } /* Attempt to create a connection bound to a given port range, returns NULL * if unable to bind to any port in the range. */ conn_t *pr_inet_create_connection_portrange(pool *p, xaset_t *servers, pr_netaddr_t *bind_addr, int low_port, int high_port) { int range_len, i; int *range, *ports; int attempt, random_index; conn_t *c = NULL; /* Make sure the temporary inet work pool exists. */ if (!inet_pool) { inet_pool = make_sub_pool(permanent_pool); pr_pool_tag(inet_pool, "Inet Pool"); } range_len = high_port - low_port + 1; range = (int *) pcalloc(inet_pool, range_len * sizeof(int)); ports = (int *) pcalloc(inet_pool, range_len * sizeof(int)); i = range_len; while (i--) range[i] = low_port + i; for (attempt = 3; attempt > 0 && !c; attempt--) { for (i = range_len - 1; i >= 0 && !c; i--) { /* If this is the first attempt through the range, randomize * the order of the port numbers used. */ if (attempt == 3) { /* Obtain a random index into the port array range. */ random_index = (int) ((1.0 * i * rand()) / (RAND_MAX+1.0)); /* Copy the port at that index into the array from which port * numbers will be selected when calling * inet_initialize_connection(). */ ports[i] = range[random_index]; /* Move non-selected numbers down so that the next randomly chosen * port will be from the range of as-yet untried ports. */ while (++random_index < i) range[random_index-1] = range[random_index]; } c = inet_initialize_connection(p, servers, -1, bind_addr, ports[i], FALSE, FALSE); if (!c && inet_errno != EADDRINUSE) { pr_log_pri(PR_LOG_ERR, "error initializing connection: %s", strerror(inet_errno)); end_login(1); } } } return c; } void pr_inet_close(pool *p, conn_t *c) { /* It is not necessary to close the fds or schedule netio streams for * removal, because the creator of the connection (either * pr_inet_create_connection() or pr_inet_copy_connection() will have * registered a pool cleanup handler (conn_cleanup_cb()) which will do all * this for us. Simply destroy the pool and all the dirty work gets done. */ destroy_pool(c->pool); } /* Perform shutdown/read on streams */ void pr_inet_lingering_close(pool *p, conn_t *c, long linger) { pr_inet_set_block(p, c); if (c->outstrm) pr_netio_lingering_close(c->outstrm, linger); /* Only close the input stream if it is actually a different stream than * the output stream. */ if (c->instrm != c->outstrm) pr_netio_lingering_close(c->instrm, linger); c->outstrm = NULL; c->instrm = NULL; destroy_pool(c->pool); } /* Similar to a lingering close, perform a lingering abort. */ void pr_inet_lingering_abort(pool *p, conn_t *c, long linger) { pr_inet_set_block(p, c); if (c->instrm) pr_netio_lingering_abort(c->instrm, linger); /* Only close the output stream if it is actually a different stream * than the input stream. * * Note: we do not call pr_netio_lingering_abort() on the input stream * since doing so would result in two 426 responses sent; we only * want and need one. */ if (c->outstrm != c->instrm) pr_netio_lingering_close(c->outstrm, linger); c->instrm = NULL; c->outstrm = NULL; destroy_pool(c->pool); } int pr_inet_set_proto_opts(pool *p, conn_t *c, int mss, int nodelay, int lowdelay, int throughput, int nopush) { int tos = 0; /* More portability fun. Traditional BSD-style sockets want the value from * getprotobyname() in the setsockopt(2) call; Linux wants SOL_TCP for * these options. Also, *BSD want IPPROTO_IP for IP_TOS options, Linux * wants SOL_IP. How many other platforms will have variations? Will * networking code always be this fragmented? */ #ifdef SOL_IP int ip_level = SOL_IP; #else int ip_level = ip_proto; #endif /* SOL_IP */ #ifdef SOL_TCP int tcp_level = SOL_TCP; #else int tcp_level = tcp_proto; #endif /* SOL_TCP */ /* Some of these setsockopt() calls may fail when they operate on IPv6 * sockets, rather than on IPv4 sockets. */ #ifdef TCP_NODELAY unsigned char *no_delay = get_param_ptr(main_server->conf, "tcpNoDelay", FALSE); if (!no_delay || *no_delay == TRUE) { if (c->wfd != -1) if (setsockopt(c->wfd, tcp_level, TCP_NODELAY, (void *) &nodelay, sizeof(nodelay)) < 0) pr_log_pri(PR_LOG_NOTICE, "error setting write fd TCP_NODELAY: %s", strerror(errno)); if (c->rfd != -1) if (setsockopt(c->rfd, IPPROTO_TCP, TCP_NODELAY, (void *) &nodelay, sizeof(nodelay)) < 0) pr_log_pri(PR_LOG_NOTICE, "error setting read fd TCP_NODELAY: %s", strerror(errno)); } #endif /* TCP_NODELAY */ #ifdef TCP_MAXSEG if (c->wfd != -1 && mss) if (setsockopt(c->wfd, tcp_level, TCP_MAXSEG, &mss, sizeof(mss)) < 0) pr_log_pri(PR_LOG_NOTICE, "error setting write fd TCP_MAXSEG(%d): %s", mss, strerror(errno)); if (c->rfd != -1 && mss) if (setsockopt(c->wfd, tcp_level, TCP_MAXSEG, &mss, sizeof(mss)) < 0) pr_log_pri(PR_LOG_NOTICE, "error setting read fd TCP_MAXSEG(%d): %s", mss, strerror(errno)); #endif /* TCP_MAXSEG */ #ifdef IPTOS_LOWDELAY if (lowdelay) tos = IPTOS_LOWDELAY; #endif /* IPTOS_LOWDELAY */ #ifdef IPTOS_THROUGHPUT if (throughput) tos = IPTOS_THROUGHPUT; #endif /* IPTOS_THROUGHPUT */ #ifdef IP_TOS /* Only set TOS flags on IPv4 sockets; IPv6 sockets don't seem to support * them. */ if (pr_netaddr_get_family(c->local_addr) == AF_INET) { if (c->wfd != -1) if (setsockopt(c->wfd, ip_level, IP_TOS, (void *) &tos, sizeof(tos)) < 0) pr_log_pri(PR_LOG_NOTICE, "error setting write fd IP_TOS: %s", strerror(errno)); if (c->rfd != -1) if (setsockopt(c->rfd, ip_level, IP_TOS, (void *) &tos, sizeof(tos)) < 0) pr_log_pri(PR_LOG_NOTICE, "error setting read fd IP_TOS: %s", strerror(errno)); } #endif /* IP_TOS */ #ifdef TCP_NOPUSH /* NOTE: TCP_NOPUSH is a BSDism. */ if (c->wfd != -1) if (setsockopt(c->wfd, tcp_level, TCP_NOPUSH, (void *) &nopush, sizeof(nopush)) < 0) pr_log_pri(PR_LOG_NOTICE, "error setting write fd TCP_NOPUSH: %s", strerror(errno)); if (c->rfd != -1) if (setsockopt(c->rfd, tcp_level, TCP_NOPUSH, (void *) &nopush, sizeof(nopush)) < 0) pr_log_pri(PR_LOG_NOTICE, "error setting read fd TCP_NOPUSH: %s", strerror(errno)); #endif /* TCP_NOPUSH */ return 0; } /* Set socket options on a connection. */ int pr_inet_set_socket_opts(pool *p, conn_t *c, int rcvbuf, int sndbuf) { int no_keep_alive = 0; int crcvbuf, csndbuf; socklen_t len; /* Linux and "most" newer networking OSes probably use a highly adaptive * window size system, which generally wouldn't require user-space * modification at all. Thus, check the current sndbuf and rcvbuf sizes * before changing them, and only change them if we are making them larger * than their current size. */ if (c->wfd != -1) { if (setsockopt(c->wfd, SOL_SOCKET, SO_KEEPALIVE, (void *) &no_keep_alive, sizeof(int)) < 0) pr_log_pri(PR_LOG_NOTICE, "error setting write fd SO_KEEPALIVE: %s", strerror(errno)); len = sizeof(csndbuf); getsockopt(c->wfd, SOL_SOCKET, SO_SNDBUF, (void *) &csndbuf, &len); if (sndbuf && sndbuf > csndbuf) if (setsockopt(c->wfd, SOL_SOCKET, SO_SNDBUF, (void *) &sndbuf, sizeof(sndbuf)) < 0) pr_log_pri(PR_LOG_NOTICE, "error setting SO_SNDBUF: %s", strerror(errno)); c->sndbuf = (sndbuf ? sndbuf : csndbuf); } if (c->rfd != -1) { if (setsockopt(c->rfd, SOL_SOCKET, SO_KEEPALIVE, (void *) &no_keep_alive, sizeof(int)) < 0) pr_log_pri(PR_LOG_NOTICE, "error setting read fd SO_KEEPALIVE: %s", strerror(errno)); len = sizeof(crcvbuf); getsockopt(c->rfd, SOL_SOCKET, SO_RCVBUF, (void *) &crcvbuf, &len); if (rcvbuf && rcvbuf > crcvbuf) if (setsockopt(c->rfd, SOL_SOCKET, SO_RCVBUF, (void *) &rcvbuf, sizeof(rcvbuf)) < 0) pr_log_pri(PR_LOG_NOTICE, "error setting SO_RCVFBUF: %s", strerror(errno)); c->rcvbuf = (rcvbuf ? rcvbuf : crcvbuf); } return 0; } #ifdef SO_OOBINLINE static void set_oobinline(int fd) { int on = 1; if (fd != -1) if (setsockopt(fd, SOL_SOCKET, SO_OOBINLINE, (void*)&on, sizeof(on)) < 0) pr_log_pri(PR_LOG_NOTICE, "error setting SO_OOBINLINE: %s", strerror(errno)); } #endif #ifdef F_SETOWN static void set_owner(int fd) { if (fd != -1) fcntl(fd, F_SETOWN, getpid()); } #endif /* Put a socket in async mode (so SIGURG is raised on OOB) */ int pr_inet_set_async(pool *p, conn_t *c) { #ifdef SO_OOBINLINE set_oobinline(c->listen_fd); set_oobinline(c->rfd); set_oobinline(c->wfd); #endif #ifdef F_SETOWN set_owner(c->listen_fd); set_owner(c->rfd); set_owner(c->wfd); #endif return 0; } /* Put a socket in nonblocking mode. */ int pr_inet_set_nonblock(pool *p, conn_t *c) { int flags; int res = -1; errno = EBADF; /* Default */ if (c->mode == CM_LISTEN) { flags = fcntl(c->listen_fd, F_GETFL); res = fcntl(c->listen_fd, F_SETFL, flags|O_NONBLOCK); } else { if (c->rfd != -1) { flags = fcntl(c->rfd, F_GETFL); res = fcntl(c->rfd, F_SETFL, flags|O_NONBLOCK); } if (c->wfd != -1) { flags = fcntl(c->wfd, F_GETFL); res = fcntl(c->wfd, F_SETFL, flags|O_NONBLOCK); } } return res; } int pr_inet_set_block(pool *p, conn_t *c) { int flags; int res = -1; errno = EBADF; /* Default */ if (c->mode == CM_LISTEN) { flags = fcntl(c->listen_fd, F_GETFL); res = fcntl(c->listen_fd, F_SETFL, flags & (U32BITS ^ O_NONBLOCK)); } else { if (c->rfd != -1) { flags = fcntl(c->rfd, F_GETFL); res = fcntl(c->rfd, F_SETFL, flags & (U32BITS ^ O_NONBLOCK)); } if (c->wfd != -1) { flags = fcntl(c->wfd, F_GETFL); res = fcntl(c->wfd, F_SETFL, flags & (U32BITS ^ O_NONBLOCK)); } } return res; } /* Put a connection in listen mode */ int pr_inet_listen(pool *p, conn_t *c, int backlog) { if (!c || c->mode == CM_LISTEN) return -1; while (TRUE) if (listen(c->listen_fd, backlog) == -1) { if (errno == EINTR) { pr_signals_handle(); continue; } pr_log_pri(PR_LOG_ERR, "unable to listen on %s#%u: %s", pr_netaddr_get_ipstr(c->local_addr), c->local_port, strerror(errno)); end_login(1); } else break; c->mode = CM_LISTEN; return 0; } /* Reset a connection back to listen mode. Enables blocking mode * for safety. */ int pr_inet_resetlisten(pool *p, conn_t *c) { c->mode = CM_LISTEN; pr_inet_set_block(c->pool, c); return 0; } int pr_inet_connect(pool *p, conn_t *c, pr_netaddr_t *addr, int port) { pr_netaddr_t remote_na; int res = 0; pr_inet_set_block(p, c); /* No need to initialize the remote_na netaddr here, as we're directly * copying the data from the given netaddr into that memory area. */ memcpy(&remote_na, addr, sizeof(remote_na)); pr_netaddr_set_port(&remote_na, htons(port)); c->mode = CM_CONNECT; while (TRUE) { if ((res = connect(c->listen_fd, pr_netaddr_get_sockaddr(&remote_na), pr_netaddr_get_sockaddr_len(&remote_na))) == -1 && errno == EINTR) { pr_signals_handle(); continue; } else break; } if (res == -1) { c->mode = CM_ERROR; c->xerrno = errno; return -1; } c->mode = CM_OPEN; if (pr_inet_get_conn_info(c, c->listen_fd) < 0) { c->xerrno = errno; return -1; } pr_inet_set_block(c->pool, c); return 1; } /* Attempt to connect a connection, returning immediately with 1 if connected, * 0 if not connected, or -1 if error. Only needs to be called once, and can * then be selected for writing. */ int pr_inet_connect_nowait(pool *p, conn_t *c, pr_netaddr_t *addr, int port) { pr_netaddr_t remote_na; pr_inet_set_nonblock(p, c); /* No need to initialize the remote_na netaddr here, as we're directly * copying the data from the given netaddr into that memory area. */ memcpy(&remote_na, addr, sizeof(remote_na)); pr_netaddr_set_port(&remote_na, htons(port)); c->mode = CM_CONNECT; if (connect(c->listen_fd, pr_netaddr_get_sockaddr(&remote_na), pr_netaddr_get_sockaddr_len(&remote_na)) == -1) { if (errno != EINPROGRESS && errno != EALREADY) { c->mode = CM_ERROR; c->xerrno = errno; return -1; } return 0; } c->mode = CM_OPEN; if (pr_inet_get_conn_info(c, c->listen_fd) < 0) { c->xerrno = errno; return -1; } pr_inet_set_block(c->pool, c); return 1; } /* Accepts a new connection, returning immediately with -1 if no connection is * available. If a connection is accepted, creating a new conn_t and potential * resolving is deferred, and a normal socket fd is returned for the new * connection, which can later be used in pr_inet_openrw() to fully open and * resolve addresses. */ int pr_inet_accept_nowait(pool *p, conn_t *c) { int fd; if (c->mode == CM_LISTEN) pr_inet_set_nonblock(c->pool, c); /* A directive could enforce only IPv4 or IPv6 connections here, by * actually using a sockaddr argument to accept(2), and checking the * family of the connecting entity. */ c->mode = CM_ACCEPT; while (TRUE) { pr_signals_handle(); fd = accept(c->listen_fd, NULL, NULL); if (fd == -1) { if (errno == EINTR) continue; if (errno != EWOULDBLOCK) { c->mode = CM_ERROR; c->xerrno = errno; return -1; } c->mode = CM_LISTEN; c->xerrno = 0; return -1; } break; } /* Leave the connection in CM_ACCEPT mode, so others can see * our state. Re-enable blocking mode, however. */ pr_inet_set_block(c->pool, c); return fd; } /* Accepts a new connection, cloning the existing conn_t and returning * it, or NULL upon error. */ conn_t *pr_inet_accept(pool *p, conn_t *d, conn_t *c, int rfd, int wfd, unsigned char resolve) { conn_t *res = NULL; unsigned char *allow_foreign_addr = NULL; int fd = -1; pr_netaddr_t na; socklen_t nalen; /* Initialize the netaddr. */ pr_netaddr_clear(&na); pr_netaddr_set_family(&na, pr_netaddr_get_family(c->remote_addr)); nalen = pr_netaddr_get_sockaddr_len(&na); d->mode = CM_ACCEPT; allow_foreign_addr = get_param_ptr(TOPLEVEL_CONF, "AllowForeignAddress", FALSE); /* A directive could enforce only IPv4 or IPv6 connections here, by * actually using a sockaddr argument to accept(2), and checking the * family of the connecting entity. */ while (TRUE) { pr_signals_handle(); fd = accept(d->listen_fd, pr_netaddr_get_sockaddr(&na), &nalen); if (fd != -1) { if ((!allow_foreign_addr || *allow_foreign_addr == FALSE) && (getpeername(fd, pr_netaddr_get_sockaddr(&na), &nalen) != -1)) { if (pr_netaddr_cmp(&na, c->remote_addr) != 0) { pr_log_pri(PR_LOG_NOTICE, "SECURITY VIOLATION: Passive connection from %s rejected.", pr_netaddr_get_ipstr(&na)); close(fd); continue; } } d->mode = CM_OPEN; res = pr_inet_openrw(p, d, NULL, PR_NETIO_STRM_DATA, fd, rfd, wfd, resolve); } else { if (errno == EINTR) continue; d->mode = CM_ERROR; d->xerrno = errno; } break; } return res; } int pr_inet_get_conn_info(conn_t *c, int fd) { pr_netaddr_t na; socklen_t nalen; /* Sanity check. */ if (fd < 0) { errno = EBADF; return -1; } /* Initialize the netaddr. */ pr_netaddr_clear(&na); #ifdef PR_USE_IPV6 pr_netaddr_set_family(&na, AF_INET6); #else pr_netaddr_set_family(&na, AF_INET); #endif /* PR_USE_IPV6 */ nalen = pr_netaddr_get_sockaddr_len(&na); if (getsockname(fd, pr_netaddr_get_sockaddr(&na), &nalen) != -1) { if (!c->local_addr) c->local_addr = pr_netaddr_alloc(c->pool); /* getsockname(2) will read the local socket information into the struct * sockaddr * given. Which means that the address family of the local * socket can be found in struct sockaddr *->sa_family, and not (yet) * via pr_netaddr_get_family(). */ pr_netaddr_set_family(c->local_addr, pr_netaddr_get_sockaddr(&na)->sa_family); pr_netaddr_set_sockaddr(c->local_addr, pr_netaddr_get_sockaddr(&na)); c->local_port = ntohs(pr_netaddr_get_port(&na)); } else return -1; /* "Reset" the pr_netaddr_t struct for the getpeername(2) call. */ #ifdef PR_USE_IPV6 pr_netaddr_set_family(&na, AF_INET6); #else pr_netaddr_set_family(&na, AF_INET); #endif /* PR_USE_IPV6 */ nalen = pr_netaddr_get_sockaddr_len(&na); if (getpeername(fd, pr_netaddr_get_sockaddr(&na), &nalen) != -1) { c->remote_addr = pr_netaddr_alloc(c->pool); pr_netaddr_set_family(c->remote_addr, pr_netaddr_get_sockaddr(&na)->sa_family); pr_netaddr_set_sockaddr(c->remote_addr, pr_netaddr_get_sockaddr(&na)); c->remote_port = ntohs(pr_netaddr_get_port(&na)); } else return -1; return 0; } /* Associate already open streams with the connection, returns NULL if either * stream points to a non-socket descriptor. If addr is non-NULL, remote * address discovery is attempted. If resolve is non-zero, the remote address * is reverse resolved. */ conn_t *pr_inet_associate(pool *p, conn_t *c, pr_netaddr_t *addr, pr_netio_stream_t *in, pr_netio_stream_t *out, int resolve) { int rfd, wfd; int socktype; socklen_t socktype_len = sizeof(socktype); conn_t *res; rfd = PR_NETIO_FD(in); wfd = PR_NETIO_FD(out); if (getsockopt(rfd, SOL_SOCKET, SO_TYPE, (void *) &socktype, &socktype_len) == -1 || socktype != SOCK_STREAM) return NULL; if (getsockopt(wfd, SOL_SOCKET, SO_TYPE, (void *) &socktype, &socktype_len) == -1 || socktype != SOCK_STREAM) return NULL; res = pr_inet_copy_connection(p, c); res->rfd = rfd; res->wfd = wfd; res->instrm = in; res->outstrm = out; res->mode = CM_OPEN; if (pr_inet_get_conn_info(res, wfd) < 0) return NULL; /* Get the remote address */ if (addr) { if (!res->remote_addr) res->remote_addr = pr_netaddr_alloc(res->pool); memcpy(res->remote_addr, addr, sizeof(pr_netaddr_t)); } if (resolve && res->remote_addr) res->remote_name = pr_netaddr_get_dnsstr(res->remote_addr); if (!res->remote_name) res->remote_name = pr_netaddr_get_ipstr(res->remote_addr); pr_inet_set_socket_opts(res->pool, res, 0, 0); return res; } /* Open streams for a new socket. If rfd and wfd != -1, two new fds are duped * to the respective read/write fds. If the fds specified correspond to the * normal stdin and stdout, the streams opened will be assigned to stdin and * stdout in an intuitive fashion (so that they may be later be used by * printf/fgets type libc functions). If inaddr is non-NULL, the address is * assigned to the connection (as the *source* of the connection). If it is * NULL, remote address discovery will be attempted. The connection structure * appropriate fields are filled in, including the *destination* address. * Finally, if resolve is non-zero, this function will attempt to reverse * resolve the remote address. A new connection structure is created in the * specified pool. * * Important, do not call any log_* functions from inside of pr_inet_openrw() * or any functions it calls, as the possibility for fd overwriting occurs. */ conn_t *pr_inet_openrw(pool *p, conn_t *c, pr_netaddr_t *addr, int strm_type, int fd, int rfd, int wfd, int resolve) { conn_t *res = NULL; int close_fd = TRUE; res = pr_inet_copy_connection(p, c); res->listen_fd = -1; /* Note: there are some cases where the given file descriptor will * intentionally be bad (e.g. in get_ident() lookups). In this case, * errno will have a value of EBADF; this is an "acceptable" error. Any * other errno value constitutes an unacceptable error. */ if (pr_inet_get_conn_info(res, fd) < 0 && errno != EBADF) return NULL; if (addr) { if (!res->remote_addr) res->remote_addr = pr_netaddr_alloc(res->pool); memcpy(res->remote_addr, addr, sizeof(pr_netaddr_t)); } if (resolve && res->remote_addr) res->remote_name = pr_netaddr_get_dnsstr(res->remote_addr); if (!res->remote_name) res->remote_name = pr_netaddr_get_ipstr(res->remote_addr); if (fd == -1 && c->listen_fd != -1) fd = c->listen_fd; if (rfd != -1) { if (fd != rfd) dup2(fd, rfd); else close_fd = FALSE; } else rfd = dup(fd); if (wfd != -1) { if (fd != wfd) { if (wfd == STDOUT_FILENO) fflush(stdout); dup2(fd, wfd); } else close_fd = FALSE; } else wfd = dup(fd); /* Now discard the original socket */ if (rfd != -1 && wfd != -1 && close_fd) close(fd); res->rfd = rfd; res->wfd = wfd; res->instrm = pr_netio_open(res->pool, strm_type, res->rfd, PR_NETIO_IO_RD); res->outstrm = pr_netio_open(res->pool, strm_type, res->wfd, PR_NETIO_IO_WR); /* Set options on the sockets. */ pr_inet_set_socket_opts(res->pool, res, 0, 0); pr_inet_set_block(res->pool, res); res->mode = CM_OPEN; #if defined(HAVE_STROPTS_H) && defined(I_SRDOPT) && defined(RPROTDIS) && \ defined(SOLARIS2) /* This is needed to work around control messages in STREAMS devices * (as on Solaris 9/NFS). */ while (ioctl(res->rfd, I_SRDOPT, RPROTDIS) < 0) { if (errno == EINTR) { pr_signals_handle(); continue; } pr_log_pri(PR_LOG_WARNING, "error calling ioctl(RPROTDIS): %s", strerror(errno)); break; } #endif return res; } void init_inet(void) { struct protoent *pr = NULL; #ifdef HAVE_SETPROTOENT setprotoent(FALSE); #endif pr = getprotobyname("ip"); if (pr != NULL) ip_proto = pr->p_proto; #ifdef PR_USE_IPV6 pr = getprotobyname("ipv6"); if (pr != NULL) ipv6_proto = pr->p_proto; #endif /* PR_USE_IPV6 */ pr = getprotobyname("tcp"); if (pr != NULL) tcp_proto = pr->p_proto; #ifdef HAVE_ENDPROTOENT endprotoent(); #endif if (inet_pool) destroy_pool(inet_pool); inet_pool = make_sub_pool(permanent_pool); pr_pool_tag(inet_pool, "Inet Pool"); }