dirmngr: Add command to only load the swdb.
[gnupg.git] / dirmngr / loadswdb.c
1 /* loadswdb.c - Load the swdb file from versions.gnupg.org
2  * Copyright (C) 2016 g10 Code GmbH
3  *
4  * This file is part of GnuPG.
5  *
6  * GnuPG is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * GnuPG is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, see <https://www.gnu.org/licenses/>.
18  */
19
20 #include <config.h>
21
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <string.h>
25
26 #include "dirmngr.h"
27 #include "../common/ccparray.h"
28 #include "../common/exectool.h"
29 #include "misc.h"
30 #include "ks-engine.h"
31
32
33 /* Get the time from the current swdb file and store it at R_TIME.  If
34  * the file does not exist 0 is stored at R_TIME.  The function
35  * returns 0 on sucess or an error code.  */
36 static gpg_error_t
37 time_of_saved_swdb (const char *fname, time_t *r_time)
38 {
39   gpg_error_t err;
40   estream_t fp = NULL;
41   char *line = NULL;
42   size_t length_of_line = 0;
43   size_t  maxlen;
44   ssize_t len;
45   char *fields[2];
46   time_t t = (time_t)(-1);
47
48   *r_time = 0;
49
50   fp = es_fopen (fname, "r");
51   err = fp? 0 : gpg_error_from_syserror ();
52   if (err)
53     {
54       if (gpg_err_code (err) == GPG_ERR_ENOENT)
55         err = 0; /* No file - assume time is the year of Unix.  */
56       goto leave;
57     }
58
59   /* Note that the parser uses the first occurance of a matching
60    * values and ignores possible duplicated values.  */
61   maxlen = 2048; /* Set limit.  */
62   while ((len = es_read_line (fp, &line, &length_of_line, &maxlen)) > 0)
63     {
64       if (!maxlen)
65         {
66           err = gpg_error (GPG_ERR_LINE_TOO_LONG);
67           goto leave;
68         }
69       /* Strip newline and carriage return, if present.  */
70       while (len > 0 && (line[len - 1] == '\n' || line[len - 1] == '\r'))
71         line[--len] = '\0';
72
73       if (split_fields (line, fields, DIM (fields)) < DIM(fields))
74         continue; /* Skip empty lines and names w/o a value.  */
75       if (*fields[0] == '#')
76         continue; /* Skip comments.  */
77
78       /* Record the meta data.  */
79       if (!strcmp (fields[0], ".filedate"))
80         {
81           gnupg_isotime_t isot;
82           if (string2isotime (isot, fields[1])
83               && (t = isotime2epoch (isot)) != (time_t)(-1))
84             break;  /* Got the time - stop reading.  */
85         }
86     }
87   if (len < 0 || es_ferror (fp))
88     {
89       err = gpg_error_from_syserror ();
90       goto leave;
91     }
92   if (t == (time_t)(-1))
93     {
94       err = gpg_error (GPG_ERR_INV_TIME);
95       goto leave;
96     }
97
98   *r_time = t;
99
100  leave:
101   if (err)
102     log_error (_("error reading '%s': %s\n"), fname, gpg_strerror (err));
103   xfree (line);
104   es_fclose (fp);
105   return err;
106 }
107
108
109
110 /* Read a file from URL and return it as an estream memory buffer at
111  * R_FP.  */
112 static gpg_error_t
113 fetch_file (ctrl_t ctrl, const char *url, estream_t *r_fp)
114 {
115   gpg_error_t err;
116   estream_t fp = NULL;
117   estream_t httpfp = NULL;
118   size_t nread, nwritten;
119   char buffer[1024];
120
121   if ((err = ks_http_fetch (ctrl, url, &httpfp)))
122     goto leave;
123
124   /* We now read the data from the web server into a memory buffer.
125    * To avoid excessive memory use in case of a ill behaving server we
126    * put a 64 k size limit on the buffer.  As of today the actual size
127    * of the swdb.lst file is 3k.  */
128   fp = es_fopenmem (64*1024, "rw");
129   if (!fp)
130     {
131       err = gpg_error_from_syserror ();
132       log_error ("error allocating memory buffer: %s\n", gpg_strerror (err));
133       goto leave;
134     }
135
136   for (;;)
137     {
138       if (es_read (httpfp, buffer, sizeof buffer, &nread))
139         {
140           err = gpg_error_from_syserror ();
141           log_error ("error reading '%s': %s\n",
142                      es_fname_get (httpfp), gpg_strerror (err));
143           goto leave;
144         }
145
146       if (!nread)
147         break; /* Ready.  */
148       if (es_write (fp, buffer, nread, &nwritten))
149         {
150           err = gpg_error_from_syserror ();
151           log_error ("error writing '%s': %s\n",
152                      es_fname_get (fp), gpg_strerror (err));
153           goto leave;
154         }
155       else if (nread != nwritten)
156         {
157           err = gpg_error (GPG_ERR_EIO);
158           log_error ("error writing '%s': %s\n",
159                      es_fname_get (fp), "short write");
160           goto leave;
161         }
162     }
163
164   es_rewind (fp);
165   *r_fp = fp;
166   fp = NULL;
167
168  leave:
169   es_fclose (httpfp);
170   es_fclose (fp);
171   return err;
172 }
173
174
175 /* Communication object for verify_status_cb.  */
176 struct verify_status_parm_s
177 {
178   time_t sigtime;
179   int anyvalid;
180 };
181
182 static void
183 verify_status_cb (void *opaque, const char *keyword, char *args)
184 {
185   struct verify_status_parm_s *parm = opaque;
186
187   /* We care only about the first valid signature.  */
188   if (!strcmp (keyword, "VALIDSIG") && !parm->anyvalid)
189     {
190       char *fields[3];
191
192       parm->anyvalid = 1;
193       if (split_fields (args, fields, DIM (fields)) >= 3)
194         parm->sigtime = parse_timestamp (fields[2], NULL);
195     }
196 }
197
198
199
200 /* Load the swdb file into the current home directory.  Do this onlky
201  * when needed unless FORCE is set which will always get a new
202  * copy.  */
203 gpg_error_t
204 dirmngr_load_swdb (ctrl_t ctrl, int force)
205 {
206   gpg_error_t err;
207   char *fname = NULL;      /* The swdb.lst file.  */
208   char *tmp_fname = NULL;  /* The temporary swdb.lst file.  */
209   char *keyfile_fname = NULL;
210   estream_t swdb = NULL;
211   estream_t swdb_sig = NULL;
212   ccparray_t ccp;
213   const char **argv = NULL;
214   struct verify_status_parm_s verify_status_parm = { (time_t)(-1), 0 };
215   estream_t outfp = NULL;
216   time_t now = gnupg_get_time ();
217   gnupg_isotime_t isotime;
218
219
220   fname = make_filename_try (gnupg_homedir (), "swdb.lst", NULL);
221   if (!fname)
222     {
223       err = gpg_error_from_syserror ();
224       goto leave;
225     }
226
227   /* Check whether there is a need to get an update.  */
228   if (!force)
229     {
230       time_t filetime;
231
232       err = time_of_saved_swdb (fname, &filetime);
233       if (err)
234         goto leave;
235       if (filetime >= now)
236         goto leave; /* Current or newer.  */
237       if (now - filetime < 3*86400)
238         goto leave; /* Not older than 3 days.  */
239     }
240
241   /* Create the filename of the file with the keys. */
242   keyfile_fname = make_filename_try (gnupg_datadir (), "distsigkey.gpg", NULL);
243   if (!keyfile_fname)
244     goto leave;
245
246   /* Fetch the swdb from the web.  */
247   err = fetch_file (ctrl, "https://versions.gnupg.org/swdb.lst", &swdb);
248   if (err)
249     goto leave;
250   err = fetch_file (ctrl, "https://versions.gnupg.org/swdb.lst.sig", &swdb_sig);
251   if (err)
252     goto leave;
253
254   /* Run gpgv.  */
255   ccparray_init (&ccp, 0);
256   ccparray_put (&ccp, "--enable-special-filenames");
257   ccparray_put (&ccp, "--status-fd=2");
258   ccparray_put (&ccp, "--keyring");
259   ccparray_put (&ccp, keyfile_fname);
260   ccparray_put (&ccp, "--");
261   ccparray_put (&ccp, "-&@INEXTRA@");
262   ccparray_put (&ccp, "-");
263   ccparray_put (&ccp, NULL);
264   argv = ccparray_get (&ccp, NULL);
265   if (!argv)
266     {
267       err = gpg_error_from_syserror ();
268       goto leave;
269     }
270
271   err = gnupg_exec_tool_stream (gnupg_module_name (GNUPG_MODULE_NAME_GPGV),
272                                 argv, swdb, swdb_sig, NULL,
273                                 verify_status_cb, &verify_status_parm);
274   if (!err && verify_status_parm.sigtime == (time_t)(-1))
275     err = gpg_error (verify_status_parm.anyvalid? GPG_ERR_BAD_SIGNATURE
276                      /**/                       : GPG_ERR_INV_TIME      );
277   if (err)
278     goto leave;
279
280   /* Create a file name for a temporary file in the home directory.
281    * We will later rename that file to the real name.  */
282   {
283     char *tmpstr;
284
285 #ifdef HAVE_W32_SYSTEM
286     tmpstr = es_bsprintf ("tmp-%u-swdb", (unsigned int)getpid ());
287 #else
288     tmpstr = es_bsprintf (".#%u.swdb", (unsigned int)getpid ());
289 #endif
290     if (!tmpstr)
291       {
292         err = gpg_error_from_syserror ();
293         goto leave;
294       }
295     tmp_fname = make_filename_try (gnupg_homedir (), tmpstr, NULL);
296     xfree (tmpstr);
297     if (!tmp_fname)
298       {
299         err = gpg_error_from_syserror ();
300         goto leave;
301       }
302   }
303
304   outfp = es_fopen (tmp_fname, "w");
305   if (!outfp)
306     {
307       err = gpg_error_from_syserror ();
308       log_error (_("error creating '%s': %s\n"), tmp_fname, gpg_strerror (err));
309       goto leave;
310     }
311
312   epoch2isotime (isotime, verify_status_parm.sigtime);
313   es_fprintf (outfp, ".filedate %s\n", isotime);
314   epoch2isotime (isotime, now);
315   es_fprintf (outfp, ".verified %s\n", isotime);
316
317   if (es_fseek (swdb, 0, SEEK_SET))
318     {
319       err = gpg_error_from_syserror ();
320       goto leave;
321     }
322
323   err = copy_stream (swdb, outfp);
324   if (err)
325     {
326       /* Well, it might also be a reading error, but that is pretty
327        * unlikely for a memory stream.  */
328       log_error (_("error writing '%s': %s\n"), tmp_fname, gpg_strerror (err));
329       goto leave;
330     }
331
332   if (es_fclose (outfp))
333     {
334       err = gpg_error_from_syserror ();
335       log_error (_("error writing '%s': %s\n"), tmp_fname, gpg_strerror (err));
336       goto leave;
337     }
338   outfp = NULL;
339
340   err = gnupg_rename_file (tmp_fname, fname, NULL);
341   if (err)
342     goto leave;
343   xfree (tmp_fname);
344   tmp_fname = NULL;
345
346
347  leave:
348   es_fclose (outfp);
349   if (tmp_fname)
350     gnupg_remove (tmp_fname);  /* This is a temporary file.  */
351   xfree (argv);
352   es_fclose (swdb_sig);
353   es_fclose (swdb);
354   xfree (keyfile_fname);
355   xfree (tmp_fname);
356   xfree (fname);
357   return err;
358 }