1 /*-
2 * Copyright (c) 2012-2020 Baptiste Daroussin <bapt@FreeBSD.org>
3 * Copyright (c) 2011-2012 Julien Laffaye <jlaffaye@FreeBSD.org>
4 * Copyright (c) 2014 Vsevolod Stakhov <vsevolod@FreeBSD.org>
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer
12 * in this position and unchanged.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in the
15 * documentation and/or other materials provided with the distribution.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29 #include <sys/param.h>
30 #include <sys/wait.h>
31 #include <sys/socket.h>
32 #include <sys/time.h>
33
34 #include <ctype.h>
35 #include <fcntl.h>
36 #include <errno.h>
37 #include <stdio.h>
38 #include <string.h>
39 #include <fetch.h>
40 #include <paths.h>
41 #include <poll.h>
42
43 #include <bsd_compat.h>
44
45 #include "pkg.h"
46 #include "private/event.h"
47 #include "private/pkg.h"
48 #include "private/utils.h"
49 #include "private/fetch.h"
50
51 static struct fetcher {
52 const char *scheme;
53 int (*open)(struct pkg_repo *, struct url *, off_t *);
54 } fetchers [] = {
55 {
56 "ssh",
57 ssh_open,
58 },
59 {
60 "pkg+https",
61 fetch_open,
62 },
63 {
64 "pkg+http",
65 fetch_open,
66 },
67 {
68 "https",
69 fetch_open,
70 },
71 {
72 "http",
73 fetch_open,
74 },
75 {
76 "pkg+ftps",
77 fetch_open,
78 },
79 {
80 "pkg+ftp",
81 fetch_open,
82 },
83 {
84 "ftps",
85 fetch_open,
86 },
87 {
88 "ftp",
89 fetch_open,
90 },
91 {
92 "file",
93 file_open,
94 },
95 };
96
97
98 int
pkg_fetch_file_tmp(struct pkg_repo * repo,const char * url,char * dest,time_t t)99 pkg_fetch_file_tmp(struct pkg_repo *repo, const char *url, char *dest,
100 time_t t)
101 {
102 int fd = -1;
103 int retcode = EPKG_FATAL;
104
105 fd = mkstemp(dest);
106
107 if (fd == -1) {
108 pkg_emit_errno("mkstemp", dest);
109 return(EPKG_FATAL);
110 }
111
112 retcode = pkg_fetch_file_to_fd(repo, url, fd, &t, 0, -1, false);
113
114 if (t != 0) {
115 struct timeval ftimes[2] = {
116 {
117 .tv_sec = t,
118 .tv_usec = 0
119 },
120 {
121 .tv_sec = t,
122 .tv_usec = 0
123 }
124 };
125 futimes(fd, ftimes);
126 }
127
128 close(fd);
129
130 /* Remove local file if fetch failed */
131 if (retcode != EPKG_OK)
132 unlink(dest);
133
134 return (retcode);
135 }
136
137 int
pkg_fetch_file(struct pkg_repo * repo,const char * url,char * dest,time_t t,ssize_t offset,int64_t size)138 pkg_fetch_file(struct pkg_repo *repo, const char *url, char *dest, time_t t,
139 ssize_t offset, int64_t size)
140 {
141 int fd = -1;
142 int retcode = EPKG_FATAL;
143
144 fd = open(dest, O_CREAT|O_APPEND|O_WRONLY, 00644);
145 if (fd == -1) {
146 pkg_emit_errno("open", dest);
147 return(EPKG_FATAL);
148 }
149
150 retcode = pkg_fetch_file_to_fd(repo, url, fd, &t, offset, size, false);
151
152 if (t != 0) {
153 struct timeval ftimes[2] = {
154 {
155 .tv_sec = t,
156 .tv_usec = 0
157 },
158 {
159 .tv_sec = t,
160 .tv_usec = 0
161 }
162 };
163 futimes(fd, ftimes);
164 }
165
166 close(fd);
167
168 /* Remove local file if fetch failed */
169 if (retcode != EPKG_OK)
170 unlink(dest);
171
172 return (retcode);
173 }
174
175 #define URL_SCHEME_PREFIX "pkg+"
176
177 int
pkg_fetch_file_to_fd(struct pkg_repo * repo,const char * url,int dest,time_t * t,ssize_t offset,int64_t size,bool silent)178 pkg_fetch_file_to_fd(struct pkg_repo *repo, const char *url, int dest,
179 time_t *t, ssize_t offset, int64_t size, bool silent)
180 {
181 struct url *u = NULL;
182 struct pkg_kv *kv, *kvtmp;
183 struct pkg_kv *envtorestore = NULL;
184 struct pkg_kv *envtounset = NULL;
185 char *tmp;
186 off_t done = 0;
187 off_t r;
188 char buf[8192];
189 int retcode = EPKG_OK;
190 off_t sz = 0;
191 size_t buflen = 0;
192 size_t left = 0;
193 struct fetcher *fetcher = NULL;
194 struct pkg_repo *fakerepo = NULL;
195
196 FILE *remote = NULL;
197
198 /* A URL of the form http://host.example.com/ where
199 * host.example.com does not resolve as a simple A record is
200 * not valid according to RFC 2616 Section 3.2.2. Our usage
201 * with SRV records is incorrect. However it is encoded into
202 * /usr/sbin/pkg in various releases so we can't just drop it.
203 *
204 * Instead, introduce new pkg+http://, pkg+https://,
205 * pkg+ssh://, pkg+ftp://, pkg+file:// to support the
206 * SRV-style server discovery, and also to allow eg. Firefox
207 * to run pkg-related stuff given a pkg+foo:// URL.
208 *
209 * Error if using plain http://, https:// etc with SRV
210 */
211
212 pkg_debug(1, "Request to fetch %s", url);
213 if (repo != NULL &&
214 strncmp(URL_SCHEME_PREFIX, url, strlen(URL_SCHEME_PREFIX)) == 0) {
215 if (repo->mirror_type != SRV) {
216 pkg_emit_error("packagesite URL error for %s -- "
217 URL_SCHEME_PREFIX
218 ":// implies SRV mirror type", url);
219
220 /* Too early for there to be anything to cleanup */
221 return(EPKG_FATAL);
222 }
223
224 url += strlen(URL_SCHEME_PREFIX);
225 u = fetchParseURL(url);
226 }
227
228 if (u == NULL)
229 u = fetchParseURL(url);
230
231 if (offset > 0)
232 u->offset = offset;
233
234 if (repo != NULL) {
235 repo->silent = silent;
236 LL_FOREACH(repo->env, kv) {
237 kvtmp = xcalloc(1, sizeof(*kvtmp));
238 kvtmp->key = xstrdup(kv->key);
239 if ((tmp = getenv(kv->key)) != NULL) {
240 kvtmp->value = xstrdup(tmp);
241 DL_APPEND(envtorestore, kvtmp);
242 } else {
243 DL_APPEND(envtounset, kvtmp);
244 }
245 setenv(kv->key, kv->value, 1);
246 }
247 } else {
248 fakerepo = xcalloc(1, sizeof(struct pkg_repo));
249 fakerepo->url = xstrdup(url);
250 repo = fakerepo;
251 }
252
253 if (u == NULL) {
254 pkg_emit_error("%s: parse error", url);
255 /* Too early for there to be anything to cleanup */
256 return(EPKG_FATAL);
257 }
258
259 if (t != NULL)
260 u->ims_time = *t;
261
262 for (int i = 0; i < nitems(fetchers); i++) {
263 if (strcmp(u->scheme, fetchers[i].scheme) == 0) {
264 fetcher = &fetchers[i];
265 if ((retcode = fetcher->open(repo, u, &sz)) != EPKG_OK)
266 goto cleanup;
267 remote = repo->ssh ? repo->ssh : repo->fh;
268 break;
269 }
270 }
271 if (fetcher == NULL) {
272 pkg_emit_error("Unknown scheme: %s", u->scheme);
273 return (EPKG_FATAL);
274 }
275 pkg_debug(1, "Fetch: fetcher chosen: %s", fetcher->scheme);
276
277 if (strcmp(u->scheme, "ssh") != 0) {
278 if (t != NULL && u->ims_time != 0) {
279 if (u->ims_time <= *t) {
280 retcode = EPKG_UPTODATE;
281 goto cleanup;
282 } else
283 *t = u->ims_time;
284 }
285 }
286
287 if (sz <= 0 && size > 0)
288 sz = size;
289
290 pkg_emit_fetch_begin(url);
291 pkg_emit_progress_start(NULL);
292 if (offset > 0)
293 done += offset;
294 buflen = sizeof(buf);
295 left = sizeof(buf);
296 if (sz > 0)
297 left = sz - done;
298 while ((r = fread(buf, 1, left < buflen ? left : buflen, remote)) > 0) {
299 if (write(dest, buf, r) != r) {
300 pkg_emit_errno("write", "");
301 retcode = EPKG_FATAL;
302 goto cleanup;
303 }
304 done += r;
305 if (sz > 0) {
306 left -= r;
307 pkg_debug(4, "Read status: %jd over %jd", (intmax_t)done, (intmax_t)sz);
308 } else
309 pkg_debug(4, "Read status: %jd", (intmax_t)done);
310 if (sz > 0)
311 pkg_emit_progress_tick(done, sz);
312 }
313
314 if (r != 0) {
315 pkg_emit_error("An error occurred while fetching package");
316 retcode = EPKG_FATAL;
317 goto cleanup;
318 } else {
319 pkg_emit_progress_tick(done, done);
320 }
321 pkg_emit_fetch_finished(url);
322
323 if (strcmp(u->scheme, "ssh") != 0 && ferror(remote)) {
324 pkg_emit_error("%s: %s", url, fetchLastErrString);
325 retcode = EPKG_FATAL;
326 goto cleanup;
327 }
328
329 cleanup:
330 if (repo != NULL) {
331 LL_FOREACH_SAFE(envtorestore, kv, kvtmp) {
332 setenv(kv->key, kv->value, 1);
333 LL_DELETE(envtorestore, kv);
334 pkg_kv_free(kv);
335 }
336 LL_FOREACH_SAFE(envtounset, kv, kvtmp) {
337 unsetenv(kv->key);
338 pkg_kv_free(kv);
339 }
340 }
341
342 if (u != NULL) {
343 if (remote != NULL && repo != NULL && remote != repo->ssh) {
344 fclose(remote);
345 repo->fh = NULL;
346 }
347 }
348 free(fakerepo);
349
350 if (retcode == EPKG_OK) {
351 struct timeval ftimes[2] = {
352 {
353 .tv_sec = *t,
354 .tv_usec = 0
355 },
356 {
357 .tv_sec = *t,
358 .tv_usec = 0
359 }
360 };
361 futimes(dest, ftimes);
362 }
363
364 /* restore original doc */
365 fetchFreeURL(u);
366
367 return (retcode);
368 }
369