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