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