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