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