/* -*- c-file-style: "linux" -*-
+ rsync -- fast file replication program
+
Copyright (C) 1992-2001 by Andrew Tridgell <tridge@samba.org>
- Copyright (C) 2001 by Martin Pool <mbp@samba.org>
+ Copyright (C) 2001, 2002 by Martin Pool <mbp@samba.org>
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
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
-/*
- socket functions used in rsync
-
- */
+/**
+ * @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"
char *cp;
snprintf(buffer, sizeof(buffer), "CONNECT %s:%d HTTP/1.0\r\n\r\n", host, port);
- if (write(fd, buffer, strlen(buffer)) != strlen(buffer)) {
+ if (write(fd, buffer, strlen(buffer)) != (int) strlen(buffer)) {
rprintf(FERROR, "failed to write to proxy: %s\n",
strerror(errno));
return -1;
}
+/**
+ * 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;
+ 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. */
+ return -1;
+}
+
-/* open a socket to a tcp remote host with the specified port
- based on code from Warren
- proxy support by Stephen Rothwell */
-static int open_socket_out (char *host,
- int port,
- struct in_addr *address)
+/**
+ * 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;
- struct sockaddr_in sock_out;
- struct sockaddr_in sock;
- int res;
- struct hostent *hp;
+ int error;
+ int s;
+ struct addrinfo hints, *res0, *res;
+ char portbuf[10];
char *h;
- unsigned p;
int proxied = 0;
char buffer[1024];
char *cp;
return -1;
}
*cp++ = '\0';
- p = atoi(cp);
+ strcpy(portbuf, cp);
h = buffer;
} else {
+ snprintf(portbuf, sizeof(portbuf), "%d", port);
h = host;
- p = port;
- }
-
- res = socket(PF_INET, type, 0);
- if (res == -1) {
- return -1;
}
- hp = gethostbyname(h);
- if (!hp) {
- rprintf(FERROR,"unknown host: \"%s\"\n", h);
- close(res);
+ 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;
}
- memcpy(&sock_out.sin_addr, hp->h_addr, hp->h_length);
- sock_out.sin_port = htons(p);
- sock_out.sin_family = PF_INET;
+ 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 (address) {
- sock.sin_addr = *address;
- sock.sin_port = 0;
- sock.sin_family = hp->h_addrtype;
- bind(res, (struct sockaddr * ) &sock,sizeof(sock));
- }
+ if (bind_address)
+ if (try_bind_local(s, res->ai_family, type,
+ bind_address) == -1) {
+ close(s);
+ s = -1;
+ continue;
+ }
- if (connect(res,(struct sockaddr *)&sock_out,sizeof(sock_out))) {
- rprintf (FERROR, RSYNC_NAME ": failed to connect to host %s: %s\n",
- h, strerror(errno));
- close(res);
- return -1;
+ if (connect(s, res->ai_addr, res->ai_addrlen) < 0) {
+ close(s);
+ s = -1;
+ continue;
+ }
+ if (proxied &&
+ establish_proxy_connection(s, host, port) != 0) {
+ close(s);
+ s = -1;
+ continue;
+ } else
+ break;
}
-
- if (proxied && establish_proxy_connection(res, host, port) != 0) {
- close(res);
+ freeaddrinfo(res0);
+ if (s < 0) {
+ rprintf(FERROR, RSYNC_NAME ": failed to connect to %s: %s\n",
+ h, strerror(errno));
return -1;
}
-
- return res;
+ return s;
}
* 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,
- struct in_addr *address)
+ const char *bind_address,
+ int af_hint)
{
char *prog;
if ((prog = getenv ("RSYNC_CONNECT_PROG")) != NULL)
return sock_exec (prog);
else
- return open_socket_out (host, port, address);
+ return open_socket_out (host, port, bind_address,
+ af_hint);
}
-/****************************************************************************
-open a socket of the specified type, port and address for incoming data
-****************************************************************************/
-static int open_socket_in(int type, int port, struct in_addr *address)
+/**
+ * Open a socket of the specified type, port and address for incoming data
+ *
+ * Try to be better about handling the results of getaddrinfo(): when
+ * opening an inbound socket, we might get several address results,
+ * e.g. for the machine's ipv4 and ipv6 name.
+ *
+ * If binding a wildcard, then any one of them should do. If an address
+ * was specified but it's insufficiently specific then that's not our
+ * fault.
+ *
+ * However, some of the advertized addresses may not work because e.g. we
+ * don't have IPv6 support in the kernel. In that case go on and try all
+ * addresses until one succeeds.
+ *
+ * @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)
{
- struct sockaddr_in sock;
- int res;
int one=1;
-
- memset((char *)&sock,0,sizeof(sock));
- sock.sin_port = htons(port);
- sock.sin_family = AF_INET;
- if (address) {
- sock.sin_addr = *address;
- } else {
- sock.sin_addr.s_addr = INADDR_ANY;
- }
- res = socket(AF_INET, type, 0);
- if (res == -1) {
- rprintf(FERROR, RSYNC_NAME ": socket failed: %s\n",
- strerror(errno));
- return -1;
+ int s;
+ 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 -1;
}
- setsockopt(res,SOL_SOCKET,SO_REUSEADDR,(char *)&one,sizeof(one));
+ /* 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; resp; resp = resp->ai_next) {
+ s = socket(resp->ai_family, resp->ai_socktype,
+ resp->ai_protocol);
- /* now we've got a socket - we need to bind it */
- if (bind(res, (struct sockaddr * ) &sock,sizeof(sock)) == -1) {
- rprintf(FERROR,"bind failed on port %d: %s\n", port,
- strerror(errno));
- if (errno == EACCES && port < 1024) {
- rprintf(FERROR, "Note: you must be root to bind "
- "to low-numbered ports");
+ if (s == -1)
+ /* See if there's another address that will work... */
+ continue;
+
+ setsockopt(s, SOL_SOCKET, SO_REUSEADDR,
+ (char *)&one, sizeof one);
+
+ /* now we've got a socket - we need to bind it */
+ if (bind(s, all_ai->ai_addr, all_ai->ai_addrlen) < 0) {
+ /* Nope, try another */
+ close(s);
+ continue;
}
- close(res);
- return -1;
+
+ return s;
}
- return res;
+ rprintf(FERROR, RSYNC_NAME ": open inbound socket on port %d failed: "
+ "%s\n",
+ port,
+ strerror(errno));
+
+ freeaddrinfo(all_ai);
+ return -1;
}
void start_accept_loop(int port, int (*fn)(int ))
{
int s;
- extern struct in_addr socket_address;
+ extern char *bind_address;
+ extern int default_af_hint;
/* open an incoming socket */
- s = open_socket_in(SOCK_STREAM, port, &socket_address);
+ s = open_socket_in(SOCK_STREAM, port, bind_address, default_af_hint);
if (s == -1)
exit_cleanup(RERR_SOCKETIO);
while (1) {
fd_set fds;
int fd;
- struct sockaddr addr;
- socklen_t in_addrlen = sizeof(addr);
+ struct sockaddr_storage addr;
+ socklen_t addrlen = sizeof addr;
/* close log file before the potentially very long select so
file can be trimmed by another process instead of growing
if(!FD_ISSET(s, &fds)) continue;
- /* See note above prototypes. */
- fd = accept(s,&addr, &in_addrlen);
+ fd = accept(s,(struct sockaddr *)&addr,&addrlen);
if (fd == -1) continue;
if (fork()==0) {
close(s);
-
/* open log file in child before possibly giving
up privileges */
log_open();
-
_exit(fn(fd));
}
}
}
-/*******************************************************************
- return the IP addr of the client as a string
- ******************************************************************/
-char *client_addr(int fd)
-{
- struct sockaddr sa;
- struct sockaddr_in *sockin = (struct sockaddr_in *) (&sa);
- socklen_t length = sizeof(sa);
- static char addr_buf[100];
- static int initialised;
-
- if (initialised) return addr_buf;
-
- initialised = 1;
-
- if (getpeername(fd, &sa, &length)) {
- exit_cleanup(RERR_SOCKETIO);
- }
-
- strlcpy(addr_buf,(char *)inet_ntoa(sockin->sin_addr), sizeof(addr_buf));
- return addr_buf;
-}
-
-
-/*******************************************************************
- return the DNS name of the client
- ******************************************************************/
-char *client_name(int fd)
-{
- struct sockaddr sa;
- struct sockaddr_in *sockin = (struct sockaddr_in *) (&sa);
- socklen_t length = sizeof(sa);
- static char name_buf[100];
- struct hostent *hp;
- char **p;
- char *def = "UNKNOWN";
- static int initialised;
-
- if (initialised) return name_buf;
-
- initialised = 1;
-
- strcpy(name_buf,def);
-
- if (getpeername(fd, &sa, &length)) {
- exit_cleanup(RERR_SOCKETIO);
- }
-
- /* Look up the remote host name. */
- if ((hp = gethostbyaddr((char *) &sockin->sin_addr,
- sizeof(sockin->sin_addr),
- AF_INET))) {
- strlcpy(name_buf,(char *)hp->h_name,sizeof(name_buf));
- }
-
-
- /* do a forward lookup as well to prevent spoofing */
- hp = gethostbyname(name_buf);
- if (!hp) {
- strcpy (name_buf,def);
- rprintf (FERROR, "reverse name lookup for \"%s\" failed\n",
- name_buf);
- } else {
- for (p=hp->h_addr_list;*p;p++) {
- if (memcmp(*p, &sockin->sin_addr, hp->h_length) == 0) {
- break;
- }
- }
- if (!*p) {
- strcpy(name_buf,def);
- rprintf(FERROR,"reverse name lookup mismatch - spoofed address?\n");
- }
- }
-
- return name_buf;
-}
-
-/**
- Convert a string to an IP address. The string can be a name or
- dotted decimal number.
-
- Returns a pointer to a static in_addr struct -- if you call this
- more than once then you should copy it.
-*/
-struct in_addr *ip_address(const char *str)
-{
- static struct in_addr ret;
- struct hostent *hp;
-
- if (!str) {
- rprintf (FERROR, "ip_address received NULL name\n");
- return NULL;
- }
-
- /* try as an IP address */
- if (inet_aton(str, &ret) != 0) {
- return &ret;
- }
-
- /* otherwise assume it's a network name of some sort and use
- gethostbyname */
- if ((hp = gethostbyname (str)) == 0) {
- rprintf(FERROR, "gethostbyname failed for \"%s\": unknown host?\n",str);
- return NULL;
- }
-
- if (hp->h_addr == NULL) {
- rprintf(FERROR, "gethostbyname: host address is invalid for host \"%s\"\n",str);
- return NULL;
- }
-
- if (hp->h_length > sizeof ret) {
- rprintf(FERROR, "gethostbyname: host address for \"%s\" is too large\n",
- str);
- return NULL;
- }
-
- if (hp->h_addrtype != AF_INET) {
- rprintf (FERROR, "gethostname: host address for \"%s\" is not IPv4\n",
- str);
- return NULL;
- }
-
- /* This is kind of difficult. The only field in ret is
- s_addr, which is the IP address as a 32-bit int. On
- UNICOS, s_addr is in fact a *bitfield* for reasons best
- know to Cray. This means we can't memcpy in to it. On the
- other hand, h_addr is a char*, so we can't just assign.
-
- Since there's meant to be only one field inside the in_addr
- structure we will try just copying over the top and see how
- that goes. */
- memcpy (&ret, hp->h_addr, hp->h_length);
-
- return &ret;
-}
-
-
/*******************************************************************
this is like socketpair but uses tcp. It is used by the Samba
}
-/*******************************************************************
-run a program on a local tcp socket, this is used to launch smbd
-when regression testing
-the return value is a socket which is attached to a subprocess
-running "prog". stdin and stdout are attached. stderr is left
-attached to the original stderr
- ******************************************************************/
+
+/**
+ * Run a program on a local tcp socket, so that we can talk to it's
+ * stdin and stdout. This is used to fake a connection to a daemon
+ * for testing -- not for the normal case of running SSH.
+ *
+ * @return a socket which is attached to a subprocess running
+ * "prog". stdin and stdout are attached. stderr is left attached to
+ * the original stderr
+ **/
int sock_exec(const char *prog)
{
int fd[2];
+
if (socketpair_tcp(fd) != 0) {
rprintf (FERROR, RSYNC_NAME
": socketpair_tcp failed (%s)\n",
close(1);
dup(fd[1]);
dup(fd[1]);
- if (verbose > 3)
+ if (verbose > 3) {
+ /* Can't use rprintf because we've forked. */
fprintf (stderr,
RSYNC_NAME ": execute socket program \"%s\"\n",
prog);
+ }
exit (system (prog));
}
close (fd[1]);