Forgot to commit this:
[gnupg.git] / g10 / hkp.c
1 /* hkp.c  -  Horowitz Keyserver Protocol
2  * Copyright (C) 1998, 1999, 2000, 2001, 2002 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 2 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, write to the Free Software
18  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
19  */
20
21 #include <config.h>
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <string.h>
25 #include <ctype.h>
26 #include <errno.h>
27 #include <assert.h>
28
29 #include "errors.h"
30 #include "util.h"
31 #include "ttyio.h"
32 #include "i18n.h"
33 #include "options.h"
34 #include "filter.h"
35 #include "http.h"
36 #include "main.h"
37 #include "keyserver-internal.h"
38
39 static int urlencode_filter( void *opaque, int control,
40                              IOBUF a, byte *buf, size_t *ret_len);
41
42 /****************
43  * Try to import the key with KEYID from a keyserver but ask the user
44  * before doing so.
45  * Returns: 0 the key was successfully imported
46  *          -1 key not found on server or user does not want to
47  *             import the key
48  *          or other error codes.
49  */
50 int
51 hkp_ask_import( KEYDB_SEARCH_DESC *desc, void *stats_handle)
52 {
53     struct http_context hd;
54     char *request;
55     int rc;
56     unsigned int hflags = opt.keyserver_options.honor_http_proxy? HTTP_FLAG_TRY_PROXY : 0;
57     u32 key[2];
58
59     if(desc->mode==KEYDB_SEARCH_MODE_FPR20)
60       keyid_from_fingerprint(desc->u.fpr,MAX_FINGERPRINT_LEN,key);
61     else if(desc->mode==KEYDB_SEARCH_MODE_LONG_KID ||
62             desc->mode==KEYDB_SEARCH_MODE_SHORT_KID)
63       {
64         key[0]=desc->u.kid[0];
65         key[1]=desc->u.kid[1];
66       }
67     else
68       return -1; /* HKP does not support v3 fingerprints */
69
70     if(opt.keyserver_options.verbose)
71       log_info(_("requesting key %08lX from %s\n"),
72                (ulong)key[1],opt.keyserver_uri);
73
74     request = m_alloc( strlen( opt.keyserver_host ) + 100 );
75     /* hkp does not accept the long keyid - we should really write a
76      * nicer one :-)
77      * FIXME: request binary mode - need to pass no_armor mode
78      * down to the import function.  Marc told that there is such a
79      * binary mode ... how?
80      */
81
82     if(opt.keyserver_options.broken_http_proxy)
83       hflags |= HTTP_FLAG_NO_SHUTDOWN;
84
85     sprintf(request,"x-hkp://%s%s%s/pks/lookup?op=get&search=0x%08lX",
86             opt.keyserver_host,
87             opt.keyserver_port?":":"",
88             opt.keyserver_port?opt.keyserver_port:"",
89             (ulong)key[1] );
90
91   if(opt.keyserver_options.verbose>2)
92     log_info("request is \"%s\"\n",request);
93
94     rc = http_open_document( &hd, request, hflags );
95     if( rc ) {
96         log_info(_("can't get key from keyserver: %s\n"),
97                         rc == G10ERR_NETWORK? strerror(errno)
98                                             : g10_errstr(rc) );
99     }
100     else {
101       rc = import_keys_stream( hd.fp_read, 0, stats_handle);
102         http_close( &hd );
103     }
104
105     m_free( request );
106     return rc;
107 }
108
109 int
110 hkp_export( STRLIST users )
111 {
112     int rc;
113     armor_filter_context_t afx;
114     IOBUF temp = iobuf_temp();
115     struct http_context hd;
116     char *request;
117     unsigned int status;
118     unsigned int hflags = opt.keyserver_options.honor_http_proxy? HTTP_FLAG_TRY_PROXY : 0;
119
120     iobuf_push_filter( temp, urlencode_filter, NULL );
121
122     memset( &afx, 0, sizeof afx);
123     afx.what = 1;
124     iobuf_push_filter( temp, armor_filter, &afx );
125
126     rc = export_pubkeys_stream( temp, users,
127                                 opt.keyserver_options.export_options );
128     if( rc == -1 ) {
129         iobuf_close(temp);
130         return 0;
131     }
132
133     iobuf_flush_temp( temp );
134
135     request = m_alloc( strlen( opt.keyserver_host ) + 100 );
136
137     if(opt.keyserver_options.broken_http_proxy)
138       hflags |= HTTP_FLAG_NO_SHUTDOWN;
139
140     sprintf( request, "x-hkp://%s%s%s/pks/add",
141              opt.keyserver_host,
142              opt.keyserver_port?":":"",
143              opt.keyserver_port?opt.keyserver_port:"");
144
145   if(opt.keyserver_options.verbose>2)
146     log_info("request is \"%s\"\n",request);
147
148     rc = http_open( &hd, HTTP_REQ_POST, request , hflags );
149     if( rc ) {
150         log_error(_("can't connect to `%s': %s\n"),
151                    opt.keyserver_host,
152                         rc == G10ERR_NETWORK? strerror(errno)
153                                             : g10_errstr(rc) );
154         iobuf_close(temp);
155         m_free( request );
156         return rc;
157     }
158
159     sprintf( request, "Content-Length: %u\n",
160                       (unsigned)iobuf_get_temp_length(temp) + 9 );
161     iobuf_writestr( hd.fp_write, request );
162     m_free( request );
163     http_start_data( &hd );
164
165     iobuf_writestr( hd.fp_write, "keytext=" );
166     iobuf_write( hd.fp_write, iobuf_get_temp_buffer(temp),
167                               iobuf_get_temp_length(temp) );
168     iobuf_put( hd.fp_write, '\n' );
169     iobuf_flush_temp( temp );
170     iobuf_close(temp);
171
172     rc = http_wait_response( &hd, &status );
173     if( rc ) {
174         log_error(_("error sending to `%s': %s\n"),
175                    opt.keyserver_host, g10_errstr(rc) );
176     }
177     else {
178       #if 1
179         if( opt.verbose ) {
180             int c;
181             while( (c=iobuf_get(hd.fp_read)) != EOF )
182               if ( c >= 32 && c < 127 )
183                 putchar( c );
184               else
185                 putchar ( '?' );
186         }
187       #endif
188         if( (status/100) == 2 )
189             log_info(_("success sending to `%s' (status=%u)\n"),
190                                         opt.keyserver_host, status  );
191         else
192             log_error(_("failed sending to `%s': status=%u\n"),
193                                         opt.keyserver_host, status  );
194     }
195     http_close( &hd );
196     return rc;
197 }
198
199 static int
200 urlencode_filter( void *opaque, int control,
201                   IOBUF a, byte *buf, size_t *ret_len)
202 {
203     size_t size = *ret_len;
204     int rc=0;
205
206     if( control == IOBUFCTRL_FLUSH ) {
207         const byte *p;
208         for(p=buf; size; p++, size-- ) {
209             if( isalnum(*p) || *p == '-' )
210                 iobuf_put( a, *p );
211             else if( *p == ' ' )
212                 iobuf_put( a, '+' );
213             else {
214                 char numbuf[5];
215                 sprintf(numbuf, "%%%02X", *p );
216                 iobuf_writestr(a, numbuf );
217             }
218         }
219     }
220     else if( control == IOBUFCTRL_DESC )
221         *(char**)buf = "urlencode_filter";
222     return rc;
223 }
224
225 static int
226 write_quoted(IOBUF a, const char *buf, char delim)
227 {
228   char quoted[5];
229
230   sprintf(quoted,"\\x%02X",delim);
231
232   while(*buf)
233     {
234       if(*buf==delim)
235         {
236           if(iobuf_writestr(a,quoted))
237             return -1;
238         }
239       else if(*buf=='\\')
240         {
241           if(iobuf_writestr(a,"\\x5c"))
242             return -1;
243         }
244       else
245         {
246           if(iobuf_writebyte(a,*buf))
247             return -1;
248         }
249
250       buf++;
251     }
252
253   return 0;
254 }
255
256 /* pub  2048/<a href="/pks/lookup?op=get&search=0x3CB3B415">3CB3B415</a> 1998/04/03 David M. Shaw &lt;<a href="/pks/lookup?op=get&search=0x3CB3B415">dshaw@jabberwocky.com</a>&gt; */
257
258 /* Luckily enough, both the HKP server and NAI HKP interface to their
259    LDAP server are close enough in output so the same function can
260    parse them both. */
261
262 static int 
263 parse_hkp_index(IOBUF buffer,char *line)
264 {
265   static int open=0,revoked=0;
266   static char *key=NULL;
267 #ifdef __riscos__
268   static char *uid=NULL;
269 #else
270   static unsigned char *uid=NULL;
271 #endif
272   static u32 bits,createtime;
273   int ret=0;
274
275   /* printf("Open %d, LINE: %s, uid: %s\n",open,line,uid); */
276
277   /* Try and catch some bastardization of HKP.  If we don't have
278      certain unchanging landmarks, we can't reliably parse the
279      response. */
280   if(open && ascii_memcasecmp(line,"</pre>",6)!=0 &&
281      ascii_memcasecmp(line,"pub  ",5)!=0 &&
282      ascii_memcasecmp(line,"     ",5)!=0)
283     {
284       m_free(key);
285       m_free(uid);
286       log_error(_("this keyserver is not fully HKP compatible\n"));
287       return -1;
288     }
289
290   /* For multiple UIDs */
291   if(open && uid!=NULL)
292     {
293       ret=0;
294
295       if(!(revoked && !opt.keyserver_options.include_revoked))
296         {
297           char intstr[11];
298
299           if(key)
300             write_quoted(buffer,key,':');
301           iobuf_writestr(buffer,":");
302           write_quoted(buffer,uid,':');
303           iobuf_writestr(buffer,":");
304           iobuf_writestr(buffer,revoked?"1:":":");
305           sprintf(intstr,"%u",createtime);
306           write_quoted(buffer,intstr,':');
307           iobuf_writestr(buffer,"::::");
308           sprintf(intstr,"%u",bits);
309           write_quoted(buffer,intstr,':');
310           iobuf_writestr(buffer,"\n");
311
312           ret=1;
313         }
314
315       if(strncmp(line,"     ",5)!=0)
316         {
317           revoked=0;
318           m_free(key);
319           m_free(uid);
320           uid=NULL;
321           open=0;
322         }
323     }
324
325   if(ascii_memcasecmp(line,"pub  ",5)==0)
326     {
327       char *tok,*temp;
328
329       open=1;
330
331       line+=4;
332
333       tok=strsep(&line,"/");
334       if(tok==NULL)
335         return ret;
336
337       bits=atoi(tok);
338
339       tok=strsep(&line,">");
340       if(tok==NULL)
341         return ret;
342
343       tok=strsep(&line,"<");
344       if(tok==NULL)
345         return ret;
346
347       key=m_strdup(tok);
348
349       tok=strsep(&line," ");
350       if(tok==NULL)
351         return ret;
352
353       tok=strsep(&line," ");
354       if(tok==NULL)
355         return ret;
356   
357       /* The date parser wants '-' instead of '/', so... */
358       temp=tok;
359       while(*temp!='\0')
360         {
361           if(*temp=='/')
362             *temp='-';
363
364           temp++;
365         }
366
367       createtime=scan_isodatestr(tok);
368     }
369
370   if(open)
371     {
372       int uidindex=0;
373
374       if(line==NULL)
375         {
376           uid=m_strdup("Key index corrupted");
377           return ret;
378         }
379
380       /* All that's left is the user name.  Strip off anything
381          <between brackets> and de-urlencode it. */
382
383       while(*line==' ' && *line!='\0')
384         line++;
385
386       if(strncmp(line,"*** KEY REVOKED ***",19)==0)
387         {
388           revoked=1;
389           return ret;
390         }
391
392       uid=m_alloc(strlen(line)+1);
393
394       while(*line!='\0')
395         {
396           switch(*line)
397             {
398             case '<':
399               while(*line!='>' && *line!='\0')
400                 line++;
401
402               if(*line!='\0')
403                 line++;
404               break;
405
406             case '&':
407               if((*(line+1)!='\0' && tolower(*(line+1))=='l') &&
408                  (*(line+2)!='\0' && tolower(*(line+2))=='t') &&
409                  (*(line+3)!='\0' && *(line+3)==';'))
410                 {
411                   uid[uidindex++]='<';
412                   line+=4;
413                   break;
414                 }
415               else if((*(line+1)!='\0' && tolower(*(line+1))=='g') &&
416                  (*(line+2)!='\0' && tolower(*(line+2))=='t') &&
417                  (*(line+3)!='\0' && *(line+3)==';'))
418                 {
419                   uid[uidindex++]='>';
420                   line+=4;
421                   break;
422                 }
423               else if((*(line+1)!='\0' && tolower(*(line+1))=='a') &&
424                  (*(line+2)!='\0' && tolower(*(line+2))=='m') &&
425                  (*(line+3)!='\0' && tolower(*(line+3))=='p') &&
426                  (*(line+4)!='\0' && *(line+4)==';'))
427                 {
428                   uid[uidindex++]='&';
429                   line+=5;
430                   break;
431                 }
432
433             default:
434               uid[uidindex++]=*line;
435               line++;
436               break;
437             }
438         }
439
440       uid[uidindex]='\0';
441
442       /* Chop off the trailing \r, \n, or both. This is fussy as the
443          true HKP servers have \r\n, and the NAI HKP servers have just
444          \n. */
445
446       if(isspace(uid[uidindex-1]))
447         uid[uidindex-1]='\0';
448
449       if(isspace(uid[uidindex-2]))
450         uid[uidindex-2]='\0';
451     }
452
453   return ret;
454 }
455
456 int hkp_search(STRLIST tokens)
457 {
458   int rc=0,len=0,max,first=1;
459   unsigned int maxlen=1024,buflen=0;
460 #ifndef __riscos__
461   unsigned char *searchstr=NULL,*searchurl;
462   unsigned char *request;
463 #else
464   char *searchstr=NULL,*searchurl;
465   char *request;
466 #endif
467   struct http_context hd;
468   unsigned int hflags=opt.keyserver_options.honor_http_proxy?HTTP_FLAG_TRY_PROXY:0;
469   byte *line=NULL;
470
471   /* Glue the tokens together to make a search string */
472
473   for(;tokens;tokens=tokens->next)
474     {
475       len+=strlen(tokens->d)+1;
476
477       searchstr=m_realloc(searchstr,len+1);
478       if(first)
479         {
480           searchstr[0]='\0';
481           first=0;
482         }
483
484       strcat(searchstr,tokens->d);
485       strcat(searchstr," ");
486     }
487
488   if(len<=1)
489     {
490       m_free(searchstr);
491       return 0;
492     }
493
494   searchstr[len-1]='\0';
495
496   log_info(_("searching for \"%s\" from HKP server %s\n"),
497            searchstr,opt.keyserver_host);
498
499   /* Now make it url-ish */
500
501   max=0;
502   len=0;
503   searchurl=NULL;
504   request=searchstr;
505
506   while(*request!='\0')
507     {
508       if(max-len<3)
509         {
510           max+=100;
511           searchurl=m_realloc(searchurl,max+1); /* Note +1 for \0 */
512         }
513
514       if(isalnum(*request) || *request=='-')
515         searchurl[len++]=*request;
516       else if(*request==' ')
517         searchurl[len++]='+';
518       else
519         {
520           sprintf(&searchurl[len],"%%%02X",*request);
521           len+=3;
522         }
523
524       request++;
525     }
526
527   searchurl[len]='\0';
528
529   request=m_alloc(strlen(opt.keyserver_host) + 100 + strlen(searchurl));
530
531   if(opt.keyserver_options.broken_http_proxy)
532     hflags |= HTTP_FLAG_NO_SHUTDOWN;
533
534   sprintf(request,"x-hkp://%s%s%s/pks/lookup?op=index&search=%s",
535           opt.keyserver_host,
536           opt.keyserver_port?":":"",
537           opt.keyserver_port?opt.keyserver_port:"",
538           searchurl);
539
540   if(opt.keyserver_options.verbose>2)
541     log_info("request is \"%s\"\n",request);
542
543   rc=http_open_document(&hd,request,hflags);
544   if(rc)
545     {
546       log_error(_("can't search keyserver: %s\n"),
547                rc==G10ERR_NETWORK?strerror(errno):g10_errstr(rc));
548     }
549   else
550     {
551       IOBUF buffer;
552       int count=1;
553       int ret;
554
555       buffer=iobuf_temp();
556
557       rc=1;
558       while(rc!=0)
559         {
560           /* This is a judgement call.  Is it better to slurp up all
561              the results before prompting the user?  On the one hand,
562              it probably makes the keyserver happier to not be blocked
563              on sending for a long time while the user picks a key.
564              On the other hand, it might be nice for the server to be
565              able to stop sending before a large search result page is
566              complete. */
567
568           rc=iobuf_read_line(hd.fp_read,&line,&buflen,&maxlen);
569
570           ret=parse_hkp_index(buffer,line);
571           if(ret==-1)
572             break;
573
574           if(rc!=0)
575             count+=ret;
576         }
577
578       http_close(&hd);
579
580       count--;
581
582       if(ret>-1)
583         keyserver_search_prompt(buffer,count,searchstr);
584
585       iobuf_close(buffer);
586       m_free(line);
587     }
588
589   m_free(request);
590   m_free(searchurl);
591   m_free(searchstr);
592
593   return rc;
594 }