Annotation of src/usr.bin/rsync/socket.c, Revision 1.3
1.3 ! benno 1: /* $Id: socket.c,v 1.2 2019/02/10 23:24:14 benno Exp $ */
1.1 benno 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 {
1.2 benno 41: int family; /* PF_INET or PF_INET6 */
42: char ip[INET6_ADDRSTRLEN]; /* formatted string */
1.1 benno 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 (-1 != *sd)
58: close(*sd);
59:
60: LOG2(sess, "trying: %s, %s", src->ip, host);
61:
62: if (-1 == (*sd = socket(src->family, SOCK_STREAM, 0))) {
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,
75: (const struct sockaddr *)&src->sa,
76: src->salen);
77: if (-1 == c) {
78: if (ECONNREFUSED == errno ||
79: EHOSTUNREACH == errno) {
80: WARNX(sess, "connect refused: "
81: "%s, %s", src->ip, host);
82: return 0;
83: }
84: ERR(sess, "connect");
85: return -1;
86: }
87:
88: /* Set up non-blocking mode. */
89:
90: if (-1 == (flags = fcntl(*sd, F_GETFL, 0))) {
91: ERR(sess, "fcntl");
92: return -1;
93: } else if (-1 == fcntl(*sd, F_SETFL, flags|O_NONBLOCK)) {
94: ERR(sess, "fcntl");
95: return -1;
96: }
97:
98: return 1;
99: }
100:
101: /*
102: * Resolve the socket addresses for host, both in IPV4 and IPV6.
103: * Once completed, the "dns" pledge may be dropped.
104: * Returns the addresses on success, NULL on failure (sz is always zero,
105: * in this case).
106: */
107: static struct source *
108: inet_resolve(struct sess *sess, const char *host, size_t *sz)
109: {
110: struct addrinfo hints, *res0, *res;
111: struct sockaddr *sa;
112: struct source *src = NULL;
113: size_t i, srcsz = 0;
114: int error;
115:
116: *sz = 0;
117:
118: memset(&hints, 0, sizeof(hints));
119: hints.ai_family = PF_UNSPEC;
120: hints.ai_socktype = SOCK_DGRAM; /* DUMMY */
121:
122: error = getaddrinfo(host, "873", &hints, &res0);
123:
124: LOG2(sess, "resolving: %s", host);
125:
126: if (error == EAI_AGAIN || error == EAI_NONAME) {
127: ERRX(sess, "DNS resolve error: %s: %s",
128: host, gai_strerror(error));
129: return NULL;
130: } else if (error) {
131: ERRX(sess, "DNS parse error: %s: %s",
132: host, gai_strerror(error));
133: return NULL;
134: }
135:
136: /* Allocate for all available addresses. */
137:
138: for (res = res0; NULL != res; res = res->ai_next)
139: if (res->ai_family == AF_INET ||
140: res->ai_family == AF_INET6)
141: srcsz++;
142:
143: if (0 == srcsz) {
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 (NULL == src) {
151: ERRX(sess, "calloc");
152: freeaddrinfo(res0);
153: return NULL;
154: }
155:
156: for (i = 0, res = res0; NULL != res; 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 (AF_INET == res->ai_family) {
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: }
1.2 benno 182:
1.1 benno 183: LOG2(sess, "DNS 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 (0 == strcmp(cp, "OK"))
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 (2 == sscanf(cp, "%d.%d", &major, &minor)) {
222: sess->rver = major;
223: return 0;
224: } else if (1 == sscanf(cp, "%d", &major)) {
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, unveil, rpath, cpath, wpath, stdio, fattr.
235: *
236: * Pledges (dry-run): -cpath, -wpath, -fattr.
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(NULL != f->host);
254: assert(NULL != f->module);
255:
256: if (NULL == (args = fargs_cmdline(&sess, f))) {
257: ERRX1(&sess, "fargs_cmdline");
258: return 0;
259: }
260:
261: /* Resolve all IP addresses from the host. */
262:
263: if (NULL == (src = inet_resolve(&sess, f->host, &srcsz))) {
264: ERRX1(&sess, "inet_resolve");
265: free(args);
266: return 0;
267: }
268:
269: /* Drop the DNS pledge. */
270:
1.3 ! benno 271: if (-1 == pledge("stdio rpath wpath cpath fattr inet unveil", NULL)) {
1.1 benno 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:
1.3 ! benno 293: if (-1 == pledge("stdio rpath wpath cpath fattr unveil", NULL)) {
1.1 benno 294: ERR(&sess, "pledge");
295: goto out;
296: }
297:
298: if (i == srcsz) {
299: ERRX(&sess, "cannot connect to host: %s", f->host);
300: goto out;
301: }
302:
303: /* Initiate with the rsyncd version and module request. */
304:
305: LOG2(&sess, "connected: %s, %s", src[i].ip, f->host);
306:
307: (void)snprintf(buf, sizeof(buf), "@RSYNCD: %d", sess.lver);
308: if ( ! io_write_line(&sess, sd, buf)) {
309: ERRX1(&sess, "io_write_line");
310: goto out;
311: }
312:
313: LOG2(&sess, "requesting module: %s, %s", f->module, f->host);
314:
315: if ( ! io_write_line(&sess, sd, f->module)) {
316: ERRX1(&sess, "io_write_line");
317: goto out;
318: }
319:
320: /*
321: * Now we read the server's response, byte-by-byte, one newline
322: * terminated at a time, limited to BUFSIZ line length.
323: * For this protocol version, this consists of either @RSYNCD
324: * followed by some text (just "ok" and the remote version) or
325: * the message of the day.
326: */
327:
328: for (;;) {
329: for (i = 0; i < sizeof(buf); i++) {
330: if ( ! io_read_byte(&sess, sd, &byte)) {
331: ERRX1(&sess, "io_read_byte");
332: goto out;
333: }
334: if ('\n' == (buf[i] = byte))
335: break;
336: }
337: if (i == sizeof(buf)) {
338: ERRX(&sess, "line buffer overrun");
339: goto out;
340: } else if (0 == i)
341: continue;
342:
343: /*
344: * The rsyncd protocol isn't very clear as to whether we
345: * get a CRLF or not: I don't actually see this being
346: * transmitted over the wire.
347: */
348:
349: assert(i > 0);
350: buf[i] = '\0';
351: if ('\r' == buf[i - 1])
352: buf[i - 1] = '\0';
353:
354: if ((c = protocol_line(&sess, f->host, buf)) < 0) {
355: ERRX1(&sess, "protocol_line");
356: goto out;
357: } else if (c > 0)
358: break;
359: }
360:
361: /*
362: * Now we've exchanged all of our protocol information.
363: * We want to send our command-line arguments over the wire,
364: * each with a newline termination.
365: * Use the same arguments when invoking the server, but leave
366: * off the binary name(s).
367: * Emit a standalone newline afterward.
368: */
369:
370: if (FARGS_RECEIVER == f->mode || FARGS_SENDER == f->mode)
371: i = 3; /* ssh host rsync... */
372: else
373: i = 1; /* rsync... */
374:
375: for ( ; NULL != args[i]; i++)
376: if ( ! io_write_line(&sess, sd, args[i])) {
377: ERRX1(&sess, "io_write_line");
378: goto out;
379: }
380: if ( ! io_write_byte(&sess, sd, '\n')) {
381: ERRX1(&sess, "io_write_line");
382: goto out;
383: }
384:
385: /*
386: * All data after this point is going to be multiplexed, so turn
387: * on the multiplexer for our reads and writes.
388: */
389:
390: /* Protocol exchange: get the random seed. */
391:
392: if ( ! io_read_int(&sess, sd, &sess.seed)) {
393: ERRX1(&sess, "io_read_int");
394: goto out;
395: }
396:
397: /* Now we've completed the handshake. */
398:
399: if (sess.rver < sess.lver) {
400: ERRX(&sess, "remote protocol is older "
401: "than our own (%" PRId32 " < %" PRId32 "): "
402: "this is not supported",
403: sess.rver, sess.lver);
404: goto out;
405: }
406:
407: sess.mplex_reads = 1;
408: LOG2(&sess, "read multiplexing enabled");
409:
410: LOG2(&sess, "socket detected client version %" PRId32
411: ", server version %" PRId32 ", seed %" PRId32,
412: sess.lver, sess.rver, sess.seed);
413:
414: assert(FARGS_RECEIVER == f->mode);
415:
416: LOG2(&sess, "client starting receiver: %s", f->host);
417: if ( ! rsync_receiver(&sess, sd, sd, f->sink)) {
418: ERRX1(&sess, "rsync_receiver");
419: goto out;
420: }
421:
422: #if 0
423: /* Probably the EOF. */
424: if (io_read_check(&sess, sd))
425: WARNX(&sess, "data remains in read pipe");
426: #endif
427:
428: rc = 1;
429: out:
430: free(src);
431: free(args);
432: if (-1 != sd)
433: close(sd);
434: return rc;
435: }