Branch data Line data Source code
1 : : /*-
2 : : * Copyright (c) 2020 Baptiste Daroussin <bapt@FreeBSD.org>
3 : : *
4 : : * Redistribution and use in source and binary forms, with or without
5 : : * modification, are permitted provided that the following conditions
6 : : * are met:
7 : : * 1. Redistributions of source code must retain the above copyright
8 : : * notice, this list of conditions and the following disclaimer
9 : : * in this position and unchanged.
10 : : * 2. Redistributions in binary form must reproduce the above copyright
11 : : * notice, this list of conditions and the following disclaimer in the
12 : : * documentation and/or other materials provided with the distribution.
13 : : *
14 : : * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
15 : : * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
16 : : * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
17 : : * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
18 : : * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
19 : : * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
20 : : * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
21 : : * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 : : * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
23 : : * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 : : */
25 : :
26 : : #include <sys/param.h>
27 : : #include <sys/wait.h>
28 : : #include <sys/socket.h>
29 : : #include <sys/time.h>
30 : :
31 : : #include <ctype.h>
32 : : #include <fcntl.h>
33 : : #include <errno.h>
34 : : #include <stdio.h>
35 : : #include <string.h>
36 : : #include <fetch.h>
37 : : #include <paths.h>
38 : : #include <poll.h>
39 : :
40 : : #include <bsd_compat.h>
41 : :
42 : : #include "pkg.h"
43 : : #include "private/event.h"
44 : : #include "private/pkg.h"
45 : : #include "private/fetch.h"
46 : : #include "private/utils.h"
47 : :
48 : : static int ssh_read(void *data, char *buf, int len);
49 : : static int ssh_write(void *data, const char *buf, int l);
50 : : static int ssh_close(void *data);
51 : :
52 : : static int
53 : 0 : ssh_connect(struct pkg_repo *repo, struct url *u)
54 : : {
55 : 0 : char *line = NULL;
56 : 0 : size_t linecap = 0;
57 : : int sshin[2];
58 : : int sshout[2];
59 : 0 : xstring *cmd = NULL;
60 : : char *cmdline;
61 : 0 : int retcode = EPKG_FATAL;
62 : : const char *ssh_args;
63 : : const char *argv[4];
64 : :
65 : : /* Use socket pair because pipe have blocking issues */
66 : 0 : if (socketpair(AF_UNIX, SOCK_STREAM, 0, sshin) <0 ||
67 : 0 : socketpair(AF_UNIX, SOCK_STREAM, 0, sshout) < 0)
68 : 0 : return(EPKG_FATAL);
69 : :
70 : 0 : repo->sshio.pid = fork();
71 [ # # ]: 0 : if (repo->sshio.pid == -1) {
72 : 0 : pkg_emit_errno("Cannot fork", "start_ssh");
73 : 0 : goto ssh_cleanup;
74 : : }
75 : :
76 : 0 : if (repo->sshio.pid == 0) {
77 : :
78 : 0 : if (dup2(sshin[0], STDIN_FILENO) < 0 ||
79 : 0 : close(sshin[1]) < 0 ||
80 : 0 : close(sshout[0]) < 0 ||
81 : 0 : dup2(sshout[1], STDOUT_FILENO) < 0) {
82 : 0 : pkg_emit_errno("Cannot prepare pipes", "start_ssh");
83 : 0 : goto ssh_cleanup;
84 : : }
85 : :
86 : 0 : cmd = xstring_new();
87 : 0 : fputs("/usr/bin/ssh -e none -T ", cmd->fp);
88 : :
89 : 0 : ssh_args = pkg_object_string(pkg_config_get("PKG_SSH_ARGS"));
90 : 0 : if (ssh_args != NULL)
91 : 0 : fprintf(cmd->fp, "%s ", ssh_args);
92 [ # # ]: 0 : if ((repo->flags & REPO_FLAGS_USE_IPV4) == REPO_FLAGS_USE_IPV4)
93 : 0 : fputs("-4 ", cmd->fp);
94 : 0 : else if ((repo->flags & REPO_FLAGS_USE_IPV6) == REPO_FLAGS_USE_IPV6)
95 : 0 : fputs("-6 ", cmd->fp);
96 : 0 : if (u->port > 0)
97 : 0 : fprintf(cmd->fp, "-p %d ", u->port);
98 : 0 : if (u->user[0] != '\0')
99 : 0 : fprintf(cmd->fp, "%s@", u->user);
100 : 0 : fprintf(cmd->fp, "%s pkg ssh", u->host);
101 : 0 : cmdline = xstring_get(cmd);
102 : 0 : pkg_debug(1, "Fetch: running '%s'", cmdline);
103 : 0 : argv[0] = _PATH_BSHELL;
104 : 0 : argv[1] = "-c";
105 : 0 : argv[2] = cmdline;
106 : 0 : argv[3] = NULL;
107 : :
108 : 0 : if (sshin[0] != STDIN_FILENO)
109 : 0 : close(sshin[0]);
110 : 0 : if (sshout[1] != STDOUT_FILENO)
111 : 0 : close(sshout[1]);
112 : 0 : execvp(argv[0], __DECONST(char **, argv));
113 : : /* NOT REACHED */
114 : 0 : }
115 : :
116 : 0 : if (close(sshout[1]) < 0 || close(sshin[0]) < 0) {
117 : 0 : pkg_emit_errno("Failed to close pipes", "start_ssh");
118 : 0 : goto ssh_cleanup;
119 : : }
120 : :
121 : 0 : pkg_debug(1, "SSH> connected");
122 : :
123 : 0 : repo->sshio.in = sshout[0];
124 : 0 : repo->sshio.out = sshin[1];
125 : 0 : set_nonblocking(repo->sshio.in);
126 : :
127 : 0 : repo->ssh = funopen(repo, ssh_read, ssh_write, NULL, ssh_close);
128 [ # # ]: 0 : if (repo->ssh == NULL) {
129 : 0 : pkg_emit_errno("Failed to open stream", "start_ssh");
130 : 0 : goto ssh_cleanup;
131 : : }
132 : :
133 [ # # ]: 0 : if (getline(&line, &linecap, repo->ssh) > 0) {
134 [ # # ]: 0 : if (strncmp(line, "ok:", 3) != 0) {
135 : 0 : pkg_debug(1, "SSH> server rejected, got: %s", line);
136 : 0 : goto ssh_cleanup;
137 : : }
138 : 0 : pkg_debug(1, "SSH> server is: %s", line +4);
139 : 0 : } else {
140 : 0 : pkg_debug(1, "SSH> nothing to read, got: %s", line);
141 : 0 : goto ssh_cleanup;
142 : : }
143 : 0 : retcode = EPKG_OK;
144 : :
145 : : ssh_cleanup:
146 : 0 : if (retcode == EPKG_FATAL && repo->ssh != NULL) {
147 : 0 : fclose(repo->ssh);
148 : 0 : repo->ssh = NULL;
149 : 0 : }
150 : 0 : free(line);
151 : 0 : return (retcode);
152 : 0 : }
153 : :
154 : : int
155 : 0 : ssh_open(struct pkg_repo *repo, struct url *u, off_t *sz)
156 : : {
157 : 0 : char *line = NULL;
158 : 0 : size_t linecap = 0;
159 : : size_t linelen;
160 : : const char *errstr;
161 : 0 : int retcode = EPKG_FATAL;
162 : :
163 [ # # ]: 0 : if (repo->ssh == NULL)
164 : 0 : retcode = ssh_connect(repo, u);
165 : : else
166 : 0 : retcode = EPKG_OK;
167 : :
168 [ # # ]: 0 : if (retcode != EPKG_OK)
169 : 0 : return (retcode);
170 : :
171 : 0 : pkg_debug(1, "SSH> get %s %" PRIdMAX "", u->doc, (intmax_t)u->ims_time);
172 : 0 : fprintf(repo->ssh, "get %s %" PRIdMAX "\n", u->doc, (intmax_t)u->ims_time);
173 : 0 : if ((linelen = getline(&line, &linecap, repo->ssh)) > 0) {
174 : 0 : if (line[linelen -1 ] == '\n')
175 : 0 : line[linelen -1 ] = '\0';
176 : :
177 : 0 : pkg_debug(1, "SSH> recv: %s", line);
178 [ # # ]: 0 : if (strncmp(line, "ok:", 3) == 0) {
179 : 0 : *sz = strtonum(line + 4, 0, LONG_MAX, &errstr);
180 [ # # ]: 0 : if (errstr) {
181 : 0 : goto out;
182 : : }
183 : :
184 [ # # ]: 0 : if (*sz == 0) {
185 : 0 : retcode = EPKG_UPTODATE;
186 : 0 : goto out;
187 : : }
188 : :
189 : 0 : retcode = EPKG_OK;
190 : 0 : goto out;
191 : : }
192 : 0 : }
193 : :
194 : : out:
195 : 0 : free(line);
196 : 0 : return (retcode);
197 : 0 : }
198 : :
199 : : static int
200 : 0 : ssh_close(void *data)
201 : : {
202 : 0 : struct pkg_repo *repo = (struct pkg_repo *)data;
203 : : int pstat;
204 : :
205 : 0 : write(repo->sshio.out, "quit\n", 5);
206 : :
207 [ # # ]: 0 : while (waitpid(repo->sshio.pid, &pstat, 0) == -1) {
208 [ # # ]: 0 : if (errno != EINTR)
209 : 0 : return (EPKG_FATAL);
210 : : }
211 : :
212 : 0 : repo->ssh = NULL;
213 : :
214 : 0 : return (WEXITSTATUS(pstat));
215 : 0 : }
216 : :
217 : : static int
218 : 0 : ssh_writev(int fd, struct iovec *iov, int iovcnt)
219 : : {
220 : : struct timeval now, timeout, delta;
221 : : struct pollfd pfd;
222 : : ssize_t wlen, total;
223 : : int deltams;
224 : : struct msghdr msg;
225 : :
226 : 0 : memset(&pfd, 0, sizeof pfd);
227 : :
228 : 0 : if (fetchTimeout) {
229 : 0 : pfd.fd = fd;
230 : 0 : pfd.events = POLLOUT | POLLERR;
231 : 0 : gettimeofday(&timeout, NULL);
232 : 0 : timeout.tv_sec += fetchTimeout;
233 : 0 : }
234 : :
235 : 0 : total = 0;
236 [ # # ]: 0 : while (iovcnt > 0) {
237 [ # # ]: 0 : while (fetchTimeout && pfd.revents == 0) {
238 : 0 : gettimeofday(&now, NULL);
239 [ # # ]: 0 : if (!timercmp(&timeout, &now, >)) {
240 : 0 : errno = ETIMEDOUT;
241 : 0 : return (-1);
242 : : }
243 : 0 : timersub(&timeout, &now, &delta);
244 : 0 : deltams = delta.tv_sec * 1000 +
245 : 0 : delta.tv_usec / 1000;
246 : 0 : errno = 0;
247 : 0 : pfd.revents = 0;
248 [ # # ]: 0 : while (poll(&pfd, 1, deltams) == -1) {
249 [ # # ]: 0 : if (errno == EINTR)
250 : 0 : continue;
251 : :
252 : 0 : return (-1);
253 : : }
254 : : }
255 : 0 : errno = 0;
256 : 0 : memset(&msg, 0, sizeof(msg));
257 : 0 : msg.msg_iov = iov;
258 : 0 : msg.msg_iovlen = iovcnt;
259 : :
260 : 0 : wlen = sendmsg(fd, &msg, 0);
261 [ # # ]: 0 : if (wlen == 0) {
262 : 0 : errno = ECONNRESET;
263 : 0 : return (-1);
264 : : }
265 [ # # ]: 0 : else if (wlen < 0)
266 : 0 : return (-1);
267 : :
268 : 0 : total += wlen;
269 : :
270 [ # # ]: 0 : while (iovcnt > 0 && wlen >= (ssize_t)iov->iov_len) {
271 : 0 : wlen -= iov->iov_len;
272 : 0 : iov++;
273 : 0 : iovcnt--;
274 : : }
275 : :
276 : 0 : if (iovcnt > 0) {
277 : 0 : iov->iov_len -= wlen;
278 : 0 : iov->iov_base = __DECONST(char *, iov->iov_base) + wlen;
279 : 0 : }
280 : : }
281 : 0 : return (total);
282 : 0 : }
283 : :
284 : : static int
285 : 0 : ssh_write(void *data, const char *buf, int l)
286 : : {
287 : 0 : struct pkg_repo *repo = (struct pkg_repo *)data;
288 : : struct iovec iov;
289 : :
290 : 0 : iov.iov_base = __DECONST(char *, buf);
291 : 0 : iov.iov_len = l;
292 : :
293 : 0 : pkg_debug(1, "writing data");
294 : :
295 : 0 : return (ssh_writev(repo->sshio.out, &iov, 1));
296 : : }
297 : :
298 : : static int
299 : 0 : ssh_read(void *data, char *buf, int len)
300 : : {
301 : 0 : struct pkg_repo *repo = (struct pkg_repo *) data;
302 : : struct timeval now, timeout, delta;
303 : : struct pollfd pfd;
304 : : ssize_t rlen;
305 : : int deltams;
306 : :
307 : 0 : pkg_debug(2, "ssh: start reading");
308 : :
309 : 0 : if (fetchTimeout > 0) {
310 : 0 : gettimeofday(&timeout, NULL);
311 : 0 : timeout.tv_sec += fetchTimeout;
312 : 0 : }
313 : :
314 : 0 : deltams = -1;
315 : 0 : memset(&pfd, 0, sizeof pfd);
316 : 0 : pfd.fd = repo->sshio.in;
317 : 0 : pfd.events = POLLIN | POLLERR;
318 : :
319 : 0 : for (;;) {
320 : 0 : rlen = read(pfd.fd, buf, len);
321 : 0 : pkg_debug(2, "read %jd", (intmax_t)rlen);
322 [ # # ]: 0 : if (rlen >= 0) {
323 : 0 : break;
324 : 0 : } else if (rlen == -1) {
325 [ # # ]: 0 : if (errno == EINTR)
326 : 0 : continue;
327 [ # # ]: 0 : if (errno != EAGAIN) {
328 : 0 : pkg_emit_errno("timeout", "ssh");
329 : 0 : return (-1);
330 : : }
331 : 0 : }
332 : :
333 : : /* only EAGAIN should get here */
334 : 0 : if (fetchTimeout > 0) {
335 : 0 : gettimeofday(&now, NULL);
336 [ # # ]: 0 : if (!timercmp(&timeout, &now, >)) {
337 : 0 : errno = ETIMEDOUT;
338 : 0 : return (-1);
339 : : }
340 : 0 : timersub(&timeout, &now, &delta);
341 : 0 : deltams = delta.tv_sec * 1000 +
342 : 0 : delta.tv_usec / 1000;
343 : 0 : }
344 : :
345 : 0 : errno = 0;
346 : 0 : pfd.revents = 0;
347 : 0 : pkg_debug(1, "begin poll()");
348 [ # # ]: 0 : if (poll(&pfd, 1, deltams) < 0) {
349 [ # # ]: 0 : if (errno == EINTR)
350 : 0 : continue;
351 : 0 : return (-1);
352 : : }
353 : 0 : pkg_debug(1, "end poll()");
354 : :
355 : :
356 : : }
357 : :
358 : 0 : pkg_debug(2, "ssh: have read %jd bytes", (intmax_t)rlen);
359 : :
360 : 0 : return (rlen);
361 : 0 : }
|