diff options
Diffstat (limited to '')
-rw-r--r-- | doc/tools.texi | 88 | ||||
-rw-r--r-- | tools/gpgconf.c | 246 |
2 files changed, 334 insertions, 0 deletions
diff --git a/doc/tools.texi b/doc/tools.texi index 199b11e0b..2d2ccefe8 100644 --- a/doc/tools.texi +++ b/doc/tools.texi @@ -250,6 +250,7 @@ throughout this section. * Listing options:: List all options of a component. * Changing options:: Changing options of a component. * Listing global options:: List all global options. +* Querying versions:: Get and compare software versions. * Files used by gpgconf:: What files are used by gpgconf. @end menu @@ -302,6 +303,13 @@ List the global configuration file in a colon separated format. If Run a syntax check on the global configuration file. If @var{filename} is given, check that file instead. + +@item --query-swdb @var{package_name} [@var{version_string}] +Returns the current version for @var{package_name} and if +@var{version_string} is given also an indicator on whether an update +is available. + + @item --reload [@var{component}] @opindex reload Reload all or the given component. This is basically the same as sending @@ -953,6 +961,80 @@ Unknown record types should be ignored. Note that there is intentionally no feature to change the global option file through @command{gpgconf}. +@node Querying versions +@subsection Get and compare software versions. + +The GnuPG Project operates a server to query the current versions of +software packages related to GnuPG. @command{gpgconf} can be used to +access this online database. To allow for offline operations, this +feature works by having @command{dirmngr} download a file from +@code{https://versions.gnupg.org}, checking the signature of that file +and storing the file in the GnuPG home directory. If +@command{gpgconf} is used and @command{dirmngr} is running, it may ask +@command{dirmngr} to refresh that file before itself uses the file. + +The command @option{--query-swdb} returns information for the given +package in a colon delimited format: + +@table @var + +@item name +This is the name of the package as requested. Note that "gnupg" is a +special name which is replaced by the actual package implementing this +version of GnuPG. For this name it is also not required to specify a +version because @command{gpgconf} takes its own version in this case. + +@item status +The status of the software package according to this table: +@table @code +@item - +No information available. This is either because no current version +has been specified or due to an error. +@item ? +The given name is not known in the online database. +@item u +An update of the software is available. +@item c +The specified version of the software is current. +@item n +The specified version is already newer than the released version. +@end table + +@item urgency +If the value (the empty string should be considered as zero) is +greater than zero an important update is available. + +@item error +This returns an @command{gpg-error} error code to distinguish between +various failure modes. + +@item filedate +This gives the date of the file with the version numbers in standard +ISO format (@code{yyyymmddThhmmss}). The date has been extracted by +@command{dirmngr} from the signature of the file. + +@item verified +This gives the date in ISO format the file was downloaded. This value +can be used to evaluate the freshness of the information. + +@item version +This returns the version string for the requested software from the +file. + +@item reldate +This returns the release date in ISO format. + +@item size +This returns the size of the package as decimal number of bytes. + +@item hash +This returns a hexified SHA-2 hash of the package. + +@end table + +@noindent +More fields may be added in future to the output. + @mansect files @node Files used by gpgconf @@ -965,6 +1047,12 @@ no feature to change the global option file through @command{gpgconf}. If this file exists, it is processed as a global configuration file. A commented example can be found in the @file{examples} directory of the distribution. + +@item @var{GNUPGHOME}/swdb.lst +@cindex swdb.lst + A file with current software versions. @command{dirmngr} creates + this file on demand from an online resource. + @end table diff --git a/tools/gpgconf.c b/tools/gpgconf.c index a1ca79fd0..1f00418ee 100644 --- a/tools/gpgconf.c +++ b/tools/gpgconf.c @@ -1,5 +1,6 @@ /* gpgconf.c - Configuration utility for GnuPG * Copyright (C) 2003, 2007, 2009, 2011 Free Software Foundation, Inc. + * Copyright (C) 2016 g10 Code GmbH. * * This file is part of GnuPG. * @@ -52,6 +53,7 @@ enum cmd_and_opt_values aApplyDefaults, aListConfig, aCheckConfig, + aQuerySWDB, aListDirs, aLaunch, aKill, @@ -79,6 +81,8 @@ static ARGPARSE_OPTS opts[] = N_("list global configuration file") }, { aCheckConfig, "check-config", 256, N_("check global configuration file") }, + { aQuerySWDB, "query-swdb", 256, + N_("query the software version database") }, { aReload, "reload", 256, N_("reload all or a given component")}, { aLaunch, "launch", 256, N_("launch a given component")}, { aKill, "kill", 256, N_("kill a given component")}, @@ -203,6 +207,235 @@ list_dirs (estream_t fp, char **names) } + +/* Check whether NAME is valid argument for query_swdb(). Valid names + * start with a letter and contain only alphanumeric characters or an + * underscore. */ +static int +valid_swdb_name_p (const char *name) +{ + if (!name || !*name || !alphap (name)) + return 0; + + for (name++; *name; name++) + if (!alnump (name) && *name != '_') + return 0; + + return 1; +} + + +/* Query the SWDB file. If necessary and possible this functions asks + * the dirmngr to load an updated version of that file. The caller + * needs to provide the NAME to query (e.g. "gnupg", "libgcrypt") and + * optional the currently installed version in CURRENT_VERSION. The + * output written to OUT is a colon delimited line with these fields: + * + * name :: The name of the package + * status :: This value tells the status of the software package + * '-' :: No information available + * (error or CURRENT_VERSION not given) + * '?' :: Unknown NAME + * 'u' :: Update available + * 'c' :: The version is Current + * 'n' :: The current version is already Newer than the + * available one. + * urgency :: If the value is greater than zero an urgent update is required. + * error :: 0 on success or an gpg_err_code_t + * Common codes seen: + * GPG_ERR_TOO_OLD :: The SWDB file is to old to be used. + * GPG_ERR_ENOENT :: The SWDB file is not available. + * GPG_ERR_BAD_SIGNATURE :: Currupted SWDB file. + * filedate:: Date of the swdb file (yyyymmddThhmmss) + * verified:: Date we checked the validity of the file (yyyyymmddThhmmss) + * version :: The version string from the swdb. + * reldate :: Release date of that version (yyyymmddThhmmss) + * size :: Size of the package in bytes. + * hash :: SHA-2 hash of the package. + * + */ +static void +query_swdb (estream_t out, const char *name, const char *current_version) +{ + gpg_error_t err; + const char *search_name; + char *fname = NULL; + estream_t fp = NULL; + char *line = NULL; + char *self_version = NULL; + size_t length_of_line = 0; + size_t maxlen; + ssize_t len; + char *fields[2]; + char *p; + gnupg_isotime_t filedate = {0}; + gnupg_isotime_t verified = {0}; + char *value_ver = NULL; + gnupg_isotime_t value_date = {0}; + char *value_size = NULL; + char *value_sha2 = NULL; + unsigned long value_size_ul; + int status, i; + + + if (!valid_swdb_name_p (name)) + { + log_error ("error in package name '%s': %s\n", + name, gpg_strerror (GPG_ERR_INV_NAME)); + goto leave; + } + if (!strcmp (name, "gnupg")) + search_name = "gnupg21"; + else if (!strcmp (name, "gnupg1")) + search_name = "gnupg1"; + else + search_name = name; + + if (!current_version && !strcmp (name, "gnupg")) + { + /* Use our own version but string a possible beta string. */ + self_version = xstrdup (PACKAGE_VERSION); + p = strchr (self_version, '-'); + if (p) + *p = 0; + current_version = self_version; + } + + if (current_version && compare_version_strings (current_version, NULL)) + { + log_error ("error in version string '%s': %s\n", + current_version, gpg_strerror (GPG_ERR_INV_ARG)); + goto leave; + } + + fname = make_filename (gnupg_homedir (), "swdb.lst", NULL); + fp = es_fopen (fname, "r"); + if (!fp) + { + err = gpg_error_from_syserror (); + es_fprintf (out, "%s:-::%u:::::::\n", name, gpg_err_code (err)); + if (gpg_err_code (err) != GPG_ERR_ENOENT) + log_error (_("error opening '%s': %s\n"), fname, gpg_strerror (err)); + goto leave; + } + + /* Note that the parser uses the first occurance of a matching + * values and ignores possible duplicated values. */ + + maxlen = 2048; /* Set limit. */ + while ((len = es_read_line (fp, &line, &length_of_line, &maxlen)) > 0) + { + if (!maxlen) + { + err = gpg_error (GPG_ERR_LINE_TOO_LONG); + log_error (_("error reading '%s': %s\n"), fname, gpg_strerror (err)); + goto leave; + } + /* Strip newline and carriage return, if present. */ + while (len > 0 && (line[len - 1] == '\n' || line[len - 1] == '\r')) + line[--len] = '\0'; + + if (split_fields (line, fields, DIM (fields)) < DIM(fields)) + continue; /* Skip empty lines and names w/o a value. */ + if (*fields[0] == '#') + continue; /* Skip comments. */ + + /* Record the meta data. */ + if (!*filedate && !strcmp (fields[0], ".filedate")) + { + string2isotime (filedate, fields[1]); + continue; + } + if (!*verified && !strcmp (fields[0], ".verified")) + { + string2isotime (verified, fields[1]); + continue; + } + + /* Tokenize the name. */ + p = strrchr (fields[0], '_'); + if (!p) + continue; /* Name w/o an underscore. */ + *p++ = 0; + + /* Wait for the requested name. */ + if (!strcmp (fields[0], search_name)) + { + if (!strcmp (p, "ver") && !value_ver) + value_ver = xstrdup (fields[1]); + else if (!strcmp (p, "date") && !*value_date) + string2isotime (value_date, fields[1]); + else if (!strcmp (p, "size") && !value_size) + value_size = xstrdup (fields[1]); + else if (!strcmp (p, "sha2") && !value_sha2) + value_sha2 = xstrdup (fields[1]); + } + } + if (len < 0 || es_ferror (fp)) + { + err = gpg_error_from_syserror (); + log_error (_("error reading '%s': %s\n"), fname, gpg_strerror (err)); + goto leave; + } + + if (!*filedate || !*verified) + { + err = gpg_error (GPG_ERR_INV_TIME); + es_fprintf (out, "%s:-::%u:::::::\n", name, gpg_err_code (err)); + goto leave; + } + + if (!value_ver) + { + es_fprintf (out, "%s:?:::::::::\n", name); + goto leave; + } + + if (value_size) + { + gpg_err_set_errno (0); + value_size_ul = strtoul (value_size, &p, 10); + if (errno) + value_size_ul = 0; + else if (*p == 'k') + value_size_ul *= 1024; + } + + err = 0; + status = '-'; + if (compare_version_strings (value_ver, NULL)) + err = gpg_error (GPG_ERR_INV_VALUE); + else if (!current_version) + ; + else if (!(i = compare_version_strings (value_ver, current_version))) + status = 'c'; + else if (i > 0) + status = 'u'; + else + status = 'n'; + + es_fprintf (out, "%s:%c::%d:%s:%s:%s:%s:%lu:%s:\n", + name, + status, + err, + filedate, + verified, + value_ver, + value_date, + value_size_ul, + value_sha2? value_sha2 : ""); + + leave: + xfree (value_ver); + xfree (value_size); + xfree (value_sha2); + xfree (line); + es_fclose (fp); + xfree (fname); + xfree (self_version); +} + + /* gpgconf main. */ int main (int argc, char **argv) @@ -250,6 +483,7 @@ main (int argc, char **argv) case aApplyDefaults: case aListConfig: case aCheckConfig: + case aQuerySWDB: case aReload: case aLaunch: case aKill: @@ -417,6 +651,18 @@ main (int argc, char **argv) list_dirs (outfp, argc? argv : NULL); break; + case aQuerySWDB: + /* Query the software version database. */ + if (!fname || argc > 2) + { + es_fprintf (es_stderr, "usage: %s --query-swdb NAME [VERSION]\n", + GPGCONF_NAME); + exit (2); + } + get_outfp (&outfp); + query_swdb (outfp, fname, argc > 1? argv[1] : NULL); + break; + case aCreateSocketDir: { char *socketdir; |