common: Rename log and gcc attribute macros (jnlib merge).
[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   assuan_fd_t 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] = assuan_fdopen (0);
586   filedes[1] = assuan_fdopen (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
616       tmp = xtryasprintf ("Home: %s\n"
617                           "Config: %s\n"
618                           "%s",
619                           opt.homedir,
620                           opt.config_filename,
621                           hello);
622       if (tmp)
623         {
624           assuan_set_hello_line (ctx, tmp);
625           xfree (tmp);
626         }
627     }
628   else
629     assuan_set_hello_line (ctx, hello);
630
631   assuan_register_reset_notify (ctx, reset_notify);
632   assuan_register_option_handler (ctx, option_handler);
633
634   ctrl->server_local = xtrycalloc (1, sizeof *ctrl->server_local);
635   if (!ctrl->server_local)
636     {
637       err = gpg_error_from_syserror ();
638       goto leave;
639     }
640   ctrl->server_local->assuan_ctx = ctx;
641
642   while ( !(err = assuan_accept (ctx)) )
643     {
644       err = assuan_process (ctx);
645       if (err)
646         log_info ("Assuan processing failed: %s\n", gpg_strerror (err));
647     }
648   if (err == -1)
649     err = 0;
650   else
651     log_info ("Assuan accept problem: %s\n", gpg_strerror (err));
652
653  leave:
654   reset_notify (ctx, NULL);  /* Release all items hold by SERVER_LOCAL.  */
655   if (ctrl->server_local)
656     {
657       xfree (ctrl->server_local);
658       ctrl->server_local = NULL;
659     }
660
661   assuan_release (ctx);
662   return err;
663 }
664
665
666 /* Send a status line with status ID NO.  The arguments are a list of
667    strings terminated by a NULL argument.  */
668 gpg_error_t
669 g13_status (ctrl_t ctrl, int no, ...)
670 {
671   gpg_error_t err = 0;
672   va_list arg_ptr;
673   const char *text;
674
675   va_start (arg_ptr, no);
676
677   if (ctrl->no_server && ctrl->status_fd == -1)
678     ; /* No status wanted. */
679   else if (ctrl->no_server)
680     {
681       if (!statusfp)
682         {
683           if (ctrl->status_fd == 1)
684             statusfp = stdout;
685           else if (ctrl->status_fd == 2)
686             statusfp = stderr;
687           else
688             statusfp = fdopen (ctrl->status_fd, "w");
689
690           if (!statusfp)
691             {
692               log_fatal ("can't open fd %d for status output: %s\n",
693                          ctrl->status_fd, strerror(errno));
694             }
695         }
696
697       fputs ("[GNUPG:] ", statusfp);
698       fputs (get_status_string (no), statusfp);
699
700       while ( (text = va_arg (arg_ptr, const char*) ))
701         {
702           putc ( ' ', statusfp );
703           for (; *text; text++)
704             {
705               if (*text == '\n')
706                 fputs ( "\\n", statusfp );
707               else if (*text == '\r')
708                 fputs ( "\\r", statusfp );
709               else
710                 putc ( *(const byte *)text,  statusfp );
711             }
712         }
713       putc ('\n', statusfp);
714       fflush (statusfp);
715     }
716   else
717     {
718       assuan_context_t ctx = ctrl->server_local->assuan_ctx;
719       char buf[950], *p;
720       size_t n;
721
722       p = buf;
723       n = 0;
724       while ( (text = va_arg (arg_ptr, const char *)) )
725         {
726           if (n)
727             {
728               *p++ = ' ';
729               n++;
730             }
731           for ( ; *text && n < DIM (buf)-2; n++)
732             *p++ = *text++;
733         }
734       *p = 0;
735       err = assuan_write_status (ctx, get_status_string (no), buf);
736     }
737
738   va_end (arg_ptr);
739   return err;
740 }
741
742
743 /* Helper to notify the client about Pinentry events.  Returns an gpg
744    error code. */
745 gpg_error_t
746 g13_proxy_pinentry_notify (ctrl_t ctrl, const unsigned char *line)
747 {
748   if (!ctrl || !ctrl->server_local)
749     return 0;
750   return assuan_inquire (ctrl->server_local->assuan_ctx, line, NULL, NULL, 0);
751 }