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