+/**
+ * @file socket.c
+ *
+ * Socket functions used in rsync.
+ *
+ * This file is now converted to use the new-style getaddrinfo()
+ * interface, which supports IPv6 but is also supported on recent
+ * IPv4-only machines. On systems that don't have that interface, we
+ * emulate it using the KAME implementation.
+ **/
+
+#include "rsync.h"
+
+extern char *bind_address;
+extern int default_af_hint;
+
+/**
+ * Establish a proxy connection on an open socket to a web proxy by
+ * using the CONNECT method. If proxy_user and proxy_pass are not NULL,
+ * they are used to authenticate to the proxy using the "Basic"
+ * proxy-authorization protocol
+ **/
+static int establish_proxy_connection(int fd, char *host, int port,
+ char *proxy_user, char *proxy_pass)
+{
+ char *cp, buffer[1024];
+ char *authhdr, authbuf[1024];
+ int len;
+
+ if (proxy_user && proxy_pass) {
+ stringjoin(buffer, sizeof buffer,
+ proxy_user, ":", proxy_pass, NULL);
+ len = strlen(buffer);
+
+ if ((len*8 + 5) / 6 >= (int)sizeof authbuf) {
+ rprintf(FERROR,
+ "authentication information is too long\n");
+ return -1;
+ }
+
+ base64_encode(buffer, len, authbuf);
+ authhdr = "\r\nProxy-Authorization: Basic ";
+ } else {
+ *authbuf = '\0';
+ authhdr = "";
+ }
+
+ snprintf(buffer, sizeof buffer, "CONNECT %s:%d HTTP/1.0%s%s\r\n\r\n",
+ host, port, authhdr, authbuf);
+ len = strlen(buffer);
+ if (write(fd, buffer, len) != len) {
+ rsyserr(FERROR, errno, "failed to write to proxy");
+ return -1;
+ }
+
+ for (cp = buffer; cp < &buffer[sizeof buffer - 1]; cp++) {
+ if (read(fd, cp, 1) != 1) {
+ rsyserr(FERROR, errno, "failed to read from proxy");
+ return -1;
+ }
+ if (*cp == '\n')
+ break;
+ }
+
+ if (*cp != '\n')
+ cp++;
+ *cp-- = '\0';
+ if (*cp == '\r')
+ *cp = '\0';
+ if (strncmp(buffer, "HTTP/", 5) != 0) {
+ rprintf(FERROR, "bad response from proxy - %s\n",
+ buffer);
+ return -1;
+ }
+ for (cp = &buffer[5]; isdigit(*(uchar*)cp) || *cp == '.'; cp++) {}
+ while (*cp == ' ')
+ cp++;
+ if (*cp != '2') {
+ rprintf(FERROR, "bad response from proxy - %s\n",
+ buffer);
+ return -1;
+ }
+ /* throw away the rest of the HTTP header */
+ while (1) {
+ for (cp = buffer; cp < &buffer[sizeof buffer - 1]; cp++) {
+ if (read(fd, cp, 1) != 1) {
+ rsyserr(FERROR, errno,
+ "failed to read from proxy");
+ return -1;
+ }
+ if (*cp == '\n')
+ break;
+ }
+ if (cp > buffer && *cp == '\n')
+ cp--;
+ if (cp == buffer && (*cp == '\n' || *cp == '\r'))
+ break;
+ }
+ return 0;
+}
+
+
+/**
+ * Try to set the local address for a newly-created socket. Return -1
+ * if this fails.
+ **/
+int try_bind_local(int s, int ai_family, int ai_socktype,
+ const char *bind_address)
+{
+ int error;
+ struct addrinfo bhints, *bres_all, *r;
+
+ memset(&bhints, 0, sizeof bhints);
+ bhints.ai_family = ai_family;
+ bhints.ai_socktype = ai_socktype;
+ bhints.ai_flags = AI_PASSIVE;
+ if ((error = getaddrinfo(bind_address, NULL, &bhints, &bres_all))) {
+ rprintf(FERROR, RSYNC_NAME ": getaddrinfo %s: %s\n",
+ bind_address, gai_strerror(error));
+ return -1;
+ }
+
+ for (r = bres_all; r; r = r->ai_next) {
+ if (bind(s, r->ai_addr, r->ai_addrlen) == -1)
+ continue;
+ freeaddrinfo(bres_all);
+ return s;
+ }
+
+ /* no error message; there might be some problem that allows
+ * creation of the socket but not binding, perhaps if the
+ * machine has no ipv6 address of this name. */
+ freeaddrinfo(bres_all);
+ return -1;
+}
+
+
+/**
+ * Open a socket to a tcp remote host with the specified port .
+ *
+ * Based on code from Warren. Proxy support by Stephen Rothwell.
+ * getaddrinfo() rewrite contributed by KAME.net.
+ *
+ * Now that we support IPv6 we need to look up the remote machine's
+ * address first, using @p af_hint to set a preference for the type
+ * of address. Then depending on whether it has v4 or v6 addresses we
+ * try to open a connection.
+ *
+ * The loop allows for machines with some addresses which may not be
+ * reachable, perhaps because we can't e.g. route ipv6 to that network
+ * but we can get ip4 packets through.
+ *
+ * @param bind_address Local address to use. Normally NULL to bind
+ * the wildcard address.
+ *
+ * @param af_hint Address family, e.g. AF_INET or AF_INET6.
+ **/
+int open_socket_out(char *host, int port, const char *bind_address,
+ int af_hint)
+{
+ int type = SOCK_STREAM;
+ int error, s;
+ struct addrinfo hints, *res0, *res;
+ char portbuf[10];
+ char *h, *cp;
+ int proxied = 0;
+ char buffer[1024];
+ char *proxy_user = NULL, *proxy_pass = NULL;
+
+ /* if we have a RSYNC_PROXY env variable then redirect our
+ * connetcion via a web proxy at the given address. */
+ h = getenv("RSYNC_PROXY");
+ proxied = h != NULL && *h != '\0';
+
+ if (proxied) {
+ strlcpy(buffer, h, sizeof buffer);
+
+ /* Is the USER:PASS@ prefix present? */
+ if ((cp = strchr(buffer, '@')) != NULL) {
+ *cp++ = '\0';
+ /* The remainder is the HOST:PORT part. */
+ h = cp;
+
+ if ((cp = strchr(buffer, ':')) == NULL) {
+ rprintf(FERROR,
+ "invalid proxy specification: should be USER:PASS@HOST:PORT\n");
+ return -1;
+ }
+ *cp++ = '\0';
+
+ proxy_user = buffer;
+ proxy_pass = cp;
+ } else {
+ /* The whole buffer is the HOST:PORT part. */
+ h = buffer;
+ }
+
+ if ((cp = strchr(h, ':')) == NULL) {
+ rprintf(FERROR,
+ "invalid proxy specification: should be HOST:PORT\n");
+ return -1;
+ }
+ *cp++ = '\0';
+ strlcpy(portbuf, cp, sizeof portbuf);
+ if (verbose >= 2) {
+ rprintf(FINFO, "connection via http proxy %s port %s\n",
+ h, portbuf);
+ }
+ } else {
+ snprintf(portbuf, sizeof portbuf, "%d", port);
+ h = host;
+ }
+
+ memset(&hints, 0, sizeof hints);
+ hints.ai_family = af_hint;
+ hints.ai_socktype = type;
+ error = getaddrinfo(h, portbuf, &hints, &res0);
+ if (error) {
+ rprintf(FERROR, RSYNC_NAME ": getaddrinfo: %s %s: %s\n",
+ h, portbuf, gai_strerror(error));
+ return -1;
+ }
+
+ s = -1;
+ /* Try to connect to all addresses for this machine until we get
+ * through. It might e.g. be multi-homed, or have both IPv4 and IPv6
+ * addresses. We need to create a socket for each record, since the
+ * address record tells us what protocol to use to try to connect. */
+ for (res = res0; res; res = res->ai_next) {
+ s = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
+ if (s < 0)
+ continue;
+
+ if (bind_address
+ && try_bind_local(s, res->ai_family, type,
+ bind_address) == -1) {
+ close(s);
+ s = -1;
+ continue;
+ }
+ if (connect(s, res->ai_addr, res->ai_addrlen) < 0) {
+ close(s);
+ s = -1;
+ continue;
+ }
+ if (proxied
+ && establish_proxy_connection(s, host, port,
+ proxy_user, proxy_pass) != 0) {
+ close(s);
+ s = -1;
+ continue;
+ }
+ break;
+ }
+ freeaddrinfo(res0);
+ if (s < 0) {
+ rsyserr(FERROR, errno, "failed to connect to %s", h);
+ return -1;
+ }
+ return s;
+}
+
+
+/**
+ * Open an outgoing socket, but allow for it to be intercepted by
+ * $RSYNC_CONNECT_PROG, which will execute a program across a TCP
+ * socketpair rather than really opening a socket.
+ *
+ * We use this primarily in testing to detect TCP flow bugs, but not
+ * cause security problems by really opening remote connections.
+ *
+ * This is based on the Samba LIBSMB_PROG feature.
+ *
+ * @param bind_address Local address to use. Normally NULL to get the stack default.
+ **/
+int open_socket_out_wrapped(char *host, int port, const char *bind_address,
+ int af_hint)
+{
+ char *prog = getenv("RSYNC_CONNECT_PROG");
+
+ if (verbose >= 2) {
+ rprintf(FINFO, "%sopening tcp connection to %s port %d\n",
+ prog ? "Using RSYNC_CONNECT_PROG instead of " : "",
+ host, port);
+ }
+ if (prog)
+ return sock_exec(prog);
+ return open_socket_out(host, port, bind_address, af_hint);
+}
+
+
+
+/**
+ * Open one or more sockets for incoming data using the specified type,
+ * port, and address.
+ *
+ * The getaddrinfo() call may return several address results, e.g. for
+ * the machine's IPv4 and IPv6 name.
+ *
+ * We return an array of file-descriptors to the sockets, with a trailing
+ * -1 value to indicate the end of the list.
+ *
+ * @param bind_address Local address to bind, or NULL to allow it to
+ * default.
+ **/
+static int *open_socket_in(int type, int port, const char *bind_address,
+ int af_hint)
+{
+ int one = 1;
+ int s, *socks, maxs, i;
+ struct addrinfo hints, *all_ai, *resp;
+ char portbuf[10];
+ int error;
+
+ memset(&hints, 0, sizeof hints);
+ hints.ai_family = af_hint;
+ hints.ai_socktype = type;
+ hints.ai_flags = AI_PASSIVE;
+ snprintf(portbuf, sizeof portbuf, "%d", port);
+ error = getaddrinfo(bind_address, portbuf, &hints, &all_ai);
+ if (error) {
+ rprintf(FERROR, RSYNC_NAME ": getaddrinfo: bind address %s: %s\n",
+ bind_address, gai_strerror(error));
+ return NULL;
+ }
+
+ /* Count max number of sockets we might open. */
+ for (maxs = 0, resp = all_ai; resp; resp = resp->ai_next, maxs++) {}
+
+ if (!(socks = new_array(int, maxs + 1)))
+ out_of_memory("open_socket_in");
+
+ /* We may not be able to create the socket, if for example the
+ * machine knows about IPv6 in the C library, but not in the
+ * kernel. */
+ for (resp = all_ai, i = 0; resp; resp = resp->ai_next) {
+ s = socket(resp->ai_family, resp->ai_socktype,
+ resp->ai_protocol);
+
+ if (s == -1) {
+ /* See if there's another address that will work... */
+ continue;
+ }
+
+ setsockopt(s, SOL_SOCKET, SO_REUSEADDR,
+ (char *)&one, sizeof one);
+
+#ifdef IPV6_V6ONLY
+ if (resp->ai_family == AF_INET6) {
+ setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY,
+ (char *)&one, sizeof one);
+ }
+#endif
+
+ /* Now we've got a socket - we need to bind it. */
+ if (bind(s, resp->ai_addr, resp->ai_addrlen) < 0) {
+ /* Nope, try another */
+ close(s);
+ continue;
+ }
+
+ socks[i++] = s;
+ }
+ socks[i] = -1;
+
+ if (all_ai)
+ freeaddrinfo(all_ai);
+
+ if (!i) {
+ rprintf(FERROR,
+ "unable to bind any inbound sockets on port %d\n",
+ port);
+ free(socks);
+ return NULL;
+ }
+ return socks;
+}
+
+