Line data Source code
1 : /* ks-action.c - OpenPGP keyserver actions
2 : * Copyright (C) 2011 Free Software Foundation, Inc.
3 : * Copyright (C) 2011, 2014 Werner Koch
4 : * Copyright (C) 2015 g10 Code GmbH
5 : *
6 : * This file is part of GnuPG.
7 : *
8 : * GnuPG is free software; you can redistribute it and/or modify
9 : * it under the terms of the GNU General Public License as published by
10 : * the Free Software Foundation; either version 3 of the License, or
11 : * (at your option) any later version.
12 : *
13 : * GnuPG is distributed in the hope that it will be useful,
14 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 : * GNU General Public License for more details.
17 : *
18 : * You should have received a copy of the GNU General Public License
19 : * along with this program; if not, see <https://www.gnu.org/licenses/>.
20 : */
21 :
22 : #include <config.h>
23 :
24 : #include <stdio.h>
25 : #include <stdlib.h>
26 : #include <string.h>
27 : #include <assert.h>
28 :
29 : #include "dirmngr.h"
30 : #include "misc.h"
31 : #include "ks-engine.h"
32 : #include "ks-action.h"
33 : #if USE_LDAP
34 : # include "ldap-parse-uri.h"
35 : #endif
36 :
37 : /* Called by the engine's help functions to print the actual help. */
38 : gpg_error_t
39 0 : ks_print_help (ctrl_t ctrl, const char *text)
40 : {
41 0 : return dirmngr_status_help (ctrl, text);
42 : }
43 :
44 :
45 : /* Called by the engine's help functions to print the actual help. */
46 : gpg_error_t
47 0 : ks_printf_help (ctrl_t ctrl, const char *format, ...)
48 : {
49 : va_list arg_ptr;
50 : gpg_error_t err;
51 : char *buf;
52 :
53 0 : va_start (arg_ptr, format);
54 0 : buf = es_vbsprintf (format, arg_ptr);
55 0 : err = buf? 0 : gpg_error_from_syserror ();
56 0 : va_end (arg_ptr);
57 0 : if (!err)
58 0 : err = dirmngr_status_help (ctrl, buf);
59 0 : es_free (buf);
60 0 : return err;
61 : }
62 :
63 :
64 : /* Run the help command for the engine responsible for URI. */
65 : gpg_error_t
66 0 : ks_action_help (ctrl_t ctrl, const char *url)
67 : {
68 : gpg_error_t err;
69 : parsed_uri_t parsed_uri; /* The broken down URI. */
70 :
71 0 : if (!url || !*url)
72 : {
73 0 : ks_print_help (ctrl, "Known schemata:\n");
74 0 : parsed_uri = NULL;
75 : }
76 : else
77 : {
78 : #if USE_LDAP
79 0 : if (ldap_uri_p (url))
80 0 : err = ldap_parse_uri (&parsed_uri, url);
81 : else
82 : #endif
83 : {
84 0 : err = http_parse_uri (&parsed_uri, url, 1);
85 : }
86 :
87 0 : if (err)
88 0 : return err;
89 : }
90 :
91 : /* Call all engines to give them a chance to print a help sting. */
92 0 : err = ks_hkp_help (ctrl, parsed_uri);
93 0 : if (!err)
94 0 : err = ks_http_help (ctrl, parsed_uri);
95 0 : if (!err)
96 0 : err = ks_finger_help (ctrl, parsed_uri);
97 0 : if (!err)
98 0 : err = ks_kdns_help (ctrl, parsed_uri);
99 : #if USE_LDAP
100 0 : if (!err)
101 0 : err = ks_ldap_help (ctrl, parsed_uri);
102 : #endif
103 :
104 0 : if (!parsed_uri)
105 0 : ks_print_help (ctrl,
106 : "(Use an URL for engine specific help.)");
107 : else
108 0 : http_release_parsed_uri (parsed_uri);
109 0 : return err;
110 : }
111 :
112 :
113 : /* Resolve all host names. This is useful for looking at the status
114 : of configured keyservers. */
115 : gpg_error_t
116 0 : ks_action_resolve (ctrl_t ctrl, uri_item_t keyservers)
117 : {
118 0 : gpg_error_t err = 0;
119 0 : int any_server = 0;
120 : uri_item_t uri;
121 :
122 0 : for (uri = keyservers; !err && uri; uri = uri->next)
123 : {
124 0 : if (uri->parsed_uri->is_http)
125 : {
126 0 : any_server = 1;
127 0 : err = ks_hkp_resolve (ctrl, uri->parsed_uri);
128 0 : if (err)
129 0 : break;
130 : }
131 : }
132 :
133 0 : if (!any_server)
134 0 : err = gpg_error (GPG_ERR_NO_KEYSERVER);
135 0 : return err;
136 : }
137 :
138 :
139 : /* Search all configured keyservers for keys matching PATTERNS and
140 : write the result to the provided output stream. */
141 : gpg_error_t
142 0 : ks_action_search (ctrl_t ctrl, uri_item_t keyservers,
143 : strlist_t patterns, estream_t outfp)
144 : {
145 0 : gpg_error_t err = 0;
146 0 : int any_server = 0;
147 0 : int any_results = 0;
148 : uri_item_t uri;
149 : estream_t infp;
150 :
151 0 : if (!patterns)
152 0 : return gpg_error (GPG_ERR_NO_USER_ID);
153 :
154 : /* FIXME: We only take care of the first pattern. To fully support
155 : multiple patterns we might either want to run several queries in
156 : parallel and merge them. We also need to decide what to do with
157 : errors - it might not be the best idea to ignore an error from
158 : one server and silently continue with another server. For now we
159 : stop at the first error, unless the server responds with '404 Not
160 : Found', in which case we try the next server. */
161 0 : for (uri = keyservers; !err && uri; uri = uri->next)
162 : {
163 0 : int is_http = uri->parsed_uri->is_http;
164 0 : int is_ldap = 0;
165 0 : unsigned int http_status = 0;
166 : #if USE_LDAP
167 0 : is_ldap = (strcmp (uri->parsed_uri->scheme, "ldap") == 0
168 0 : || strcmp (uri->parsed_uri->scheme, "ldaps") == 0
169 0 : || strcmp (uri->parsed_uri->scheme, "ldapi") == 0);
170 : #endif
171 0 : if (is_http || is_ldap)
172 : {
173 0 : any_server = 1;
174 : #if USE_LDAP
175 0 : if (is_ldap)
176 0 : err = ks_ldap_search (ctrl, uri->parsed_uri, patterns->d, &infp);
177 : else
178 : #endif
179 : {
180 0 : err = ks_hkp_search (ctrl, uri->parsed_uri, patterns->d,
181 : &infp, &http_status);
182 : }
183 :
184 0 : if (err == gpg_error (GPG_ERR_NO_DATA)
185 0 : && http_status == 404 /* not found */)
186 : {
187 : /* No record found. Clear error and try next server. */
188 0 : err = 0;
189 0 : continue;
190 : }
191 :
192 0 : if (!err)
193 : {
194 0 : err = copy_stream (infp, outfp);
195 0 : es_fclose (infp);
196 0 : any_results = 1;
197 0 : break;
198 : }
199 : }
200 : }
201 :
202 0 : if (!any_server)
203 0 : err = gpg_error (GPG_ERR_NO_KEYSERVER);
204 0 : else if (err == 0 && !any_results)
205 0 : err = gpg_error (GPG_ERR_NO_DATA);
206 0 : return err;
207 : }
208 :
209 :
210 : /* Get the requested keys (matching PATTERNS) using all configured
211 : keyservers and write the result to the provided output stream. */
212 : gpg_error_t
213 0 : ks_action_get (ctrl_t ctrl, uri_item_t keyservers,
214 : strlist_t patterns, estream_t outfp)
215 : {
216 0 : gpg_error_t err = 0;
217 0 : gpg_error_t first_err = 0;
218 0 : int any_server = 0;
219 0 : int any_data = 0;
220 : strlist_t sl;
221 : uri_item_t uri;
222 : estream_t infp;
223 :
224 0 : if (!patterns)
225 0 : return gpg_error (GPG_ERR_NO_USER_ID);
226 :
227 : /* FIXME: We only take care of the first keyserver. To fully
228 : support multiple keyservers we need to track the result for each
229 : pattern and use the next keyserver if one key was not found. The
230 : keyservers might not all be fully synced thus it is not clear
231 : whether the first keyserver has the freshest copy of the key.
232 : Need to think about a better strategy. */
233 0 : for (uri = keyservers; !err && uri; uri = uri->next)
234 : {
235 0 : int is_http = uri->parsed_uri->is_http;
236 0 : int is_ldap = 0;
237 :
238 : #if USE_LDAP
239 0 : is_ldap = (strcmp (uri->parsed_uri->scheme, "ldap") == 0
240 0 : || strcmp (uri->parsed_uri->scheme, "ldaps") == 0
241 0 : || strcmp (uri->parsed_uri->scheme, "ldapi") == 0);
242 : #endif
243 :
244 0 : if (is_http || is_ldap)
245 : {
246 0 : any_server = 1;
247 0 : for (sl = patterns; !err && sl; sl = sl->next)
248 : {
249 : #if USE_LDAP
250 0 : if (is_ldap)
251 0 : err = ks_ldap_get (ctrl, uri->parsed_uri, sl->d, &infp);
252 : else
253 : #endif
254 : {
255 0 : err = ks_hkp_get (ctrl, uri->parsed_uri, sl->d, &infp);
256 : }
257 :
258 0 : if (err)
259 : {
260 : /* It is possible that a server does not carry a
261 : key, thus we only save the error and continue
262 : with the next pattern. FIXME: It is an open
263 : question how to return such an error condition to
264 : the caller. */
265 0 : first_err = err;
266 0 : err = 0;
267 : }
268 : else
269 : {
270 0 : err = copy_stream (infp, outfp);
271 : /* Reading from the keyserver should never fail, thus
272 : return this error. */
273 0 : if (!err)
274 0 : any_data = 1;
275 0 : es_fclose (infp);
276 0 : infp = NULL;
277 : }
278 : }
279 : }
280 0 : if (any_data)
281 0 : break; /* Stop loop after a keyserver returned something. */
282 : }
283 :
284 0 : if (!any_server)
285 0 : err = gpg_error (GPG_ERR_NO_KEYSERVER);
286 0 : else if (!err && first_err && !any_data)
287 0 : err = first_err;
288 0 : return err;
289 : }
290 :
291 :
292 : /* Retrieve keys from URL and write the result to the provided output
293 : stream OUTFP. */
294 : gpg_error_t
295 0 : ks_action_fetch (ctrl_t ctrl, const char *url, estream_t outfp)
296 : {
297 0 : gpg_error_t err = 0;
298 : estream_t infp;
299 : parsed_uri_t parsed_uri; /* The broken down URI. */
300 :
301 0 : if (!url)
302 0 : return gpg_error (GPG_ERR_INV_URI);
303 :
304 0 : err = http_parse_uri (&parsed_uri, url, 1);
305 0 : if (err)
306 0 : return err;
307 :
308 0 : if (parsed_uri->is_http)
309 : {
310 0 : err = ks_http_fetch (ctrl, url, &infp);
311 0 : if (!err)
312 : {
313 0 : err = copy_stream (infp, outfp);
314 0 : es_fclose (infp);
315 : }
316 : }
317 0 : else if (!parsed_uri->opaque)
318 : {
319 0 : err = gpg_error (GPG_ERR_INV_URI);
320 : }
321 0 : else if (!strcmp (parsed_uri->scheme, "finger"))
322 : {
323 0 : err = ks_finger_fetch (ctrl, parsed_uri, &infp);
324 0 : if (!err)
325 : {
326 0 : err = copy_stream (infp, outfp);
327 0 : es_fclose (infp);
328 : }
329 : }
330 0 : else if (!strcmp (parsed_uri->scheme, "kdns"))
331 : {
332 0 : err = ks_kdns_fetch (ctrl, parsed_uri, &infp);
333 0 : if (!err)
334 : {
335 0 : err = copy_stream (infp, outfp);
336 0 : es_fclose (infp);
337 : }
338 : }
339 : else
340 0 : err = gpg_error (GPG_ERR_INV_URI);
341 :
342 0 : http_release_parsed_uri (parsed_uri);
343 0 : return err;
344 : }
345 :
346 :
347 :
348 : /* Send an OpenPGP key to all keyservers. The key in {DATA,DATALEN}
349 : is expected to be in OpenPGP binary transport format. The metadata
350 : in {INFO,INFOLEN} is in colon-separated format (concretely, it is
351 : the output of 'for x in keys sigs; do gpg --list-$x --with-colons
352 : KEYID; done'. This function may modify DATA and INFO. If this is
353 : a problem, then the caller should create a copy. */
354 : gpg_error_t
355 0 : ks_action_put (ctrl_t ctrl, uri_item_t keyservers,
356 : void *data, size_t datalen,
357 : void *info, size_t infolen)
358 : {
359 0 : gpg_error_t err = 0;
360 0 : gpg_error_t first_err = 0;
361 0 : int any_server = 0;
362 : uri_item_t uri;
363 :
364 : (void) info;
365 : (void) infolen;
366 :
367 0 : for (uri = keyservers; !err && uri; uri = uri->next)
368 : {
369 0 : int is_http = uri->parsed_uri->is_http;
370 0 : int is_ldap = 0;
371 :
372 : #if USE_LDAP
373 0 : is_ldap = (strcmp (uri->parsed_uri->scheme, "ldap") == 0
374 0 : || strcmp (uri->parsed_uri->scheme, "ldaps") == 0
375 0 : || strcmp (uri->parsed_uri->scheme, "ldapi") == 0);
376 : #endif
377 :
378 0 : if (is_http || is_ldap)
379 : {
380 0 : any_server = 1;
381 : #if USE_LDAP
382 0 : if (is_ldap)
383 0 : err = ks_ldap_put (ctrl, uri->parsed_uri, data, datalen,
384 : info, infolen);
385 : else
386 : #endif
387 : {
388 0 : err = ks_hkp_put (ctrl, uri->parsed_uri, data, datalen);
389 : }
390 0 : if (err)
391 : {
392 0 : first_err = err;
393 0 : err = 0;
394 : }
395 : }
396 : }
397 :
398 0 : if (!any_server)
399 0 : err = gpg_error (GPG_ERR_NO_KEYSERVER);
400 0 : else if (!err && first_err)
401 0 : err = first_err;
402 0 : return err;
403 : }
|