/ src / upgrade.c
upgrade.c
  1  /*-
  2   * Copyright (c) 2011-2012 Baptiste Daroussin <bapt@FreeBSD.org>
  3   * Copyright (c) 2013 Matthew Seaman <matthew@FreeBSD.org>
  4   * Copyright (c) 2012-2013 Bryan Drewery <bdrewery@FreeBSD.org>
  5   * Copyright (c) 2016 Vsevolod Stakhov <vsevolod@FreeBSD.org>
  6   * All rights reserved.
  7   *
  8   * Redistribution and use in source and binary forms, with or without
  9   * modification, are permitted provided that the following conditions
 10   * are met:
 11   * 1. Redistributions of source code must retain the above copyright
 12   *    notice, this list of conditions and the following disclaimer
 13   *    in this position and unchanged.
 14   * 2. Redistributions in binary form must reproduce the above copyright
 15   *    notice, this list of conditions and the following disclaimer in the
 16   *    documentation and/or other materials provided with the distribution.
 17   *
 18   * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
 19   * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 20   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 21   * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
 22   * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 23   * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 24   * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 25   * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 26   * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 27   * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 28   */
 29  
 30  #include <sys/types.h>
 31  #include <sys/socket.h>
 32  #include <sys/wait.h>
 33  #include <err.h>
 34  #include <getopt.h>
 35  #include <stdio.h>
 36  #include <unistd.h>
 37  #include <errno.h>
 38  #include <signal.h>
 39  #include <xstring.h>
 40  #include <pkghash.h>
 41  #include <pkg.h>
 42  
 43  #if __has_include(<sys/capsicum.h>)
 44  #include <sys/capsicum.h>
 45  #define HAVE_CAPSICUM 1
 46  #endif
 47  
 48  #include "pkgcli.h"
 49  #include <pkg/audit.h>
 50  
 51  static const char vuln_end_lit[] = "**END**";
 52  
 53  void
 54  usage_upgrade(void)
 55  {
 56  	fprintf(stderr, "Usage: pkg upgrade [-fInFqUy] [--autoremove] [-r reponame] [-Cgix] <pkg-name> ...\n\n");
 57  	fprintf(stderr, "For more information see 'pkg help upgrade'.\n");
 58  }
 59  
 60  static void
 61  add_to_check(pkghash *check, struct pkg *pkg)
 62  {
 63  	const char *uid = NULL;
 64  
 65  	pkg_get(pkg, PKG_ATTR_UNIQUEID, &uid);
 66  	pkghash_safe_add(check, uid, pkg, NULL);
 67  }
 68  
 69  static void
 70  check_vulnerable(struct pkg_audit *audit, struct pkgdb *db, int sock)
 71  {
 72  	struct pkg_audit_issues	*issues;
 73  	struct pkgdb_it	*it = NULL;
 74  	struct pkg		*pkg = NULL;
 75  	pkghash			*check = NULL;
 76  	pkghash_it		hit;
 77  	const char		*uid;
 78  	FILE			*out;
 79  
 80  	out = fdopen(sock, "w");
 81  	if (out == NULL) {
 82  		warn("unable to open stream");
 83  		return;
 84  	}
 85  
 86  	if ((it = pkgdb_query(db, NULL, MATCH_ALL)) == NULL) {
 87  		warnx("Error accessing the package database");
 88  		pkg_audit_free(audit);
 89  		fclose(out);
 90  		return;
 91  	}
 92  	check = pkghash_new();
 93  
 94  	while (pkgdb_it_next(it, &pkg, PKG_LOAD_BASIC|PKG_LOAD_RDEPS) == EPKG_OK) {
 95  		if (pkg_type(pkg) == PKG_INSTALLED) {
 96  			add_to_check(check, pkg);
 97  			pkg = NULL;
 98  		}
 99  	}
100  
101  	pkgdb_it_free(it);
102  	pkgdb_close(db);
103  
104  	if (check == NULL)
105  	        goto out_cleanup;
106  
107  	if (pkg_audit_load(audit, NULL) != EPKG_OK) {
108  		warn("unable to open vulnxml file");
109  	        goto out_cleanup;
110  	}
111  
112  	pkg_drop_privileges();
113  
114  #ifdef HAVE_CAPSICUM
115  #ifndef COVERAGE
116  	if (cap_enter() < 0 && errno != ENOSYS) {
117  		warn("cap_enter() failed");
118  		goto out_cleanup;
119  	}
120  #endif
121  #endif
122  
123  	if (pkg_audit_process(audit) == EPKG_OK) {
124  		hit = pkghash_iterator(check);
125  		while (pkghash_next(&hit)) {
126  				issues = NULL;
127  				pkg = (struct pkg *)hit.value;
128  				if (pkg_audit_is_vulnerable(audit, pkg, &issues, true)) {
129  					pkg_get(pkg, PKG_ATTR_UNIQUEID, &uid);
130  					fprintf(out, "%s\n", uid);
131  					fflush(out);
132  				}
133  				pkg_audit_issues_free(issues);
134  				pkg_free(pkg);
135  		}
136  
137  		fprintf(out, "%s\n", vuln_end_lit);
138  		fflush(out);
139  	} else {
140  		warnx("cannot process vulnxml");
141  	}
142  
143  out_cleanup:
144  	pkg_audit_free(audit);
145  	pkghash_destroy(check);
146  	fclose(out);
147  }
148  
149  static int
150  add_vulnerable_upgrades(struct pkg_jobs	*jobs, struct pkgdb *db)
151  {
152  	int 				sp[2], retcode, ret = EPKG_FATAL;
153  	pid_t 				cld;
154  	FILE				*in;
155  	struct pkg_audit	*audit;
156  	char				*line = NULL;
157  	size_t				linecap = 0;
158  	ssize_t				linelen;
159  
160  	/* Fetch audit file */
161  	if (pkg_audit_fetch(NULL, NULL) != EPKG_OK)
162  		return (EXIT_FAILURE);
163  
164  	/* Create socketpair to execute audit check in a detached mode */
165  	if (socketpair(AF_UNIX, SOCK_STREAM, 0, sp) == -1)  {
166  		warnx("Cannot create socketpair");
167  
168  		return (EPKG_FATAL);
169  	}
170  
171  	audit = pkg_audit_new();
172  	cld = fork();
173  
174  	switch (cld) {
175  	case 0:
176  		close(sp[1]);
177  		check_vulnerable(audit, db, sp[0]);
178  		close(sp[0]);
179  		_exit(EXIT_SUCCESS);
180  		break;
181  	case -1:
182  		warnx("Cannot fork");
183  	        pkg_audit_free(audit);
184  		return (EPKG_FATAL);
185  	default:
186  		/* Parent code */
187  		close(sp[0]);
188  		pkg_audit_free(audit);
189  		in = fdopen(sp[1], "r");
190  
191  		if (in == NULL) {
192  			warnx("Cannot create stream");
193  			close(sp[1]);
194  
195  			return (EPKG_FATAL);
196  		}
197  		break;
198  	}
199  
200  	while ((linelen = getline(&line, &linecap, in)) > 0) {
201  		if (line[linelen - 1] == '\n') {
202  			line[linelen - 1] = '\0';
203  		}
204  
205  		if (STREQ(line, vuln_end_lit)) {
206  			ret = EPKG_OK;
207  			break;
208  		}
209  
210  		if (pkg_jobs_add(jobs, MATCH_EXACT, &line, 1) == EPKG_FATAL) {
211  			warnx("Cannot update %s which is vulnerable", line);
212  			/* TODO: assume it non-fatal for now */
213  		}
214  	}
215  
216  	free(line);
217  
218  	fclose(in);
219  
220  	while (waitpid(cld, &retcode, 0) == -1) {
221  		if (errno != EINTR) {
222  			warnx("Cannot wait");
223  			return (EPKG_FATAL);
224  		}
225  	}
226  
227  	if (ret != EPKG_OK) {
228  		warn("Cannot get the complete list of vulnerable packages");
229  	}
230  
231  	return (ret);
232  }
233  
234  int
235  exec_upgrade(int argc, char **argv)
236  {
237  	struct pkgdb	*db = NULL;
238  	struct pkg_jobs	*jobs = NULL;
239  	int		 retcode;
240  	int		 updcode;
241  	int		 ch;
242  	int		 lock_type = PKGDB_LOCK_ADVISORY;
243  	match_t		 match = MATCH_EXACT;
244  	int		 done = 0;
245  	int		 nbactions = 0;
246  	int		 scriptnoexec = 0;
247  	int		 autoremove = 0;
248  	bool	rc = true;
249  	bool	autoremove_flag = false;
250  	pkg_flags	 f = PKG_FLAG_NONE | PKG_FLAG_PKG_VERSION_TEST;
251  	c_charv_t	reponames = vec_init();
252  
253  	struct option longopts[] = {
254  		{ "autoremove",		no_argument,		&autoremove,	1 },
255  		{ "case-sensitive",	no_argument,		NULL,	'C' },
256  		{ "force",		no_argument,		NULL,	'f' },
257  		{ "fetch-only",		no_argument,		NULL,	'F' },
258  		{ "glob",		no_argument,		NULL,	'g' },
259  		{ "case-insensitive",	no_argument,		NULL,	'i' },
260  		{ "no-scripts",		no_argument,		NULL,	'I' },
261  		{ "script-no-exec",	no_argument,		&scriptnoexec,	1 },
262  		{ "dry-run",		no_argument,		NULL,	'n' },
263  		{ "quiet",		no_argument,		NULL,	'q' },
264  		{ "repository",		required_argument,	NULL,	'r' },
265  		{ "no-repo-update",	no_argument,		NULL,	'U' },
266  		{ "regex",		no_argument,		NULL,	'x' },
267  		{ "yes",		no_argument,		NULL,	'y' },
268  		{ "vulnerable",		no_argument,		NULL,		'v' },
269  		{ NULL,			0,			NULL,	0   },
270  	};
271  
272  	while ((ch = getopt_long(argc, argv, "+CfFgiInqr:Uxyv", longopts, NULL)) != -1) {
273  		switch (ch) {
274  		case 'C':
275  			pkgdb_set_case_sensitivity(true);
276  			break;
277  		case 'f':
278  			f |= PKG_FLAG_FORCE;
279  			break;
280  		case 'F':
281  			f |= PKG_FLAG_SKIP_INSTALL;
282  			lock_type = PKGDB_LOCK_READONLY;
283  			break;
284  		case 'g':
285  			match = MATCH_GLOB;
286  			break;
287  		case 'i':
288  			pkgdb_set_case_sensitivity(false);
289  			break;
290  		case 'I':
291  			f |= PKG_FLAG_NOSCRIPT;
292  			break;
293  		case 'n':
294  			f |= PKG_FLAG_DRY_RUN;
295  			lock_type = PKGDB_LOCK_READONLY;
296  			dry_run = true;
297  			break;
298  		case 'q':
299  			quiet = true;
300  			break;
301  		case 'r':
302  			vec_push(&reponames, optarg);
303  			break;
304  		case 'U':
305  			auto_update = false;
306  			break;
307  		case 'x':
308  			match = MATCH_REGEX;
309  			break;
310  		case 'y':
311  			yes = true;
312  			break;
313  		case 'v':
314  			f |= PKG_FLAG_UPGRADE_VULNERABLE;
315  			break;
316  		case 0:
317  			if (scriptnoexec == 1)
318  				f |= PKG_FLAG_NOEXEC;
319  			if (autoremove)
320  				autoremove_flag = true;
321  			break;
322  		default:
323  			usage_upgrade();
324  			return (EXIT_FAILURE);
325  			/* NOTREACHED */
326  		}
327  	}
328  	argc -= optind;
329  	argv += optind;
330  
331  	if (dry_run && !auto_update)
332  		retcode = pkgdb_access2(PKGDB_MODE_READ,
333  				       PKGDB_DB_LOCAL|PKGDB_DB_REPO, &reponames);
334  	else
335  		retcode = pkgdb_access2(PKGDB_MODE_READ  |
336  				       PKGDB_MODE_WRITE |
337  				       PKGDB_MODE_CREATE,
338  				       PKGDB_DB_LOCAL|PKGDB_DB_REPO, &reponames);
339  	if (retcode == EPKG_ENOACCESS && dry_run) {
340  		auto_update = false;
341  		retcode = pkgdb_access2(PKGDB_MODE_READ,
342  				       PKGDB_DB_LOCAL|PKGDB_DB_REPO, &reponames);
343  	}
344  
345  	if (retcode == EPKG_ENOACCESS) {
346  		warnx("Insufficient privilege to upgrade packages");
347  		return (EXIT_FAILURE);
348  	} else if (retcode != EPKG_OK)
349  		return (EXIT_FAILURE);
350  	else
351  		retcode = EXIT_FAILURE;
352  
353  	/* first update the remote repositories if needed */
354  	if (auto_update &&
355  	    (updcode = pkgcli_update(false, false, &reponames)) != EPKG_OK)
356  		return (updcode);
357  
358  	if (pkgdb_open_all2(&db, PKGDB_REMOTE, &reponames) != EPKG_OK)
359  		return (EXIT_FAILURE);
360  
361  	if (pkgdb_obtain_lock(db, lock_type) != EPKG_OK) {
362  		pkgdb_close(db);
363  		warnx("Cannot get an advisory lock on a database, it is locked by another process");
364  		return (EXIT_FAILURE);
365  	}
366  
367  	if (pkg_jobs_new(&jobs, PKG_JOBS_UPGRADE, db) != EPKG_OK)
368  		goto cleanup;
369  
370  	if (reponames.len > 0 && pkg_jobs_set_repositories(jobs, &reponames) != EPKG_OK)
371  		goto cleanup;
372  
373  	pkg_jobs_set_flags(jobs, f);
374  
375  	if (argc > 0)
376  		if (pkg_jobs_add(jobs, match, argv, argc) == EPKG_FATAL)
377  				goto cleanup;
378  
379  	if (f & PKG_FLAG_UPGRADE_VULNERABLE) {
380  		/* We need to load audit info and add packages that are vulnerable */
381  		if (add_vulnerable_upgrades(jobs, db) != EPKG_OK) {
382  			goto cleanup;
383  		}
384  	}
385  
386  	if (pkg_jobs_solve(jobs) != EPKG_OK)
387  		goto cleanup;
388  
389  	while ((nbactions = pkg_jobs_count(jobs)) > 0) {
390  		/* print a summary before applying the jobs */
391  		rc = yes;
392  		if (!quiet || dry_run) {
393  			print_jobs_summary(jobs,
394  				"The following %d package(s) will be affected (of %d checked):\n\n",
395  				nbactions, pkg_jobs_total(jobs));
396  
397  			if (!dry_run) {
398  				rc = query_yesno(false, "\nProceed with this "
399  						"action? ");
400  			} else {
401  				rc = false;
402  			}
403  		}
404  
405  		if (rc) {
406  			retcode = pkg_jobs_apply(jobs);
407  			done = 1;
408  			if (retcode == EPKG_CONFLICT) {
409  				printf("Conflicts with the existing packages "
410  				    "have been found.\nOne more solver "
411  				    "iteration is needed to resolve them.\n");
412  				continue;
413  			}
414  			else if (retcode != EPKG_OK)
415  				goto cleanup;
416  		}
417  
418  		if (messages != NULL) {
419  			fflush(messages->fp);
420  			printf("%s", messages->buf);
421  		}
422  		break;
423  	}
424  
425  	if (done == 0 && rc && !quiet)
426  		printf("Your packages are up to date.\n");
427  
428  	if (rc || dry_run)
429  		retcode = EXIT_SUCCESS;
430  	else
431  		retcode = EXIT_FAILURE;
432  
433  	if (done && retcode == EXIT_SUCCESS)
434  		pkgcli_autoremove(db, autoremove_flag);
435  
436  cleanup:
437  	pkg_jobs_free(jobs);
438  	pkgdb_release_lock(db, lock_type);
439  	pkgdb_close(db);
440  
441  	if (!dry_run)
442  		pkg_cache_full_clean();
443  
444  	if (newpkgversion && (!rc || !done))
445  		newpkgversion = false;
446  
447  	return (retcode);
448  }