xref: /openbsd-src/usr.bin/rsync/socket.c (revision 6af255d52c85da7aa17820954102af02d97f2271)
1*6af255d5Sjsg /*	$OpenBSD: socket.c,v 1.34 2024/10/13 03:35:59 jsg Exp $ */
260a32ee9Sbenno /*
360a32ee9Sbenno  * Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
460a32ee9Sbenno  *
560a32ee9Sbenno  * Permission to use, copy, modify, and distribute this software for any
660a32ee9Sbenno  * purpose with or without fee is hereby granted, provided that the above
760a32ee9Sbenno  * copyright notice and this permission notice appear in all copies.
860a32ee9Sbenno  *
960a32ee9Sbenno  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
1060a32ee9Sbenno  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
1160a32ee9Sbenno  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
1260a32ee9Sbenno  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
1360a32ee9Sbenno  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
1460a32ee9Sbenno  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
1560a32ee9Sbenno  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1660a32ee9Sbenno  */
1760a32ee9Sbenno #include <sys/stat.h>
1860a32ee9Sbenno #include <sys/socket.h>
1960a32ee9Sbenno #include <arpa/inet.h>
2060a32ee9Sbenno #include <netinet/in.h>
2160a32ee9Sbenno 
2260a32ee9Sbenno #include <assert.h>
2360a32ee9Sbenno #include <ctype.h>
2460a32ee9Sbenno #include <errno.h>
2560a32ee9Sbenno #include <fcntl.h>
2660a32ee9Sbenno #include <inttypes.h>
2760a32ee9Sbenno #include <netdb.h>
2860a32ee9Sbenno #include <poll.h>
2960a32ee9Sbenno #include <resolv.h>
3060a32ee9Sbenno #include <stdlib.h>
3160a32ee9Sbenno #include <string.h>
3260a32ee9Sbenno #include <unistd.h>
33ef859540Sderaadt #include <err.h>
34*6af255d5Sjsg #include <stdio.h>
3560a32ee9Sbenno 
3660a32ee9Sbenno #include "extern.h"
3760a32ee9Sbenno 
3860a32ee9Sbenno /*
3960a32ee9Sbenno  * Defines a resolved IP address for the host
4060a32ee9Sbenno  * There can be many, IPV4 or IPV6.
4160a32ee9Sbenno  */
4260a32ee9Sbenno struct	source {
4360a32ee9Sbenno 	int		 family; /* PF_INET or PF_INET6 */
4460a32ee9Sbenno 	char		 ip[INET6_ADDRSTRLEN]; /* formatted string */
4560a32ee9Sbenno 	struct sockaddr_storage sa; /* socket */
4660a32ee9Sbenno 	socklen_t	 salen; /* length of socket buffer */
4760a32ee9Sbenno };
4860a32ee9Sbenno 
4960a32ee9Sbenno /*
50d9a51c35Sjmc  * Try to bind to a local IP address matching the address family passed.
51a52e5c3aSclaudio  * Return -1 on failure to bind to any address, 0 on success.
52a52e5c3aSclaudio  */
53a52e5c3aSclaudio static int
54a52e5c3aSclaudio inet_bind(int s, sa_family_t af, const struct source *bsrc, size_t bsrcsz)
55a52e5c3aSclaudio {
56a52e5c3aSclaudio 	size_t i;
57a52e5c3aSclaudio 
58a52e5c3aSclaudio 	if (bsrc == NULL)
59a52e5c3aSclaudio 		return 0;
60a52e5c3aSclaudio 	for (i = 0; i < bsrcsz; i++) {
61a52e5c3aSclaudio 		if (bsrc[i].family != af)
62a52e5c3aSclaudio 			continue;
63a52e5c3aSclaudio 		if (bind(s, (const struct sockaddr *)&bsrc[i].sa,
64a52e5c3aSclaudio 		    bsrc[i].salen) == -1)
65a52e5c3aSclaudio 			continue;
66a52e5c3aSclaudio 		return 0;
67a52e5c3aSclaudio 	}
68a52e5c3aSclaudio 	return -1;
69a52e5c3aSclaudio }
70a52e5c3aSclaudio 
71a52e5c3aSclaudio /*
7260a32ee9Sbenno  * Connect to an IP address representing a host.
7360a32ee9Sbenno  * Return <0 on failure, 0 on try another address, >0 on success.
7460a32ee9Sbenno  */
7560a32ee9Sbenno static int
76a52e5c3aSclaudio inet_connect(int *sd, const struct source *src, const char *host,
77a52e5c3aSclaudio     const struct source *bsrc, size_t bsrcsz)
7860a32ee9Sbenno {
792eaa6f4eSjob 	struct pollfd	pfd;
802eaa6f4eSjob 	socklen_t	optlen;
812eaa6f4eSjob 	int		c;
822eaa6f4eSjob 	int		optval;
8360a32ee9Sbenno 
84f1dcb30aSderaadt 	if (*sd != -1)
8560a32ee9Sbenno 		close(*sd);
8660a32ee9Sbenno 
87b2a7eac7Sbenno 	LOG2("trying: %s, %s", src->ip, host);
8860a32ee9Sbenno 
892eaa6f4eSjob 	if ((*sd = socket(src->family, SOCK_STREAM | SOCK_NONBLOCK, 0))
902eaa6f4eSjob 	    == -1) {
91b2a7eac7Sbenno 		ERR("socket");
9260a32ee9Sbenno 		return -1;
9360a32ee9Sbenno 	}
9460a32ee9Sbenno 
95a52e5c3aSclaudio 	if (inet_bind(*sd, src->family, bsrc, bsrcsz) == -1) {
96a52e5c3aSclaudio 		ERR("bind");
97a52e5c3aSclaudio 		return -1;
98a52e5c3aSclaudio 	}
99a52e5c3aSclaudio 
10060a32ee9Sbenno 	/*
10160a32ee9Sbenno 	 * Initiate blocking connection.
1022eaa6f4eSjob 	 * We use non-blocking connect() so we can poll() for contimeout.
10360a32ee9Sbenno 	 */
10460a32ee9Sbenno 
1052eaa6f4eSjob 	if ((c = connect(*sd, (const struct sockaddr *)&src->sa, src->salen))
1062eaa6f4eSjob 	    != 0 && errno == EINPROGRESS) {
1072eaa6f4eSjob 		pfd.fd = *sd;
1082eaa6f4eSjob 		pfd.events = POLLOUT;
1092eaa6f4eSjob 		switch (c = poll(&pfd, 1, poll_contimeout)) {
1102eaa6f4eSjob 		case 1:
1112eaa6f4eSjob 			optlen = sizeof(optval);
1122eaa6f4eSjob 			if ((c = getsockopt(*sd, SOL_SOCKET, SO_ERROR, &optval,
1132eaa6f4eSjob 			    &optlen)) == 0) {
1142eaa6f4eSjob 				errno = optval;
1152eaa6f4eSjob 				if (optval != 0)
1162eaa6f4eSjob 					c = -1;
1172eaa6f4eSjob 			}
1182eaa6f4eSjob 			break;
1192eaa6f4eSjob 		case 0:
1202eaa6f4eSjob 			errno = ETIMEDOUT;
1212eaa6f4eSjob 			WARNX("connect timeout: %s, %s", src->ip, host);
1222eaa6f4eSjob 			return 0;
1232eaa6f4eSjob 		default:
1242eaa6f4eSjob 			ERR("poll failed");
1252eaa6f4eSjob 			return -1;
1262eaa6f4eSjob 		}
1272eaa6f4eSjob 	}
128f1dcb30aSderaadt 	if (c == -1) {
12955a1066fSkn 		if (errno == EADDRNOTAVAIL)
13055a1066fSkn 			return 0;
131f1dcb30aSderaadt 		if (errno == ECONNREFUSED || errno == EHOSTUNREACH) {
1322eaa6f4eSjob 			WARNX("connect refused: %s, %s", src->ip, host);
13360a32ee9Sbenno 			return 0;
13460a32ee9Sbenno 		}
135b2a7eac7Sbenno 		ERR("connect");
13660a32ee9Sbenno 		return -1;
13760a32ee9Sbenno 	}
13860a32ee9Sbenno 
13960a32ee9Sbenno 	return 1;
14060a32ee9Sbenno }
14160a32ee9Sbenno 
14260a32ee9Sbenno /*
14360a32ee9Sbenno  * Resolve the socket addresses for host, both in IPV4 and IPV6.
14460a32ee9Sbenno  * Once completed, the "dns" pledge may be dropped.
14560a32ee9Sbenno  * Returns the addresses on success, NULL on failure (sz is always zero,
14660a32ee9Sbenno  * in this case).
14760a32ee9Sbenno  */
14860a32ee9Sbenno static struct source *
149a52e5c3aSclaudio inet_resolve(struct sess *sess, const char *host, size_t *sz, int passive)
15060a32ee9Sbenno {
15160a32ee9Sbenno 	struct addrinfo	 hints, *res0, *res;
15260a32ee9Sbenno 	struct sockaddr	*sa;
15360a32ee9Sbenno 	struct source	*src = NULL;
154a52e5c3aSclaudio 	const char	*port = sess->opts->port;
15560a32ee9Sbenno 	size_t		 i, srcsz = 0;
15660a32ee9Sbenno 	int		 error;
15760a32ee9Sbenno 
15860a32ee9Sbenno 	*sz = 0;
15960a32ee9Sbenno 
16060a32ee9Sbenno 	memset(&hints, 0, sizeof(hints));
16160a32ee9Sbenno 	hints.ai_family = PF_UNSPEC;
162971126b2Sderaadt 	hints.ai_socktype = SOCK_STREAM;
163a52e5c3aSclaudio 	if (passive) {
164a52e5c3aSclaudio 		hints.ai_flags = SOCK_STREAM;
165a52e5c3aSclaudio 		port = NULL;
166a52e5c3aSclaudio 	}
16760a32ee9Sbenno 
168a52e5c3aSclaudio 	error = getaddrinfo(host, port, &hints, &res0);
16960a32ee9Sbenno 
170b2a7eac7Sbenno 	LOG2("resolving: %s", host);
17160a32ee9Sbenno 
17260a32ee9Sbenno 	if (error == EAI_AGAIN || error == EAI_NONAME) {
173b2a7eac7Sbenno 		ERRX("could not resolve hostname %s: %s",
17460a32ee9Sbenno 		    host, gai_strerror(error));
17560a32ee9Sbenno 		return NULL;
176882186e9Sderaadt 	} else if (error == EAI_SERVICE) {
177b2a7eac7Sbenno 		ERRX("could not resolve service rsync: %s",
17855cb9f91Sbenno 		    gai_strerror(error));
179882186e9Sderaadt 		return NULL;
18060a32ee9Sbenno 	} else if (error) {
181b2a7eac7Sbenno 		ERRX("getaddrinfo: %s: %s", host, gai_strerror(error));
18260a32ee9Sbenno 		return NULL;
18360a32ee9Sbenno 	}
18460a32ee9Sbenno 
18560a32ee9Sbenno 	/* Allocate for all available addresses. */
18660a32ee9Sbenno 
187ed5cc9fbSderaadt 	for (res = res0; res != NULL; res = res->ai_next)
18860a32ee9Sbenno 		if (res->ai_family == AF_INET ||
18960a32ee9Sbenno 		    res->ai_family == AF_INET6)
19060a32ee9Sbenno 			srcsz++;
19160a32ee9Sbenno 
192f1dcb30aSderaadt 	if (srcsz == 0) {
193b2a7eac7Sbenno 		ERRX("no addresses resolved: %s", host);
19460a32ee9Sbenno 		freeaddrinfo(res0);
19560a32ee9Sbenno 		return NULL;
19660a32ee9Sbenno 	}
19760a32ee9Sbenno 
19860a32ee9Sbenno 	src = calloc(srcsz, sizeof(struct source));
199f1dcb30aSderaadt 	if (src == NULL) {
200b2a7eac7Sbenno 		ERRX("calloc");
20160a32ee9Sbenno 		freeaddrinfo(res0);
20260a32ee9Sbenno 		return NULL;
20360a32ee9Sbenno 	}
20460a32ee9Sbenno 
205ed5cc9fbSderaadt 	for (i = 0, res = res0; res != NULL; res = res->ai_next) {
20660a32ee9Sbenno 		if (res->ai_family != AF_INET &&
20760a32ee9Sbenno 		    res->ai_family != AF_INET6)
20860a32ee9Sbenno 			continue;
20960a32ee9Sbenno 
21060a32ee9Sbenno 		assert(i < srcsz);
21160a32ee9Sbenno 
21260a32ee9Sbenno 		/* Copy the socket address. */
21360a32ee9Sbenno 
21460a32ee9Sbenno 		src[i].salen = res->ai_addrlen;
21560a32ee9Sbenno 		memcpy(&src[i].sa, res->ai_addr, src[i].salen);
21660a32ee9Sbenno 
21760a32ee9Sbenno 		/* Format as a string, too. */
21860a32ee9Sbenno 
21960a32ee9Sbenno 		sa = res->ai_addr;
220f1dcb30aSderaadt 		if (res->ai_family == AF_INET) {
22160a32ee9Sbenno 			src[i].family = PF_INET;
22260a32ee9Sbenno 			inet_ntop(AF_INET,
22360a32ee9Sbenno 			    &(((struct sockaddr_in *)sa)->sin_addr),
22460a32ee9Sbenno 			    src[i].ip, INET6_ADDRSTRLEN);
22560a32ee9Sbenno 		} else {
22660a32ee9Sbenno 			src[i].family = PF_INET6;
22760a32ee9Sbenno 			inet_ntop(AF_INET6,
22860a32ee9Sbenno 			    &(((struct sockaddr_in6 *)sa)->sin6_addr),
22960a32ee9Sbenno 			    src[i].ip, INET6_ADDRSTRLEN);
23060a32ee9Sbenno 		}
23160a32ee9Sbenno 
232b2a7eac7Sbenno 		LOG2("hostname resolved: %s: %s", host, src[i].ip);
23360a32ee9Sbenno 		i++;
23460a32ee9Sbenno 	}
23560a32ee9Sbenno 
23660a32ee9Sbenno 	freeaddrinfo(res0);
23760a32ee9Sbenno 	*sz = srcsz;
23860a32ee9Sbenno 	return src;
23960a32ee9Sbenno }
24060a32ee9Sbenno 
24160a32ee9Sbenno /*
24260a32ee9Sbenno  * Process an rsyncd preamble line.
24360a32ee9Sbenno  * This is either free-form text or @RSYNCD commands.
24460a32ee9Sbenno  * Return <0 on failure, 0 on try more lines, >0 on finished.
24560a32ee9Sbenno  */
24660a32ee9Sbenno static int
247ba617adaSbenno protocol_line(struct sess *sess, __attribute__((unused)) const char *host,
248ba617adaSbenno     const char *cp)
24960a32ee9Sbenno {
25060a32ee9Sbenno 	int	major, minor;
25160a32ee9Sbenno 
25260a32ee9Sbenno 	if (strncmp(cp, "@RSYNCD: ", 9)) {
2533bf4cfb6Sjob 		if (sess->opts->no_motd == 0)
2547b590dfaSclaudio 			LOG1("%s", cp);
25560a32ee9Sbenno 		return 0;
25660a32ee9Sbenno 	}
25760a32ee9Sbenno 
25860a32ee9Sbenno 	cp += 9;
25960a32ee9Sbenno 	while (isspace((unsigned char)*cp))
26060a32ee9Sbenno 		cp++;
26160a32ee9Sbenno 
26260a32ee9Sbenno 	/* @RSYNCD: OK indicates that we're finished. */
26360a32ee9Sbenno 
264f1dcb30aSderaadt 	if (strcmp(cp, "OK") == 0)
26560a32ee9Sbenno 		return 1;
26660a32ee9Sbenno 
26760a32ee9Sbenno 	/*
26860a32ee9Sbenno 	 * Otherwise, all we have left is our version.
26960a32ee9Sbenno 	 * There are two formats: x.y (w/submodule) and x.
27060a32ee9Sbenno 	 */
27160a32ee9Sbenno 
272f1dcb30aSderaadt 	if (sscanf(cp, "%d.%d", &major, &minor) == 2) {
27360a32ee9Sbenno 		sess->rver = major;
27460a32ee9Sbenno 		return 0;
275f1dcb30aSderaadt 	} else if (sscanf(cp, "%d", &major) == 1) {
27660a32ee9Sbenno 		sess->rver = major;
27760a32ee9Sbenno 		return 0;
27860a32ee9Sbenno 	}
27960a32ee9Sbenno 
280b2a7eac7Sbenno 	ERRX("rsyncd protocol error: unknown command");
28160a32ee9Sbenno 	return -1;
28260a32ee9Sbenno }
28360a32ee9Sbenno 
28460a32ee9Sbenno /*
285ac024dd4Snaddy  * Connect to a remote rsync://-enabled server sender.
286ac024dd4Snaddy  * Returns exit code 0 on success, 1 on failure.
28760a32ee9Sbenno  */
28860a32ee9Sbenno int
289ac024dd4Snaddy rsync_connect(const struct opts *opts, int *sd, const struct fargs *f)
29060a32ee9Sbenno {
29160a32ee9Sbenno 	struct sess	  sess;
292a52e5c3aSclaudio 	struct source	 *src = NULL, *bsrc = NULL;
293a52e5c3aSclaudio 	size_t		  i, srcsz = 0, bsrcsz = 0;
294ac024dd4Snaddy 	int		  c, rc = 1;
29560a32ee9Sbenno 
296ef859540Sderaadt 	if (pledge("stdio unix rpath wpath cpath dpath inet fattr chown dns getpw unveil",
297ef859540Sderaadt 	    NULL) == -1)
298dd3c9d24Sclaudio 		err(ERR_IPC, "pledge");
299ef859540Sderaadt 
30060a32ee9Sbenno 	memset(&sess, 0, sizeof(struct sess));
30160a32ee9Sbenno 	sess.opts = opts;
30260a32ee9Sbenno 
303f1dcb30aSderaadt 	assert(f->host != NULL);
30460a32ee9Sbenno 
30560a32ee9Sbenno 	/* Resolve all IP addresses from the host. */
30660a32ee9Sbenno 
307a52e5c3aSclaudio 	if ((src = inet_resolve(&sess, f->host, &srcsz, 0)) == NULL) {
308b2a7eac7Sbenno 		ERRX1("inet_resolve");
309ef859540Sderaadt 		exit(1);
31060a32ee9Sbenno 	}
311a52e5c3aSclaudio 	if (opts->address != NULL)
312a52e5c3aSclaudio 		if ((bsrc = inet_resolve(&sess, opts->address, &bsrcsz, 1)) ==
313a52e5c3aSclaudio 		    NULL) {
314a52e5c3aSclaudio 			ERRX1("inet_resolve bind");
315a52e5c3aSclaudio 			exit(1);
316a52e5c3aSclaudio 		}
31760a32ee9Sbenno 
31860a32ee9Sbenno 	/* Drop the DNS pledge. */
31960a32ee9Sbenno 
320ef859540Sderaadt 	if (pledge("stdio unix rpath wpath cpath dpath fattr chown getpw inet unveil",
321ef859540Sderaadt 	    NULL) == -1) {
322b2a7eac7Sbenno 		ERR("pledge");
323ef859540Sderaadt 		exit(1);
32460a32ee9Sbenno 	}
32560a32ee9Sbenno 
32660a32ee9Sbenno 	/*
32760a32ee9Sbenno 	 * Iterate over all addresses, trying to connect.
32860a32ee9Sbenno 	 * When we succeed, then continue using the connected socket.
32960a32ee9Sbenno 	 */
33060a32ee9Sbenno 
33160a32ee9Sbenno 	assert(srcsz);
33260a32ee9Sbenno 	for (i = 0; i < srcsz; i++) {
333a52e5c3aSclaudio 		c = inet_connect(sd, &src[i], f->host, bsrc, bsrcsz);
33483649630Sderaadt 		if (c < 0) {
335b2a7eac7Sbenno 			ERRX1("inet_connect");
33660a32ee9Sbenno 			goto out;
33783649630Sderaadt 		} else if (c > 0)
33860a32ee9Sbenno 			break;
33960a32ee9Sbenno 	}
34060a32ee9Sbenno 
34160a32ee9Sbenno 	/* Drop the inet pledge. */
342ef859540Sderaadt 	if (pledge("stdio unix rpath wpath cpath dpath fattr chown getpw unveil",
343ef859540Sderaadt 	    NULL) == -1) {
344b2a7eac7Sbenno 		ERR("pledge");
34560a32ee9Sbenno 		goto out;
34660a32ee9Sbenno 	}
34760a32ee9Sbenno 
34860a32ee9Sbenno 	if (i == srcsz) {
349b2a7eac7Sbenno 		ERRX("cannot connect to host: %s", f->host);
35060a32ee9Sbenno 		goto out;
35160a32ee9Sbenno 	}
35260a32ee9Sbenno 
353b2a7eac7Sbenno 	LOG2("connected: %s, %s", src[i].ip, f->host);
35460a32ee9Sbenno 
355ac024dd4Snaddy 	free(src);
356a52e5c3aSclaudio 	free(bsrc);
357ac024dd4Snaddy 	return 0;
358ac024dd4Snaddy out:
359ac024dd4Snaddy 	free(src);
360a52e5c3aSclaudio 	free(bsrc);
361ac024dd4Snaddy 	if (*sd != -1)
362ac024dd4Snaddy 		close(*sd);
363ac024dd4Snaddy 	return rc;
364ac024dd4Snaddy }
365ac024dd4Snaddy 
366ac024dd4Snaddy /*
367ac024dd4Snaddy  * Talk to a remote rsync://-enabled server sender.
368ac024dd4Snaddy  * Returns exit code 0 on success, 1 on failure, 2 on failure with
369ac024dd4Snaddy  * incompatible protocols.
370ac024dd4Snaddy  */
371ac024dd4Snaddy int
372ac024dd4Snaddy rsync_socket(const struct opts *opts, int sd, const struct fargs *f)
373ac024dd4Snaddy {
374ac024dd4Snaddy 	struct sess	  sess;
375ac024dd4Snaddy 	size_t		  i, skip;
376ac024dd4Snaddy 	int		  c, rc = 1;
377ac024dd4Snaddy 	char		**args, buf[BUFSIZ];
378ac024dd4Snaddy 	uint8_t		  byte;
379ac024dd4Snaddy 
380ac024dd4Snaddy 	if (pledge("stdio unix rpath wpath cpath dpath fattr chown getpw unveil",
381ac024dd4Snaddy 	    NULL) == -1)
382dd3c9d24Sclaudio 		err(ERR_IPC, "pledge");
383ac024dd4Snaddy 
384ac024dd4Snaddy 	memset(&sess, 0, sizeof(struct sess));
385ac024dd4Snaddy 	sess.lver = RSYNC_PROTOCOL;
386ac024dd4Snaddy 	sess.opts = opts;
387ac024dd4Snaddy 
388ac024dd4Snaddy 	assert(f->host != NULL);
389ac024dd4Snaddy 	assert(f->module != NULL);
390ac024dd4Snaddy 
391dd3c9d24Sclaudio 	args = fargs_cmdline(&sess, f, &skip);
392ac024dd4Snaddy 
393ac024dd4Snaddy 	/* Initiate with the rsyncd version and module request. */
394ac024dd4Snaddy 
39560a32ee9Sbenno 	(void)snprintf(buf, sizeof(buf), "@RSYNCD: %d", sess.lver);
39660a32ee9Sbenno 	if (!io_write_line(&sess, sd, buf)) {
397b2a7eac7Sbenno 		ERRX1("io_write_line");
39860a32ee9Sbenno 		goto out;
39960a32ee9Sbenno 	}
40060a32ee9Sbenno 
401b2a7eac7Sbenno 	LOG2("requesting module: %s, %s", f->module, f->host);
40260a32ee9Sbenno 
40360a32ee9Sbenno 	if (!io_write_line(&sess, sd, f->module)) {
404b2a7eac7Sbenno 		ERRX1("io_write_line");
40560a32ee9Sbenno 		goto out;
40660a32ee9Sbenno 	}
40760a32ee9Sbenno 
40860a32ee9Sbenno 	/*
40960a32ee9Sbenno 	 * Now we read the server's response, byte-by-byte, one newline
41060a32ee9Sbenno 	 * terminated at a time, limited to BUFSIZ line length.
41160a32ee9Sbenno 	 * For this protocol version, this consists of either @RSYNCD
41260a32ee9Sbenno 	 * followed by some text (just "ok" and the remote version) or
41360a32ee9Sbenno 	 * the message of the day.
41460a32ee9Sbenno 	 */
41560a32ee9Sbenno 
41660a32ee9Sbenno 	for (;;) {
41760a32ee9Sbenno 		for (i = 0; i < sizeof(buf); i++) {
41860a32ee9Sbenno 			if (!io_read_byte(&sess, sd, &byte)) {
419b2a7eac7Sbenno 				ERRX1("io_read_byte");
42060a32ee9Sbenno 				goto out;
42160a32ee9Sbenno 			}
422f1dcb30aSderaadt 			if ((buf[i] = byte) == '\n')
42360a32ee9Sbenno 				break;
42460a32ee9Sbenno 		}
42560a32ee9Sbenno 		if (i == sizeof(buf)) {
426b2a7eac7Sbenno 			ERRX("line buffer overrun");
42760a32ee9Sbenno 			goto out;
428f1dcb30aSderaadt 		} else if (i == 0)
42960a32ee9Sbenno 			continue;
43060a32ee9Sbenno 
43160a32ee9Sbenno 		/*
43260a32ee9Sbenno 		 * The rsyncd protocol isn't very clear as to whether we
43360a32ee9Sbenno 		 * get a CRLF or not: I don't actually see this being
43460a32ee9Sbenno 		 * transmitted over the wire.
43560a32ee9Sbenno 		 */
43660a32ee9Sbenno 
43760a32ee9Sbenno 		assert(i > 0);
43860a32ee9Sbenno 		buf[i] = '\0';
439f1dcb30aSderaadt 		if (buf[i - 1] == '\r')
44060a32ee9Sbenno 			buf[i - 1] = '\0';
44160a32ee9Sbenno 
44283649630Sderaadt 		if ((c = protocol_line(&sess, f->host, buf)) < 0) {
443b2a7eac7Sbenno 			ERRX1("protocol_line");
44460a32ee9Sbenno 			goto out;
44583649630Sderaadt 		} else if (c > 0)
44660a32ee9Sbenno 			break;
44760a32ee9Sbenno 	}
44860a32ee9Sbenno 
44960a32ee9Sbenno 	/*
45060a32ee9Sbenno 	 * Now we've exchanged all of our protocol information.
45160a32ee9Sbenno 	 * We want to send our command-line arguments over the wire,
45260a32ee9Sbenno 	 * each with a newline termination.
45360a32ee9Sbenno 	 * Use the same arguments when invoking the server, but leave
45460a32ee9Sbenno 	 * off the binary name(s).
45560a32ee9Sbenno 	 * Emit a standalone newline afterward.
45660a32ee9Sbenno 	 */
45760a32ee9Sbenno 
458ac024dd4Snaddy 	for (i = skip ; args[i] != NULL; i++)
45960a32ee9Sbenno 		if (!io_write_line(&sess, sd, args[i])) {
460b2a7eac7Sbenno 			ERRX1("io_write_line");
46160a32ee9Sbenno 			goto out;
46260a32ee9Sbenno 		}
46360a32ee9Sbenno 	if (!io_write_byte(&sess, sd, '\n')) {
464b2a7eac7Sbenno 		ERRX1("io_write_line");
46560a32ee9Sbenno 		goto out;
46660a32ee9Sbenno 	}
46760a32ee9Sbenno 
46860a32ee9Sbenno 	/*
46960a32ee9Sbenno 	 * All data after this point is going to be multiplexed, so turn
47060a32ee9Sbenno 	 * on the multiplexer for our reads and writes.
47160a32ee9Sbenno 	 */
47260a32ee9Sbenno 
47360a32ee9Sbenno 	/* Protocol exchange: get the random seed. */
47460a32ee9Sbenno 
47560a32ee9Sbenno 	if (!io_read_int(&sess, sd, &sess.seed)) {
476b2a7eac7Sbenno 		ERRX1("io_read_int");
47760a32ee9Sbenno 		goto out;
47860a32ee9Sbenno 	}
47960a32ee9Sbenno 
48060a32ee9Sbenno 	/* Now we've completed the handshake. */
48160a32ee9Sbenno 
48260a32ee9Sbenno 	if (sess.rver < sess.lver) {
483b2a7eac7Sbenno 		ERRX("remote protocol is older than our own (%d < %d): "
48455cb9f91Sbenno 		    "this is not supported",
48560a32ee9Sbenno 		    sess.rver, sess.lver);
48683649630Sderaadt 		rc = 2;
48760a32ee9Sbenno 		goto out;
48860a32ee9Sbenno 	}
48960a32ee9Sbenno 
49060a32ee9Sbenno 	sess.mplex_reads = 1;
491b2a7eac7Sbenno 	LOG2("read multiplexing enabled");
49260a32ee9Sbenno 
493b2a7eac7Sbenno 	LOG2("socket detected client version %d, server version %d, seed %d",
49460a32ee9Sbenno 	    sess.lver, sess.rver, sess.seed);
49560a32ee9Sbenno 
496f1dcb30aSderaadt 	assert(f->mode == FARGS_RECEIVER);
49760a32ee9Sbenno 
498b2a7eac7Sbenno 	LOG2("client starting receiver: %s", f->host);
49960a32ee9Sbenno 	if (!rsync_receiver(&sess, sd, sd, f->sink)) {
500b2a7eac7Sbenno 		ERRX1("rsync_receiver");
50160a32ee9Sbenno 		goto out;
50260a32ee9Sbenno 	}
50360a32ee9Sbenno 
50460a32ee9Sbenno #if 0
50560a32ee9Sbenno 	/* Probably the EOF. */
50660a32ee9Sbenno 	if (io_read_check(&sess, sd))
507b2a7eac7Sbenno 		WARNX("data remains in read pipe");
50860a32ee9Sbenno #endif
50960a32ee9Sbenno 
51083649630Sderaadt 	rc = 0;
51160a32ee9Sbenno out:
51260a32ee9Sbenno 	free(args);
51360a32ee9Sbenno 	return rc;
51460a32ee9Sbenno }
515