Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/pkg
Path: blob/main/libpkg/fetch_ssh.c
2065 views
1
/*-
2
* Copyright (c) 2020 Baptiste Daroussin <[email protected]>
3
* Copyright (c) 2023 Serenity Cyber Security, LLC <[email protected]>
4
* Author: Gleb Popov <[email protected]>
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
tcp_connect(struct pkg_repo *repo, struct yuarel *u)
78
{
79
char *line = NULL;
80
size_t linecap = 0;
81
struct addrinfo *ai = NULL, *curai, hints;
82
char srv[NI_MAXSERV];
83
int sd = -1;
84
int retcode;
85
86
pkg_dbg(PKG_DBG_FETCH, 1, "TCP> tcp_connect");
87
memset(&hints, 0, sizeof(hints));
88
hints.ai_family = PF_UNSPEC;
89
if (repo->ip == IPV4)
90
hints.ai_family = PF_INET;
91
else if (repo->ip == IPV6)
92
hints.ai_family = PF_INET6;
93
hints.ai_socktype = SOCK_STREAM;
94
snprintf(srv, sizeof(srv), "%d", u->port);
95
retcode = getaddrinfo(u->host, srv, &hints, &ai);
96
if (retcode != 0) {
97
pkg_emit_pkg_errno(EPKG_NONETWORK, "tcp_connect", gai_strerror(retcode));
98
pkg_emit_error("Unable to lookup for '%s'", u->host);
99
return (EPKG_FATAL);
100
}
101
for (curai = ai; curai != NULL; curai = curai->ai_next) {
102
if ((sd = socket(curai->ai_family, curai->ai_socktype,
103
curai->ai_protocol)) == -1)
104
continue;
105
if (connect(sd, curai->ai_addr, curai->ai_addrlen) == -1) {
106
close(sd);
107
sd = -1;
108
continue;
109
}
110
break;
111
}
112
freeaddrinfo(ai);
113
if (sd == -1) {
114
pkg_emit_pkg_errno(EPKG_NONETWORK, "tcp_connect", NULL);
115
pkg_emit_error("Could not connect to tcp://%s:%d", u->host,
116
u->port);
117
return (EPKG_FATAL);
118
}
119
if (setsockopt(sd, SOL_SOCKET, SO_KEEPALIVE, &(int){ 1 }, sizeof(int)) != 0) {
120
pkg_emit_errno("Could not connect", "setsockopt");
121
close(sd);
122
return (EPKG_FATAL);
123
}
124
repo->sshio.in = dup(sd);
125
repo->sshio.out = dup(sd);
126
repo->fh = funopen(repo, ssh_read, ssh_write, NULL, tcp_close);
127
128
retcode = EPKG_FATAL;
129
if (repo->fh == NULL) {
130
pkg_emit_errno("Failed to open stream", "tcp_connect");
131
goto tcp_cleanup;
132
}
133
134
if (getline(&line, &linecap, repo->fh) > 0) {
135
if (strncmp(line, "ok:", 3) != 0) {
136
pkg_dbg(PKG_DBG_FETCH, 1, "SSH> server rejected, got: %s", line);
137
goto tcp_cleanup;
138
}
139
pkg_dbg(PKG_DBG_FETCH, 1, "SSH> server is: %s", line +4);
140
} else {
141
pkg_dbg(PKG_DBG_FETCH, 1, "SSH> nothing to read, got: %s", line);
142
goto tcp_cleanup;
143
}
144
retcode = EPKG_OK;
145
tcp_cleanup:
146
if (retcode == EPKG_FATAL && repo->fh != NULL) {
147
fclose(repo->fh);
148
repo->fh = NULL;
149
}
150
free(line);
151
return (retcode);
152
}
153
154
static int
155
ssh_connect(struct pkg_repo *repo, struct yuarel *u)
156
{
157
char *line = NULL;
158
size_t linecap = 0;
159
int sshin[2];
160
int sshout[2];
161
xstring *cmd = NULL;
162
char *cmdline;
163
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
if (socketpair(AF_UNIX, SOCK_STREAM, 0, sshin) <0 ||
169
socketpair(AF_UNIX, SOCK_STREAM, 0, sshout) < 0)
170
return(EPKG_FATAL);
171
172
repo->sshio.pid = fork();
173
if (repo->sshio.pid == -1) {
174
pkg_emit_errno("Cannot fork", "start_ssh");
175
goto ssh_cleanup;
176
}
177
178
if (repo->sshio.pid == 0) {
179
180
if (dup2(sshin[0], STDIN_FILENO) < 0 ||
181
close(sshin[1]) < 0 ||
182
close(sshout[0]) < 0 ||
183
dup2(sshout[1], STDOUT_FILENO) < 0) {
184
pkg_emit_errno("Cannot prepare pipes", "start_ssh");
185
goto ssh_cleanup;
186
}
187
188
cmd = xstring_new();
189
fputs("/usr/bin/ssh -e none -T ", cmd->fp);
190
191
ssh_args = pkg_object_string(pkg_config_get("PKG_SSH_ARGS"));
192
if (ssh_args != NULL)
193
fprintf(cmd->fp, "%s ", ssh_args);
194
if (repo->ip == IPV4)
195
fputs("-4 ", cmd->fp);
196
else if (repo->ip == IPV6)
197
fputs("-6 ", cmd->fp);
198
if (u->port > 0)
199
fprintf(cmd->fp, "-p %d ", u->port);
200
if (u->username != NULL)
201
fprintf(cmd->fp, "%s@", u->username);
202
fprintf(cmd->fp, "%s pkg ssh", u->host);
203
cmdline = xstring_get(cmd);
204
pkg_dbg(PKG_DBG_FETCH, 1, "Fetch: running '%s'", cmdline);
205
argv[0] = _PATH_BSHELL;
206
argv[1] = "-c";
207
argv[2] = cmdline;
208
argv[3] = NULL;
209
210
if (sshin[0] != STDIN_FILENO)
211
close(sshin[0]);
212
if (sshout[1] != STDOUT_FILENO)
213
close(sshout[1]);
214
execvp(argv[0], __DECONST(char **, argv));
215
/* NOT REACHED */
216
}
217
218
if (close(sshout[1]) < 0 || close(sshin[0]) < 0) {
219
pkg_emit_errno("Failed to close pipes", "start_ssh");
220
goto ssh_cleanup;
221
}
222
223
pkg_dbg(PKG_DBG_FETCH, 1, "SSH> connected");
224
225
repo->sshio.in = sshout[0];
226
repo->sshio.out = sshin[1];
227
set_nonblocking(repo->sshio.in);
228
229
repo->fh = funopen(repo, ssh_read, ssh_write, NULL, ssh_close);
230
if (repo->fh == NULL) {
231
pkg_emit_errno("Failed to open stream", "start_ssh");
232
goto ssh_cleanup;
233
}
234
235
if (getline(&line, &linecap, repo->fh) > 0) {
236
if (strncmp(line, "ok:", 3) != 0) {
237
pkg_dbg(PKG_DBG_FETCH, 1, "SSH> server rejected, got: %s", line);
238
goto ssh_cleanup;
239
}
240
pkg_dbg(PKG_DBG_FETCH, 1, "SSH> server is: %s", line +4);
241
} else {
242
pkg_dbg(PKG_DBG_FETCH, 1, "SSH> nothing to read, got: %s", line);
243
goto ssh_cleanup;
244
}
245
retcode = EPKG_OK;
246
247
ssh_cleanup:
248
if (retcode == EPKG_FATAL && repo->fh != NULL) {
249
fclose(repo->fh);
250
repo->fh = NULL;
251
}
252
free(line);
253
return (retcode);
254
}
255
256
static int
257
pkgprotocol_open(struct pkg_repo *repo, struct fetch_item *fi,
258
int (*proto_connect)(struct pkg_repo *, struct yuarel *))
259
{
260
char *line = NULL;
261
size_t linecap = 0;
262
size_t linelen;
263
const char *errstr;
264
int retcode = EPKG_FATAL;
265
struct yuarel url;
266
char *url_to_free = xstrdup(fi->url);
267
268
if (yuarel_parse(&url, url_to_free) == -1) {
269
free(url_to_free);
270
pkg_emit_error("Invalid url: '%s'", fi->url);
271
return (EPKG_FATAL);
272
}
273
274
pkg_dbg(PKG_DBG_FETCH, 1, "SSH> tcp_open");
275
if (repo->fh == NULL)
276
retcode = proto_connect(repo, &url);
277
else
278
retcode = EPKG_OK;
279
280
if (retcode != EPKG_OK)
281
return (retcode);
282
283
pkg_dbg(PKG_DBG_FETCH, 1, "SSH> get %s %" PRIdMAX "", url.path, (intmax_t)fi->mtime);
284
fprintf(repo->fh, "get %s %" PRIdMAX "\n", url.path, (intmax_t)fi->mtime);
285
if ((linelen = getline(&line, &linecap, repo->fh)) > 0) {
286
if (line[linelen -1 ] == '\n')
287
line[linelen -1 ] = '\0';
288
289
pkg_dbg(PKG_DBG_FETCH, 1, "SSH> recv: %s", line);
290
if (strncmp(line, "ok:", 3) == 0) {
291
fi->size = strtonum(line + 4, 0, LONG_MAX, &errstr);
292
if (errstr) {
293
goto out;
294
}
295
296
if (fi->size == 0) {
297
retcode = EPKG_UPTODATE;
298
goto out;
299
}
300
301
retcode = EPKG_OK;
302
goto out;
303
}
304
if (strncmp(line, "ko:", 3) == 0) {
305
retcode = EPKG_FATAL;
306
goto out;
307
}
308
}
309
310
out:
311
free(url_to_free);
312
free(line);
313
return (retcode);
314
}
315
316
int
317
tcp_open(struct pkg_repo *repo, struct fetch_item *fi)
318
{
319
return (pkgprotocol_open(repo, fi, tcp_connect));
320
}
321
322
int
323
ssh_open(struct pkg_repo *repo, struct fetch_item *fi)
324
{
325
return (pkgprotocol_open(repo, fi, ssh_connect));
326
}
327
328
static int
329
tcp_close(void *data)
330
{
331
struct pkg_repo *repo = (struct pkg_repo *)data;
332
333
write(repo->sshio.out, "quit\n", 5);
334
close(repo->sshio.out);
335
close(repo->sshio.in);
336
repo->fh = NULL;
337
return (0);
338
}
339
340
static int
341
ssh_close(void *data)
342
{
343
struct pkg_repo *repo = (struct pkg_repo *)data;
344
int pstat;
345
346
write(repo->sshio.out, "quit\n", 5);
347
348
while (waitpid(repo->sshio.pid, &pstat, 0) == -1) {
349
if (errno != EINTR)
350
return (EPKG_FATAL);
351
}
352
close(repo->sshio.out);
353
close(repo->sshio.in);
354
355
repo->fh = NULL;
356
357
return (WEXITSTATUS(pstat));
358
}
359
360
static int
361
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
memset(&pfd, 0, sizeof pfd);
370
371
if (tmout > 0) {
372
pfd.fd = fd;
373
pfd.events = POLLOUT | POLLERR;
374
clock_gettime(CLOCK_REALTIME, &timeout);
375
timeout.tv_sec += tmout;
376
}
377
378
total = 0;
379
while (iovcnt > 0) {
380
while (tmout && pfd.revents == 0) {
381
clock_gettime(CLOCK_REALTIME, &now);
382
if (!timespeccmp(&timeout, &now, >)) {
383
errno = ETIMEDOUT;
384
return (-1);
385
}
386
timespecsub(&timeout, &now, &delta);
387
deltams = delta.tv_sec * 1000 +
388
delta.tv_nsec / 1000000;
389
errno = 0;
390
pfd.revents = 0;
391
while (poll(&pfd, 1, deltams) == -1) {
392
if (errno == EINTR)
393
continue;
394
395
return (-1);
396
}
397
}
398
errno = 0;
399
memset(&msg, 0, sizeof(msg));
400
msg.msg_iov = iov;
401
msg.msg_iovlen = iovcnt;
402
403
wlen = sendmsg(fd, &msg, 0);
404
if (wlen == 0) {
405
errno = ECONNRESET;
406
return (-1);
407
}
408
else if (wlen < 0)
409
return (-1);
410
411
total += wlen;
412
413
while (iovcnt > 0 && wlen >= (ssize_t)iov->iov_len) {
414
wlen -= iov->iov_len;
415
iov++;
416
iovcnt--;
417
}
418
419
if (iovcnt > 0) {
420
iov->iov_len -= wlen;
421
iov->iov_base = __DECONST(char *, iov->iov_base) + wlen;
422
}
423
}
424
return (total);
425
}
426
427
static int
428
ssh_write(void *data, const char *buf, int l)
429
{
430
struct pkg_repo *repo = (struct pkg_repo *)data;
431
struct iovec iov;
432
433
iov.iov_base = __DECONST(char *, buf);
434
iov.iov_len = l;
435
436
pkg_dbg(PKG_DBG_FETCH, 1, "SSH> writing data");
437
438
return (ssh_writev(repo->sshio.out, &iov, 1, repo->fetcher->timeout));
439
}
440
441
static int
442
ssh_read(void *data, char *buf, int len)
443
{
444
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
pkg_dbg(PKG_DBG_FETCH, 1, "SSH> start reading");
451
452
if (repo->fetcher->timeout > 0) {
453
clock_gettime(CLOCK_REALTIME, &timeout);
454
timeout.tv_sec += repo->fetcher->timeout;
455
}
456
457
deltams = -1;
458
memset(&pfd, 0, sizeof pfd);
459
pfd.fd = repo->sshio.in;
460
pfd.events = POLLIN | POLLERR;
461
462
for (;;) {
463
rlen = read(pfd.fd, buf, len);
464
pkg_dbg(PKG_DBG_FETCH, 1, "SSH> read %jd", (intmax_t)rlen);
465
if (rlen >= 0) {
466
break;
467
} else if (rlen == -1) {
468
if (errno == EINTR)
469
continue;
470
if (errno != EAGAIN) {
471
pkg_emit_errno("timeout", "ssh");
472
return (-1);
473
}
474
}
475
476
/* only EAGAIN should get here */
477
if (repo->fetcher->timeout > 0) {
478
clock_gettime(CLOCK_REALTIME, &now);
479
if (!timespeccmp(&timeout, &now, >)) {
480
errno = ETIMEDOUT;
481
return (-1);
482
}
483
timespecsub(&timeout, &now, &delta);
484
deltams = delta.tv_sec * 1000 +
485
delta.tv_nsec / 1000000;
486
}
487
488
errno = 0;
489
pfd.revents = 0;
490
pkg_dbg(PKG_DBG_FETCH, 2, "SSH> begin poll()");
491
if (poll(&pfd, 1, deltams) < 0) {
492
if (errno == EINTR)
493
continue;
494
return (-1);
495
}
496
pkg_dbg(PKG_DBG_FETCH, 2, "SSH> end poll()");
497
498
}
499
500
pkg_dbg(PKG_DBG_FETCH, 1, "SSH> have read %jd bytes", (intmax_t)rlen);
501
502
return (rlen);
503
}
504
505