dirmngr: Fix HKP host selection code.
[gnupg.git] / dirmngr / ks-engine-hkp.c
1 /* ks-engine-hkp.c - HKP keyserver engine
2  * Copyright (C) 2011, 2012 Free Software Foundation, Inc.
3  * Copyright (C) 2011, 2012, 2014 Werner Koch
4  *
5  * This file is part of GnuPG.
6  *
7  * GnuPG is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 3 of the License, or
10  * (at your option) any later version.
11  *
12  * GnuPG is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, see <http://www.gnu.org/licenses/>.
19  */
20
21 #warning fixme Windows part not yet done
22 #include <config.h>
23
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <assert.h>
28 #ifdef HAVE_W32_SYSTEM
29 # ifdef HAVE_WINSOCK2_H
30 #  include <winsock2.h>
31 # endif
32 # include <windows.h>
33 #else /*!HAVE_W32_SYSTEM*/
34 # include <sys/types.h>
35 # include <sys/socket.h>
36 # include <netdb.h>
37 #endif /*!HAVE_W32_SYSTEM*/
38
39 #include "dirmngr.h"
40 #include "misc.h"
41 #include "userids.h"
42 #include "ks-engine.h"
43
44 /* To match the behaviour of our old gpgkeys helper code we escape
45    more characters than actually needed. */
46 #define EXTRA_ESCAPE_CHARS "@!\"#$%&'()*+,-./:;<=>?[\\]^_{|}~"
47
48 /* How many redirections do we allow.  */
49 #define MAX_REDIRECTS 2
50
51 /* Objects used to maintain information about hosts.  */
52 struct hostinfo_s;
53 typedef struct hostinfo_s *hostinfo_t;
54 struct hostinfo_s
55 {
56   time_t lastfail;   /* Time we tried to connect and failed.  */
57   time_t lastused;   /* Time of last use.  */
58   int *pool;         /* A -1 terminated array with indices into
59                         HOSTTABLE or NULL if NAME is not a pool
60                         name.  */
61   int poolidx;       /* Index into POOL with the used host.  */
62   unsigned int v4:1; /* Host supports AF_INET.  */
63   unsigned int v6:1; /* Host supports AF_INET6.  */
64   unsigned int dead:1; /* Host is currently unresponsive.  */
65   char name[1];      /* The hostname.  */
66 };
67
68
69 /* An array of hostinfo_t for all hosts requested by the caller or
70    resolved from a pool name and its allocated size.*/
71 static hostinfo_t *hosttable;
72 static int hosttable_size;
73
74 /* The number of host slots we initally allocate for HOSTTABLE.  */
75 #define INITIAL_HOSTTABLE_SIZE 10
76
77
78 /* Create a new hostinfo object, fill in NAME and put it into
79    HOSTTABLE.  Return the index into hosttable on success or -1 on
80    error. */
81 static int
82 create_new_hostinfo (const char *name)
83 {
84   hostinfo_t hi, *newtable;
85   int newsize;
86   int idx, rc;
87
88   hi = xtrymalloc (sizeof *hi + strlen (name));
89   if (!hi)
90     return -1;
91   strcpy (hi->name, name);
92   hi->pool = NULL;
93   hi->poolidx = -1;
94   hi->lastused = (time_t)(-1);
95   hi->lastfail = (time_t)(-1);
96   hi->v4 = 0;
97   hi->v6 = 0;
98   hi->dead = 0;
99
100   /* Add it to the hosttable. */
101   for (idx=0; idx < hosttable_size; idx++)
102     if (!hosttable[idx])
103       {
104         hosttable[idx] = hi;
105         return idx;
106       }
107   /* Need to extend the hosttable.  */
108   newsize = hosttable_size + INITIAL_HOSTTABLE_SIZE;
109   newtable = xtryrealloc (hosttable, newsize * sizeof *hosttable);
110   if (!newtable)
111     {
112       xfree (hi);
113       return -1;
114     }
115   hosttable = newtable;
116   idx = hosttable_size;
117   hosttable_size = newsize;
118   rc = idx;
119   hosttable[idx++] = hi;
120   while (idx < hosttable_size)
121     hosttable[idx++] = NULL;
122
123   return rc;
124 }
125
126
127 /* Find the host NAME in our table.  Return the index into the
128    hosttable or -1 if not found.  */
129 static int
130 find_hostinfo (const char *name)
131 {
132   int idx;
133
134   for (idx=0; idx < hosttable_size; idx++)
135     if (hosttable[idx] && !ascii_strcasecmp (hosttable[idx]->name, name))
136       return idx;
137   return -1;
138 }
139
140
141 static int
142 sort_hostpool (const void *xa, const void *xb)
143 {
144   int a = *(int *)xa;
145   int b = *(int *)xb;
146
147   assert (a >= 0 && a < hosttable_size);
148   assert (b >= 0 && b < hosttable_size);
149   assert (hosttable[a]);
150   assert (hosttable[b]);
151
152   return ascii_strcasecmp (hosttable[a]->name, hosttable[b]->name);
153 }
154
155
156 /* Select a random host.  Consult TABLE which indices into the global
157    hosttable.  Returns index into TABLE or -1 if no host could be
158    selected.  */
159 static int
160 select_random_host (int *table)
161 {
162   int *tbl;
163   size_t tblsize;
164   int pidx, idx;
165
166   /* We create a new table so that we randomly select only from
167      currently alive hosts.  */
168   for (idx=0, tblsize=0; (pidx = table[idx]) != -1; idx++)
169     if (hosttable[pidx] && !hosttable[pidx]->dead)
170       tblsize++;
171   if (!tblsize)
172     return -1; /* No hosts.  */
173
174   tbl = xtrymalloc (tblsize * sizeof *tbl);
175   if (!tbl)
176     return -1;
177   for (idx=0, tblsize=0; (pidx = table[idx]) != -1; idx++)
178     if (hosttable[pidx] && !hosttable[pidx]->dead)
179       tbl[tblsize++] = pidx;
180
181   if (tblsize == 1)  /* Save a get_uint_nonce.  */
182     pidx = tbl[0];
183   else
184     pidx = tbl[get_uint_nonce () % tblsize];
185
186   xfree (tbl);
187   return pidx;
188 }
189
190
191 /* Map the host name NAME to the actual to be used host name.  This
192    allows us to manage round robin DNS names.  We use our own strategy
193    to choose one of the hosts.  For example we skip those hosts which
194    failed for some time and we stick to one host for a time
195    independent of DNS retry times.  If FORCE_RESELECT is true a new
196    host is always selected. */
197 static char *
198 map_host (const char *name, int force_reselect)
199 {
200   hostinfo_t hi;
201   int idx;
202
203   /* No hostname means localhost.  */
204   if (!name || !*name)
205     return xtrystrdup ("localhost");
206
207   /* See whether the host is in our table.  */
208   idx = find_hostinfo (name);
209   if (idx == -1)
210     {
211       /* We never saw this host.  Allocate a new entry.  */
212       struct addrinfo hints, *aibuf, *ai;
213       int *reftbl;
214       size_t reftblsize;
215       int refidx;
216
217       reftblsize = 100;
218       reftbl = xtrymalloc (reftblsize * sizeof *reftbl);
219       if (!reftbl)
220         return NULL;
221       refidx = 0;
222
223       idx = create_new_hostinfo (name);
224       if (idx == -1)
225         {
226           xfree (reftbl);
227           return NULL;
228         }
229       hi = hosttable[idx];
230
231       /* Find all A records for this entry and put them into the pool
232          list - if any.  */
233       memset (&hints, 0, sizeof (hints));
234       hints.ai_socktype = SOCK_STREAM;
235       if (!getaddrinfo (name, NULL, &hints, &aibuf))
236         {
237           for (ai = aibuf; ai; ai = ai->ai_next)
238             {
239               char tmphost[NI_MAXHOST];
240               int tmpidx;
241               int ec;
242               int i;
243
244               if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6)
245                 continue;
246
247               if ((ec=getnameinfo (ai->ai_addr, ai->ai_addrlen,
248                                    tmphost, sizeof tmphost,
249                                    NULL, 0, 0)))
250                 {
251                   log_info ("getnameinfo failed while checking '%s': %s\n",
252                             name, gai_strerror (ec));
253                 }
254               else if (refidx+1 >= reftblsize)
255                 {
256                   log_error ("getnameinfo returned for '%s': '%s'"
257                             " [index table full - ignored]\n", name, tmphost);
258                 }
259               else
260                 {
261
262                   if ((tmpidx = find_hostinfo (tmphost)) != -1)
263                     {
264                       log_info ("getnameinfo returned for '%s': '%s'"
265                                 " [already known]\n", name, tmphost);
266                       if (ai->ai_family == AF_INET)
267                         hosttable[tmpidx]->v4 = 1;
268                       if (ai->ai_family == AF_INET6)
269                         hosttable[tmpidx]->v6 = 1;
270
271                       for (i=0; i < refidx; i++)
272                         if (reftbl[i] == tmpidx)
273                           break;
274                       if (!(i < refidx) && tmpidx != idx)
275                         reftbl[refidx++] = tmpidx;
276                     }
277                   else
278                     {
279                       log_info ("getnameinfo returned for '%s': '%s'\n",
280                                 name, tmphost);
281                       /* Create a new entry.  */
282                       tmpidx = create_new_hostinfo (tmphost);
283                       if (tmpidx == -1)
284                         log_error ("map_host for '%s' problem: %s - '%s'"
285                                    " [ignored]\n",
286                                    name, strerror (errno), tmphost);
287                       else
288                         {
289                           if (ai->ai_family == AF_INET)
290                             hosttable[tmpidx]->v4 = 1;
291                           if (ai->ai_family == AF_INET6)
292                             hosttable[tmpidx]->v6 = 1;
293
294                           for (i=0; i < refidx; i++)
295                             if (reftbl[i] == tmpidx)
296                               break;
297                           if (!(i < refidx) && tmpidx != idx)
298                             reftbl[refidx++] = tmpidx;
299                         }
300                     }
301                 }
302             }
303         }
304       reftbl[refidx] = -1;
305       if (refidx)
306         {
307           assert (!hi->pool);
308           hi->pool = xtryrealloc (reftbl, (refidx+1) * sizeof *reftbl);
309           if (!hi->pool)
310             {
311               log_error ("shrinking index table in map_host failed: %s\n",
312                          strerror (errno));
313               xfree (reftbl);
314             }
315           qsort (reftbl, refidx, sizeof *reftbl, sort_hostpool);
316         }
317       else
318         xfree (reftbl);
319     }
320
321   hi = hosttable[idx];
322   if (hi->pool)
323     {
324       /* If the currently selected host is now marked dead, force a
325          re-selection .  */
326       if (force_reselect)
327         hi->poolidx = -1;
328       else if (hi->poolidx >= 0 && hi->poolidx < hosttable_size
329                && hosttable[hi->poolidx] && hosttable[hi->poolidx]->dead)
330         hi->poolidx = -1;
331
332       /* Select a host if needed.  */
333       if (hi->poolidx == -1)
334         {
335           hi->poolidx = select_random_host (hi->pool);
336           if (hi->poolidx == -1)
337             {
338               log_error ("no alive host found in pool '%s'\n", name);
339               return NULL;
340             }
341         }
342
343       assert (hi->poolidx >= 0 && hi->poolidx < hosttable_size);
344       hi = hosttable[hi->poolidx];
345       assert (hi);
346     }
347
348   if (hi->dead)
349     {
350       log_error ("host '%s' marked as dead\n", hi->name);
351       return NULL;
352     }
353
354   return xtrystrdup (hi->name);
355 }
356
357
358 /* Mark the host NAME as dead.  */
359 static void
360 mark_host_dead (const char *name)
361 {
362   hostinfo_t hi;
363   int idx;
364
365   if (!name || !*name || !strcmp (name, "localhost"))
366     return;
367
368   idx = find_hostinfo (name);
369   if (idx == -1)
370     return;
371   hi = hosttable[idx];
372   log_info ("marking host '%s' as dead%s\n", hi->name, hi->dead? " (again)":"");
373   hi->dead = 1;
374 }
375
376
377 /* Debug function to print the entire hosttable.  */
378 gpg_error_t
379 ks_hkp_print_hosttable (ctrl_t ctrl)
380 {
381   gpg_error_t err;
382   int idx, idx2;
383   hostinfo_t hi;
384   membuf_t mb;
385   char *p;
386
387   err = ks_print_help (ctrl, "hosttable (idx, ipv4, ipv6, dead, name):");
388   if (err)
389     return err;
390
391   for (idx=0; idx < hosttable_size; idx++)
392     if ((hi=hosttable[idx]))
393       {
394         err = ks_printf_help (ctrl, "%3d %s %s %s %s\n",
395                               idx, hi->v4? "4":" ", hi->v6? "6":" ",
396                               hi->dead? "d":" ", hi->name);
397         if (err)
398           return err;
399         if (hi->pool)
400           {
401             init_membuf (&mb, 256);
402             put_membuf_printf (&mb, "  .   -->");
403             for (idx2=0; hi->pool[idx2] != -1; idx2++)
404               {
405                 put_membuf_printf (&mb, " %d", hi->pool[idx2]);
406                 if (hi->poolidx == hi->pool[idx2])
407                   put_membuf_printf (&mb, "*");
408               }
409             put_membuf( &mb, "", 1);
410             p = get_membuf (&mb, NULL);
411             if (!p)
412               return gpg_error_from_syserror ();
413             err = ks_print_help (ctrl, p);
414             xfree (p);
415             if (err)
416               return err;
417           }
418       }
419   return 0;
420 }
421
422
423
424 /* Print a help output for the schemata supported by this module. */
425 gpg_error_t
426 ks_hkp_help (ctrl_t ctrl, parsed_uri_t uri)
427 {
428   const char const data[] =
429     "Handler for HKP URLs:\n"
430     "  hkp://\n"
431     "Supported methods: search, get, put\n";
432   gpg_error_t err;
433
434   if (!uri)
435     err = ks_print_help (ctrl, "  hkp");
436   else if (uri->is_http && !strcmp (uri->scheme, "hkp"))
437     err = ks_print_help (ctrl, data);
438   else
439     err = 0;
440
441   return err;
442 }
443
444
445 /* Build the remote part or the URL from SCHEME, HOST and an optional
446    PORT.  Returns an allocated string or NULL on failure and sets
447    ERRNO.  */
448 static char *
449 make_host_part (const char *scheme, const char *host, unsigned short port,
450                 int force_reselect)
451 {
452   char portstr[10];
453   char *hostname;
454   char *hostport;
455
456   /* Map scheme and port.  */
457   if (!strcmp (scheme, "hkps") || !strcmp (scheme,"https"))
458     {
459       scheme = "https";
460       strcpy (portstr, "443");
461     }
462   else /* HKP or HTTP.  */
463     {
464       scheme = "http";
465       strcpy (portstr, "11371");
466     }
467   if (port)
468     snprintf (portstr, sizeof portstr, "%hu", port);
469   else
470     {
471       /*fixme_do_srv_lookup ()*/
472     }
473
474   hostname = map_host (host, force_reselect);
475   if (!hostname)
476     return NULL;
477
478   hostport = strconcat (scheme, "://", hostname, ":", portstr, NULL);
479   xfree (hostname);
480   return hostport;
481 }
482
483
484 /* Resolve all known keyserver names and update the hosttable.  This
485    is mainly useful for debugging because the resolving is anyway done
486    on demand.  */
487 gpg_error_t
488 ks_hkp_resolve (ctrl_t ctrl, parsed_uri_t uri)
489 {
490   gpg_error_t err;
491   char *hostport = NULL;
492
493   hostport = make_host_part (uri->scheme, uri->host, uri->port, 1);
494   if (!hostport)
495     {
496       err = gpg_error_from_syserror ();
497       err = ks_printf_help (ctrl, "%s://%s:%hu: resolve failed: %s",
498                             uri->scheme, uri->host, uri->port,
499                             gpg_strerror (err));
500     }
501   else
502     {
503       err = ks_printf_help (ctrl, "%s", hostport);
504       xfree (hostport);
505     }
506   return err;
507 }
508
509
510 /* Send an HTTP request.  On success returns an estream object at
511    R_FP.  HOSTPORTSTR is only used for diagnostics.  If POST_CB is not
512    NULL a post request is used and that callback is called to allow
513    writing the post data.  */
514 static gpg_error_t
515 send_request (ctrl_t ctrl, const char *request, const char *hostportstr,
516               gpg_error_t (*post_cb)(void *, http_t), void *post_cb_value,
517               estream_t *r_fp)
518 {
519   gpg_error_t err;
520   http_t http = NULL;
521   int redirects_left = MAX_REDIRECTS;
522   estream_t fp = NULL;
523   char *request_buffer = NULL;
524
525   *r_fp = NULL;
526
527  once_more:
528   err = http_open (&http,
529                    post_cb? HTTP_REQ_POST : HTTP_REQ_GET,
530                    request,
531                    /* fixme: AUTH */ NULL,
532                    0,
533                    /* fixme: proxy*/ NULL,
534                    NULL, NULL,
535                    /*FIXME curl->srvtag*/NULL);
536   if (!err)
537     {
538       fp = http_get_write_ptr (http);
539       /* Avoid caches to get the most recent copy of the key.  We set
540          both the Pragma and Cache-Control versions of the header, so
541          we're good with both HTTP 1.0 and 1.1.  */
542       es_fputs ("Pragma: no-cache\r\n"
543                 "Cache-Control: no-cache\r\n", fp);
544       if (post_cb)
545         err = post_cb (post_cb_value, http);
546       if (!err)
547         {
548           http_start_data (http);
549           if (es_ferror (fp))
550             err = gpg_error_from_syserror ();
551         }
552     }
553   if (err)
554     {
555       /* Fixme: After a redirection we show the old host name.  */
556       log_error (_("error connecting to '%s': %s\n"),
557                  hostportstr, gpg_strerror (err));
558       goto leave;
559     }
560
561   /* Wait for the response.  */
562   dirmngr_tick (ctrl);
563   err = http_wait_response (http);
564   if (err)
565     {
566       log_error (_("error reading HTTP response for '%s': %s\n"),
567                  hostportstr, gpg_strerror (err));
568       goto leave;
569     }
570
571   switch (http_get_status_code (http))
572     {
573     case 200:
574       err = 0;
575       break; /* Success.  */
576
577     case 301:
578     case 302:
579       {
580         const char *s = http_get_header (http, "Location");
581
582         log_info (_("URL '%s' redirected to '%s' (%u)\n"),
583                   request, s?s:"[none]", http_get_status_code (http));
584         if (s && *s && redirects_left-- )
585           {
586             xfree (request_buffer);
587             request_buffer = xtrystrdup (s);
588             if (request_buffer)
589               {
590                 request = request_buffer;
591                 http_close (http, 0);
592                 http = NULL;
593                 goto once_more;
594               }
595             err = gpg_error_from_syserror ();
596           }
597         else
598           err = gpg_error (GPG_ERR_NO_DATA);
599         log_error (_("too many redirections\n"));
600       }
601       goto leave;
602
603     default:
604       log_error (_("error accessing '%s': http status %u\n"),
605                  request, http_get_status_code (http));
606       err = gpg_error (GPG_ERR_NO_DATA);
607       goto leave;
608     }
609
610   fp = http_get_read_ptr (http);
611   if (!fp)
612     {
613       err = gpg_error (GPG_ERR_BUG);
614       goto leave;
615     }
616
617   /* Return the read stream and close the HTTP context.  */
618   *r_fp = fp;
619   http_close (http, 1);
620   http = NULL;
621
622  leave:
623   http_close (http, 0);
624   xfree (request_buffer);
625   return err;
626 }
627
628
629 static gpg_error_t
630 armor_data (char **r_string, const void *data, size_t datalen)
631 {
632   gpg_error_t err;
633   struct b64state b64state;
634   estream_t fp;
635   long length;
636   char *buffer;
637   size_t nread;
638
639   *r_string = NULL;
640
641   fp = es_fopenmem (0, "rw");
642   if (!fp)
643     return gpg_error_from_syserror ();
644
645   if ((err=b64enc_start_es (&b64state, fp, "PGP PUBLIC KEY BLOCK"))
646       || (err=b64enc_write (&b64state, data, datalen))
647       || (err = b64enc_finish (&b64state)))
648     {
649       es_fclose (fp);
650       return err;
651     }
652
653   /* FIXME: To avoid the extra buffer allocation estream should
654      provide a function to snatch the internal allocated memory from
655      such a memory stream.  */
656   length = es_ftell (fp);
657   if (length < 0)
658     {
659       err = gpg_error_from_syserror ();
660       es_fclose (fp);
661       return err;
662     }
663
664   buffer = xtrymalloc (length+1);
665   if (!buffer)
666     {
667       err = gpg_error_from_syserror ();
668       es_fclose (fp);
669       return err;
670     }
671
672   es_rewind (fp);
673   if (es_read (fp, buffer, length, &nread))
674     {
675       err = gpg_error_from_syserror ();
676       es_fclose (fp);
677       return err;
678     }
679   buffer[nread] = 0;
680   es_fclose (fp);
681
682   *r_string = buffer;
683   return 0;
684 }
685
686
687 \f
688 /* Search the keyserver identified by URI for keys matching PATTERN.
689    On success R_FP has an open stream to read the data.  */
690 gpg_error_t
691 ks_hkp_search (ctrl_t ctrl, parsed_uri_t uri, const char *pattern,
692                estream_t *r_fp)
693 {
694   gpg_error_t err;
695   KEYDB_SEARCH_DESC desc;
696   char fprbuf[2+40+1];
697   char *hostport = NULL;
698   char *request = NULL;
699   estream_t fp = NULL;
700
701   *r_fp = NULL;
702
703   /* Remove search type indicator and adjust PATTERN accordingly.
704      Note that HKP keyservers like the 0x to be present when searching
705      by keyid.  We need to re-format the fingerprint and keyids so to
706      remove the gpg specific force-use-of-this-key flag ("!").  */
707   err = classify_user_id (pattern, &desc, 1);
708   if (err)
709     return err;
710   switch (desc.mode)
711     {
712     case KEYDB_SEARCH_MODE_EXACT:
713     case KEYDB_SEARCH_MODE_SUBSTR:
714     case KEYDB_SEARCH_MODE_MAIL:
715     case KEYDB_SEARCH_MODE_MAILSUB:
716       pattern = desc.u.name;
717       break;
718     case KEYDB_SEARCH_MODE_SHORT_KID:
719       snprintf (fprbuf, sizeof fprbuf, "0x%08lX", (ulong)desc.u.kid[1]);
720       pattern = fprbuf;
721       break;
722     case KEYDB_SEARCH_MODE_LONG_KID:
723       snprintf (fprbuf, sizeof fprbuf, "0x%08lX%08lX",
724                 (ulong)desc.u.kid[0], (ulong)desc.u.kid[1]);
725       pattern = fprbuf;
726       break;
727     case KEYDB_SEARCH_MODE_FPR16:
728       bin2hex (desc.u.fpr, 16, fprbuf);
729       pattern = fprbuf;
730       break;
731     case KEYDB_SEARCH_MODE_FPR20:
732     case KEYDB_SEARCH_MODE_FPR:
733       bin2hex (desc.u.fpr, 20, fprbuf);
734       pattern = fprbuf;
735       break;
736     default:
737       return gpg_error (GPG_ERR_INV_USER_ID);
738     }
739
740   /* Build the request string.  */
741   {
742     char *searchkey;
743
744     hostport = make_host_part (uri->scheme, uri->host, uri->port, 0);
745     if (!hostport)
746       {
747         err = gpg_error_from_syserror ();
748         goto leave;
749       }
750
751     searchkey = http_escape_string (pattern, EXTRA_ESCAPE_CHARS);
752     if (!searchkey)
753       {
754         err = gpg_error_from_syserror ();
755         goto leave;
756       }
757
758     request = strconcat (hostport,
759                          "/pks/lookup?op=index&options=mr&search=",
760                          searchkey,
761                          NULL);
762     xfree (searchkey);
763     if (!request)
764       {
765         err = gpg_error_from_syserror ();
766         goto leave;
767       }
768   }
769
770   /* Send the request.  */
771   err = send_request (ctrl, request, hostport, NULL, NULL, &fp);
772   if (err)
773     goto leave;
774
775   /* Peek at the response.  */
776   {
777     int c = es_getc (fp);
778     if (c == -1)
779       {
780         err = es_ferror (fp)?gpg_error_from_syserror ():gpg_error (GPG_ERR_EOF);
781         log_error ("error reading response: %s\n", gpg_strerror (err));
782         goto leave;
783       }
784     if (c == '<')
785       {
786         /* The document begins with a '<': Assume a HTML response,
787            which we don't support.  */
788         err = gpg_error (GPG_ERR_UNSUPPORTED_ENCODING);
789         goto leave;
790       }
791     es_ungetc (c, fp);
792   }
793
794   /* Return the read stream.  */
795   *r_fp = fp;
796   fp = NULL;
797
798  leave:
799   es_fclose (fp);
800   xfree (request);
801   xfree (hostport);
802   return err;
803 }
804
805
806 /* Get the key described key the KEYSPEC string from the keyserver
807    identified by URI.  On success R_FP has an open stream to read the
808    data.  */
809 gpg_error_t
810 ks_hkp_get (ctrl_t ctrl, parsed_uri_t uri, const char *keyspec, estream_t *r_fp)
811 {
812   gpg_error_t err;
813   KEYDB_SEARCH_DESC desc;
814   char kidbuf[40+1];
815   char *hostport = NULL;
816   char *request = NULL;
817   estream_t fp = NULL;
818
819   *r_fp = NULL;
820
821   /* Remove search type indicator and adjust PATTERN accordingly.
822      Note that HKP keyservers like the 0x to be present when searching
823      by keyid.  We need to re-format the fingerprint and keyids so to
824      remove the gpg specific force-use-of-this-key flag ("!").  */
825   err = classify_user_id (keyspec, &desc, 1);
826   if (err)
827     return err;
828   switch (desc.mode)
829     {
830     case KEYDB_SEARCH_MODE_SHORT_KID:
831       snprintf (kidbuf, sizeof kidbuf, "%08lX", (ulong)desc.u.kid[1]);
832       break;
833     case KEYDB_SEARCH_MODE_LONG_KID:
834       snprintf (kidbuf, sizeof kidbuf, "%08lX%08lX",
835                 (ulong)desc.u.kid[0], (ulong)desc.u.kid[1]);
836       break;
837     case KEYDB_SEARCH_MODE_FPR20:
838     case KEYDB_SEARCH_MODE_FPR:
839       /* This is a v4 fingerprint. */
840       bin2hex (desc.u.fpr, 20, kidbuf);
841       break;
842
843     case KEYDB_SEARCH_MODE_FPR16:
844       log_error ("HKP keyservers do not support v3 fingerprints\n");
845     default:
846       return gpg_error (GPG_ERR_INV_USER_ID);
847     }
848
849   /* Build the request string.  */
850   hostport = make_host_part (uri->scheme, uri->host, uri->port, 0);
851   if (!hostport)
852     {
853       err = gpg_error_from_syserror ();
854       goto leave;
855     }
856
857   request = strconcat (hostport,
858                        "/pks/lookup?op=get&options=mr&search=0x",
859                        kidbuf,
860                        NULL);
861   if (!request)
862     {
863       err = gpg_error_from_syserror ();
864       goto leave;
865     }
866
867   /* Send the request.  */
868   err = send_request (ctrl, request, hostport, NULL, NULL, &fp);
869   if (err)
870     goto leave;
871
872   /* Return the read stream and close the HTTP context.  */
873   *r_fp = fp;
874   fp = NULL;
875
876  leave:
877   es_fclose (fp);
878   xfree (request);
879   xfree (hostport);
880   return err;
881 }
882
883
884
885 \f
886 /* Callback parameters for put_post_cb.  */
887 struct put_post_parm_s
888 {
889   char *datastring;
890 };
891
892
893 /* Helper for ks_hkp_put.  */
894 static gpg_error_t
895 put_post_cb (void *opaque, http_t http)
896 {
897   struct put_post_parm_s *parm = opaque;
898   gpg_error_t err = 0;
899   estream_t fp;
900   size_t len;
901
902   fp = http_get_write_ptr (http);
903   len = strlen (parm->datastring);
904
905   es_fprintf (fp,
906               "Content-Type: application/x-www-form-urlencoded\r\n"
907               "Content-Length: %zu\r\n", len+8 /* 8 is for "keytext" */);
908   http_start_data (http);
909   if (es_fputs ("keytext=", fp) || es_write (fp, parm->datastring, len, NULL))
910     err = gpg_error_from_syserror ();
911   return err;
912 }
913
914
915 /* Send the key in {DATA,DATALEN} to the keyserver identified by  URI.  */
916 gpg_error_t
917 ks_hkp_put (ctrl_t ctrl, parsed_uri_t uri, const void *data, size_t datalen)
918 {
919   gpg_error_t err;
920   char *hostport = NULL;
921   char *request = NULL;
922   estream_t fp = NULL;
923   struct put_post_parm_s parm;
924   char *armored = NULL;
925
926   parm.datastring = NULL;
927
928   err = armor_data (&armored, data, datalen);
929   if (err)
930     goto leave;
931
932   parm.datastring = http_escape_string (armored, EXTRA_ESCAPE_CHARS);
933   if (!parm.datastring)
934     {
935       err = gpg_error_from_syserror ();
936       goto leave;
937     }
938   xfree (armored);
939   armored = NULL;
940
941   /* Build the request string.  */
942   hostport = make_host_part (uri->scheme, uri->host, uri->port, 0);
943   if (!hostport)
944     {
945       err = gpg_error_from_syserror ();
946       goto leave;
947     }
948
949   request = strconcat (hostport, "/pks/add", NULL);
950   if (!request)
951     {
952       err = gpg_error_from_syserror ();
953       goto leave;
954     }
955
956   /* Send the request.  */
957   err = send_request (ctrl, request, hostport, put_post_cb, &parm, &fp);
958   if (err)
959     goto leave;
960
961  leave:
962   es_fclose (fp);
963   xfree (parm.datastring);
964   xfree (armored);
965   xfree (request);
966   xfree (hostport);
967   return err;
968 }