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