/* server.c - The G13 Assuan server * Copyright (C) 2009 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GnuPG is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include #include #include #include #include #include #include #include "g13.h" #include #include "i18n.h" #include "keyblob.h" #include "./server.h" #include "./mount.h" /* Local data for this server module. A pointer to this is stored in the CTRL object of each connection. */ struct server_local_s { /* The Assuan contect we are working on. */ assuan_context_t assuan_ctx; char *mountpoint; /* Malloced current mountpoint. */ }; /* Cookie definition for assuan data line output. */ static ssize_t data_line_cookie_write (void *cookie, const void *buffer, size_t size); static int data_line_cookie_close (void *cookie); static es_cookie_io_functions_t data_line_cookie_functions = { NULL, data_line_cookie_write, NULL, data_line_cookie_close }; /* The filepointer for status message used in non-server mode. */ /* static FILE *statusfp; FIXME; */ static int command_has_option (const char *cmd, const char *cmdopt); #define set_error(e,t) assuan_set_error (ctx, gpg_error (e), (t)) /* Skip over options. Blanks after the options are also removed. */ static char * skip_options (const char *line) { while (spacep (line)) line++; while ( *line == '-' && line[1] == '-' ) { while (*line && !spacep (line)) line++; while (spacep (line)) line++; } return (char*)line; } /* Check whether the option NAME appears in LINE. */ static int has_option (const char *line, const char *name) { const char *s; int n = strlen (name); s = strstr (line, name); if (s && s >= skip_options (line)) return 0; return (s && (s == line || spacep (s-1)) && (!s[n] || spacep (s+n))); } /* A write handler used by es_fopencookie to write Assuan data lines. */ static ssize_t data_line_cookie_write (void *cookie, const void *buffer, size_t size) { assuan_context_t ctx = cookie; if (assuan_send_data (ctx, buffer, size)) { errno = EIO; return -1; } return size; } /* A close handler used by es_fopencookie to write Assuan data lines. */ static int data_line_cookie_close (void *cookie) { assuan_context_t ctx = cookie; if (assuan_send_data (ctx, NULL, 0)) { errno = EIO; return -1; } return 0; } /* The handler for Assuan OPTION commands. */ static gpg_error_t option_handler (assuan_context_t ctx, const char *key, const char *value) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err = 0; if (!strcmp (key, "putenv")) { /* Change the session's environment to be used for the Pinentry. Valid values are: Delete envvar NAME = Set envvar NAME to the empty string = Set envvar NAME to VALUE */ err = session_env_putenv (opt.session_env, value); } else if (!strcmp (key, "display")) { err = session_env_setenv (opt.session_env, "DISPLAY", value); } else if (!strcmp (key, "ttyname")) { err = session_env_setenv (opt.session_env, "GPG_TTY", value); } else if (!strcmp (key, "ttytype")) { err = session_env_setenv (opt.session_env, "TERM", value); } else if (!strcmp (key, "lc-ctype")) { xfree (opt.lc_ctype); opt.lc_ctype = xtrystrdup (value); if (!opt.lc_ctype) err = gpg_error_from_syserror (); } else if (!strcmp (key, "lc-messages")) { xfree (opt.lc_messages); opt.lc_messages = xtrystrdup (value); if (!opt.lc_messages) err = gpg_error_from_syserror (); } else if (!strcmp (key, "xauthority")) { err = session_env_setenv (opt.session_env, "XAUTHORITY", value); } else if (!strcmp (key, "pinentry-user-data")) { err = session_env_setenv (opt.session_env, "PINENTRY_USER_DATA", value); } else if (!strcmp (key, "enable-audit-log")) { /* This is not yet used. */ /* int i = *value? atoi (value) : 0; */ /* ctrl->server_local->enable_audit_log = i; */ } else if (!strcmp (key, "allow-pinentry-notify")) { ; /* We always allow it. */ } else err = gpg_error (GPG_ERR_UNKNOWN_OPTION); return err; } /* The handler for an Assuan RESET command. */ static void reset_notify (assuan_context_t ctx) { ctrl_t ctrl = assuan_get_pointer (ctx); xfree (ctrl->server_local->mountpoint); ctrl->server_local->mountpoint = NULL; assuan_close_input_fd (ctx); assuan_close_output_fd (ctx); } /* Helper to print a message while leaving a command. */ static gpg_error_t leave_cmd (assuan_context_t ctx, gpg_error_t err) { if (err) { const char *name = assuan_get_command_name (ctx); if (!name) name = "?"; if (gpg_err_source (err) == GPG_ERR_SOURCE_DEFAULT) log_error ("command '%s' failed: %s\n", name, gpg_strerror (err)); else log_error ("command '%s' failed: %s <%s>\n", name, gpg_strerror (err), gpg_strsource (err)); } return err; } /* RECIPIENT FIXME - description. All RECIPIENT commands are cumulative until a RESET or an successful CREATE command. */ static gpg_error_t cmd_recipient (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err; (void)ctrl; err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); /* err = gpgsm_add_to_certlist (ctrl, line, 0, */ /* &ctrl->server_local->recplist, 0); */ /* if (err) */ /* { */ /* gpgsm_status2 (ctrl, STATUS_INV_RECP, */ /* get_inv_recpsgnr_code (rc), line, NULL); */ /* } */ return leave_cmd (ctx, err); } /* SIGNER FIXME - description. */ static gpg_error_t cmd_signer (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err; (void)ctrl; err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); return leave_cmd (ctx, err); } /* SETMOUNTPOINT [options] [] Set DIRNAME as the new mount point for future operations. */ static gpg_error_t cmd_setmountpoint (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err = 0; char *p, *pend; size_t len; line = skip_options (line); for (p=line; *p && !spacep (p); p++) ; pend = p; while (spacep(p)) p++; if (*p) { err = gpg_error (GPG_ERR_ASS_SYNTAX); goto leave; } *pend = 0; /* Unescape the line and check for embedded Nul bytes. */ len = percent_plus_unescape_inplace (line, 0); line[len] = 0; if (memchr (line, 0, len)) { err = gpg_error (GPG_ERR_INV_NAME); goto leave; } xfree (ctrl->server_local->mountpoint); if (!len) /* Reset mountpoint. */ ctrl->server_local->mountpoint = NULL; else { ctrl->server_local->mountpoint = xtrystrdup (line); if (!ctrl->server_local->mountpoint) err = gpg_error_from_syserror (); } if (!err) log_debug ("mountpoint is now `%s'\n", ctrl->server_local->mountpoint ? ctrl->server_local->mountpoint: "[none]"); leave: return leave_cmd (ctx, err); } /* MOUNT [options] Mount CONTAINERNAME onto the current mount point. CONTAINERNAME is the name of a file in the g13 format and must be percent-plus escaped to allow for arbitrary names. The mount poiunt must have been set already. A reason why we use a separate command for the mount point is to allow for longer filenames (an assuan command line is limited to ~1000 byte. */ static gpg_error_t cmd_mount (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err = 0; char *p, *pend; size_t len; line = skip_options (line); for (p=line; *p && !spacep (p); p++) ; pend = p; while (spacep(p)) p++; if (*p || pend == line) { err = gpg_error (GPG_ERR_ASS_SYNTAX); goto leave; } *pend = 0; /* Unescape the line and check for embedded Nul bytes. */ len = percent_plus_unescape_inplace (line, 0); line[len] = 0; if (!len || memchr (line, 0, len)) { err = gpg_error (GPG_ERR_INV_NAME); goto leave; } /* Perform the mount. */ err = g13_mount_container (ctrl, line, ctrl->server_local->mountpoint); leave: return leave_cmd (ctx, err); } /* GETINFO Multipurpose function to return a variety of information. Supported values for WHAT are: version - Return the version of the program. pid - Return the process id of the server. cmd_has_option CMD OPT - Returns OK if the command CMD implements the option OPT. */ static gpg_error_t cmd_getinfo (assuan_context_t ctx, char *line) { gpg_error_t err = 0; if (!strcmp (line, "version")) { const char *s = PACKAGE_VERSION; err = assuan_send_data (ctx, s, strlen (s)); } else if (!strcmp (line, "pid")) { char numbuf[50]; snprintf (numbuf, sizeof numbuf, "%lu", (unsigned long)getpid ()); err = assuan_send_data (ctx, numbuf, strlen (numbuf)); } else if (!strncmp (line, "cmd_has_option", 14) && (line[14] == ' ' || line[14] == '\t' || !line[14])) { char *cmd, *cmdopt; line += 14; while (*line == ' ' || *line == '\t') line++; if (!*line) err = gpg_error (GPG_ERR_MISSING_VALUE); else { cmd = line; while (*line && (*line != ' ' && *line != '\t')) line++; if (!*line) err = gpg_error (GPG_ERR_MISSING_VALUE); else { *line++ = 0; while (*line == ' ' || *line == '\t') line++; if (!*line) err = gpg_error (GPG_ERR_MISSING_VALUE); else { cmdopt = line; if (!command_has_option (cmd, cmdopt)) err = gpg_error (GPG_ERR_GENERAL); } } } } else err = set_error (GPG_ERR_ASS_PARAMETER, "unknown value for WHAT"); return leave_cmd (ctx, err); } /* Return true if the command CMD implements the option CMDOPT. */ static int command_has_option (const char *cmd, const char *cmdopt) { (void)cmd; (void)cmdopt; return 0; } /* Tell the Assuan library about our commands. */ static int register_commands (assuan_context_t ctx) { static struct { const char *name; gpg_error_t (*handler)(assuan_context_t, char *line); } table[] = { { "RECIPIENT", cmd_recipient }, { "SIGNER", cmd_signer }, { "MOUNT", cmd_mount }, { "SETMOUNTPOINT", cmd_setmountpoint }, { "INPUT", NULL }, { "OUTPUT", NULL }, { "GETINFO", cmd_getinfo }, { NULL } }; gpg_error_t err; int i; for (i=0; table[i].name; i++) { err = assuan_register_command (ctx, table[i].name, table[i].handler); if (err) return err; } return 0; } /* Startup the server. DEFAULT_RECPLIST is the list of recipients as set from the command line or config file. We only require those marked as encrypt-to. */ gpg_error_t g13_server (ctrl_t ctrl) { gpg_error_t err; int filedes[2]; assuan_context_t ctx = NULL; static const char hello[] = ("GNU Privacy Guard's G13 server " PACKAGE_VERSION " ready"); /* We use a pipe based server so that we can work from scripts. assuan_init_pipe_server will automagically detect when we are called with a socketpair and ignore FIELDES in this case. */ filedes[0] = 0; filedes[1] = 1; err = assuan_new (&ctx); if (err) { log_error ("failed to allocate an Assuan context: %s\n", gpg_strerror (err)); goto leave; } err = assuan_init_pipe_server (ctx, filedes); if (err) { log_error ("failed to initialize the server: %s\n", gpg_strerror (err)); goto leave; } err = register_commands (ctx); if (err) { log_error ("failed to the register commands with Assuan: %s\n", gpg_strerror (err)); goto leave; } assuan_set_pointer (ctx, ctrl); if (opt.verbose || opt.debug) { char *tmp = NULL; const char *s1 = getenv ("GPG_AGENT_INFO"); tmp = xtryasprintf ("Home: %s\n" "Config: %s\n" "AgentInfo: %s\n" "%s", opt.homedir, opt.config_filename, s1?s1:"[not set]", hello); if (tmp) { assuan_set_hello_line (ctx, tmp); xfree (tmp); } } else assuan_set_hello_line (ctx, hello); assuan_register_reset_notify (ctx, reset_notify); assuan_register_option_handler (ctx, option_handler); ctrl->server_local = xtrycalloc (1, sizeof *ctrl->server_local); if (!ctrl->server_local) { err = gpg_error_from_syserror (); goto leave; } ctrl->server_local->assuan_ctx = ctx; if (DBG_ASSUAN) assuan_set_log_stream (ctx, log_get_stream ()); while ( !(err = assuan_accept (ctx)) ) { err = assuan_process (ctx); if (err) log_info ("Assuan processing failed: %s\n", gpg_strerror (err)); } if (err == -1) err = 0; else log_info ("Assuan accept problem: %s\n", gpg_strerror (err)); leave: if (ctrl->server_local) { xfree (ctrl->server_local); ctrl->server_local = NULL; } assuan_release (ctx); return err; } /* gpg_error_t */ /* gpgsm_status2 (ctrl_t ctrl, int no, ...) */ /* { */ /* gpg_error_t err = 0; */ /* va_list arg_ptr; */ /* const char *text; */ /* va_start (arg_ptr, no); */ /* if (ctrl->no_server && ctrl->status_fd == -1) */ /* ; /\* No status wanted. *\/ */ /* else if (ctrl->no_server) */ /* { */ /* if (!statusfp) */ /* { */ /* if (ctrl->status_fd == 1) */ /* statusfp = stdout; */ /* else if (ctrl->status_fd == 2) */ /* statusfp = stderr; */ /* else */ /* statusfp = fdopen (ctrl->status_fd, "w"); */ /* if (!statusfp) */ /* { */ /* log_fatal ("can't open fd %d for status output: %s\n", */ /* ctrl->status_fd, strerror(errno)); */ /* } */ /* } */ /* fputs ("[GNUPG:] ", statusfp); */ /* fputs (get_status_string (no), statusfp); */ /* while ( (text = va_arg (arg_ptr, const char*) )) */ /* { */ /* putc ( ' ', statusfp ); */ /* for (; *text; text++) */ /* { */ /* if (*text == '\n') */ /* fputs ( "\\n", statusfp ); */ /* else if (*text == '\r') */ /* fputs ( "\\r", statusfp ); */ /* else */ /* putc ( *(const byte *)text, statusfp ); */ /* } */ /* } */ /* putc ('\n', statusfp); */ /* fflush (statusfp); */ /* } */ /* else */ /* { */ /* assuan_context_t ctx = ctrl->server_local->assuan_ctx; */ /* char buf[950], *p; */ /* size_t n; */ /* p = buf; */ /* n = 0; */ /* while ( (text = va_arg (arg_ptr, const char *)) ) */ /* { */ /* if (n) */ /* { */ /* *p++ = ' '; */ /* n++; */ /* } */ /* for ( ; *text && n < DIM (buf)-2; n++) */ /* *p++ = *text++; */ /* } */ /* *p = 0; */ /* err = assuan_write_status (ctx, get_status_string (no), buf); */ /* } */ /* va_end (arg_ptr); */ /* return err; */ /* } */ /* gpg_error_t */ /* gpgsm_status (ctrl_t ctrl, int no, const char *text) */ /* { */ /* return gpgsm_status2 (ctrl, no, text, NULL); */ /* } */ /* gpg_error_t */ /* gpgsm_status_with_err_code (ctrl_t ctrl, int no, const char *text, */ /* gpg_err_code_t ec) */ /* { */ /* char buf[30]; */ /* sprintf (buf, "%u", (unsigned int)ec); */ /* if (text) */ /* return gpgsm_status2 (ctrl, no, text, buf, NULL); */ /* else */ /* return gpgsm_status2 (ctrl, no, buf, NULL); */ /* } */ /* Helper to notify the client about Pinentry events. Returns an gpg error code. */ gpg_error_t g13_proxy_pinentry_notify (ctrl_t ctrl, const unsigned char *line) { if (!ctrl || !ctrl->server_local) return 0; return assuan_inquire (ctrl->server_local->assuan_ctx, line, NULL, NULL, 0); }