585b9e7aa0a6ecfb5206dd8ea6315a3e1c14bfcb
[gnupg.git] / g13 / server.c
1 /* server.c - The G13 Assuan server
2  * Copyright (C) 2009 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 #include <stdio.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <stdarg.h>
25 #include <errno.h>
26 #include <assert.h>
27
28 #include "g13.h"
29 #include <assuan.h>
30 #include "i18n.h"
31 #include "keyblob.h"
32 #include "./server.h"
33 #include "./mount.h"
34
35
36 /* Local data for this server module.  A pointer to this is stored in
37    the CTRL object of each connection.  */
38 struct server_local_s 
39 {
40   /* The Assuan contect we are working on.  */
41   assuan_context_t assuan_ctx;
42
43   char *containername;  /* Malloced active containername.  */
44
45 };
46
47
48
49 \f
50 /* Local prototypes.  */
51 static int command_has_option (const char *cmd, const char *cmdopt);
52
53
54
55 \f
56 /*
57    Helper functions. 
58  */
59
60 /* Set an error and a description.  */
61 #define set_error(e,t) assuan_set_error (ctx, gpg_error (e), (t))
62
63
64 /* Skip over options.  Blanks after the options are also removed.  */
65 static char *
66 skip_options (const char *line)
67 {
68   while (spacep (line))
69     line++;
70   while ( *line == '-' && line[1] == '-' )
71     {
72       while (*line && !spacep (line))
73         line++;
74       while (spacep (line))
75         line++;
76     }
77   return (char*)line;
78 }
79
80
81 /* Check whether the option NAME appears in LINE.  */
82 /* static int */
83 /* has_option (const char *line, const char *name) */
84 /* { */
85 /*   const char *s; */
86 /*   int n = strlen (name); */
87
88 /*   s = strstr (line, name); */
89 /*   if (s && s >= skip_options (line)) */
90 /*     return 0; */
91 /*   return (s && (s == line || spacep (s-1)) && (!s[n] || spacep (s+n))); */
92 /* } */
93
94
95 /* Helper to print a message while leaving a command.  */
96 static gpg_error_t
97 leave_cmd (assuan_context_t ctx, gpg_error_t err)
98 {
99   if (err)
100     {
101       const char *name = assuan_get_command_name (ctx);
102       if (!name)
103         name = "?";
104       if (gpg_err_source (err) == GPG_ERR_SOURCE_DEFAULT)
105         log_error ("command '%s' failed: %s\n", name,
106                    gpg_strerror (err));
107       else
108         log_error ("command '%s' failed: %s <%s>\n", name,
109                    gpg_strerror (err), gpg_strsource (err));
110     }
111   return err;
112 }
113
114
115
116 \f
117 /* The handler for Assuan OPTION commands.  */
118 static gpg_error_t
119 option_handler (assuan_context_t ctx, const char *key, const char *value)
120 {
121   ctrl_t ctrl = assuan_get_pointer (ctx);
122   gpg_error_t err = 0;
123
124   (void)ctrl;
125
126   if (!strcmp (key, "putenv"))
127     {
128       /* Change the session's environment to be used for the
129          Pinentry.  Valid values are:
130           <NAME>            Delete envvar NAME
131           <KEY>=            Set envvar NAME to the empty string
132           <KEY>=<VALUE>     Set envvar NAME to VALUE
133       */
134       err = session_env_putenv (opt.session_env, value);
135     }
136   else if (!strcmp (key, "display"))
137     {
138       err = session_env_setenv (opt.session_env, "DISPLAY", value);
139     }
140   else if (!strcmp (key, "ttyname"))
141     {
142       err = session_env_setenv (opt.session_env, "GPG_TTY", value);
143     }
144   else if (!strcmp (key, "ttytype"))
145     {
146       err = session_env_setenv (opt.session_env, "TERM", value);
147     }
148   else if (!strcmp (key, "lc-ctype"))
149     {
150       xfree (opt.lc_ctype);
151       opt.lc_ctype = xtrystrdup (value);
152       if (!opt.lc_ctype)
153         err = gpg_error_from_syserror ();
154     }
155   else if (!strcmp (key, "lc-messages"))
156     {
157       xfree (opt.lc_messages);
158       opt.lc_messages = xtrystrdup (value);
159       if (!opt.lc_messages)
160         err = gpg_error_from_syserror ();
161     }
162   else if (!strcmp (key, "xauthority"))
163     {
164       err = session_env_setenv (opt.session_env, "XAUTHORITY", value);
165     }
166   else if (!strcmp (key, "pinentry-user-data"))
167     {
168       err = session_env_setenv (opt.session_env, "PINENTRY_USER_DATA", value);
169     }
170   else if (!strcmp (key, "allow-pinentry-notify"))
171     {
172       ; /* We always allow it.  */
173     }
174   else
175     err = gpg_error (GPG_ERR_UNKNOWN_OPTION);
176
177   return err;
178 }
179
180
181 /* The handler for an Assuan RESET command.  */
182 static void
183 reset_notify (assuan_context_t ctx)
184 {
185   ctrl_t ctrl = assuan_get_pointer (ctx);
186
187   xfree (ctrl->server_local->containername);
188   ctrl->server_local->containername = NULL;
189
190   assuan_close_input_fd (ctx);
191   assuan_close_output_fd (ctx);
192 }
193
194
195
196 /* OPEN [options] <filename>
197
198    Open the container FILENAME.  FILENAME must be percent-plus
199    escaped.  A quick check to see whether this is a suitable G13
200    container file is done.  However no cryptographic check or any
201    other check is done.  This command is used to define the target for
202    further commands.  The filename is reset with the RESET command,
203    another OPEN or the CREATE command.
204  */
205 static gpg_error_t
206 cmd_open (assuan_context_t ctx, char *line)
207 {
208   ctrl_t ctrl = assuan_get_pointer (ctx);
209   gpg_error_t err = 0;
210   char *p, *pend;
211   size_t len;
212
213   /* In any case reset the active container.  */
214   xfree (ctrl->server_local->containername);
215   ctrl->server_local->containername = NULL;
216
217   /* Parse the line.  */
218   line = skip_options (line);
219   for (p=line; *p && !spacep (p); p++)
220     ;
221   pend = p;
222   while (spacep(p))
223     p++;
224   if (*p || pend == line)
225     {
226       err = gpg_error (GPG_ERR_ASS_SYNTAX);
227       goto leave;
228     }
229   *pend = 0;
230
231   /* Unescape the line and check for embedded Nul bytes.  */
232   len = percent_plus_unescape_inplace (line, 0);
233   line[len] = 0;
234   if (!len || memchr (line, 0, len))
235     {
236       err = gpg_error (GPG_ERR_INV_NAME);
237       goto leave;
238     }
239
240   /* Do a basic check.  */
241   err = g13_is_container (ctrl, line);
242   if (err)
243     goto leave;
244
245   /* Store the filename.  */
246   ctrl->server_local->containername = xtrystrdup (line);
247   if (!ctrl->server_local->containername)
248     err = gpg_error_from_syserror ();
249   
250   
251  leave:
252   return leave_cmd (ctx, err);
253 }
254
255
256 /* MOUNT [options] [<mountpoint>]
257
258    Mount the currently open file onto MOUNTPOINT.  If MOUNTPOINT is
259    not given the system picks an unused mountpoint.  MOUNTPOINT must
260    be percent-plus escaped to allow for arbitrary names.
261  */
262 static gpg_error_t
263 cmd_mount (assuan_context_t ctx, char *line)
264 {
265   ctrl_t ctrl = assuan_get_pointer (ctx);
266   gpg_error_t err = 0;
267   char *p, *pend;
268   size_t len;
269
270   line = skip_options (line);
271   for (p=line; *p && !spacep (p); p++)
272     ;
273   pend = p;
274   while (spacep(p))
275     p++;
276   if (*p)
277     {
278       err = gpg_error (GPG_ERR_ASS_SYNTAX);
279       goto leave;
280     }
281   *pend = 0;
282
283   /* Unescape the line and check for embedded Nul bytes.  */
284   len = percent_plus_unescape_inplace (line, 0);
285   line[len] = 0;
286   if (memchr (line, 0, len))
287     {
288       err = gpg_error (GPG_ERR_INV_NAME);
289       goto leave;
290     }
291
292   if (!ctrl->server_local->containername)
293     {
294       err = gpg_error (GPG_ERR_MISSING_ACTION);
295       goto leave;
296     }
297
298   /* Perform the mount.  */
299   err = g13_mount_container (ctrl, ctrl->server_local->containername, 
300                              *line? line : NULL);
301
302  leave:
303   return leave_cmd (ctx, err);
304 }
305
306
307 /* UMOUNT [options] [<mountpoint>]
308
309    Unmount the currently open file or the one opened at MOUNTPOINT.
310    MOUNTPOINT must be percent-plus escaped.
311  */
312 static gpg_error_t
313 cmd_umount (assuan_context_t ctx, char *line)
314 {
315   ctrl_t ctrl = assuan_get_pointer (ctx);
316   gpg_error_t err = 0;
317   char *p, *pend;
318   size_t len;
319
320   line = skip_options (line);
321   for (p=line; *p && !spacep (p); p++)
322     ;
323   pend = p;
324   while (spacep(p))
325     p++;
326   if (*p)
327     {
328       err = gpg_error (GPG_ERR_ASS_SYNTAX);
329       goto leave;
330     }
331   *pend = 0;
332
333   /* Unescape the line and check for embedded Nul bytes.  */
334   len = percent_plus_unescape_inplace (line, 0);
335   line[len] = 0;
336   if (memchr (line, 0, len))
337     {
338       err = gpg_error (GPG_ERR_INV_NAME);
339       goto leave;
340     }
341
342   /* Perform the unmount.  */
343   err = g13_umount_container (ctrl, ctrl->server_local->containername, 
344                               *line? line : NULL);
345
346  leave:
347   return leave_cmd (ctx, err);
348 }
349
350
351
352 /* RECIPIENT <userID>
353
354    FIXME - description. 
355    All RECIPIENT commands are cumulative until a RESET or an
356    successful CREATE command.
357  */
358 static gpg_error_t
359 cmd_recipient (assuan_context_t ctx, char *line)
360 {
361   ctrl_t ctrl = assuan_get_pointer (ctx);
362   gpg_error_t err;
363
364   (void)ctrl;
365   err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
366   /* err = gpgsm_add_to_certlist (ctrl, line, 0, */
367   /*                              &ctrl->server_local->recplist, 0); */
368   /* if (err) */
369   /*   { */
370   /*     gpgsm_status2 (ctrl, STATUS_INV_RECP, */
371   /*                    get_inv_recpsgnr_code (rc), line, NULL); */
372   /*   } */
373
374   return leave_cmd (ctx, err);
375 }
376
377
378 /* SIGNER <userID>
379
380    FIXME - description. 
381  */
382 static gpg_error_t
383 cmd_signer (assuan_context_t ctx, char *line)
384 {
385   ctrl_t ctrl = assuan_get_pointer (ctx);
386   gpg_error_t err;
387
388   (void)ctrl;
389
390   err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
391   return leave_cmd (ctx, err);
392 }
393
394
395 /* CREATE [options] filename
396
397    Create a new container.  On success the OPEN command is done
398    implictly for the new container.
399  */
400 static gpg_error_t
401 cmd_create (assuan_context_t ctx, char *line)
402 {
403   ctrl_t ctrl = assuan_get_pointer (ctx);
404   gpg_error_t err;
405
406   (void)ctrl;
407
408   /* First we close the active container.  */
409   xfree (ctrl->server_local->containername);
410   ctrl->server_local->containername = NULL;
411
412
413
414   err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
415   return leave_cmd (ctx, err);
416 }
417
418
419 /* GETINFO <what>
420
421    Multipurpose function to return a variety of information.
422    Supported values for WHAT are:
423
424      version     - Return the version of the program.
425      pid         - Return the process id of the server.
426      cmd_has_option CMD OPT
427                  - Returns OK if the command CMD implements the option OPT.
428
429  */
430 static gpg_error_t
431 cmd_getinfo (assuan_context_t ctx, char *line)
432 {
433   gpg_error_t err = 0;
434
435   if (!strcmp (line, "version"))
436     {
437       const char *s = PACKAGE_VERSION;
438       err = assuan_send_data (ctx, s, strlen (s));
439     }
440   else if (!strcmp (line, "pid"))
441     {
442       char numbuf[50];
443
444       snprintf (numbuf, sizeof numbuf, "%lu", (unsigned long)getpid ());
445       err = assuan_send_data (ctx, numbuf, strlen (numbuf));
446     }
447   else if (!strncmp (line, "cmd_has_option", 14)
448            && (line[14] == ' ' || line[14] == '\t' || !line[14]))
449     {
450       char *cmd, *cmdopt;
451       line += 14;
452       while (*line == ' ' || *line == '\t')
453         line++;
454       if (!*line)
455         err = gpg_error (GPG_ERR_MISSING_VALUE);
456       else
457         {
458           cmd = line;
459           while (*line && (*line != ' ' && *line != '\t'))
460             line++;
461           if (!*line)
462             err = gpg_error (GPG_ERR_MISSING_VALUE);
463           else
464             {
465               *line++ = 0;
466               while (*line == ' ' || *line == '\t')
467                 line++;
468               if (!*line)
469                 err = gpg_error (GPG_ERR_MISSING_VALUE);
470               else
471                 {
472                   cmdopt = line;
473                   if (!command_has_option (cmd, cmdopt))
474                     err = gpg_error (GPG_ERR_GENERAL);
475                 }
476             }
477         }
478     }
479   else
480     err = set_error (GPG_ERR_ASS_PARAMETER, "unknown value for WHAT");
481
482   return leave_cmd (ctx, err);
483 }
484
485
486 \f
487 /* Return true if the command CMD implements the option CMDOPT.  */
488 static int
489 command_has_option (const char *cmd, const char *cmdopt)
490 {
491   (void)cmd;
492   (void)cmdopt;
493       
494   return 0;
495 }
496
497
498 /* Tell the Assuan library about our commands.  */
499 static int
500 register_commands (assuan_context_t ctx)
501 {
502   static struct {
503     const char *name;
504     gpg_error_t (*handler)(assuan_context_t, char *line);
505   } table[] =  {
506     { "OPEN",          cmd_open },
507     { "MOUNT",         cmd_mount },
508     { "UMOUNT",        cmd_umount },
509     { "RECIPIENT",     cmd_recipient },
510     { "SIGNER",        cmd_signer },
511     { "CREATE",        cmd_create },
512     { "INPUT",         NULL }, 
513     { "OUTPUT",        NULL }, 
514     { "GETINFO",       cmd_getinfo },
515     { NULL }
516   };
517   gpg_error_t err;
518   int i;
519
520   for (i=0; table[i].name; i++)
521     {
522       err = assuan_register_command (ctx, table[i].name, table[i].handler);
523       if (err)
524         return err;
525     } 
526   return 0;
527 }
528
529
530 /* Startup the server. DEFAULT_RECPLIST is the list of recipients as
531    set from the command line or config file.  We only require those
532    marked as encrypt-to. */
533 gpg_error_t
534 g13_server (ctrl_t ctrl)
535 {
536   gpg_error_t err;
537   int filedes[2];
538   assuan_context_t ctx = NULL;
539   static const char hello[] = ("GNU Privacy Guard's G13 server "
540                                PACKAGE_VERSION " ready");
541
542   /* We use a pipe based server so that we can work from scripts.
543      assuan_init_pipe_server will automagically detect when we are
544      called with a socketpair and ignore FIELDES in this case. */
545   filedes[0] = 0;
546   filedes[1] = 1;
547   err = assuan_new (&ctx);
548   if (err)
549     {
550       log_error ("failed to allocate an Assuan context: %s\n",
551                  gpg_strerror (err));
552       goto leave;
553     }
554
555   err = assuan_init_pipe_server (ctx, filedes);
556   if (err)
557     {
558       log_error ("failed to initialize the server: %s\n", gpg_strerror (err));
559       goto leave;
560     }
561
562   err = register_commands (ctx);
563   if (err)
564     {
565       log_error ("failed to the register commands with Assuan: %s\n",
566                  gpg_strerror (err));
567       goto leave;
568     }
569
570   assuan_set_pointer (ctx, ctrl);
571
572   if (opt.verbose || opt.debug)
573     {
574       char *tmp = NULL;
575       const char *s1 = getenv ("GPG_AGENT_INFO");
576
577       tmp = xtryasprintf ("Home: %s\n"
578                           "Config: %s\n"
579                           "AgentInfo: %s\n"
580                           "%s",
581                           opt.homedir,
582                           opt.config_filename,
583                           s1?s1:"[not set]",
584                           hello);
585       if (tmp)
586         {
587           assuan_set_hello_line (ctx, tmp);
588           xfree (tmp);
589         }
590     }
591   else
592     assuan_set_hello_line (ctx, hello);
593
594   assuan_register_reset_notify (ctx, reset_notify);
595   assuan_register_option_handler (ctx, option_handler);
596
597   ctrl->server_local = xtrycalloc (1, sizeof *ctrl->server_local);
598   if (!ctrl->server_local)
599     {
600       err = gpg_error_from_syserror ();
601       goto leave;
602     }
603   ctrl->server_local->assuan_ctx = ctx;
604
605   if (DBG_ASSUAN)
606     assuan_set_log_stream (ctx, log_get_stream ());
607
608   while ( !(err = assuan_accept (ctx)) )
609     {
610       err = assuan_process (ctx);
611       if (err)
612         log_info ("Assuan processing failed: %s\n", gpg_strerror (err));
613     }
614   if (err == -1)
615     err = 0;
616   else
617     log_info ("Assuan accept problem: %s\n", gpg_strerror (err));
618   
619  leave:
620   if (ctrl->server_local)
621     {
622       xfree (ctrl->server_local);
623       ctrl->server_local = NULL;
624     }
625
626   assuan_release (ctx);
627   return err;
628 }
629
630
631
632 /* gpg_error_t */
633 /* gpgsm_status2 (ctrl_t ctrl, int no, ...) */
634 /* { */
635 /*   gpg_error_t err = 0; */
636 /*   va_list arg_ptr; */
637 /*   const char *text; */
638
639 /*   va_start (arg_ptr, no); */
640
641 /*   if (ctrl->no_server && ctrl->status_fd == -1) */
642 /*     ; /\* No status wanted. *\/ */
643 /*   else if (ctrl->no_server) */
644 /*     { */
645 /*       if (!statusfp) */
646 /*         { */
647 /*           if (ctrl->status_fd == 1) */
648 /*             statusfp = stdout; */
649 /*           else if (ctrl->status_fd == 2) */
650 /*             statusfp = stderr; */
651 /*           else */
652 /*             statusfp = fdopen (ctrl->status_fd, "w"); */
653       
654 /*           if (!statusfp) */
655 /*             { */
656 /*               log_fatal ("can't open fd %d for status output: %s\n", */
657 /*                          ctrl->status_fd, strerror(errno)); */
658 /*             } */
659 /*         } */
660       
661 /*       fputs ("[GNUPG:] ", statusfp); */
662 /*       fputs (get_status_string (no), statusfp); */
663     
664 /*       while ( (text = va_arg (arg_ptr, const char*) )) */
665 /*         { */
666 /*           putc ( ' ', statusfp ); */
667 /*           for (; *text; text++)  */
668 /*             { */
669 /*               if (*text == '\n') */
670 /*                 fputs ( "\\n", statusfp ); */
671 /*               else if (*text == '\r') */
672 /*                 fputs ( "\\r", statusfp ); */
673 /*               else  */
674 /*                 putc ( *(const byte *)text,  statusfp ); */
675 /*             } */
676 /*         } */
677 /*       putc ('\n', statusfp); */
678 /*       fflush (statusfp); */
679 /*     } */
680 /*   else  */
681 /*     { */
682 /*       assuan_context_t ctx = ctrl->server_local->assuan_ctx; */
683 /*       char buf[950], *p; */
684 /*       size_t n; */
685
686 /*       p = buf;  */
687 /*       n = 0; */
688 /*       while ( (text = va_arg (arg_ptr, const char *)) ) */
689 /*         { */
690 /*           if (n) */
691 /*             { */
692 /*               *p++ = ' '; */
693 /*               n++; */
694 /*             } */
695 /*           for ( ; *text && n < DIM (buf)-2; n++) */
696 /*             *p++ = *text++; */
697 /*         } */
698 /*       *p = 0; */
699 /*       err = assuan_write_status (ctx, get_status_string (no), buf); */
700 /*     } */
701
702 /*   va_end (arg_ptr); */
703 /*   return err; */
704 /* } */
705
706 /* gpg_error_t */
707 /* gpgsm_status (ctrl_t ctrl, int no, const char *text) */
708 /* { */
709 /*   return gpgsm_status2 (ctrl, no, text, NULL); */
710 /* } */
711
712 /* gpg_error_t */
713 /* gpgsm_status_with_err_code (ctrl_t ctrl, int no, const char *text, */
714 /*                             gpg_err_code_t ec) */
715 /* { */
716 /*   char buf[30]; */
717
718 /*   sprintf (buf, "%u", (unsigned int)ec); */
719 /*   if (text) */
720 /*     return gpgsm_status2 (ctrl, no, text, buf, NULL); */
721 /*   else */
722 /*     return gpgsm_status2 (ctrl, no, buf, NULL); */
723 /* } */
724
725
726 /* Helper to notify the client about Pinentry events.  Returns an gpg
727    error code. */
728 gpg_error_t
729 g13_proxy_pinentry_notify (ctrl_t ctrl, const unsigned char *line)
730 {
731   if (!ctrl || !ctrl->server_local)
732     return 0;
733   return assuan_inquire (ctrl->server_local->assuan_ctx, line, NULL, NULL, 0);
734 }
735
736
737
738