xref: /openbsd-src/usr.bin/rsync/socket.c (revision dd7183d711827caf3ffa441a8d52da6bdd7ae879)
1 /*	$Id: socket.c,v 1.18 2019/02/18 22:47:34 benno Exp $ */
2 /*
3  * Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
4  *
5  * Permission to use, copy, modify, and distribute this software for any
6  * purpose with or without fee is hereby granted, provided that the above
7  * copyright notice and this permission notice appear in all copies.
8  *
9  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16  */
17 #include <sys/stat.h>
18 #include <sys/socket.h>
19 #include <arpa/inet.h>
20 #include <netinet/in.h>
21 
22 #include <assert.h>
23 #include <ctype.h>
24 #include <errno.h>
25 #include <fcntl.h>
26 #include <inttypes.h>
27 #include <netdb.h>
28 #include <poll.h>
29 #include <resolv.h>
30 #include <stdlib.h>
31 #include <string.h>
32 #include <unistd.h>
33 
34 #include "extern.h"
35 
36 /*
37  * Defines a resolved IP address for the host
38  * There can be many, IPV4 or IPV6.
39  */
40 struct	source {
41 	int		 family; /* PF_INET or PF_INET6 */
42 	char		 ip[INET6_ADDRSTRLEN]; /* formatted string */
43 	struct sockaddr_storage sa; /* socket */
44 	socklen_t	 salen; /* length of socket buffer */
45 };
46 
47 /*
48  * Connect to an IP address representing a host.
49  * Return <0 on failure, 0 on try another address, >0 on success.
50  */
51 static int
52 inet_connect(struct sess *sess, int *sd,
53 	const struct source *src, const char *host)
54 {
55 	int	 c, flags;
56 
57 	if (*sd != -1)
58 		close(*sd);
59 
60 	LOG2(sess, "trying: %s, %s", src->ip, host);
61 
62 	if ((*sd = socket(src->family, SOCK_STREAM, 0)) == -1) {
63 		ERR(sess, "socket");
64 		return -1;
65 	}
66 
67 	/*
68 	 * Initiate blocking connection.
69 	 * We use the blocking connect() instead of passing NONBLOCK to
70 	 * the socket() function because we don't need to do anything
71 	 * while waiting for this to finish.
72 	 */
73 
74 	c = connect(*sd, (const struct sockaddr *)&src->sa, src->salen);
75 	if (c == -1) {
76 		if (errno == ECONNREFUSED || errno == EHOSTUNREACH) {
77 			WARNX(sess, "connect refused: %s, %s",
78 			    src->ip, host);
79 			return 0;
80 		}
81 		ERR(sess, "connect");
82 		return -1;
83 	}
84 
85 	/* Set up non-blocking mode. */
86 
87 	if ((flags = fcntl(*sd, F_GETFL, 0)) == -1) {
88 		ERR(sess, "fcntl");
89 		return -1;
90 	} else if (fcntl(*sd, F_SETFL, flags|O_NONBLOCK) == -1) {
91 		ERR(sess, "fcntl");
92 		return -1;
93 	}
94 
95 	return 1;
96 }
97 
98 /*
99  * Resolve the socket addresses for host, both in IPV4 and IPV6.
100  * Once completed, the "dns" pledge may be dropped.
101  * Returns the addresses on success, NULL on failure (sz is always zero,
102  * in this case).
103  */
104 static struct source *
105 inet_resolve(struct sess *sess, const char *host, size_t *sz)
106 {
107 	struct addrinfo	 hints, *res0, *res;
108 	struct sockaddr	*sa;
109 	struct source	*src = NULL;
110 	size_t		 i, srcsz = 0;
111 	int		 error;
112 
113 	*sz = 0;
114 
115 	memset(&hints, 0, sizeof(hints));
116 	hints.ai_family = PF_UNSPEC;
117 	hints.ai_socktype = SOCK_STREAM;
118 
119 	error = getaddrinfo(host, sess->opts->port, &hints, &res0);
120 
121 	LOG2(sess, "resolving: %s", host);
122 
123 	if (error == EAI_AGAIN || error == EAI_NONAME) {
124 		ERRX(sess, "could not resolve hostname %s: %s",
125 		    host, gai_strerror(error));
126 		return NULL;
127 	} else if (error == EAI_SERVICE) {
128 		ERRX(sess, "could not resolve service rsync: %s",
129 		    gai_strerror(error));
130 		return NULL;
131 	} else if (error) {
132 		ERRX(sess, "getaddrinfo: %s: %s", host, gai_strerror(error));
133 		return NULL;
134 	}
135 
136 	/* Allocate for all available addresses. */
137 
138 	for (res = res0; res != NULL; res = res->ai_next)
139 		if (res->ai_family == AF_INET ||
140 		    res->ai_family == AF_INET6)
141 			srcsz++;
142 
143 	if (srcsz == 0) {
144 		ERRX(sess, "no addresses resolved: %s", host);
145 		freeaddrinfo(res0);
146 		return NULL;
147 	}
148 
149 	src = calloc(srcsz, sizeof(struct source));
150 	if (src == NULL) {
151 		ERRX(sess, "calloc");
152 		freeaddrinfo(res0);
153 		return NULL;
154 	}
155 
156 	for (i = 0, res = res0; res != NULL; res = res->ai_next) {
157 		if (res->ai_family != AF_INET &&
158 		    res->ai_family != AF_INET6)
159 			continue;
160 
161 		assert(i < srcsz);
162 
163 		/* Copy the socket address. */
164 
165 		src[i].salen = res->ai_addrlen;
166 		memcpy(&src[i].sa, res->ai_addr, src[i].salen);
167 
168 		/* Format as a string, too. */
169 
170 		sa = res->ai_addr;
171 		if (res->ai_family == AF_INET) {
172 			src[i].family = PF_INET;
173 			inet_ntop(AF_INET,
174 			    &(((struct sockaddr_in *)sa)->sin_addr),
175 			    src[i].ip, INET6_ADDRSTRLEN);
176 		} else {
177 			src[i].family = PF_INET6;
178 			inet_ntop(AF_INET6,
179 			    &(((struct sockaddr_in6 *)sa)->sin6_addr),
180 			    src[i].ip, INET6_ADDRSTRLEN);
181 		}
182 
183 		LOG2(sess, "hostname resolved: %s: %s", host, src[i].ip);
184 		i++;
185 	}
186 
187 	freeaddrinfo(res0);
188 	*sz = srcsz;
189 	return src;
190 }
191 
192 /*
193  * Process an rsyncd preamble line.
194  * This is either free-form text or @RSYNCD commands.
195  * Return <0 on failure, 0 on try more lines, >0 on finished.
196  */
197 static int
198 protocol_line(struct sess *sess, const char *host, const char *cp)
199 {
200 	int	major, minor;
201 
202 	if (strncmp(cp, "@RSYNCD: ", 9)) {
203 		LOG0(sess, "%s", cp);
204 		return 0;
205 	}
206 
207 	cp += 9;
208 	while (isspace((unsigned char)*cp))
209 		cp++;
210 
211 	/* @RSYNCD: OK indicates that we're finished. */
212 
213 	if (strcmp(cp, "OK") == 0)
214 		return 1;
215 
216 	/*
217 	 * Otherwise, all we have left is our version.
218 	 * There are two formats: x.y (w/submodule) and x.
219 	 */
220 
221 	if (sscanf(cp, "%d.%d", &major, &minor) == 2) {
222 		sess->rver = major;
223 		return 0;
224 	} else if (sscanf(cp, "%d", &major) == 1) {
225 		sess->rver = major;
226 		return 0;
227 	}
228 
229 	ERRX(sess, "rsyncd protocol error: unknown command");
230 	return -1;
231 }
232 
233 /*
234  * Pledges: dns, inet, unix, unveil, rpath, cpath, wpath, stdio, fattr, chown.
235  *
236  * Pledges (dry-run): -unix, -cpath, -wpath, -fattr, -chown.
237  * Pledges (!preserve_times): -fattr.
238  */
239 int
240 rsync_socket(const struct opts *opts, const struct fargs *f)
241 {
242 	struct sess	  sess;
243 	struct source	 *src = NULL;
244 	size_t		  i, srcsz = 0;
245 	int		  sd = -1, rc = 0, c;
246 	char		**args, buf[BUFSIZ];
247 	uint8_t		  byte;
248 
249 	memset(&sess, 0, sizeof(struct sess));
250 	sess.lver = RSYNC_PROTOCOL;
251 	sess.opts = opts;
252 
253 	assert(f->host != NULL);
254 	assert(f->module != NULL);
255 
256 	if ((args = fargs_cmdline(&sess, f)) == NULL) {
257 		ERRX1(&sess, "fargs_cmdline");
258 		return 0;
259 	}
260 
261 	/* Resolve all IP addresses from the host. */
262 
263 	if ((src = inet_resolve(&sess, f->host, &srcsz)) == NULL) {
264 		ERRX1(&sess, "inet_resolve");
265 		free(args);
266 		return 0;
267 	}
268 
269 	/* Drop the DNS pledge. */
270 
271 	if (pledge("stdio unix rpath wpath cpath dpath fattr chown getpw inet unveil", NULL) == -1) {
272 		ERR(&sess, "pledge");
273 		goto out;
274 	}
275 
276 	/*
277 	 * Iterate over all addresses, trying to connect.
278 	 * When we succeed, then continue using the connected socket.
279 	 */
280 
281 	assert(srcsz);
282 	for (i = 0; i < srcsz; i++) {
283 		c = inet_connect(&sess, &sd, &src[i], f->host);
284 		if (c < 0) {
285 			ERRX1(&sess, "inet_connect");
286 			goto out;
287 		} else if (c > 0)
288 			break;
289 	}
290 
291 	/* Drop the inet pledge. */
292 	if (pledge("stdio unix rpath wpath cpath dpath fattr chown getpw unveil", NULL) == -1) {
293 		ERR(&sess, "pledge");
294 		goto out;
295 	}
296 
297 	if (i == srcsz) {
298 		ERRX(&sess, "cannot connect to host: %s", f->host);
299 		goto out;
300 	}
301 
302 	/* Initiate with the rsyncd version and module request. */
303 
304 	LOG2(&sess, "connected: %s, %s", src[i].ip, f->host);
305 
306 	(void)snprintf(buf, sizeof(buf), "@RSYNCD: %d", sess.lver);
307 	if (!io_write_line(&sess, sd, buf)) {
308 		ERRX1(&sess, "io_write_line");
309 		goto out;
310 	}
311 
312 	LOG2(&sess, "requesting module: %s, %s", f->module, f->host);
313 
314 	if (!io_write_line(&sess, sd, f->module)) {
315 		ERRX1(&sess, "io_write_line");
316 		goto out;
317 	}
318 
319 	/*
320 	 * Now we read the server's response, byte-by-byte, one newline
321 	 * terminated at a time, limited to BUFSIZ line length.
322 	 * For this protocol version, this consists of either @RSYNCD
323 	 * followed by some text (just "ok" and the remote version) or
324 	 * the message of the day.
325 	 */
326 
327 	for (;;) {
328 		for (i = 0; i < sizeof(buf); i++) {
329 			if (!io_read_byte(&sess, sd, &byte)) {
330 				ERRX1(&sess, "io_read_byte");
331 				goto out;
332 			}
333 			if ((buf[i] = byte) == '\n')
334 				break;
335 		}
336 		if (i == sizeof(buf)) {
337 			ERRX(&sess, "line buffer overrun");
338 			goto out;
339 		} else if (i == 0)
340 			continue;
341 
342 		/*
343 		 * The rsyncd protocol isn't very clear as to whether we
344 		 * get a CRLF or not: I don't actually see this being
345 		 * transmitted over the wire.
346 		 */
347 
348 		assert(i > 0);
349 		buf[i] = '\0';
350 		if (buf[i - 1] == '\r')
351 			buf[i - 1] = '\0';
352 
353 		if ((c = protocol_line(&sess, f->host, buf)) < 0) {
354 			ERRX1(&sess, "protocol_line");
355 			goto out;
356 		} else if (c > 0)
357 			break;
358 	}
359 
360 	/*
361 	 * Now we've exchanged all of our protocol information.
362 	 * We want to send our command-line arguments over the wire,
363 	 * each with a newline termination.
364 	 * Use the same arguments when invoking the server, but leave
365 	 * off the binary name(s).
366 	 * Emit a standalone newline afterward.
367 	 */
368 
369 	if (f->mode == FARGS_RECEIVER || f->mode == FARGS_SENDER)
370 		i = 3; /* ssh host rsync... */
371 	else
372 		i = 1; /* rsync... */
373 
374 	for ( ; args[i] != NULL; i++)
375 		if (!io_write_line(&sess, sd, args[i])) {
376 			ERRX1(&sess, "io_write_line");
377 			goto out;
378 		}
379 	if (!io_write_byte(&sess, sd, '\n')) {
380 		ERRX1(&sess, "io_write_line");
381 		goto out;
382 	}
383 
384 	/*
385 	 * All data after this point is going to be multiplexed, so turn
386 	 * on the multiplexer for our reads and writes.
387 	 */
388 
389 	/* Protocol exchange: get the random seed. */
390 
391 	if (!io_read_int(&sess, sd, &sess.seed)) {
392 		ERRX1(&sess, "io_read_int");
393 		goto out;
394 	}
395 
396 	/* Now we've completed the handshake. */
397 
398 	if (sess.rver < sess.lver) {
399 		ERRX(&sess, "remote protocol is older "
400 			"than our own (%" PRId32 " < %" PRId32 "): "
401 			"this is not supported",
402 			sess.rver, sess.lver);
403 		goto out;
404 	}
405 
406 	sess.mplex_reads = 1;
407 	LOG2(&sess, "read multiplexing enabled");
408 
409 	LOG2(&sess, "socket detected client version %" PRId32
410 		", server version %" PRId32 ", seed %" PRId32,
411 		sess.lver, sess.rver, sess.seed);
412 
413 	assert(f->mode == FARGS_RECEIVER);
414 
415 	LOG2(&sess, "client starting receiver: %s", f->host);
416 	if (!rsync_receiver(&sess, sd, sd, f->sink)) {
417 		ERRX1(&sess, "rsync_receiver");
418 		goto out;
419 	}
420 
421 #if 0
422 	/* Probably the EOF. */
423 	if (io_read_check(&sess, sd))
424 		WARNX(&sess, "data remains in read pipe");
425 #endif
426 
427 	rc = 1;
428 out:
429 	free(src);
430 	free(args);
431 	if (sd != -1)
432 		close(sd);
433 	return rc;
434 }
435