Keyserver search and get basically works again.
[gnupg.git] / dirmngr / ks-engine-hkp.c
1 /* ks-engine-hkp.c - HKP keyserver engine
2  * Copyright (C) 2011 Free Software Foundation, Inc.
3  *
4  * This file is part of GnuPG.
5  *
6  * GnuPG is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * GnuPG is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, see <http://www.gnu.org/licenses/>.
18  */
19
20 #include <config.h>
21
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <string.h>
25 #include <assert.h>
26
27 #include "dirmngr.h"
28 #include "misc.h"
29 #include "userids.h"
30 #include "ks-engine.h"
31
32 /* To match the behaviour of our old gpgkeys helper code we escape
33    more characters than actually needed. */
34 #define EXTRA_ESCAPE_CHARS "@!\"#$%&'()*+,-./:;<=>?[\\]^_{|}~"
35
36 /* How many redirections do we allow.  */
37 #define MAX_REDIRECTS 2
38
39
40 /* Send an HTTP request.  On success returns an estream object at
41    R_FP.  HOSTPORTSTR is only used for diagnostics. */
42 static gpg_error_t
43 send_request (ctrl_t ctrl, const char *request, const char *hostportstr,
44               estream_t *r_fp)
45 {
46   gpg_error_t err;
47   http_t http = NULL;
48   int redirects_left = MAX_REDIRECTS;
49   estream_t fp = NULL;
50   char *request_buffer = NULL;
51
52   *r_fp = NULL;
53  once_more:
54   err = http_open (&http, HTTP_REQ_GET, request,
55                    /* fixme: AUTH */ NULL,
56                    0,
57                    /* fixme: proxy*/ NULL,
58                    NULL, NULL,
59                    /*FIXME curl->srvtag*/NULL);
60   if (!err)
61     {
62       fp = http_get_write_ptr (http);
63       /* Avoid caches to get the most recent copy of the key.  We set
64          both the Pragma and Cache-Control versions of the header, so
65          we're good with both HTTP 1.0 and 1.1.  */
66       es_fputs ("Pragma: no-cache\r\n"
67                 "Cache-Control: no-cache\r\n", fp);
68       http_start_data (http);
69       if (es_ferror (fp))
70         err = gpg_error_from_syserror ();
71     }
72   if (err)
73     {
74       /* Fixme: After a redirection we show the old host name.  */
75       log_error (_("error connecting to `%s': %s\n"),
76                  hostportstr, gpg_strerror (err));
77       goto leave;
78     }
79
80   /* Wait for the response.  */
81   dirmngr_tick (ctrl);
82   err = http_wait_response (http);
83   if (err)
84     {
85       log_error (_("error reading HTTP response for `%s': %s\n"),
86                  hostportstr, gpg_strerror (err));
87       goto leave;
88     }
89
90   switch (http_get_status_code (http))
91     {
92     case 200:
93       err = 0;
94       break; /* Success.  */
95
96     case 301:
97     case 302:
98       {
99         const char *s = http_get_header (http, "Location");
100         
101         log_info (_("URL `%s' redirected to `%s' (%u)\n"),
102                   request, s?s:"[none]", http_get_status_code (http));
103         if (s && *s && redirects_left-- )
104           {
105             xfree (request_buffer);
106             request_buffer = xtrystrdup (s);
107             if (request_buffer)
108               {
109                 request = request_buffer;
110                 http_close (http, 0);
111                 http = NULL;
112                 goto once_more;
113               }
114             err = gpg_error_from_syserror ();
115           }
116         else
117           err = gpg_error (GPG_ERR_NO_DATA);
118         log_error (_("too many redirections\n"));
119       }
120       goto leave;
121
122     default:
123       log_error (_("error accessing `%s': http status %u\n"),
124                  request, http_get_status_code (http));
125       err = gpg_error (GPG_ERR_NO_DATA);
126       goto leave;
127     }
128
129   fp = http_get_read_ptr (http);
130   if (!fp)
131     {
132       err = gpg_error (GPG_ERR_BUG);
133       goto leave;
134     }
135
136   /* Return the read stream and close the HTTP context.  */
137   *r_fp = fp;
138   fp = NULL;
139   http_close (http, 1);
140   http = NULL;
141
142  leave:
143   es_fclose (fp);
144   http_close (http, 0);
145   xfree (request_buffer);
146   return err;
147 }
148
149
150
151 /* Search the keyserver identified by URI for keys matching PATTERN.
152    On success R_FP has an open stream to read the data.  */
153 gpg_error_t
154 ks_hkp_search (ctrl_t ctrl, parsed_uri_t uri, const char *pattern,
155                estream_t *r_fp)
156 {
157   gpg_error_t err;
158   KEYDB_SEARCH_DESC desc;
159   char fprbuf[2+40+1];
160   const char *scheme;
161   char portstr[10];
162   char *hostport = NULL;
163   char *request = NULL;
164   estream_t fp = NULL;
165
166   *r_fp = NULL;
167
168   /* Remove search type indicator and adjust PATTERN accordingly.
169      Note that HKP keyservers like the 0x to be present when searching
170      by keyid.  We need to re-format the fingerprint and keyids so to
171      remove the gpg specific force-use-of-this-key flag ("!").  */
172   err = classify_user_id (pattern, &desc);
173   if (err)
174     return err;
175   switch (desc.mode)
176     {
177     case KEYDB_SEARCH_MODE_EXACT:
178     case KEYDB_SEARCH_MODE_SUBSTR:
179     case KEYDB_SEARCH_MODE_MAIL:
180     case KEYDB_SEARCH_MODE_MAILSUB:
181       pattern = desc.u.name;
182       break;
183     case KEYDB_SEARCH_MODE_SHORT_KID:
184       snprintf (fprbuf, sizeof fprbuf, "0x%08lX", (ulong)desc.u.kid[1]);
185       pattern = fprbuf;
186       break;
187     case KEYDB_SEARCH_MODE_LONG_KID:
188       snprintf (fprbuf, sizeof fprbuf, "0x%08lX%08lX", 
189                 (ulong)desc.u.kid[0], (ulong)desc.u.kid[1]);
190       pattern = fprbuf;
191       break;
192     case KEYDB_SEARCH_MODE_FPR16:
193       bin2hex (desc.u.fpr, 16, fprbuf);
194       pattern = fprbuf;
195       break;
196     case KEYDB_SEARCH_MODE_FPR20:
197     case KEYDB_SEARCH_MODE_FPR:
198       bin2hex (desc.u.fpr, 20, fprbuf);
199       pattern = fprbuf;
200       break;
201     default:
202       return gpg_error (GPG_ERR_INV_USER_ID);
203     }
204   
205   /* Map scheme and port.  */
206   if (!strcmp (uri->scheme,"hkps") || !strcmp (uri->scheme,"https"))
207     {
208       scheme = "https";
209       strcpy (portstr, "443");
210     }
211   else /* HKP or HTTP.  */
212     {
213       scheme = "http";
214       strcpy (portstr, "11371");
215     }
216   if (uri->port)
217     snprintf (portstr, sizeof portstr, "%hu", uri->port);
218   else
219     {} /*fixme_do_srv_lookup ()*/
220
221   /* Build the request string.  */
222   {
223     char *searchkey;
224
225     hostport = strconcat (scheme, "://", 
226                           *uri->host? uri->host: "localhost",
227                           ":", portstr, NULL);
228     if (!hostport)
229       {
230         err = gpg_error_from_syserror ();
231         goto leave;
232       }
233
234     searchkey = http_escape_string (pattern, EXTRA_ESCAPE_CHARS);
235     if (!searchkey)
236       {
237         err = gpg_error_from_syserror ();
238         goto leave;
239       }
240
241     request = strconcat (hostport,
242                          "/pks/lookup?op=index&options=mr&search=",
243                          searchkey,
244                          NULL);
245     xfree (searchkey);
246     if (!request)
247       {
248         err = gpg_error_from_syserror ();
249         goto leave;
250       }
251   }
252   
253   /* Send the request.  */
254   err = send_request (ctrl, request, hostport, &fp);
255   if (err)
256     goto leave;
257
258   /* Start reading the response.  */
259   {
260     int c = es_getc (fp);
261     if (c == -1)
262       {
263         err = es_ferror (fp)?gpg_error_from_syserror ():gpg_error (GPG_ERR_EOF);
264         log_error ("error reading response: %s\n", gpg_strerror (err));
265         goto leave;
266       }
267     if (c == '<')
268       {
269         /* The document begins with a '<', assume it's a HTML
270            response, which we don't support.  */
271         err = gpg_error (GPG_ERR_UNSUPPORTED_ENCODING);
272         goto leave;
273       }
274     es_ungetc (c, fp);
275   }
276
277   /* Return the read stream.  */
278   *r_fp = fp;
279   fp = NULL;
280
281  leave:
282   es_fclose (fp);
283   xfree (request);
284   xfree (hostport);
285   return err;
286 }
287
288
289 /* Get the key described key the KEYSPEC string from the keyserver
290    identified by URI.  On success R_FP has an open stream to read the
291    data.  */
292 gpg_error_t
293 ks_hkp_get (ctrl_t ctrl, parsed_uri_t uri, const char *keyspec, estream_t *r_fp)
294 {
295   gpg_error_t err;
296   KEYDB_SEARCH_DESC desc;
297   char kidbuf[8+1];
298   const char *scheme;
299   char portstr[10];
300   char *hostport = NULL;
301   char *request = NULL;
302   estream_t fp = NULL;
303
304   *r_fp = NULL;
305
306   /* Remove search type indicator and adjust PATTERN accordingly.
307      Note that HKP keyservers like the 0x to be present when searching
308      by keyid.  We need to re-format the fingerprint and keyids so to
309      remove the gpg specific force-use-of-this-key flag ("!").  */
310   err = classify_user_id (keyspec, &desc);
311   if (err)
312     return err;
313   switch (desc.mode)
314     {
315     case KEYDB_SEARCH_MODE_SHORT_KID:
316     case KEYDB_SEARCH_MODE_LONG_KID:
317       snprintf (kidbuf, sizeof kidbuf, "%08lX", (ulong)desc.u.kid[1]);
318       break;
319     case KEYDB_SEARCH_MODE_FPR20:
320     case KEYDB_SEARCH_MODE_FPR:
321       /* This is a v4 fingerprint.  Take the last 8 hex digits from
322          the fingerprint which is the expected short keyid.  */
323       bin2hex (desc.u.fpr+16, 4, kidbuf);
324       break;
325
326     case KEYDB_SEARCH_MODE_FPR16:
327       log_error ("HKP keyserver do not support v3 fingerprints\n");
328     default:
329       return gpg_error (GPG_ERR_INV_USER_ID);
330     }
331   
332   /* Map scheme and port.  */
333   if (!strcmp (uri->scheme,"hkps") || !strcmp (uri->scheme,"https"))
334     {
335       scheme = "https";
336       strcpy (portstr, "443");
337     }
338   else /* HKP or HTTP.  */
339     {
340       scheme = "http";
341       strcpy (portstr, "11371");
342     }
343   if (uri->port)
344     snprintf (portstr, sizeof portstr, "%hu", uri->port);
345   else
346     {} /*fixme_do_srv_lookup ()*/
347
348   /* Build the request string.  */
349   {
350     hostport = strconcat (scheme, "://", 
351                           *uri->host? uri->host: "localhost",
352                           ":", portstr, NULL);
353     if (!hostport)
354       {
355         err = gpg_error_from_syserror ();
356         goto leave;
357       }
358
359     request = strconcat (hostport,
360                          "/pks/lookup?op=get&options=mr&search=0x",
361                          kidbuf,
362                          NULL);
363     if (!request)
364       {
365         err = gpg_error_from_syserror ();
366         goto leave;
367       }
368   }
369   
370   /* Send the request.  */
371   err = send_request (ctrl, request, hostport, &fp);
372   if (err)
373     goto leave;
374
375   /* Return the read stream and close the HTTP context.  */
376   *r_fp = fp;
377   fp = NULL;
378
379  leave:
380   es_fclose (fp);
381   xfree (request);
382   xfree (hostport);
383   return err;
384 }
385
386