/* tinyproxy - A fast light-weight HTTP proxy * Copyright (C) 1998 Steven Young * Copyright (C) 1999, 2004 Robert James Kaes * Copyright (C) 2000 Chris Lightfoot * * 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ /* Sockets are created and destroyed here. When a new connection comes in from * a client, we need to copy the socket and the create a second socket to the * remote server the client is trying to connect to. Also, the listening * socket is created and destroyed here. Sounds more impressive than it * actually is. */ #include "main.h" #include "log.h" #include "heap.h" #include "network.h" #include "sock.h" #include "text.h" #include "conf.h" #include "loop.h" #include "sblist.h" /* * Return a human readable error for getaddrinfo() and getnameinfo(). */ static const char * get_gai_error (int n) { if (n == EAI_SYSTEM) return strerror (errno); else return gai_strerror (n); } /* * Bind the given socket to the supplied address. The socket is * returned if the bind succeeded. Otherwise, -1 is returned * to indicate an error. */ static int bind_socket (int sockfd, const char *addr, int family) { struct addrinfo hints, *res, *ressave; int n; assert (sockfd >= 0); assert (addr != NULL && strlen (addr) != 0); memset (&hints, 0, sizeof (struct addrinfo)); hints.ai_family = family; hints.ai_socktype = SOCK_STREAM; /* The local port is not important */ n = getaddrinfo (addr, NULL, &hints, &res); if (n != 0) { log_message (LOG_INFO, "bind_socket: getaddrinfo failed for %s: %s", addr, get_gai_error (n)); return -1; } ressave = res; /* Loop through the addresses and try to bind to each */ do { if (bind (sockfd, res->ai_addr, res->ai_addrlen) == 0) break; /* success */ } while ((res = res->ai_next) != NULL); freeaddrinfo (ressave); if (res == NULL) /* was not able to bind to any address */ return -1; return sockfd; } /** * Try binding the given socket to supplied addresses, stopping when one succeeds. */ static int bind_socket_list (int sockfd, sblist *addresses, int family) { size_t nb_addresses = sblist_getsize(addresses); size_t i; for (i = 0; i < nb_addresses; i++) { const char *address = *(const char **)sblist_get(addresses, i); if (bind_socket(sockfd, address, family) >= 0) { log_message(LOG_INFO, "Bound to %s", address); return 0; } } return -1; } /* * Open a connection to a remote host. It's been re-written to use * the getaddrinfo() library function, which allows for a protocol * independent implementation (mostly for IPv4 and IPv6 addresses.) */ int opensock (const char *host, int port, const char *bind_to, const char *bind_to_alt) { int sockfd, n; struct addrinfo hints, *res, *ressave; char portstr[6]; assert (host != NULL); assert (port > 0); log_message(LOG_INFO, "opensock: opening connection to %s:%d", host, port); if (bind_to) { log_message(LOG_INFO, "opensock: bind to %s or %s", bind_to, bind_to_alt); } memset (&hints, 0, sizeof (struct addrinfo)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; snprintf (portstr, sizeof (portstr), "%d", port); n = getaddrinfo (host, portstr, &hints, &res); if (n != 0) { log_message (LOG_ERR, "opensock: Could not retrieve address info for %s:%d: %s", host, port, get_gai_error (n)); return -1; } log_message(LOG_INFO, "opensock: getaddrinfo returned for %s:%d", host, port); ressave = res; do { sockfd = socket (res->ai_family, res->ai_socktype, res->ai_protocol); if (sockfd < 0) continue; /* ignore this one */ /* Bind to the specified address */ if (bind_to) { if (bind_socket (sockfd, bind_to, res->ai_family) < 0 && (!bind_to_alt || bind_socket (sockfd, bind_to_alt, res->ai_family) < 0)) { close (sockfd); continue; /* can't bind, so try again */ } } else if (config->bind_addrs) { if (bind_socket_list (sockfd, config->bind_addrs, res->ai_family) < 0) { close (sockfd); continue; /* can't bind, so try again */ } } if (connect (sockfd, res->ai_addr, res->ai_addrlen) == 0) { union sockaddr_union *p = (void*) res->ai_addr, u; int af = res->ai_addr->sa_family; unsigned dport = ntohs(af == AF_INET ? p->v4.sin_port : p->v6.sin6_port); socklen_t slen = sizeof u; if (dport == config->port) { getsockname(sockfd, (void*)&u, &slen); loop_records_add(&u); } break; /* success */ } close (sockfd); } while ((res = res->ai_next) != NULL); freeaddrinfo (ressave); if (res == NULL) { log_message (LOG_ERR, "opensock: Could not establish a connection to %s:%d", host, port); return -1; } return sockfd; } /* * Set the socket to non blocking -rjkaes */ int socket_nonblocking (int sock) { int flags; assert (sock >= 0); flags = fcntl (sock, F_GETFL, 0); return fcntl (sock, F_SETFL, flags | O_NONBLOCK); } /* * Set the socket to blocking -rjkaes */ int socket_blocking (int sock) { int flags; assert (sock >= 0); flags = fcntl (sock, F_GETFL, 0); return fcntl (sock, F_SETFL, flags & ~O_NONBLOCK); } /** * Try to listen on one socket based on the addrinfo * as returned from getaddrinfo. * * Return the file descriptor upon success, -1 upon error. */ static int listen_on_one_socket(struct addrinfo *ad) { int listenfd; int ret; const int on = 1; char numerichost[NI_MAXHOST]; int flags = NI_NUMERICHOST; ret = getnameinfo(ad->ai_addr, ad->ai_addrlen, numerichost, NI_MAXHOST, NULL, 0, flags); if (ret != 0) { log_message(LOG_ERR, "getnameinfo failed: %s", get_gai_error (ret)); return -1; } log_message(LOG_INFO, "trying to listen on host[%s], family[%d], " "socktype[%d], proto[%d]", numerichost, ad->ai_family, ad->ai_socktype, ad->ai_protocol); listenfd = socket(ad->ai_family, ad->ai_socktype, ad->ai_protocol); if (listenfd == -1) { log_message(LOG_ERR, "socket() failed: %s", strerror(errno)); return -1; } ret = setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); if (ret != 0) { log_message(LOG_ERR, "setsockopt failed to set SO_REUSEADDR: %s", strerror(errno)); close(listenfd); return -1; } if (ad->ai_family == AF_INET6) { ret = setsockopt(listenfd, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on)); if (ret != 0) { log_message(LOG_ERR, "setsockopt failed to set IPV6_V6ONLY: %s", strerror(errno)); close(listenfd); return -1; } } ret = bind(listenfd, ad->ai_addr, ad->ai_addrlen); if (ret != 0) { log_message(LOG_ERR, "bind failed: %s", strerror (errno)); close(listenfd); return -1; } ret = listen(listenfd, MAXLISTEN); if (ret != 0) { log_message(LOG_ERR, "listen failed: %s", strerror(errno)); close(listenfd); return -1; } log_message(LOG_INFO, "listening on fd [%d]", listenfd); return listenfd; } /* * Start listening on a socket. Create a socket with the selected port. * If the provided address is NULL, we may listen on multiple sockets, * e.g. the wildcard addresse for IPv4 and IPv6, depending on what is * supported. If the address is not NULL, we only listen on the first * address reported by getaddrinfo that works. * * Upon success, the listen-fds are added to the listen_fds list * and 0 is returned. Upon error, -1 is returned. */ int listen_sock (const char *addr, uint16_t port, sblist* listen_fds) { struct addrinfo hints, *result, *rp; char portstr[6]; int ret = -1; int n; assert (port > 0); assert (listen_fds != NULL); log_message(LOG_INFO, "listen_sock called with addr = '%s'", addr == NULL ? "(NULL)" : addr); memset (&hints, 0, sizeof (struct addrinfo)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_flags = AI_PASSIVE; snprintf (portstr, sizeof (portstr), "%d", port); n = getaddrinfo (addr, portstr, &hints, &result); if (n != 0) { log_message (LOG_ERR, "Unable to getaddrinfo() for %s:%d because of %s", addr, port, get_gai_error (n)); return -1; } for (rp = result; rp != NULL; rp = rp->ai_next) { int listenfd; listenfd = listen_on_one_socket(rp); if (listenfd == -1) { continue; } sblist_add (listen_fds, &listenfd); /* success */ ret = 0; if (addr != NULL) { /* * Unless wildcard is requested, only listen * on the first result that works. */ break; } } if (ret != 0) { log_message (LOG_ERR, "Unable to listen on any address."); } freeaddrinfo (result); return ret; } /* * Takes a socket descriptor and returns the socket's IP address. */ int getsock_ip (int fd, char *ipaddr) { struct sockaddr_storage name; socklen_t namelen = sizeof (name); assert (fd >= 0); if (getsockname (fd, (struct sockaddr *) &name, &namelen) != 0) { log_message (LOG_ERR, "getsock_ip: getsockname() error: %s", strerror (errno)); return -1; } if (get_ip_string ((struct sockaddr *) &name, ipaddr, IP_LENGTH) == NULL) return -1; return 0; } int getmapped_ipv4 (const char *ipv6mapped_conf, const char *ipaddr, char *ipaddr_mapped) { struct in6_addr addr6; struct in_addr addr; memset(&addr6, 0, sizeof(addr6)); if ( inet_pton(AF_INET6, ipaddr, &addr6) < 0 ) return -1; memcpy(&addr, addr6.s6_addr + 12, sizeof(addr)); return inet_ntop(AF_INET, &addr, ipaddr_mapped, IP_LENGTH) == NULL ? -1 : 0; } int getmapped_ipv6 (const char *ipv4mapped_conf, const char *ipaddr, char *ipaddr_mapped) { memset(ipaddr_mapped, 0, IP_LENGTH); strncpy(ipaddr_mapped, ipv4mapped_conf, IP_LENGTH-1); strncat(ipaddr_mapped, ipaddr, IP_LENGTH-1-strlen(ipaddr_mapped)); return 0; } /* * Return the peer's socket information. */ void getpeer_information (union sockaddr_union* addr, char *ipaddr, size_t ipaddr_len) { int af = addr->v4.sin_family; void *ipdata = af == AF_INET ? (void*)&addr->v4.sin_addr : (void*)&addr->v6.sin6_addr; inet_ntop(af, ipdata, ipaddr, ipaddr_len); }