Преглед изворни кода

dpkg: Add new --verify command

For now this checks that the files have not been modified by computing
and comparing their md5sum hashes against the ones in the database.

The only currently supported output format will try to mimick the one
from «rpm --verify», but the default might change in the future in case
it is found cumbersome or lacking, and as such programs parsing the
command output should be explicit about the format they want.

Closes: #187019
Guillem Jover пре 14 година
родитељ
комит
b4dd2a29d6
6 измењених фајлова са 215 додато и 1 уклоњено
  1. 4 0
      debian/changelog
  2. 23 0
      man/dpkg.1
  3. 3 1
      src/Makefile.am
  4. 11 0
      src/main.c
  5. 6 0
      src/main.h
  6. 168 0
      src/verify.c

+ 4 - 0
debian/changelog

@@ -47,6 +47,10 @@ dpkg (1.17.2) UNRELEASED; urgency=low
   * When update-alternatives is told to change slave links, do not warn that
     the link group is broken, just print a notice that the alternative is
     being updated due to the changes.
+  * Add a new «dpkg --verify» command to check the integrity of packages
+    installed files. Add a --verify-format option to excplicitly select the
+    output format, currently only rpm compatible output is supported, but
+    the default might change in the future. Closes: #187019
 
   [ Updated programs translations ]
   * German (Sven Joachim).

+ 23 - 0
man/dpkg.1

@@ -204,6 +204,16 @@ Removing of a package consists of the following steps:
 \fB3.\fP Run \fIpostrm\fP script
 .br
 .TP
+.BR \-V ", " \-\-verify " [\fIpackage-name\fP...]
+Verifies the integrity of \fIpackage-name\fP or all packages if omitted,
+by comparing information from the installed paths with the database
+metadata.
+
+The output format is selectable with the \fB\-\-verify\-format\fP
+option, which by default uses the \fBrpm\fP format, but that might
+change in the future, and as such programs parsing this command
+output should be explicit about the format they expect.
+.TP
 \fB\-\-update\-avail\fP, \fB\-\-merge\-avail\fP \fIPackages-file\fP
 Update \fBdpkg\fP's and \fBdselect\fP's idea of which packages are
 available. With action \fB\-\-merge\-avail\fP, old information is
@@ -615,6 +625,19 @@ These two options can be specified multiple times, and interleaved with
 each other. Both are processed in the given order, with the last rule that
 matches a file name making the decision.
 .TP
+.BI \-\-verify\-format " format-name"
+Sets the output format for the \fB\-\-verify\fP command.
+
+The only currently supported output format is \fBrpm\fP, which consists
+of a line for every path that failed any check. The lines start with 9
+characters to report the specific check results, a '\fB?\fP' implies the
+check could not be done (lack of support, file permissions, etc), '\fB.\fP'
+implies the check passed, and an alphanumeric character implies a specific
+check failed; the only functional check is an md5sum verification denoted
+with a '\fB5\fP' on the third character. The line is followed by a space
+and an attribute character (currently '\fBc\fP' for conffiles), another
+space and the pathname.
+.TP
 \fB\-\-status\-fd \fR\fIn\fR
 Send machine-readable package status and progress information to file
 descriptor \fIn\fP. This option can be specified multiple times. The

+ 3 - 1
src/Makefile.am

@@ -53,7 +53,9 @@ dpkg_SOURCES = \
 	select.c \
 	trigproc.c \
 	unpack.c \
-	update.c
+	update.c \
+	verify.c \
+	$(nil)
 
 dpkg_LDADD = \
 	$(LDADD) \

+ 11 - 0
src/main.c

@@ -91,6 +91,7 @@ usage(const struct cmdinfo *ci, const char *value)
 "  --triggers-only    <package> ... | -a|--pending\n"
 "  -r|--remove        <package> ... | -a|--pending\n"
 "  -P|--purge         <package> ... | -a|--pending\n"
+"  -V|--verify <package> ...        Verify the integrity of package(s).\n"
 "  --get-selections [<pattern> ...] Get list of selections to stdout.\n"
 "  --set-selections                 Set package selections from stdin.\n"
 "  --clear-selections               Deselect every non-essential package.\n"
@@ -141,6 +142,7 @@ usage(const struct cmdinfo *ci, const char *value)
 "  -G|--refuse-downgrade      Skip packages with earlier version than installed.\n"
 "  -B|--auto-deconfigure      Install even if it would break some other package.\n"
 "  --[no-]triggers            Skip or force consequential trigger processing.\n"
+"  --verify-format=<format>   Verify output format (supported: 'rpm').\n"
 "  --no-debsig                Do not try to verify package signatures.\n"
 "  --no-act|--dry-run|--simulate\n"
 "                             Just say what we would do - don't do it.\n"
@@ -333,6 +335,13 @@ setfilter(const struct cmdinfo *cip, const char *value)
   filter_add(value, cip->arg_int);
 }
 
+static void
+set_verify_format(const struct cmdinfo *cip, const char *value)
+{
+  if (!verify_set_output(value))
+    badusage(_("unknown verify output format '%s'"), value);
+}
+
 static void setroot(const struct cmdinfo *cip, const char *value) {
   char *p;
   instdir= value;
@@ -639,6 +648,7 @@ static const struct cmdinfo cmdinfos[]= {
   ACTION( "remove",                         'r', act_remove,               packages        ),
   ACTION( "purge",                          'P', act_purge,                packages        ),
   ACTION( "triggers-only",                   0,  act_triggers,             packages        ),
+  ACTION( "verify",                         'V', act_verify,               verify          ),
   ACTIONBACKEND( "listfiles",               'L', DPKGQUERY),
   ACTIONBACKEND( "status",                  's', DPKGQUERY),
   ACTION( "get-selections",                  0,  act_getselections,        getselections   ),
@@ -673,6 +683,7 @@ static const struct cmdinfo cmdinfos[]= {
   { "post-invoke",       0,   1, NULL,          NULL,      set_invoke_hook, 0, &post_invoke_hooks_tail },
   { "path-exclude",      0,   1, NULL,          NULL,      setfilter,     0 },
   { "path-include",      0,   1, NULL,          NULL,      setfilter,     1 },
+  { "verify-format",     0,   1, NULL,          NULL,      set_verify_format },
   { "status-logger",     0,   1, NULL,          NULL,      set_invoke_hook, 0, &status_loggers_tail },
   { "status-fd",         0,   1, NULL,          NULL,      setpipe, 0 },
   { "log",               0,   1, NULL,          &log_file, NULL,    0 },

+ 6 - 0
src/main.h

@@ -68,6 +68,7 @@ enum action {
 	act_triggers,
 	act_remove,
 	act_purge,
+	act_verify,
 	act_commandfd,
 
 	act_status,
@@ -161,6 +162,11 @@ int printinstarch(const char *const *argv);
 int print_foreign_arches(const char *const *argv);
 int cmpversions(const char *const *argv);
 
+/* from verify.c */
+
+int verify_set_output(const char *name);
+int verify(const char *const *argv);
+
 /* from select.c */
 
 int getselections(const char *const *argv);

+ 168 - 0
src/verify.c

@@ -0,0 +1,168 @@
+/*
+ * dpkg - main program for package management
+ * verify.c - verify package integrity
+ *
+ * Copyright © 2012 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <string.h>
+#include <stdbool.h>
+#include <stdio.h>
+
+#include <dpkg/i18n.h>
+#include <dpkg/dpkg.h>
+#include <dpkg/dpkg-db.h>
+#include <dpkg/pkg-spec.h>
+#include <dpkg/options.h>
+
+#include "filesdb.h"
+#include "infodb.h"
+#include "main.h"
+
+
+enum verify_result {
+	VERIFY_NONE,
+	VERIFY_PASS,
+	VERIFY_FAIL,
+};
+
+struct verify_checks {
+	enum verify_result md5sum;
+};
+
+typedef void verify_output_func(struct filenamenode *, struct verify_checks *);
+
+static verify_output_func *verify_output;
+
+static int
+verify_result_rpm(enum verify_result result, int check)
+{
+	switch (result) {
+	case VERIFY_FAIL:
+		return check;
+	case VERIFY_PASS:
+		return '.';
+	case VERIFY_NONE:
+	default:
+		return '?';
+	}
+}
+
+static void
+verify_output_rpm(struct filenamenode *namenode, struct verify_checks *checks)
+{
+	char result[9];
+	int attr;
+
+	memset(result, '?', sizeof(result));
+
+	result[2] = verify_result_rpm(checks->md5sum, '5');
+
+	if (namenode->flags & fnnf_old_conff)
+		attr = 'c';
+	else
+		attr = ' ';
+
+	printf("%.9s %c %s\n", result, attr, namenode->name);
+}
+
+int
+verify_set_output(const char *name)
+{
+	if (strcmp(name, "rpm") == 0)
+		verify_output = verify_output_rpm;
+	else
+		return 1;
+
+	return 0;
+}
+
+static void
+verify_package(struct pkginfo *pkg)
+{
+	struct fileinlist *file;
+
+	ensure_packagefiles_available(pkg);
+	parse_filehash(pkg, &pkg->installed);
+	oldconffsetflags(pkg->installed.conffiles);
+
+	for (file = pkg->clientdata->files; file; file = file->next) {
+		struct verify_checks checks;
+		struct filenamenode *fnn;
+		char hash[MD5HASHLEN + 1];
+		int failures = 0;
+
+		fnn = namenodetouse(file->namenode, pkg, &pkg->installed);
+
+		if (strcmp(fnn->newhash, EMPTYHASHFLAG) == 0) {
+			if (fnn->oldhash == NULL)
+				continue;
+			else
+				fnn->newhash = fnn->oldhash;
+		}
+
+		memset(&checks, 0, sizeof(checks));
+
+		md5hash(pkg, hash, fnn->name);
+		if (strcmp(hash, fnn->newhash) != 0) {
+			checks.md5sum = VERIFY_FAIL;
+			failures++;
+		}
+
+		if (failures)
+			verify_output(fnn, &checks);
+	}
+}
+
+int
+verify(const char *const *argv)
+{
+	struct pkginfo *pkg;
+
+	modstatdb_open(msdbrw_readonly);
+	ensure_diversions();
+
+	if (!*argv) {
+		struct pkgiterator *it;
+
+		it = pkg_db_iter_new();
+		while ((pkg = pkg_db_iter_next_pkg(it)))
+			verify_package(pkg);
+		pkg_db_iter_free(it);
+	} else {
+		const char *thisarg;
+
+		while ((thisarg = *argv++)) {
+			struct dpkg_error err;
+
+			pkg = pkg_spec_parse_pkg(thisarg, &err);
+			if (pkg == NULL)
+				badusage(_("--%s needs a valid package name but '%.250s' is not: %s"),
+				         cipaction->olong, thisarg, err.str);
+
+			verify_package(pkg);
+		}
+	}
+
+	modstatdb_shutdown();
+
+	m_output(stdout, _("<standard output>"));
+
+	return 0;
+}