Branch data Line data Source code
1 : : /*-
2 : : * Copyright (c) 2020 Baptiste Daroussin <bapt@FreeBSD.org>
3 : : * Copyright (c) 2023 Serenity Cyber Security, LLC <license@futurecrew.ru>
4 : : * Author: Gleb Popov <arrowd@FreeBSD.org>
5 : : *
6 : : * Redistribution and use in source and binary forms, with or without
7 : : * modification, are permitted provided that the following conditions
8 : : * are met:
9 : : * 1. Redistributions of source code must retain the above copyright
10 : : * notice, this list of conditions and the following disclaimer
11 : : * in this position and unchanged.
12 : : * 2. Redistributions in binary form must reproduce the above copyright
13 : : * notice, this list of conditions and the following disclaimer in the
14 : : * documentation and/or other materials provided with the distribution.
15 : : *
16 : : * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
17 : : * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18 : : * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19 : : * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
20 : : * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
21 : : * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22 : : * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23 : : * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 : : * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
25 : : * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 : : */
27 : :
28 : : #include <sys/param.h>
29 : : #include <sys/wait.h>
30 : : #include <sys/socket.h>
31 : : #include <sys/time.h>
32 : : #include <sys/types.h>
33 : :
34 : : #include <ctype.h>
35 : : #include <fcntl.h>
36 : : #include <errno.h>
37 : : #include <stdio.h>
38 : : #include <string.h>
39 : : #include <paths.h>
40 : : #include <poll.h>
41 : : #include <netdb.h>
42 : : #include <time.h>
43 : :
44 : : #include <bsd_compat.h>
45 : :
46 : : #include "pkg.h"
47 : : #include "private/event.h"
48 : : #include "private/pkg.h"
49 : : #include "private/fetch.h"
50 : : #include "private/utils.h"
51 : : #include "yuarel.h"
52 : :
53 : : #ifndef timespeccmp
54 : : #define timespeccmp(tsp, usp, cmp) \
55 : : (((tsp)->tv_sec == (usp)->tv_sec) ? \
56 : : ((tsp)->tv_nsec cmp (usp)->tv_nsec) : \
57 : : ((tsp)->tv_sec cmp (usp)->tv_sec))
58 : : #endif
59 : : #ifndef timespecsub
60 : : #define timespecsub(tsp, usp, vsp) \
61 : : do { \
62 : : (vsp)->tv_sec = (tsp)->tv_sec - (usp)->tv_sec; \
63 : : (vsp)->tv_nsec = (tsp)->tv_nsec - (usp)->tv_nsec; \
64 : : if ((vsp)->tv_nsec < 0) { \
65 : : (vsp)->tv_sec--; \
66 : : (vsp)->tv_nsec += 1000000000L; \
67 : : } \
68 : : } while (0)
69 : : #endif
70 : :
71 : : static int ssh_read(void *data, char *buf, int len);
72 : : static int ssh_write(void *data, const char *buf, int l);
73 : : static int ssh_close(void *data);
74 : : static int tcp_close(void *data);
75 : :
76 : : static int
77 : 0 : tcp_connect(struct pkg_repo *repo, struct yuarel *u)
78 : : {
79 : 0 : char *line = NULL;
80 : 0 : size_t linecap = 0;
81 : 0 : struct addrinfo *ai = NULL, *curai, hints;
82 : : char srv[NI_MAXSERV];
83 : 0 : int sd = -1;
84 : : int retcode;
85 : :
86 : 0 : pkg_dbg(PKG_DBG_FETCH, 1, "TCP> tcp_connect");
87 : 0 : memset(&hints, 0, sizeof(hints));
88 : 0 : hints.ai_family = PF_UNSPEC;
89 [ # # ]: 0 : if (repo->ip == IPV4)
90 : 0 : hints.ai_family = PF_INET;
91 : 0 : else if (repo->ip == IPV6)
92 : 0 : hints.ai_family = PF_INET6;
93 : 0 : hints.ai_socktype = SOCK_STREAM;
94 : 0 : snprintf(srv, sizeof(srv), "%d", u->port);
95 : 0 : retcode = getaddrinfo(u->host, srv, &hints, &ai);
96 [ # # ]: 0 : if (retcode != 0) {
97 : 0 : pkg_emit_pkg_errno(EPKG_NONETWORK, "tcp_connect", gai_strerror(retcode));
98 : 0 : pkg_emit_error("Unable to lookup for '%s'", u->host);
99 : 0 : return (EPKG_FATAL);
100 : : }
101 : 0 : for (curai = ai; curai != NULL; curai = curai->ai_next) {
102 [ # # # # : 0 : if ((sd = socket(curai->ai_family, curai->ai_socktype,
# # ]
103 : 0 : curai->ai_protocol)) == -1)
104 : 0 : continue;
105 [ # # ]: 0 : if (connect(sd, curai->ai_addr, curai->ai_addrlen) == -1) {
106 : 0 : close(sd);
107 : 0 : sd = -1;
108 : 0 : continue;
109 : : }
110 : 0 : break;
111 : : }
112 : 0 : freeaddrinfo(ai);
113 [ # # ]: 0 : if (sd == -1) {
114 : 0 : pkg_emit_pkg_errno(EPKG_NONETWORK, "tcp_connect", NULL);
115 : 0 : pkg_emit_error("Could not connect to tcp://%s:%d", u->host,
116 : 0 : u->port);
117 : 0 : return (EPKG_FATAL);
118 : : }
119 [ # # ]: 0 : if (setsockopt(sd, SOL_SOCKET, SO_KEEPALIVE, &(int){ 1 }, sizeof(int)) != 0) {
120 : 0 : pkg_emit_errno("Could not connect", "setsockopt");
121 : 0 : close(sd);
122 : 0 : return (EPKG_FATAL);
123 : : }
124 : 0 : repo->sshio.in = dup(sd);
125 : 0 : repo->sshio.out = dup(sd);
126 : 0 : repo->fh = funopen(repo, ssh_read, ssh_write, NULL, tcp_close);
127 : :
128 : 0 : retcode = EPKG_FATAL;
129 [ # # ]: 0 : if (repo->fh == NULL) {
130 : 0 : pkg_emit_errno("Failed to open stream", "tcp_connect");
131 : 0 : goto tcp_cleanup;
132 : : }
133 : :
134 [ # # ]: 0 : if (getline(&line, &linecap, repo->fh) > 0) {
135 [ # # ]: 0 : if (strncmp(line, "ok:", 3) != 0) {
136 : 0 : pkg_dbg(PKG_DBG_FETCH, 1, "SSH> server rejected, got: %s", line);
137 : 0 : goto tcp_cleanup;
138 : : }
139 : 0 : pkg_dbg(PKG_DBG_FETCH, 1, "SSH> server is: %s", line +4);
140 : 0 : } else {
141 : 0 : pkg_dbg(PKG_DBG_FETCH, 1, "SSH> nothing to read, got: %s", line);
142 : 0 : goto tcp_cleanup;
143 : : }
144 : 0 : retcode = EPKG_OK;
145 : : tcp_cleanup:
146 : 0 : if (retcode == EPKG_FATAL && repo->fh != NULL) {
147 : 0 : fclose(repo->fh);
148 : 0 : repo->fh = NULL;
149 : 0 : }
150 : 0 : free(line);
151 : 0 : return (retcode);
152 : 0 : }
153 : :
154 : : static int
155 : 0 : ssh_connect(struct pkg_repo *repo, struct yuarel *u)
156 : : {
157 : 0 : char *line = NULL;
158 : 0 : size_t linecap = 0;
159 : : int sshin[2];
160 : : int sshout[2];
161 : 0 : xstring *cmd = NULL;
162 : : char *cmdline;
163 : 0 : int retcode = EPKG_FATAL;
164 : : const char *ssh_args;
165 : : const char *argv[4];
166 : :
167 : : /* Use socket pair because pipe have blocking issues */
168 : 0 : if (socketpair(AF_UNIX, SOCK_STREAM, 0, sshin) <0 ||
169 : 0 : socketpair(AF_UNIX, SOCK_STREAM, 0, sshout) < 0)
170 : 0 : return(EPKG_FATAL);
171 : :
172 : 0 : repo->sshio.pid = fork();
173 [ # # ]: 0 : if (repo->sshio.pid == -1) {
174 : 0 : pkg_emit_errno("Cannot fork", "start_ssh");
175 : 0 : goto ssh_cleanup;
176 : : }
177 : :
178 : 0 : if (repo->sshio.pid == 0) {
179 : :
180 : 0 : if (dup2(sshin[0], STDIN_FILENO) < 0 ||
181 : 0 : close(sshin[1]) < 0 ||
182 : 0 : close(sshout[0]) < 0 ||
183 : 0 : dup2(sshout[1], STDOUT_FILENO) < 0) {
184 : 0 : pkg_emit_errno("Cannot prepare pipes", "start_ssh");
185 : 0 : goto ssh_cleanup;
186 : : }
187 : :
188 : 0 : cmd = xstring_new();
189 : 0 : fputs("/usr/bin/ssh -e none -T ", cmd->fp);
190 : :
191 : 0 : ssh_args = pkg_object_string(pkg_config_get("PKG_SSH_ARGS"));
192 : 0 : if (ssh_args != NULL)
193 : 0 : fprintf(cmd->fp, "%s ", ssh_args);
194 [ # # ]: 0 : if (repo->ip == IPV4)
195 : 0 : fputs("-4 ", cmd->fp);
196 : 0 : else if (repo->ip == IPV6)
197 : 0 : fputs("-6 ", cmd->fp);
198 : 0 : if (u->port > 0)
199 : 0 : fprintf(cmd->fp, "-p %d ", u->port);
200 : 0 : if (u->username != NULL)
201 : 0 : fprintf(cmd->fp, "%s@", u->username);
202 : 0 : fprintf(cmd->fp, "%s pkg ssh", u->host);
203 : 0 : cmdline = xstring_get(cmd);
204 : 0 : pkg_dbg(PKG_DBG_FETCH, 1, "Fetch: running '%s'", cmdline);
205 : 0 : argv[0] = _PATH_BSHELL;
206 : 0 : argv[1] = "-c";
207 : 0 : argv[2] = cmdline;
208 : 0 : argv[3] = NULL;
209 : :
210 : 0 : if (sshin[0] != STDIN_FILENO)
211 : 0 : close(sshin[0]);
212 : 0 : if (sshout[1] != STDOUT_FILENO)
213 : 0 : close(sshout[1]);
214 : 0 : execvp(argv[0], __DECONST(char **, argv));
215 : : /* NOT REACHED */
216 : 0 : }
217 : :
218 : 0 : if (close(sshout[1]) < 0 || close(sshin[0]) < 0) {
219 : 0 : pkg_emit_errno("Failed to close pipes", "start_ssh");
220 : 0 : goto ssh_cleanup;
221 : : }
222 : :
223 : 0 : pkg_dbg(PKG_DBG_FETCH, 1, "SSH> connected");
224 : :
225 : 0 : repo->sshio.in = sshout[0];
226 : 0 : repo->sshio.out = sshin[1];
227 : 0 : set_nonblocking(repo->sshio.in);
228 : :
229 : 0 : repo->fh = funopen(repo, ssh_read, ssh_write, NULL, ssh_close);
230 [ # # ]: 0 : if (repo->fh == NULL) {
231 : 0 : pkg_emit_errno("Failed to open stream", "start_ssh");
232 : 0 : goto ssh_cleanup;
233 : : }
234 : :
235 [ # # ]: 0 : if (getline(&line, &linecap, repo->fh) > 0) {
236 [ # # ]: 0 : if (strncmp(line, "ok:", 3) != 0) {
237 : 0 : pkg_dbg(PKG_DBG_FETCH, 1, "SSH> server rejected, got: %s", line);
238 : 0 : goto ssh_cleanup;
239 : : }
240 : 0 : pkg_dbg(PKG_DBG_FETCH, 1, "SSH> server is: %s", line +4);
241 : 0 : } else {
242 : 0 : pkg_dbg(PKG_DBG_FETCH, 1, "SSH> nothing to read, got: %s", line);
243 : 0 : goto ssh_cleanup;
244 : : }
245 : 0 : retcode = EPKG_OK;
246 : :
247 : : ssh_cleanup:
248 : 0 : if (retcode == EPKG_FATAL && repo->fh != NULL) {
249 : 0 : fclose(repo->fh);
250 : 0 : repo->fh = NULL;
251 : 0 : }
252 : 0 : free(line);
253 : 0 : return (retcode);
254 : 0 : }
255 : :
256 : : static int
257 : 0 : pkgprotocol_open(struct pkg_repo *repo, struct fetch_item *fi,
258 : : int (*proto_connect)(struct pkg_repo *, struct yuarel *))
259 : : {
260 : 0 : char *line = NULL;
261 : 0 : size_t linecap = 0;
262 : : size_t linelen;
263 : : const char *errstr;
264 : 0 : int retcode = EPKG_FATAL;
265 : : struct yuarel url;
266 : 0 : char *url_to_free = xstrdup(fi->url);
267 : :
268 [ # # ]: 0 : if (yuarel_parse(&url, url_to_free) == -1) {
269 : 0 : free(url_to_free);
270 : 0 : pkg_emit_error("Invalid url: '%s'", fi->url);
271 : 0 : return (EPKG_FATAL);
272 : : }
273 : :
274 : 0 : pkg_dbg(PKG_DBG_FETCH, 1, "SSH> tcp_open");
275 [ # # ]: 0 : if (repo->fh == NULL)
276 : 0 : retcode = proto_connect(repo, &url);
277 : : else
278 : 0 : retcode = EPKG_OK;
279 : :
280 [ # # ]: 0 : if (retcode != EPKG_OK)
281 : 0 : return (retcode);
282 : :
283 : 0 : pkg_dbg(PKG_DBG_FETCH, 1, "SSH> get %s %" PRIdMAX "", url.path, (intmax_t)fi->mtime);
284 : 0 : fprintf(repo->fh, "get %s %" PRIdMAX "\n", url.path, (intmax_t)fi->mtime);
285 : 0 : if ((linelen = getline(&line, &linecap, repo->fh)) > 0) {
286 : 0 : if (line[linelen -1 ] == '\n')
287 : 0 : line[linelen -1 ] = '\0';
288 : :
289 : 0 : pkg_dbg(PKG_DBG_FETCH, 1, "SSH> recv: %s", line);
290 [ # # ]: 0 : if (strncmp(line, "ok:", 3) == 0) {
291 : 0 : fi->size = strtonum(line + 4, 0, LONG_MAX, &errstr);
292 [ # # ]: 0 : if (errstr) {
293 : 0 : goto out;
294 : : }
295 : :
296 [ # # ]: 0 : if (fi->size == 0) {
297 : 0 : retcode = EPKG_UPTODATE;
298 : 0 : goto out;
299 : : }
300 : :
301 : 0 : retcode = EPKG_OK;
302 : 0 : goto out;
303 : : }
304 [ # # ]: 0 : if (strncmp(line, "ko:", 3) == 0) {
305 : 0 : retcode = EPKG_FATAL;
306 : 0 : goto out;
307 : : }
308 : 0 : }
309 : :
310 : : out:
311 : 0 : free(url_to_free);
312 : 0 : free(line);
313 : 0 : return (retcode);
314 : 0 : }
315 : :
316 : : int
317 : 0 : tcp_open(struct pkg_repo *repo, struct fetch_item *fi)
318 : : {
319 : 0 : return (pkgprotocol_open(repo, fi, tcp_connect));
320 : : }
321 : :
322 : : int
323 : 0 : ssh_open(struct pkg_repo *repo, struct fetch_item *fi)
324 : : {
325 : 0 : return (pkgprotocol_open(repo, fi, ssh_connect));
326 : : }
327 : :
328 : : static int
329 : 0 : tcp_close(void *data)
330 : : {
331 : 0 : struct pkg_repo *repo = (struct pkg_repo *)data;
332 : :
333 : 0 : write(repo->sshio.out, "quit\n", 5);
334 : 0 : close(repo->sshio.out);
335 : 0 : close(repo->sshio.in);
336 : 0 : repo->fh = NULL;
337 : 0 : return (0);
338 : : }
339 : :
340 : : static int
341 : 0 : ssh_close(void *data)
342 : : {
343 : 0 : struct pkg_repo *repo = (struct pkg_repo *)data;
344 : : int pstat;
345 : :
346 : 0 : write(repo->sshio.out, "quit\n", 5);
347 : :
348 [ # # ]: 0 : while (waitpid(repo->sshio.pid, &pstat, 0) == -1) {
349 [ # # ]: 0 : if (errno != EINTR)
350 : 0 : return (EPKG_FATAL);
351 : : }
352 : 0 : close(repo->sshio.out);
353 : 0 : close(repo->sshio.in);
354 : :
355 : 0 : repo->fh = NULL;
356 : :
357 : 0 : return (WEXITSTATUS(pstat));
358 : 0 : }
359 : :
360 : : static int
361 : 0 : ssh_writev(int fd, struct iovec *iov, int iovcnt, int64_t tmout)
362 : : {
363 : : struct timespec now, timeout, delta;
364 : : struct pollfd pfd;
365 : : ssize_t wlen, total;
366 : : int deltams;
367 : : struct msghdr msg;
368 : :
369 : 0 : memset(&pfd, 0, sizeof pfd);
370 : :
371 : 0 : if (tmout > 0) {
372 : 0 : pfd.fd = fd;
373 : 0 : pfd.events = POLLOUT | POLLERR;
374 : 0 : clock_gettime(CLOCK_REALTIME, &timeout);
375 : 0 : timeout.tv_sec += tmout;
376 : 0 : }
377 : :
378 : 0 : total = 0;
379 [ # # ]: 0 : while (iovcnt > 0) {
380 [ # # ]: 0 : while (tmout && pfd.revents == 0) {
381 : 0 : clock_gettime(CLOCK_REALTIME, &now);
382 [ # # ]: 0 : if (!timespeccmp(&timeout, &now, >)) {
383 : 0 : errno = ETIMEDOUT;
384 : 0 : return (-1);
385 : : }
386 : 0 : timespecsub(&timeout, &now, &delta);
387 : 0 : deltams = delta.tv_sec * 1000 +
388 : 0 : delta.tv_nsec / 1000000;
389 : 0 : errno = 0;
390 : 0 : pfd.revents = 0;
391 [ # # ]: 0 : while (poll(&pfd, 1, deltams) == -1) {
392 [ # # ]: 0 : if (errno == EINTR)
393 : 0 : continue;
394 : :
395 : 0 : return (-1);
396 : : }
397 : : }
398 : 0 : errno = 0;
399 : 0 : memset(&msg, 0, sizeof(msg));
400 : 0 : msg.msg_iov = iov;
401 : 0 : msg.msg_iovlen = iovcnt;
402 : :
403 : 0 : wlen = sendmsg(fd, &msg, 0);
404 [ # # ]: 0 : if (wlen == 0) {
405 : 0 : errno = ECONNRESET;
406 : 0 : return (-1);
407 : : }
408 [ # # ]: 0 : else if (wlen < 0)
409 : 0 : return (-1);
410 : :
411 : 0 : total += wlen;
412 : :
413 [ # # ]: 0 : while (iovcnt > 0 && wlen >= (ssize_t)iov->iov_len) {
414 : 0 : wlen -= iov->iov_len;
415 : 0 : iov++;
416 : 0 : iovcnt--;
417 : : }
418 : :
419 : 0 : if (iovcnt > 0) {
420 : 0 : iov->iov_len -= wlen;
421 : 0 : iov->iov_base = __DECONST(char *, iov->iov_base) + wlen;
422 : 0 : }
423 : : }
424 : 0 : return (total);
425 : 0 : }
426 : :
427 : : static int
428 : 0 : ssh_write(void *data, const char *buf, int l)
429 : : {
430 : 0 : struct pkg_repo *repo = (struct pkg_repo *)data;
431 : : struct iovec iov;
432 : :
433 : 0 : iov.iov_base = __DECONST(char *, buf);
434 : 0 : iov.iov_len = l;
435 : :
436 : 0 : pkg_dbg(PKG_DBG_FETCH, 1, "SSH> writing data");
437 : :
438 : 0 : return (ssh_writev(repo->sshio.out, &iov, 1, repo->fetcher->timeout));
439 : : }
440 : :
441 : : static int
442 : 0 : ssh_read(void *data, char *buf, int len)
443 : : {
444 : 0 : struct pkg_repo *repo = (struct pkg_repo *) data;
445 : : struct timespec now, timeout, delta;
446 : : struct pollfd pfd;
447 : : ssize_t rlen;
448 : : int deltams;
449 : :
450 : 0 : pkg_dbg(PKG_DBG_FETCH, 1, "SSH> start reading");
451 : :
452 : 0 : if (repo->fetcher->timeout > 0) {
453 : 0 : clock_gettime(CLOCK_REALTIME, &timeout);
454 : 0 : timeout.tv_sec += repo->fetcher->timeout;
455 : 0 : }
456 : :
457 : 0 : deltams = -1;
458 : 0 : memset(&pfd, 0, sizeof pfd);
459 : 0 : pfd.fd = repo->sshio.in;
460 : 0 : pfd.events = POLLIN | POLLERR;
461 : :
462 : 0 : for (;;) {
463 : 0 : rlen = read(pfd.fd, buf, len);
464 : 0 : pkg_dbg(PKG_DBG_FETCH, 1, "SSH> read %jd", (intmax_t)rlen);
465 [ # # ]: 0 : if (rlen >= 0) {
466 : 0 : break;
467 : 0 : } else if (rlen == -1) {
468 [ # # ]: 0 : if (errno == EINTR)
469 : 0 : continue;
470 [ # # ]: 0 : if (errno != EAGAIN) {
471 : 0 : pkg_emit_errno("timeout", "ssh");
472 : 0 : return (-1);
473 : : }
474 : 0 : }
475 : :
476 : : /* only EAGAIN should get here */
477 : 0 : if (repo->fetcher->timeout > 0) {
478 : 0 : clock_gettime(CLOCK_REALTIME, &now);
479 [ # # ]: 0 : if (!timespeccmp(&timeout, &now, >)) {
480 : 0 : errno = ETIMEDOUT;
481 : 0 : return (-1);
482 : : }
483 : 0 : timespecsub(&timeout, &now, &delta);
484 : 0 : deltams = delta.tv_sec * 1000 +
485 : 0 : delta.tv_nsec / 1000000;
486 : 0 : }
487 : :
488 : 0 : errno = 0;
489 : 0 : pfd.revents = 0;
490 : 0 : pkg_dbg(PKG_DBG_FETCH, 2, "SSH> begin poll()");
491 [ # # ]: 0 : if (poll(&pfd, 1, deltams) < 0) {
492 [ # # ]: 0 : if (errno == EINTR)
493 : 0 : continue;
494 : 0 : return (-1);
495 : : }
496 : 0 : pkg_dbg(PKG_DBG_FETCH, 2, "SSH> end poll()");
497 : :
498 : : }
499 : :
500 : 0 : pkg_dbg(PKG_DBG_FETCH, 1, "SSH> have read %jd bytes", (intmax_t)rlen);
501 : :
502 : 0 : return (rlen);
503 : 0 : }
|