mirror of git://git.gnupg.org/gnupg.git
710 lines
17 KiB
C
710 lines
17 KiB
C
/* 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 <http://www.gnu.org/licenses/>.
|
||
*/
|
||
|
||
#include <config.h>
|
||
#include <stdio.h>
|
||
#include <stdlib.h>
|
||
#include <string.h>
|
||
#include <stdarg.h>
|
||
#include <errno.h>
|
||
#include <assert.h>
|
||
|
||
#include "g13.h"
|
||
#include <assuan.h>
|
||
#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:
|
||
<NAME> Delete envvar NAME
|
||
<KEY>= Set envvar NAME to the empty string
|
||
<KEY>=<VALUE> 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 <userID>
|
||
|
||
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 <userID>
|
||
|
||
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] [<dirname>]
|
||
|
||
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] <containername>
|
||
|
||
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 <what>
|
||
|
||
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);
|
||
}
|
||
|
||
|
||
|
||
|