/* 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" /* * 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; 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 it not important */ if (getaddrinfo (addr, NULL, &hints, &res) != 0) 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; } /* * 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) { int sockfd, n; struct addrinfo hints, *res, *ressave; char portstr[6]; assert (host != NULL); assert (port > 0); 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 info for %s", host); return -1; } 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) { close (sockfd); continue; /* can't bind, so try again */ } } else if (config.bind_address) { if (bind_socket (sockfd, config.bind_address, res->ai_family) < 0) { close (sockfd); continue; /* can't bind, so try again */ } } if (connect (sockfd, res->ai_addr, res->ai_addrlen) == 0) 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", host); 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, "error calling getnameinfo: %s", gai_strerror(errno)); 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, vector_t listen_fds) { struct addrinfo hints, *result, *rp; char portstr[6]; int ret = -1; 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); if (getaddrinfo (addr, portstr, &hints, &result) != 0) { log_message (LOG_ERR, "Unable to getaddrinfo() because of %s", strerror (errno)); return -1; } for (rp = result; rp != NULL; rp = rp->ai_next) { int listenfd; listenfd = listen_on_one_socket(rp); if (listenfd == -1) { continue; } vector_append (listen_fds, &listenfd, sizeof(int)); /* 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; } /* * Return the peer's socket information. */ int getpeer_information (int fd, char *ipaddr, char *string_addr) { struct sockaddr_storage sa; socklen_t salen = sizeof sa; assert (fd >= 0); assert (ipaddr != NULL); assert (string_addr != NULL); /* Set the strings to default values */ ipaddr[0] = '\0'; strlcpy (string_addr, "[unknown]", HOSTNAME_LENGTH); /* Look up the IP address */ if (getpeername (fd, (struct sockaddr *) &sa, &salen) != 0) return -1; if (get_ip_string ((struct sockaddr *) &sa, ipaddr, IP_LENGTH) == NULL) return -1; /* Get the full host name */ return getnameinfo ((struct sockaddr *) &sa, salen, string_addr, HOSTNAME_LENGTH, NULL, 0, 0); }