Updated from latest NewPG project
[gnupg.git] / sm / call-dirmngr.c
1 /* call-dirmngr.c - communication with the dromngr 
2  *      Copyright (C) 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 <errno.h>
26 #include <unistd.h> 
27 #include <time.h>
28 #include <assert.h>
29 #include <ctype.h>
30
31 #include <gcrypt.h>
32
33 #include "gpgsm.h"
34 #include "../assuan/assuan.h"
35 #include "i18n.h"
36
37 struct membuf {
38   size_t len;
39   size_t size;
40   char *buf;
41   int out_of_core;
42 };
43
44
45
46 static ASSUAN_CONTEXT dirmngr_ctx = NULL;
47 static int force_pipe_server = 0;
48
49 struct inq_certificate_parm_s {
50   ASSUAN_CONTEXT ctx;
51   KsbaCert cert;
52 };
53
54 struct lookup_parm_s {
55   CTRL ctrl;
56   ASSUAN_CONTEXT ctx;
57   void (*cb)(void *, KsbaCert);
58   void *cb_value;
59   struct membuf data;
60   int error;
61 };
62
63 struct run_command_parm_s {
64   ASSUAN_CONTEXT ctx;
65 };
66
67
68 /* A simple implementation of a dynamic buffer.  Use init_membuf() to
69    create a buffer, put_membuf to append bytes and get_membuf to
70    release and return the buffer.  Allocation errors are detected but
71    only returned at the final get_membuf(), this helps not to clutter
72    the code with out of core checks.  */
73
74 static void
75 init_membuf (struct membuf *mb, int initiallen)
76 {
77   mb->len = 0;
78   mb->size = initiallen;
79   mb->out_of_core = 0;
80   mb->buf = xtrymalloc (initiallen);
81   if (!mb->buf)
82       mb->out_of_core = 1;
83 }
84
85 static void
86 put_membuf (struct membuf *mb, const void *buf, size_t len)
87 {
88   if (mb->out_of_core)
89     return;
90
91   if (mb->len + len >= mb->size)
92     {
93       char *p;
94       
95       mb->size += len + 1024;
96       p = xtryrealloc (mb->buf, mb->size);
97       if (!p)
98         {
99           mb->out_of_core = 1;
100           return;
101         }
102       mb->buf = p;
103     }
104   memcpy (mb->buf + mb->len, buf, len);
105   mb->len += len;
106 }
107
108 static void *
109 get_membuf (struct membuf *mb, size_t *len)
110 {
111   char *p;
112
113   if (mb->out_of_core)
114     {
115       xfree (mb->buf);
116       mb->buf = NULL;
117       return NULL;
118     }
119
120   p = mb->buf;
121   *len = mb->len;
122   mb->buf = NULL;
123   mb->out_of_core = 1; /* don't allow a reuse */
124   return p;
125 }
126
127
128
129
130 \f
131 /* Try to connect to the agent via socket or fork it off and work by
132    pipes.  Handle the server's initial greeting */
133 static int
134 start_dirmngr (void)
135 {
136   int rc;
137   char *infostr, *p;
138   ASSUAN_CONTEXT ctx;
139
140   if (dirmngr_ctx)
141     return 0; /* fixme: We need a context for each thread or serialize
142                  the access to the dirmngr */
143
144   infostr = force_pipe_server? NULL : getenv ("DIRMNGR_INFO");
145   if (!infostr)
146     {
147       const char *pgmname;
148       const char *argv[3];
149       int no_close_list[3];
150       int i;
151
152       if (opt.verbose)
153         log_info (_("no running dirmngr - starting one\n"));
154       
155       if (fflush (NULL))
156         {
157           log_error ("error flushing pending output: %s\n", strerror (errno));
158           return seterr (Write_Error);
159         }
160
161       if (!opt.dirmngr_program || !*opt.dirmngr_program)
162         opt.dirmngr_program = GNUPG_DEFAULT_DIRMNGR;
163       if ( !(pgmname = strrchr (opt.dirmngr_program, '/')))
164         pgmname = opt.dirmngr_program;
165       else
166         pgmname++;
167
168       argv[0] = pgmname;
169       argv[1] = "--server";
170       argv[2] = NULL;
171
172       i=0;
173       if (log_get_fd () != -1)
174         no_close_list[i++] = log_get_fd ();
175       no_close_list[i++] = fileno (stderr);
176       no_close_list[i] = -1;
177
178       /* connect to the agent and perform initial handshaking */
179       rc = assuan_pipe_connect (&ctx, opt.dirmngr_program, (char**)argv,
180                                 no_close_list);
181     }
182   else
183     {
184       int prot;
185       int pid;
186
187       infostr = xstrdup (infostr);
188       if ( !(p = strchr (infostr, ':')) || p == infostr)
189         {
190           log_error (_("malformed DIRMNGR_INFO environment variable\n"));
191           xfree (infostr);
192           force_pipe_server = 1;
193           return start_dirmngr ();
194         }
195       *p++ = 0;
196       pid = atoi (p);
197       while (*p && *p != ':')
198         p++;
199       prot = *p? atoi (p+1) : 0;
200       if (prot != 1)
201         {
202           log_error (_("dirmngr protocol version %d is not supported\n"),
203                      prot);
204           xfree (infostr);
205           force_pipe_server = 1;
206           return start_dirmngr ();
207         }
208
209       rc = assuan_socket_connect (&ctx, infostr, pid);
210       xfree (infostr);
211       if (rc == ASSUAN_Connect_Failed)
212         {
213           log_error (_("can't connect to the dirmngr - trying fall back\n"));
214           force_pipe_server = 1;
215           return start_dirmngr ();
216         }
217     }
218
219   if (rc)
220     {
221       log_error ("can't connect to the dirmngr: %s\n", assuan_strerror (rc));
222       return seterr (No_Dirmngr);
223     }
224   dirmngr_ctx = ctx;
225
226   if (DBG_ASSUAN)
227     log_debug ("connection to dirmngr established\n");
228   return 0;
229 }
230
231
232 \f
233 /* Handle a SENDCERT inquiry. */
234 static AssuanError
235 inq_certificate (void *opaque, const char *line)
236 {
237   struct inq_certificate_parm_s *parm = opaque;
238   AssuanError rc;
239   const unsigned char *der;
240   size_t derlen;
241
242   if (!(!strncmp (line, "SENDCERT", 8) && (line[8] == ' ' || !line[8])))
243     {
244       log_error ("unsupported inquiry `%s'\n", line);
245       return ASSUAN_Inquire_Unknown;
246     }
247   line += 8;
248
249   if (!*line)
250     { /* send the current certificate */
251       der = ksba_cert_get_image (parm->cert, &derlen);
252       if (!der)
253         rc = ASSUAN_Inquire_Error;
254       else
255         rc = assuan_send_data (parm->ctx, der, derlen);
256     }
257   else 
258     { /* send the given certificate */
259       int err;
260       KsbaCert cert;
261
262       err = gpgsm_find_cert (line, &cert);
263       if (err)
264         {
265           log_error ("certificate not found: %s\n", gnupg_strerror (err));
266           rc = ASSUAN_Inquire_Error;
267         }
268       else
269         {
270           der = ksba_cert_get_image (cert, &derlen);
271           if (!der)
272             rc = ASSUAN_Inquire_Error;
273           else
274             rc = assuan_send_data (parm->ctx, der, derlen);
275           ksba_cert_release (cert);
276         }
277     }
278
279   return rc; 
280 }
281
282
283 \f
284 /* Call the directory manager to check whether the certificate is valid
285    Returns 0 for valid or usually one of the errors:
286
287   GNUPG_Certificate_Revoked
288   GNUPG_No_CRL_Known
289   GNUPG_CRL_Too_Old
290  */
291 int
292 gpgsm_dirmngr_isvalid (KsbaCert cert)
293 {
294   int rc;
295   char *certid;
296   char line[ASSUAN_LINELENGTH];
297   struct inq_certificate_parm_s parm;
298
299   rc = start_dirmngr ();
300   if (rc)
301     return rc;
302
303   certid = gpgsm_get_certid (cert);
304   if (!certid)
305     {
306       log_error ("error getting the certificate ID\n");
307       return seterr (General_Error);
308     }
309
310   if (opt.verbose > 1)
311     {
312       char *fpr = gpgsm_get_fingerprint_string (cert, GCRY_MD_SHA1);
313       log_info ("asking dirmngr about %s\n", fpr);
314       xfree (fpr);
315     }
316
317   parm.ctx = dirmngr_ctx;
318   parm.cert = cert;
319
320   snprintf (line, DIM(line)-1, "ISVALID %s", certid);
321   line[DIM(line)-1] = 0;
322   xfree (certid);
323
324   rc = assuan_transact (dirmngr_ctx, line, NULL, NULL,
325                         inq_certificate, &parm, NULL, NULL);
326   if (opt.verbose > 1)
327     log_info ("response of dirmngr: %s\n", rc? assuan_strerror (rc): "okay");
328   return map_assuan_err (rc);
329 }
330
331
332 \f
333 /* Lookup helpers*/
334 static AssuanError
335 lookup_cb (void *opaque, const void *buffer, size_t length)
336 {
337   struct lookup_parm_s *parm = opaque;
338   size_t len;
339   char *buf;
340   KsbaCert cert;
341   int rc;
342
343   if (parm->error)
344     return 0;
345
346   if (buffer)
347     {
348       put_membuf (&parm->data, buffer, length);
349       return 0;
350     }
351   /* END encountered - process what we have */
352   buf = get_membuf (&parm->data, &len);
353   if (!buf)
354     {
355       parm->error = GNUPG_Out_Of_Core;
356       return 0;
357     }
358
359   cert = ksba_cert_new ();
360   if (!cert)
361     {
362       parm->error = GNUPG_Out_Of_Core;
363       return 0;
364     }
365   rc = ksba_cert_init_from_mem (cert, buf, len);
366   if (rc)
367     {
368       log_error ("failed to parse a certificate: %s\n", ksba_strerror (rc));
369     }
370   else
371     {
372       parm->cb (parm->cb_value, cert);
373     }
374
375   ksba_cert_release (cert);
376   init_membuf (&parm->data, 4096);
377   return 0;
378 }
379
380 /* Return a properly escaped pattern from NAMES.  The only error
381    return is NULL to indicate a malloc failure. */
382 static char *
383 pattern_from_strlist (STRLIST names)
384 {
385   STRLIST sl;
386   int n;
387   const char *s;
388   char *pattern, *p;
389
390   for (n=0, sl=names; sl; sl = sl->next)
391     {
392       for (s=sl->d; *s; s++, n++)
393         {
394           if (*s == '%' || *s == ' ' || *s == '+')
395             n += 2;
396         }
397       n++;
398     }
399
400   p = pattern = xtrymalloc (n+1);
401   if (!pattern)
402     return NULL;
403
404   for (n=0, sl=names; sl; sl = sl->next)
405     {
406       for (s=sl->d; *s; s++)
407         {
408           switch (*s)
409             {
410             case '%':
411               *p++ = '%';
412               *p++ = '2';
413               *p++ = '5';
414               break;
415             case ' ':
416               *p++ = '%';
417               *p++ = '2';
418               *p++ = '0';
419               break;
420             case '+':
421               *p++ = '%';
422               *p++ = '2';
423               *p++ = 'B';
424               break;
425             default:
426               *p++ = *s;
427               break;
428             }
429         }
430       *p++ = ' ';
431     }
432   if (p == pattern)
433     *pattern = 0; /* is empty */
434   else
435     p[-1] = '\0'; /* remove trailing blank */
436   
437   return pattern;
438 }
439
440 static AssuanError
441 lookup_status_cb (void *opaque, const char *line)
442 {
443   struct lookup_parm_s *parm = opaque;
444
445   if (!strncmp (line, "TRUNCATED", 9) && (line[9]==' ' || !line[9]))
446     {
447       if (parm->ctrl)
448         {
449           for (line +=9; *line == ' '; line++)
450             ;
451           gpgsm_status (parm->ctrl, STATUS_TRUNCATED, line);
452         }
453     }
454   return 0;
455 }
456
457
458 /* Run the Directroy Managers lookup command using the pattern
459    compiled from the strings given in NAMES.  The caller must provide
460    the callback CB which will be passed cert by cert.  Note that CTRL
461    is optional. */
462 int 
463 gpgsm_dirmngr_lookup (CTRL ctrl, STRLIST names,
464                       void (*cb)(void*, KsbaCert), void *cb_value)
465
466   int rc;
467   char *pattern;
468   char line[ASSUAN_LINELENGTH];
469   struct lookup_parm_s parm;
470   size_t len;
471
472   rc = start_dirmngr ();
473   if (rc)
474     return rc;
475
476   pattern = pattern_from_strlist (names);
477   if (!pattern)
478     return GNUPG_Out_Of_Core;
479   snprintf (line, DIM(line)-1, "LOOKUP %s", pattern);
480   line[DIM(line)-1] = 0;
481   xfree (pattern);
482
483   parm.ctrl = ctrl;
484   parm.ctx = dirmngr_ctx;
485   parm.cb = cb;
486   parm.cb_value = cb_value;
487   parm.error = 0;
488   init_membuf (&parm.data, 4096);
489
490   rc = assuan_transact (dirmngr_ctx, line, lookup_cb, &parm,
491                         NULL, NULL, lookup_status_cb, &parm);
492   xfree (get_membuf (&parm.data, &len));
493   if (rc)
494     return map_assuan_err (rc);
495   return parm.error;
496 }
497
498
499 \f
500 /* Run Command helpers*/
501
502 /* Fairly simple callback to write all output of dirmngr to stdout. */
503 static AssuanError
504 run_command_cb (void *opaque, const void *buffer, size_t length)
505 {
506   if (buffer)
507     {
508       if ( fwrite (buffer, length, 1, stdout) != 1 )
509         log_error ("error writing to stdout: %s\n", strerror (errno));
510     }
511   return 0;
512 }
513
514 /* Handle inquiries from the dirmngr COMMAND. */
515 static AssuanError
516 run_command_inq_cb (void *opaque, const char *line)
517 {
518   struct run_command_parm_s *parm = opaque;
519   AssuanError rc = 0;
520
521   if ( !strncmp (line, "SENDCERT", 8) && (line[8] == ' ' || !line[8]) )
522     { /* send the given certificate */
523       int err;
524       KsbaCert cert;
525       const unsigned char *der;
526       size_t derlen;
527
528       line += 8;
529       if (!*line)
530         return ASSUAN_Inquire_Error;
531
532       err = gpgsm_find_cert (line, &cert);
533       if (err)
534         {
535           log_error ("certificate not found: %s\n", gnupg_strerror (err));
536           rc = ASSUAN_Inquire_Error;
537         }
538       else
539         {
540           der = ksba_cert_get_image (cert, &derlen);
541           if (!der)
542             rc = ASSUAN_Inquire_Error;
543           else
544             rc = assuan_send_data (parm->ctx, der, derlen);
545           ksba_cert_release (cert);
546         }
547     }
548   else if ( !strncmp (line, "PRINTINFO", 9) && (line[9] == ' ' || !line[9]) )
549     { /* Simply show the message given in the argument. */
550       line += 9;
551       log_info ("dirmngr: %s\n", line);
552     }
553   else
554     {
555       log_error ("unsupported inquiry `%s'\n", line);
556       rc = ASSUAN_Inquire_Unknown;
557     }
558
559   return rc; 
560 }
561
562 static AssuanError
563 run_command_status_cb (void *opaque, const char *line)
564 {
565   if (opt.verbose)
566     {
567       log_info ("dirmngr status: %s\n", line);
568     }
569   return 0;
570 }
571
572
573
574 /* Pass COMMAND to dirmngr and print all output generated by Dirmngr
575    to stdout.  A couple of inquiries are defined (see above).  ARGC
576    arguments in ARGV are given to the Dirmngr.  Spaces, plus and
577    percent characters within the argument strings are percent escaped
578    so that blanks can act as delimiters. */
579 int
580 gpgsm_dirmngr_run_command (CTRL ctrl, const char *command,
581                            int argc, char **argv)
582
583   int rc;
584   int i;
585   const char *s;
586   char *line, *p;
587   size_t len;
588   struct run_command_parm_s parm;
589
590   rc = start_dirmngr ();
591   if (rc)
592     return rc;
593
594   parm.ctx = dirmngr_ctx;
595
596   len = strlen (command) + 1;
597   for (i=0; i < argc; i++)
598     len += 1 + 3*strlen (argv[i]); /* enough space for percent escaping */
599   line = xtrymalloc (len);
600   if (!line)
601     return GNUPG_Out_Of_Core;
602
603   p = stpcpy (line, command);
604   for (i=0; i < argc; i++)
605     {
606       *p++ = ' ';
607       for (s=argv[i]; *s; s++)
608         {
609           if (!isascii (*s))
610             *p++ = *s;
611           else if (*s == ' ')
612             *p++ = '+';
613           else if (!isprint (*s) || *s == '+')
614             {
615               sprintf (p, "%%%02X", *s);
616               p += 3;
617             }
618           else
619             *p++ = *s;
620         }
621     }
622   *p = 0;
623
624   rc = assuan_transact (dirmngr_ctx, line,
625                         run_command_cb, NULL,
626                         run_command_inq_cb, &parm,
627                         run_command_status_cb, NULL);
628   xfree (line);
629   log_info ("response of dirmngr: %s\n", rc? assuan_strerror (rc): "okay");
630   return map_assuan_err (rc);
631 }