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