mirror of
git://git.gnupg.org/gnupg.git
synced 2024-12-22 10:19:57 +01:00
1de4462974
* dirmngr/ks-engine.h (KS_HTTP_FETCH_NOCACHE): New flag. (KS_HTTP_FETCH_TRUST_CFG): Ditto. (KS_HTTP_FETCH_NO_CRL): Ditto. (KS_HTTP_FETCH_ALLOW_DOWNGRADE): Ditto. * dirmngr/ks-engine-http.c (ks_http_fetch): Replace args send_no_cache and extra_http_trust_flags by a new flags arg. Allow redirectiong from https to http it KS_HTTP_FETCH_ALLOW_DOWNGRADE is set. * dirmngr/loadswdb.c (fetch_file): Call with KS_HTTP_FETCH_NOCACHE. * dirmngr/ks-action.c (ks_action_get): Ditto. (ks_action_fetch): Ditto. * dirmngr/crlfetch.c (crl_fetch): Call with the appropriate flags. -- Signed-off-by: Werner Koch <wk@gnupg.org>
406 lines
11 KiB
C
406 lines
11 KiB
C
/* loadswdb.c - Load the swdb file from versions.gnupg.org
|
|
* Copyright (C) 2016 g10 Code GmbH
|
|
* Copyright (C) 2016 Bundesamt für Sicherheit in der Informationstechnik
|
|
*
|
|
* This file is part of GnuPG.
|
|
*
|
|
* GnuPG 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 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* GnuPG 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 <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <config.h>
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "dirmngr.h"
|
|
#include "../common/ccparray.h"
|
|
#include "../common/exectool.h"
|
|
#include "misc.h"
|
|
#include "ks-engine.h"
|
|
|
|
|
|
/* Get the time from the current swdb file and store it at R_FILEDATE
|
|
* and R_VERIFIED. If the file does not exist 0 is stored at there.
|
|
* The function returns 0 on success or an error code. */
|
|
static gpg_error_t
|
|
time_of_saved_swdb (const char *fname, time_t *r_filedate, time_t *r_verified)
|
|
{
|
|
gpg_error_t err;
|
|
estream_t fp = NULL;
|
|
char *line = NULL;
|
|
size_t length_of_line = 0;
|
|
size_t maxlen;
|
|
ssize_t len;
|
|
char *fields[2];
|
|
gnupg_isotime_t isot;
|
|
time_t filedate = (time_t)(-1);
|
|
time_t verified = (time_t)(-1);
|
|
|
|
*r_filedate = 0;
|
|
*r_verified = 0;
|
|
|
|
fp = es_fopen (fname, "r");
|
|
err = fp? 0 : gpg_error_from_syserror ();
|
|
if (err)
|
|
{
|
|
if (gpg_err_code (err) == GPG_ERR_ENOENT)
|
|
err = 0; /* No file - assume time is the year of Unix. */
|
|
goto leave;
|
|
}
|
|
|
|
/* Note that the parser uses the first occurrence 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);
|
|
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 == (time_t)(-1) && !strcmp (fields[0], ".filedate"))
|
|
{
|
|
if (string2isotime (isot, fields[1]))
|
|
filedate = isotime2epoch (isot);
|
|
}
|
|
else if (verified == (time_t)(-1) && !strcmp (fields[0], ".verified"))
|
|
{
|
|
if (string2isotime (isot, fields[1]))
|
|
verified = isotime2epoch (isot);
|
|
}
|
|
}
|
|
if (len < 0 || es_ferror (fp))
|
|
{
|
|
err = gpg_error_from_syserror ();
|
|
goto leave;
|
|
}
|
|
if (filedate == (time_t)(-1) || verified == (time_t)(-1))
|
|
{
|
|
err = gpg_error (GPG_ERR_INV_TIME);
|
|
goto leave;
|
|
}
|
|
|
|
*r_filedate = filedate;
|
|
*r_verified = verified;
|
|
|
|
leave:
|
|
if (err)
|
|
log_error (_("error reading '%s': %s\n"), fname, gpg_strerror (err));
|
|
xfree (line);
|
|
es_fclose (fp);
|
|
return err;
|
|
}
|
|
|
|
|
|
|
|
/* Read a file from URL and return it as an estream memory buffer at
|
|
* R_FP. */
|
|
static gpg_error_t
|
|
fetch_file (ctrl_t ctrl, const char *url, estream_t *r_fp)
|
|
{
|
|
gpg_error_t err;
|
|
estream_t fp = NULL;
|
|
estream_t httpfp = NULL;
|
|
size_t nread, nwritten;
|
|
char buffer[1024];
|
|
|
|
if ((err = ks_http_fetch (ctrl, url, KS_HTTP_FETCH_NOCACHE, &httpfp)))
|
|
goto leave;
|
|
|
|
/* We now read the data from the web server into a memory buffer.
|
|
* To avoid excessive memory use in case of a ill behaving server we
|
|
* put a 64 k size limit on the buffer. As of today the actual size
|
|
* of the swdb.lst file is 3k. */
|
|
fp = es_fopenmem (64*1024, "rw");
|
|
if (!fp)
|
|
{
|
|
err = gpg_error_from_syserror ();
|
|
log_error ("error allocating memory buffer: %s\n", gpg_strerror (err));
|
|
goto leave;
|
|
}
|
|
|
|
for (;;)
|
|
{
|
|
if (es_read (httpfp, buffer, sizeof buffer, &nread))
|
|
{
|
|
err = gpg_error_from_syserror ();
|
|
log_error ("error reading '%s': %s\n",
|
|
es_fname_get (httpfp), gpg_strerror (err));
|
|
goto leave;
|
|
}
|
|
|
|
if (!nread)
|
|
break; /* Ready. */
|
|
if (es_write (fp, buffer, nread, &nwritten))
|
|
{
|
|
err = gpg_error_from_syserror ();
|
|
log_error ("error writing '%s': %s\n",
|
|
es_fname_get (fp), gpg_strerror (err));
|
|
goto leave;
|
|
}
|
|
else if (nread != nwritten)
|
|
{
|
|
err = gpg_error (GPG_ERR_EIO);
|
|
log_error ("error writing '%s': %s\n",
|
|
es_fname_get (fp), "short write");
|
|
goto leave;
|
|
}
|
|
}
|
|
|
|
es_rewind (fp);
|
|
*r_fp = fp;
|
|
fp = NULL;
|
|
|
|
leave:
|
|
es_fclose (httpfp);
|
|
es_fclose (fp);
|
|
return err;
|
|
}
|
|
|
|
|
|
/* Communication object for verify_status_cb. */
|
|
struct verify_status_parm_s
|
|
{
|
|
time_t sigtime;
|
|
int anyvalid;
|
|
};
|
|
|
|
static void
|
|
verify_status_cb (void *opaque, const char *keyword, char *args)
|
|
{
|
|
struct verify_status_parm_s *parm = opaque;
|
|
|
|
if (DBG_EXTPROG)
|
|
log_debug ("gpgv status: %s %s\n", keyword, args);
|
|
|
|
/* We care only about the first valid signature. */
|
|
if (!strcmp (keyword, "VALIDSIG") && !parm->anyvalid)
|
|
{
|
|
char *fields[3];
|
|
|
|
parm->anyvalid = 1;
|
|
if (split_fields (args, fields, DIM (fields)) >= 3)
|
|
parm->sigtime = parse_timestamp (fields[2], NULL);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/* Load the swdb file into the current home directory. Do this onlky
|
|
* when needed unless FORCE is set which will always get a new
|
|
* copy. */
|
|
gpg_error_t
|
|
dirmngr_load_swdb (ctrl_t ctrl, int force)
|
|
{
|
|
gpg_error_t err;
|
|
char *fname = NULL; /* The swdb.lst file. */
|
|
char *tmp_fname = NULL; /* The temporary swdb.lst file. */
|
|
char *keyfile_fname = NULL;
|
|
estream_t swdb = NULL;
|
|
estream_t swdb_sig = NULL;
|
|
ccparray_t ccp;
|
|
const char **argv = NULL;
|
|
struct verify_status_parm_s verify_status_parm = { (time_t)(-1), 0 };
|
|
estream_t outfp = NULL;
|
|
time_t now = gnupg_get_time ();
|
|
time_t filedate = 0; /* ".filedate" from our swdb. */
|
|
time_t verified = 0; /* ".verified" from our swdb. */
|
|
gnupg_isotime_t isotime;
|
|
|
|
|
|
fname = make_filename_try (gnupg_homedir (), "swdb.lst", NULL);
|
|
if (!fname)
|
|
{
|
|
err = gpg_error_from_syserror ();
|
|
goto leave;
|
|
}
|
|
|
|
/* Check whether there is a need to get an update. */
|
|
if (!force)
|
|
{
|
|
static int not_older_than;
|
|
static time_t lastcheck;
|
|
|
|
if (!not_older_than)
|
|
{
|
|
/* To balance access to the server we use a random time from
|
|
* 5 to 7 days for update checks. */
|
|
not_older_than = 5 * 86400;
|
|
not_older_than += (get_uint_nonce () % (2*86400));
|
|
}
|
|
|
|
if (now - lastcheck < 3600)
|
|
{
|
|
/* We checked our swdb file in the last hour - don't check
|
|
* again to avoid unnecessary disk access. */
|
|
err = 0;
|
|
goto leave;
|
|
}
|
|
lastcheck = now;
|
|
|
|
err = time_of_saved_swdb (fname, &filedate, &verified);
|
|
if (gpg_err_code (err) == GPG_ERR_INV_TIME)
|
|
err = 0; /* Force reading. */
|
|
if (err)
|
|
goto leave;
|
|
if (filedate >= now)
|
|
goto leave; /* Current or newer. */
|
|
if (now - filedate < not_older_than)
|
|
goto leave; /* Our copy is pretty new (not older than 7 days). */
|
|
if (verified > now && now - verified < 3*3600)
|
|
goto leave; /* We downloaded and verified in the last 3 hours. */
|
|
}
|
|
|
|
/* Create the filename of the file with the keys. */
|
|
keyfile_fname = make_filename_try (gnupg_datadir (), "distsigkey.gpg", NULL);
|
|
if (!keyfile_fname)
|
|
{
|
|
err = gpg_error_from_syserror ();
|
|
goto leave;
|
|
}
|
|
|
|
/* Fetch the swdb from the web. */
|
|
err = fetch_file (ctrl, "https://versions.gnupg.org/swdb.lst", &swdb);
|
|
if (err)
|
|
goto leave;
|
|
err = fetch_file (ctrl, "https://versions.gnupg.org/swdb.lst.sig", &swdb_sig);
|
|
if (err)
|
|
goto leave;
|
|
|
|
/* Run gpgv. */
|
|
ccparray_init (&ccp, 0);
|
|
ccparray_put (&ccp, "--enable-special-filenames");
|
|
ccparray_put (&ccp, "--status-fd=2");
|
|
ccparray_put (&ccp, "--keyring");
|
|
ccparray_put (&ccp, keyfile_fname);
|
|
ccparray_put (&ccp, "--");
|
|
ccparray_put (&ccp, "-&@INEXTRA@");
|
|
ccparray_put (&ccp, "-");
|
|
ccparray_put (&ccp, NULL);
|
|
argv = ccparray_get (&ccp, NULL);
|
|
if (!argv)
|
|
{
|
|
err = gpg_error_from_syserror ();
|
|
goto leave;
|
|
}
|
|
|
|
if (DBG_EXTPROG)
|
|
log_debug ("starting gpgv\n");
|
|
err = gnupg_exec_tool_stream (gnupg_module_name (GNUPG_MODULE_NAME_GPGV),
|
|
argv, swdb, swdb_sig, NULL,
|
|
verify_status_cb, &verify_status_parm);
|
|
if (!err && verify_status_parm.sigtime == (time_t)(-1))
|
|
err = gpg_error (verify_status_parm.anyvalid? GPG_ERR_BAD_SIGNATURE
|
|
/**/ : GPG_ERR_INV_TIME );
|
|
if (DBG_EXTPROG)
|
|
log_debug ("gpgv finished: err=%d\n", err);
|
|
if (err)
|
|
goto leave;
|
|
|
|
/* If our swdb is not older than the downloaded one. We don't
|
|
* bother to update. */
|
|
if (!force && filedate >= verify_status_parm.sigtime)
|
|
goto leave;
|
|
|
|
/* Create a file name for a temporary file in the home directory.
|
|
* We will later rename that file to the real name. */
|
|
{
|
|
char *tmpstr;
|
|
|
|
#ifdef HAVE_W32_SYSTEM
|
|
tmpstr = es_bsprintf ("tmp-%u-swdb", (unsigned int)getpid ());
|
|
#else
|
|
tmpstr = es_bsprintf (".#%u.swdb", (unsigned int)getpid ());
|
|
#endif
|
|
if (!tmpstr)
|
|
{
|
|
err = gpg_error_from_syserror ();
|
|
goto leave;
|
|
}
|
|
tmp_fname = make_filename_try (gnupg_homedir (), tmpstr, NULL);
|
|
xfree (tmpstr);
|
|
if (!tmp_fname)
|
|
{
|
|
err = gpg_error_from_syserror ();
|
|
goto leave;
|
|
}
|
|
}
|
|
|
|
outfp = es_fopen (tmp_fname, "w");
|
|
if (!outfp)
|
|
{
|
|
err = gpg_error_from_syserror ();
|
|
log_error (_("error creating '%s': %s\n"), tmp_fname, gpg_strerror (err));
|
|
goto leave;
|
|
}
|
|
|
|
epoch2isotime (isotime, verify_status_parm.sigtime);
|
|
es_fprintf (outfp, ".filedate %s\n", isotime);
|
|
epoch2isotime (isotime, now);
|
|
es_fprintf (outfp, ".verified %s\n", isotime);
|
|
|
|
if (es_fseek (swdb, 0, SEEK_SET))
|
|
{
|
|
err = gpg_error_from_syserror ();
|
|
goto leave;
|
|
}
|
|
|
|
err = copy_stream (swdb, outfp);
|
|
if (err)
|
|
{
|
|
/* Well, it might also be a reading error, but that is pretty
|
|
* unlikely for a memory stream. */
|
|
log_error (_("error writing '%s': %s\n"), tmp_fname, gpg_strerror (err));
|
|
goto leave;
|
|
}
|
|
|
|
if (es_fclose (outfp))
|
|
{
|
|
err = gpg_error_from_syserror ();
|
|
log_error (_("error writing '%s': %s\n"), tmp_fname, gpg_strerror (err));
|
|
goto leave;
|
|
}
|
|
outfp = NULL;
|
|
|
|
err = gnupg_rename_file (tmp_fname, fname, NULL);
|
|
if (err)
|
|
goto leave;
|
|
xfree (tmp_fname);
|
|
tmp_fname = NULL;
|
|
|
|
|
|
leave:
|
|
es_fclose (outfp);
|
|
if (tmp_fname)
|
|
gnupg_remove (tmp_fname); /* This is a temporary file. */
|
|
xfree (argv);
|
|
es_fclose (swdb_sig);
|
|
es_fclose (swdb);
|
|
xfree (keyfile_fname);
|
|
xfree (tmp_fname);
|
|
xfree (fname);
|
|
return err;
|
|
}
|