Merge branch 'master' into keyserver-via-dirmngr
[gnupg.git] / dirmngr / ks-engine-hkp.c
1 /* ks-engine-hkp.c - HKP keyserver engine
2  * Copyright (C) 2011 Free Software Foundation, Inc.
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 <http://www.gnu.org/licenses/>.
18  */
19
20 #include <config.h>
21
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <string.h>
25 #include <assert.h>
26
27 #include "dirmngr.h"
28 #include "misc.h"
29 #include "userids.h"
30 #include "ks-engine.h"
31
32 /* To match the behaviour of our old gpgkeys helper code we escape
33    more characters than actually needed. */
34 #define EXTRA_ESCAPE_CHARS "@!\"#$%&'()*+,-./:;<=>?[\\]^_{|}~"
35
36 /* How many redirections do we allow.  */
37 #define MAX_REDIRECTS 2
38
39
40 /* Send an HTTP request.  On success returns an estream object at
41    R_FP.  HOSTPORTSTR is only used for diagnostics.  If POST_CB is not
42    NULL a post request is used and that callback is called to allow
43    writing the post data.  */
44 static gpg_error_t
45 send_request (ctrl_t ctrl, const char *request, const char *hostportstr,
46               gpg_error_t (*post_cb)(void *, http_t), void *post_cb_value,
47               estream_t *r_fp)
48 {
49   gpg_error_t err;
50   http_t http = NULL;
51   int redirects_left = MAX_REDIRECTS;
52   estream_t fp = NULL;
53   char *request_buffer = NULL;
54
55   *r_fp = NULL;
56  once_more:
57   err = http_open (&http,
58                    post_cb? HTTP_REQ_POST : HTTP_REQ_GET,
59                    request,
60                    /* fixme: AUTH */ NULL,
61                    0,
62                    /* fixme: proxy*/ NULL,
63                    NULL, NULL,
64                    /*FIXME curl->srvtag*/NULL);
65   if (!err)
66     {
67       fp = http_get_write_ptr (http);
68       /* Avoid caches to get the most recent copy of the key.  We set
69          both the Pragma and Cache-Control versions of the header, so
70          we're good with both HTTP 1.0 and 1.1.  */
71       es_fputs ("Pragma: no-cache\r\n"
72                 "Cache-Control: no-cache\r\n", fp);
73       if (post_cb)
74         err = post_cb (post_cb_value, http);
75       if (!err)
76         {
77           http_start_data (http);
78           if (es_ferror (fp))
79             err = gpg_error_from_syserror ();
80         }
81     }
82   if (err)
83     {
84       /* Fixme: After a redirection we show the old host name.  */
85       log_error (_("error connecting to `%s': %s\n"),
86                  hostportstr, gpg_strerror (err));
87       goto leave;
88     }
89
90   /* Wait for the response.  */
91   dirmngr_tick (ctrl);
92   err = http_wait_response (http);
93   if (err)
94     {
95       log_error (_("error reading HTTP response for `%s': %s\n"),
96                  hostportstr, gpg_strerror (err));
97       goto leave;
98     }
99
100   switch (http_get_status_code (http))
101     {
102     case 200:
103       err = 0;
104       break; /* Success.  */
105
106     case 301:
107     case 302:
108       {
109         const char *s = http_get_header (http, "Location");
110         
111         log_info (_("URL `%s' redirected to `%s' (%u)\n"),
112                   request, s?s:"[none]", http_get_status_code (http));
113         if (s && *s && redirects_left-- )
114           {
115             xfree (request_buffer);
116             request_buffer = xtrystrdup (s);
117             if (request_buffer)
118               {
119                 request = request_buffer;
120                 http_close (http, 0);
121                 http = NULL;
122                 goto once_more;
123               }
124             err = gpg_error_from_syserror ();
125           }
126         else
127           err = gpg_error (GPG_ERR_NO_DATA);
128         log_error (_("too many redirections\n"));
129       }
130       goto leave;
131
132     default:
133       log_error (_("error accessing `%s': http status %u\n"),
134                  request, http_get_status_code (http));
135       err = gpg_error (GPG_ERR_NO_DATA);
136       goto leave;
137     }
138
139   fp = http_get_read_ptr (http);
140   if (!fp)
141     {
142       err = gpg_error (GPG_ERR_BUG);
143       goto leave;
144     }
145
146   /* Return the read stream and close the HTTP context.  */
147   *r_fp = fp;
148   http_close (http, 1);
149   http = NULL;
150
151  leave:
152   http_close (http, 0);
153   xfree (request_buffer);
154   return err;
155 }
156
157
158 static gpg_error_t
159 armor_data (char **r_string, const void *data, size_t datalen)
160 {
161   gpg_error_t err;
162   struct b64state b64state;
163   estream_t fp;
164   long length;
165   char *buffer;
166   size_t nread;
167
168   *r_string = NULL;
169
170   fp = es_fopenmem (0, "rw");
171   if (!fp)
172     return gpg_error_from_syserror ();
173
174   if ((err=b64enc_start_es (&b64state, fp, "PGP PUBLIC KEY BLOCK"))
175       || (err=b64enc_write (&b64state, data, datalen))
176       || (err = b64enc_finish (&b64state)))
177     {
178       es_fclose (fp);
179       return err;
180     }
181
182   /* FIXME: To avoid the extra buffer allocation estream should
183      provide a function to snatch the internal allocated memory from
184      such a memory stream.  */
185   length = es_ftell (fp);
186   if (length < 0)
187     {
188       err = gpg_error_from_syserror ();
189       es_fclose (fp);
190       return err;
191     }
192
193   buffer = xtrymalloc (length+1);
194   if (!buffer)
195     {
196       err = gpg_error_from_syserror ();
197       es_fclose (fp);
198       return err;
199     }
200   
201   es_rewind (fp);
202   if (es_read (fp, buffer, length, &nread))
203     {
204       err = gpg_error_from_syserror ();
205       es_fclose (fp);
206       return err;
207     }
208   buffer[nread] = 0;
209   es_fclose (fp);
210   
211   *r_string = buffer;
212   return 0;
213 }
214
215
216
217 \f
218 /* Search the keyserver identified by URI for keys matching PATTERN.
219    On success R_FP has an open stream to read the data.  */
220 gpg_error_t
221 ks_hkp_search (ctrl_t ctrl, parsed_uri_t uri, const char *pattern,
222                estream_t *r_fp)
223 {
224   gpg_error_t err;
225   KEYDB_SEARCH_DESC desc;
226   char fprbuf[2+40+1];
227   const char *scheme;
228   char portstr[10];
229   char *hostport = NULL;
230   char *request = NULL;
231   estream_t fp = NULL;
232
233   *r_fp = NULL;
234
235   /* Remove search type indicator and adjust PATTERN accordingly.
236      Note that HKP keyservers like the 0x to be present when searching
237      by keyid.  We need to re-format the fingerprint and keyids so to
238      remove the gpg specific force-use-of-this-key flag ("!").  */
239   err = classify_user_id (pattern, &desc);
240   if (err)
241     return err;
242   switch (desc.mode)
243     {
244     case KEYDB_SEARCH_MODE_EXACT:
245     case KEYDB_SEARCH_MODE_SUBSTR:
246     case KEYDB_SEARCH_MODE_MAIL:
247     case KEYDB_SEARCH_MODE_MAILSUB:
248       pattern = desc.u.name;
249       break;
250     case KEYDB_SEARCH_MODE_SHORT_KID:
251       snprintf (fprbuf, sizeof fprbuf, "0x%08lX", (ulong)desc.u.kid[1]);
252       pattern = fprbuf;
253       break;
254     case KEYDB_SEARCH_MODE_LONG_KID:
255       snprintf (fprbuf, sizeof fprbuf, "0x%08lX%08lX", 
256                 (ulong)desc.u.kid[0], (ulong)desc.u.kid[1]);
257       pattern = fprbuf;
258       break;
259     case KEYDB_SEARCH_MODE_FPR16:
260       bin2hex (desc.u.fpr, 16, fprbuf);
261       pattern = fprbuf;
262       break;
263     case KEYDB_SEARCH_MODE_FPR20:
264     case KEYDB_SEARCH_MODE_FPR:
265       bin2hex (desc.u.fpr, 20, fprbuf);
266       pattern = fprbuf;
267       break;
268     default:
269       return gpg_error (GPG_ERR_INV_USER_ID);
270     }
271   
272   /* Map scheme and port.  */
273   if (!strcmp (uri->scheme,"hkps") || !strcmp (uri->scheme,"https"))
274     {
275       scheme = "https";
276       strcpy (portstr, "443");
277     }
278   else /* HKP or HTTP.  */
279     {
280       scheme = "http";
281       strcpy (portstr, "11371");
282     }
283   if (uri->port)
284     snprintf (portstr, sizeof portstr, "%hu", uri->port);
285   else
286     {} /*fixme_do_srv_lookup ()*/
287
288   /* Build the request string.  */
289   {
290     char *searchkey;
291
292     hostport = strconcat (scheme, "://", 
293                           *uri->host? uri->host: "localhost",
294                           ":", portstr, NULL);
295     if (!hostport)
296       {
297         err = gpg_error_from_syserror ();
298         goto leave;
299       }
300
301     searchkey = http_escape_string (pattern, EXTRA_ESCAPE_CHARS);
302     if (!searchkey)
303       {
304         err = gpg_error_from_syserror ();
305         goto leave;
306       }
307
308     request = strconcat (hostport,
309                          "/pks/lookup?op=index&options=mr&search=",
310                          searchkey,
311                          NULL);
312     xfree (searchkey);
313     if (!request)
314       {
315         err = gpg_error_from_syserror ();
316         goto leave;
317       }
318   }
319   
320   /* Send the request.  */
321   err = send_request (ctrl, request, hostport, NULL, NULL, &fp);
322   if (err)
323     goto leave;
324
325   /* Start reading the response.  */
326   {
327     int c = es_getc (fp);
328     if (c == -1)
329       {
330         err = es_ferror (fp)?gpg_error_from_syserror ():gpg_error (GPG_ERR_EOF);
331         log_error ("error reading response: %s\n", gpg_strerror (err));
332         goto leave;
333       }
334     if (c == '<')
335       {
336         /* The document begins with a '<', assume it's a HTML
337            response, which we don't support.  */
338         err = gpg_error (GPG_ERR_UNSUPPORTED_ENCODING);
339         goto leave;
340       }
341     es_ungetc (c, fp);
342   }
343
344   /* Return the read stream.  */
345   *r_fp = fp;
346   fp = NULL;
347
348  leave:
349   es_fclose (fp);
350   xfree (request);
351   xfree (hostport);
352   return err;
353 }
354
355
356 /* Get the key described key the KEYSPEC string from the keyserver
357    identified by URI.  On success R_FP has an open stream to read the
358    data.  */
359 gpg_error_t
360 ks_hkp_get (ctrl_t ctrl, parsed_uri_t uri, const char *keyspec, estream_t *r_fp)
361 {
362   gpg_error_t err;
363   KEYDB_SEARCH_DESC desc;
364   char kidbuf[8+1];
365   const char *scheme;
366   char portstr[10];
367   char *hostport = NULL;
368   char *request = NULL;
369   estream_t fp = NULL;
370
371   *r_fp = NULL;
372
373   /* Remove search type indicator and adjust PATTERN accordingly.
374      Note that HKP keyservers like the 0x to be present when searching
375      by keyid.  We need to re-format the fingerprint and keyids so to
376      remove the gpg specific force-use-of-this-key flag ("!").  */
377   err = classify_user_id (keyspec, &desc);
378   if (err)
379     return err;
380   switch (desc.mode)
381     {
382     case KEYDB_SEARCH_MODE_SHORT_KID:
383     case KEYDB_SEARCH_MODE_LONG_KID:
384       snprintf (kidbuf, sizeof kidbuf, "%08lX", (ulong)desc.u.kid[1]);
385       break;
386     case KEYDB_SEARCH_MODE_FPR20:
387     case KEYDB_SEARCH_MODE_FPR:
388       /* This is a v4 fingerprint.  Take the last 8 hex digits from
389          the fingerprint which is the expected short keyid.  */
390       bin2hex (desc.u.fpr+16, 4, kidbuf);
391       break;
392
393     case KEYDB_SEARCH_MODE_FPR16:
394       log_error ("HKP keyserver do not support v3 fingerprints\n");
395     default:
396       return gpg_error (GPG_ERR_INV_USER_ID);
397     }
398   
399   /* Map scheme and port.  */
400   if (!strcmp (uri->scheme,"hkps") || !strcmp (uri->scheme,"https"))
401     {
402       scheme = "https";
403       strcpy (portstr, "443");
404     }
405   else /* HKP or HTTP.  */
406     {
407       scheme = "http";
408       strcpy (portstr, "11371");
409     }
410   if (uri->port)
411     snprintf (portstr, sizeof portstr, "%hu", uri->port);
412   else
413     {} /*fixme_do_srv_lookup ()*/
414
415   /* Build the request string.  */
416   {
417     hostport = strconcat (scheme, "://", 
418                           *uri->host? uri->host: "localhost",
419                           ":", portstr, NULL);
420     if (!hostport)
421       {
422         err = gpg_error_from_syserror ();
423         goto leave;
424       }
425
426     request = strconcat (hostport,
427                          "/pks/lookup?op=get&options=mr&search=0x",
428                          kidbuf,
429                          NULL);
430     if (!request)
431       {
432         err = gpg_error_from_syserror ();
433         goto leave;
434       }
435   }
436   
437   /* Send the request.  */
438   err = send_request (ctrl, request, hostport, NULL, NULL, &fp);
439   if (err)
440     goto leave;
441
442   /* Return the read stream and close the HTTP context.  */
443   *r_fp = fp;
444   fp = NULL;
445
446  leave:
447   es_fclose (fp);
448   xfree (request);
449   xfree (hostport);
450   return err;
451 }
452
453
454
455 \f
456 /* Callback parameters for put_post_cb.  */
457 struct put_post_parm_s
458 {
459   char *datastring;
460 };
461
462
463 /* Helper for ks_hkp_put.  */
464 static gpg_error_t
465 put_post_cb (void *opaque, http_t http)
466 {
467   struct put_post_parm_s *parm = opaque;
468   gpg_error_t err = 0;
469   estream_t fp;
470   size_t len;
471
472   fp = http_get_write_ptr (http);
473   len = strlen (parm->datastring);
474
475   es_fprintf (fp,
476               "Content-Type: application/x-www-form-urlencoded\r\n"
477               "Content-Length: %zu\r\n", len+8 /* 8 is for "keytext" */);
478   http_start_data (http);
479   if (es_fputs ("keytext=", fp) || es_write (fp, parm->datastring, len, NULL))
480     err = gpg_error_from_syserror ();
481   return err;
482 }
483
484
485 /* Send the key in {DATA,DATALEN} to the keyserver identified by  URI.  */
486 gpg_error_t
487 ks_hkp_put (ctrl_t ctrl, parsed_uri_t uri, const void *data, size_t datalen)
488 {
489   gpg_error_t err;
490   const char *scheme;
491   char portstr[10];
492   char *hostport = NULL;
493   char *request = NULL;
494   estream_t fp = NULL;
495   struct put_post_parm_s parm;
496   char *armored = NULL;
497
498   parm.datastring = NULL;
499
500   /* Map scheme and port.  */
501   if (!strcmp (uri->scheme,"hkps") || !strcmp (uri->scheme,"https"))
502     {
503       scheme = "https";
504       strcpy (portstr, "443");
505     }
506   else /* HKP or HTTP.  */
507     {
508       scheme = "http";
509       strcpy (portstr, "11371");
510     }
511   if (uri->port)
512     snprintf (portstr, sizeof portstr, "%hu", uri->port);
513   else
514     {} /*fixme_do_srv_lookup ()*/
515
516   err = armor_data (&armored, data, datalen);
517   if (err)
518     goto leave;
519
520   parm.datastring = http_escape_string (armored, EXTRA_ESCAPE_CHARS);
521   if (!parm.datastring)
522     {
523       err = gpg_error_from_syserror ();
524       goto leave;
525     }
526   xfree (armored);
527   armored = NULL;
528
529   /* Build the request string.  */
530   hostport = strconcat (scheme, "://", 
531                         *uri->host? uri->host: "localhost",
532                         ":", portstr, NULL);
533   if (!hostport)
534     {
535       err = gpg_error_from_syserror ();
536       goto leave;
537     }
538
539   request = strconcat (hostport, "/pks/add", NULL);
540   if (!request)
541     {
542       err = gpg_error_from_syserror ();
543       goto leave;
544     }
545   
546   /* Send the request.  */
547   err = send_request (ctrl, request, hostport, put_post_cb, &parm, &fp);
548   if (err)
549     goto leave;
550
551  leave:
552   es_fclose (fp);
553   xfree (parm.datastring);
554   xfree (armored);
555   xfree (request);
556   xfree (hostport);
557   return err;
558 }