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