jcs's openbsd hax
openbsd
1/* $OpenBSD: socket.c,v 1.34 2024/10/13 03:35:59 jsg 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#include <err.h>
34#include <stdio.h>
35
36#include "extern.h"
37
38/*
39 * Defines a resolved IP address for the host
40 * There can be many, IPV4 or IPV6.
41 */
42struct source {
43 int family; /* PF_INET or PF_INET6 */
44 char ip[INET6_ADDRSTRLEN]; /* formatted string */
45 struct sockaddr_storage sa; /* socket */
46 socklen_t salen; /* length of socket buffer */
47};
48
49/*
50 * Try to bind to a local IP address matching the address family passed.
51 * Return -1 on failure to bind to any address, 0 on success.
52 */
53static int
54inet_bind(int s, sa_family_t af, const struct source *bsrc, size_t bsrcsz)
55{
56 size_t i;
57
58 if (bsrc == NULL)
59 return 0;
60 for (i = 0; i < bsrcsz; i++) {
61 if (bsrc[i].family != af)
62 continue;
63 if (bind(s, (const struct sockaddr *)&bsrc[i].sa,
64 bsrc[i].salen) == -1)
65 continue;
66 return 0;
67 }
68 return -1;
69}
70
71/*
72 * Connect to an IP address representing a host.
73 * Return <0 on failure, 0 on try another address, >0 on success.
74 */
75static int
76inet_connect(int *sd, const struct source *src, const char *host,
77 const struct source *bsrc, size_t bsrcsz)
78{
79 struct pollfd pfd;
80 socklen_t optlen;
81 int c;
82 int optval;
83
84 if (*sd != -1)
85 close(*sd);
86
87 LOG2("trying: %s, %s", src->ip, host);
88
89 if ((*sd = socket(src->family, SOCK_STREAM | SOCK_NONBLOCK, 0))
90 == -1) {
91 ERR("socket");
92 return -1;
93 }
94
95 if (inet_bind(*sd, src->family, bsrc, bsrcsz) == -1) {
96 ERR("bind");
97 return -1;
98 }
99
100 /*
101 * Initiate blocking connection.
102 * We use non-blocking connect() so we can poll() for contimeout.
103 */
104
105 if ((c = connect(*sd, (const struct sockaddr *)&src->sa, src->salen))
106 != 0 && errno == EINPROGRESS) {
107 pfd.fd = *sd;
108 pfd.events = POLLOUT;
109 switch (c = poll(&pfd, 1, poll_contimeout)) {
110 case 1:
111 optlen = sizeof(optval);
112 if ((c = getsockopt(*sd, SOL_SOCKET, SO_ERROR, &optval,
113 &optlen)) == 0) {
114 errno = optval;
115 if (optval != 0)
116 c = -1;
117 }
118 break;
119 case 0:
120 errno = ETIMEDOUT;
121 WARNX("connect timeout: %s, %s", src->ip, host);
122 return 0;
123 default:
124 ERR("poll failed");
125 return -1;
126 }
127 }
128 if (c == -1) {
129 if (errno == EADDRNOTAVAIL)
130 return 0;
131 if (errno == ECONNREFUSED || errno == EHOSTUNREACH) {
132 WARNX("connect refused: %s, %s", src->ip, host);
133 return 0;
134 }
135 ERR("connect");
136 return -1;
137 }
138
139 return 1;
140}
141
142/*
143 * Resolve the socket addresses for host, both in IPV4 and IPV6.
144 * Once completed, the "dns" pledge may be dropped.
145 * Returns the addresses on success, NULL on failure (sz is always zero,
146 * in this case).
147 */
148static struct source *
149inet_resolve(struct sess *sess, const char *host, size_t *sz, int passive)
150{
151 struct addrinfo hints, *res0, *res;
152 struct sockaddr *sa;
153 struct source *src = NULL;
154 const char *port = sess->opts->port;
155 size_t i, srcsz = 0;
156 int error;
157
158 *sz = 0;
159
160 memset(&hints, 0, sizeof(hints));
161 hints.ai_family = PF_UNSPEC;
162 hints.ai_socktype = SOCK_STREAM;
163 if (passive) {
164 hints.ai_flags = SOCK_STREAM;
165 port = NULL;
166 }
167
168 error = getaddrinfo(host, port, &hints, &res0);
169
170 LOG2("resolving: %s", host);
171
172 if (error == EAI_AGAIN || error == EAI_NONAME) {
173 ERRX("could not resolve hostname %s: %s",
174 host, gai_strerror(error));
175 return NULL;
176 } else if (error == EAI_SERVICE) {
177 ERRX("could not resolve service rsync: %s",
178 gai_strerror(error));
179 return NULL;
180 } else if (error) {
181 ERRX("getaddrinfo: %s: %s", host, gai_strerror(error));
182 return NULL;
183 }
184
185 /* Allocate for all available addresses. */
186
187 for (res = res0; res != NULL; res = res->ai_next)
188 if (res->ai_family == AF_INET ||
189 res->ai_family == AF_INET6)
190 srcsz++;
191
192 if (srcsz == 0) {
193 ERRX("no addresses resolved: %s", host);
194 freeaddrinfo(res0);
195 return NULL;
196 }
197
198 src = calloc(srcsz, sizeof(struct source));
199 if (src == NULL) {
200 ERRX("calloc");
201 freeaddrinfo(res0);
202 return NULL;
203 }
204
205 for (i = 0, res = res0; res != NULL; res = res->ai_next) {
206 if (res->ai_family != AF_INET &&
207 res->ai_family != AF_INET6)
208 continue;
209
210 assert(i < srcsz);
211
212 /* Copy the socket address. */
213
214 src[i].salen = res->ai_addrlen;
215 memcpy(&src[i].sa, res->ai_addr, src[i].salen);
216
217 /* Format as a string, too. */
218
219 sa = res->ai_addr;
220 if (res->ai_family == AF_INET) {
221 src[i].family = PF_INET;
222 inet_ntop(AF_INET,
223 &(((struct sockaddr_in *)sa)->sin_addr),
224 src[i].ip, INET6_ADDRSTRLEN);
225 } else {
226 src[i].family = PF_INET6;
227 inet_ntop(AF_INET6,
228 &(((struct sockaddr_in6 *)sa)->sin6_addr),
229 src[i].ip, INET6_ADDRSTRLEN);
230 }
231
232 LOG2("hostname resolved: %s: %s", host, src[i].ip);
233 i++;
234 }
235
236 freeaddrinfo(res0);
237 *sz = srcsz;
238 return src;
239}
240
241/*
242 * Process an rsyncd preamble line.
243 * This is either free-form text or @RSYNCD commands.
244 * Return <0 on failure, 0 on try more lines, >0 on finished.
245 */
246static int
247protocol_line(struct sess *sess, __attribute__((unused)) const char *host,
248 const char *cp)
249{
250 int major, minor;
251
252 if (strncmp(cp, "@RSYNCD: ", 9)) {
253 if (sess->opts->no_motd == 0)
254 LOG1("%s", cp);
255 return 0;
256 }
257
258 cp += 9;
259 while (isspace((unsigned char)*cp))
260 cp++;
261
262 /* @RSYNCD: OK indicates that we're finished. */
263
264 if (strcmp(cp, "OK") == 0)
265 return 1;
266
267 /*
268 * Otherwise, all we have left is our version.
269 * There are two formats: x.y (w/submodule) and x.
270 */
271
272 if (sscanf(cp, "%d.%d", &major, &minor) == 2) {
273 sess->rver = major;
274 return 0;
275 } else if (sscanf(cp, "%d", &major) == 1) {
276 sess->rver = major;
277 return 0;
278 }
279
280 ERRX("rsyncd protocol error: unknown command");
281 return -1;
282}
283
284/*
285 * Connect to a remote rsync://-enabled server sender.
286 * Returns exit code 0 on success, 1 on failure.
287 */
288int
289rsync_connect(const struct opts *opts, int *sd, const struct fargs *f)
290{
291 struct sess sess;
292 struct source *src = NULL, *bsrc = NULL;
293 size_t i, srcsz = 0, bsrcsz = 0;
294 int c, rc = 1;
295
296 if (pledge("stdio unix rpath wpath cpath dpath inet fattr chown dns getpw unveil",
297 NULL) == -1)
298 err(ERR_IPC, "pledge");
299
300 memset(&sess, 0, sizeof(struct sess));
301 sess.opts = opts;
302
303 assert(f->host != NULL);
304
305 /* Resolve all IP addresses from the host. */
306
307 if ((src = inet_resolve(&sess, f->host, &srcsz, 0)) == NULL) {
308 ERRX1("inet_resolve");
309 exit(1);
310 }
311 if (opts->address != NULL)
312 if ((bsrc = inet_resolve(&sess, opts->address, &bsrcsz, 1)) ==
313 NULL) {
314 ERRX1("inet_resolve bind");
315 exit(1);
316 }
317
318 /* Drop the DNS pledge. */
319
320 if (pledge("stdio unix rpath wpath cpath dpath fattr chown getpw inet unveil",
321 NULL) == -1) {
322 ERR("pledge");
323 exit(1);
324 }
325
326 /*
327 * Iterate over all addresses, trying to connect.
328 * When we succeed, then continue using the connected socket.
329 */
330
331 assert(srcsz);
332 for (i = 0; i < srcsz; i++) {
333 c = inet_connect(sd, &src[i], f->host, bsrc, bsrcsz);
334 if (c < 0) {
335 ERRX1("inet_connect");
336 goto out;
337 } else if (c > 0)
338 break;
339 }
340
341 /* Drop the inet pledge. */
342 if (pledge("stdio unix rpath wpath cpath dpath fattr chown getpw unveil",
343 NULL) == -1) {
344 ERR("pledge");
345 goto out;
346 }
347
348 if (i == srcsz) {
349 ERRX("cannot connect to host: %s", f->host);
350 goto out;
351 }
352
353 LOG2("connected: %s, %s", src[i].ip, f->host);
354
355 free(src);
356 free(bsrc);
357 return 0;
358out:
359 free(src);
360 free(bsrc);
361 if (*sd != -1)
362 close(*sd);
363 return rc;
364}
365
366/*
367 * Talk to a remote rsync://-enabled server sender.
368 * Returns exit code 0 on success, 1 on failure, 2 on failure with
369 * incompatible protocols.
370 */
371int
372rsync_socket(const struct opts *opts, int sd, const struct fargs *f)
373{
374 struct sess sess;
375 size_t i, skip;
376 int c, rc = 1;
377 char **args, buf[BUFSIZ];
378 uint8_t byte;
379
380 if (pledge("stdio unix rpath wpath cpath dpath fattr chown getpw unveil",
381 NULL) == -1)
382 err(ERR_IPC, "pledge");
383
384 memset(&sess, 0, sizeof(struct sess));
385 sess.lver = RSYNC_PROTOCOL;
386 sess.opts = opts;
387
388 assert(f->host != NULL);
389 assert(f->module != NULL);
390
391 args = fargs_cmdline(&sess, f, &skip);
392
393 /* Initiate with the rsyncd version and module request. */
394
395 (void)snprintf(buf, sizeof(buf), "@RSYNCD: %d", sess.lver);
396 if (!io_write_line(&sess, sd, buf)) {
397 ERRX1("io_write_line");
398 goto out;
399 }
400
401 LOG2("requesting module: %s, %s", f->module, f->host);
402
403 if (!io_write_line(&sess, sd, f->module)) {
404 ERRX1("io_write_line");
405 goto out;
406 }
407
408 /*
409 * Now we read the server's response, byte-by-byte, one newline
410 * terminated at a time, limited to BUFSIZ line length.
411 * For this protocol version, this consists of either @RSYNCD
412 * followed by some text (just "ok" and the remote version) or
413 * the message of the day.
414 */
415
416 for (;;) {
417 for (i = 0; i < sizeof(buf); i++) {
418 if (!io_read_byte(&sess, sd, &byte)) {
419 ERRX1("io_read_byte");
420 goto out;
421 }
422 if ((buf[i] = byte) == '\n')
423 break;
424 }
425 if (i == sizeof(buf)) {
426 ERRX("line buffer overrun");
427 goto out;
428 } else if (i == 0)
429 continue;
430
431 /*
432 * The rsyncd protocol isn't very clear as to whether we
433 * get a CRLF or not: I don't actually see this being
434 * transmitted over the wire.
435 */
436
437 assert(i > 0);
438 buf[i] = '\0';
439 if (buf[i - 1] == '\r')
440 buf[i - 1] = '\0';
441
442 if ((c = protocol_line(&sess, f->host, buf)) < 0) {
443 ERRX1("protocol_line");
444 goto out;
445 } else if (c > 0)
446 break;
447 }
448
449 /*
450 * Now we've exchanged all of our protocol information.
451 * We want to send our command-line arguments over the wire,
452 * each with a newline termination.
453 * Use the same arguments when invoking the server, but leave
454 * off the binary name(s).
455 * Emit a standalone newline afterward.
456 */
457
458 for (i = skip ; args[i] != NULL; i++)
459 if (!io_write_line(&sess, sd, args[i])) {
460 ERRX1("io_write_line");
461 goto out;
462 }
463 if (!io_write_byte(&sess, sd, '\n')) {
464 ERRX1("io_write_line");
465 goto out;
466 }
467
468 /*
469 * All data after this point is going to be multiplexed, so turn
470 * on the multiplexer for our reads and writes.
471 */
472
473 /* Protocol exchange: get the random seed. */
474
475 if (!io_read_int(&sess, sd, &sess.seed)) {
476 ERRX1("io_read_int");
477 goto out;
478 }
479
480 /* Now we've completed the handshake. */
481
482 if (sess.rver < sess.lver) {
483 ERRX("remote protocol is older than our own (%d < %d): "
484 "this is not supported",
485 sess.rver, sess.lver);
486 rc = 2;
487 goto out;
488 }
489
490 sess.mplex_reads = 1;
491 LOG2("read multiplexing enabled");
492
493 LOG2("socket detected client version %d, server version %d, seed %d",
494 sess.lver, sess.rver, sess.seed);
495
496 assert(f->mode == FARGS_RECEIVER);
497
498 LOG2("client starting receiver: %s", f->host);
499 if (!rsync_receiver(&sess, sd, sd, f->sink)) {
500 ERRX1("rsync_receiver");
501 goto out;
502 }
503
504#if 0
505 /* Probably the EOF. */
506 if (io_read_check(&sess, sd))
507 WARNX("data remains in read pipe");
508#endif
509
510 rc = 0;
511out:
512 free(args);
513 return rc;
514}