2015-10-21 08:38:10 +02:00
|
|
|
|
/* sh-cmd.c - The Assuan server for g13-syshelp
|
|
|
|
|
* Copyright (C) 2015 Werner Koch
|
|
|
|
|
*
|
|
|
|
|
* 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
|
2016-11-05 12:02:19 +01:00
|
|
|
|
* along with this program; if not, see <https://www.gnu.org/licenses/>.
|
2015-10-21 08:38:10 +02:00
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#include <config.h>
|
|
|
|
|
#include <stdio.h>
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
|
#include <string.h>
|
|
|
|
|
#include <stdarg.h>
|
|
|
|
|
#include <errno.h>
|
|
|
|
|
#include <assert.h>
|
|
|
|
|
|
|
|
|
|
#include "g13-syshelp.h"
|
|
|
|
|
#include <assuan.h>
|
2017-03-07 20:21:23 +09:00
|
|
|
|
#include "../common/i18n.h"
|
2018-02-14 12:21:23 +01:00
|
|
|
|
#include "../common/asshelp.h"
|
2015-10-21 08:38:10 +02:00
|
|
|
|
#include "keyblob.h"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Local data for this server module. A pointer to this is stored in
|
|
|
|
|
the CTRL object of each connection. */
|
|
|
|
|
struct server_local_s
|
|
|
|
|
{
|
2017-02-20 16:25:15 -05:00
|
|
|
|
/* The Assuan context we are working on. */
|
2015-10-21 08:38:10 +02:00
|
|
|
|
assuan_context_t assuan_ctx;
|
|
|
|
|
|
|
|
|
|
/* The malloced name of the device. */
|
|
|
|
|
char *devicename;
|
|
|
|
|
|
|
|
|
|
/* A stream open for read of the device set by the DEVICE command or
|
|
|
|
|
NULL if no DEVICE command has been used. */
|
|
|
|
|
estream_t devicefp;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Local prototypes. */
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
Helper functions.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/* Set an error and a description. */
|
|
|
|
|
#define set_error(e,t) assuan_set_error (ctx, gpg_error (e), (t))
|
|
|
|
|
#define set_error_fail_cmd() set_error (GPG_ERR_NOT_INITIALIZED, \
|
|
|
|
|
"not called via userv or unknown user")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* 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))); */
|
|
|
|
|
/* } */
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* 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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* 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;
|
|
|
|
|
|
|
|
|
|
(void)ctrl;
|
|
|
|
|
(void)key;
|
|
|
|
|
(void)value;
|
|
|
|
|
|
|
|
|
|
if (ctrl->fail_all_cmds)
|
|
|
|
|
err = set_error_fail_cmd ();
|
|
|
|
|
else
|
|
|
|
|
err = gpg_error (GPG_ERR_UNKNOWN_OPTION);
|
|
|
|
|
|
|
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* The handler for an Assuan RESET command. */
|
|
|
|
|
static gpg_error_t
|
|
|
|
|
reset_notify (assuan_context_t ctx, char *line)
|
|
|
|
|
{
|
|
|
|
|
ctrl_t ctrl = assuan_get_pointer (ctx);
|
|
|
|
|
|
|
|
|
|
(void)line;
|
|
|
|
|
|
|
|
|
|
xfree (ctrl->server_local->devicename);
|
|
|
|
|
ctrl->server_local->devicename = NULL;
|
|
|
|
|
es_fclose (ctrl->server_local->devicefp);
|
|
|
|
|
ctrl->server_local->devicefp = NULL;
|
|
|
|
|
ctrl->devti = NULL;
|
|
|
|
|
|
|
|
|
|
assuan_close_input_fd (ctx);
|
|
|
|
|
assuan_close_output_fd (ctx);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2016-08-13 12:49:54 +02:00
|
|
|
|
static const char hlp_finddevice[] =
|
|
|
|
|
"FINDDEVICE <name>\n"
|
|
|
|
|
"\n"
|
|
|
|
|
"Find the device matching NAME. NAME be any identifier from\n"
|
Fix more spelling
* NEWS, acinclude.m4, agent/command-ssh.c, agent/command.c,
agent/gpg-agent.c, agent/keyformat.txt, agent/protect-tool.c,
common/asshelp.c, common/b64enc.c, common/recsel.c, doc/DETAILS,
doc/HACKING, doc/Notes, doc/TRANSLATE, doc/dirmngr.texi,
doc/faq.org, doc/gpg-agent.texi, doc/gpg.texi, doc/gpgsm.texi,
doc/instguide.texi, g10/armor.c, g10/gpg.c, g10/keyedit.c,
g10/mainproc.c, g10/pkclist.c, g10/tofu.c, g13/sh-cmd.c,
g13/sh-dmcrypt.c, kbx/keybox-init.c, m4/pkg.m4, sm/call-dirmngr.c,
sm/gpgsm.c, tests/Makefile.am, tests/gpgscm/Manual.txt,
tests/gpgscm/scheme.c, tests/openpgp/gpgv-forged-keyring.scm,
tests/openpgp/multisig.test, tests/openpgp/verify.scm,
tests/pkits/README, tools/applygnupgdefaults,
tools/gpg-connect-agent.c, tools/mime-maker.c, tools/mime-parser.c:
minor spelling cleanup.
Signed-off-by: Daniel Kahn Gillmor <dkg@fifthhorseman.net>
2016-09-15 14:21:15 -04:00
|
|
|
|
"g13tab permissible for the user. The corresponding block\n"
|
2016-10-27 14:58:01 +02:00
|
|
|
|
"device is returned using a status line.";
|
2016-08-13 12:49:54 +02:00
|
|
|
|
static gpg_error_t
|
|
|
|
|
cmd_finddevice (assuan_context_t ctx, char *line)
|
|
|
|
|
{
|
|
|
|
|
ctrl_t ctrl = assuan_get_pointer (ctx);
|
|
|
|
|
gpg_error_t err = 0;
|
|
|
|
|
tab_item_t ti;
|
|
|
|
|
const char *s;
|
|
|
|
|
const char *name;
|
|
|
|
|
|
|
|
|
|
name = skip_options (line);
|
|
|
|
|
|
|
|
|
|
/* Are we allowed to use the given device? We check several names:
|
|
|
|
|
* 1. The full block device
|
|
|
|
|
* 2. The label
|
|
|
|
|
* 3. The final part of the block device if NAME does not have a slash.
|
|
|
|
|
* 4. The mountpoint
|
|
|
|
|
*/
|
|
|
|
|
for (ti=ctrl->client.tab; ti; ti = ti->next)
|
|
|
|
|
if (!strcmp (name, ti->blockdev))
|
|
|
|
|
break;
|
|
|
|
|
if (!ti)
|
|
|
|
|
{
|
|
|
|
|
for (ti=ctrl->client.tab; ti; ti = ti->next)
|
|
|
|
|
if (ti->label && !strcmp (name, ti->label))
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
if (!ti && !strchr (name, '/'))
|
|
|
|
|
{
|
|
|
|
|
for (ti=ctrl->client.tab; ti; ti = ti->next)
|
|
|
|
|
{
|
|
|
|
|
s = strrchr (ti->blockdev, '/');
|
|
|
|
|
if (s && s[1] && !strcmp (name, s+1))
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (!ti)
|
|
|
|
|
{
|
|
|
|
|
for (ti=ctrl->client.tab; ti; ti = ti->next)
|
|
|
|
|
if (ti->mountpoint && !strcmp (name, ti->mountpoint))
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!ti)
|
|
|
|
|
{
|
|
|
|
|
err = set_error (GPG_ERR_NOT_FOUND, "device not configured for user");
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Check whether we have permissions to open the device. */
|
|
|
|
|
{
|
|
|
|
|
estream_t fp = es_fopen (ti->blockdev, "rb");
|
|
|
|
|
if (!fp)
|
|
|
|
|
{
|
|
|
|
|
err = gpg_error_from_syserror ();
|
|
|
|
|
log_error ("error opening '%s': %s\n",
|
|
|
|
|
ti->blockdev, gpg_strerror (err));
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
es_fclose (fp);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
err = g13_status (ctrl, STATUS_BLOCKDEV, ti->blockdev, NULL);
|
|
|
|
|
if (err)
|
|
|
|
|
return err;
|
|
|
|
|
|
|
|
|
|
leave:
|
|
|
|
|
return leave_cmd (ctx, err);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2015-10-21 08:38:10 +02:00
|
|
|
|
static const char hlp_device[] =
|
|
|
|
|
"DEVICE <name>\n"
|
|
|
|
|
"\n"
|
|
|
|
|
"Set the device used by further commands.\n"
|
2016-02-02 18:18:31 +01:00
|
|
|
|
"A device name or a PARTUUID string may be used.\n"
|
|
|
|
|
"Access to that device (by the g13 system) is locked\n"
|
|
|
|
|
"until a new DEVICE command or end of this process\n";
|
2015-10-21 08:38:10 +02:00
|
|
|
|
static gpg_error_t
|
|
|
|
|
cmd_device (assuan_context_t ctx, char *line)
|
|
|
|
|
{
|
|
|
|
|
ctrl_t ctrl = assuan_get_pointer (ctx);
|
|
|
|
|
gpg_error_t err = 0;
|
|
|
|
|
tab_item_t ti;
|
|
|
|
|
estream_t fp = NULL;
|
|
|
|
|
|
|
|
|
|
line = skip_options (line);
|
|
|
|
|
|
2016-02-13 17:01:45 +01:00
|
|
|
|
/* # warning hardwired to /dev/sdb1 ! */
|
|
|
|
|
/* if (strcmp (line, "/dev/sdb1")) */
|
|
|
|
|
/* { */
|
|
|
|
|
/* err = gpg_error (GPG_ERR_ENOENT); */
|
|
|
|
|
/* goto leave; */
|
|
|
|
|
/* } */
|
|
|
|
|
|
2015-10-21 08:38:10 +02:00
|
|
|
|
/* Always close an open device stream of this session. */
|
|
|
|
|
xfree (ctrl->server_local->devicename);
|
|
|
|
|
ctrl->server_local->devicename = NULL;
|
|
|
|
|
es_fclose (ctrl->server_local->devicefp);
|
|
|
|
|
ctrl->server_local->devicefp = NULL;
|
|
|
|
|
|
|
|
|
|
/* Are we allowed to use the given device? */
|
|
|
|
|
for (ti=ctrl->client.tab; ti; ti = ti->next)
|
|
|
|
|
if (!strcmp (line, ti->blockdev))
|
|
|
|
|
break;
|
|
|
|
|
if (!ti)
|
|
|
|
|
{
|
2016-02-02 18:18:31 +01:00
|
|
|
|
err = set_error (GPG_ERR_EACCES, "device not configured for user");
|
2015-10-21 08:38:10 +02:00
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ctrl->server_local->devicename = xtrystrdup (line);
|
|
|
|
|
if (!ctrl->server_local->devicename)
|
|
|
|
|
{
|
|
|
|
|
err = gpg_error_from_syserror ();
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Check whether we have permissions to open the device and keep an
|
|
|
|
|
FD open. */
|
|
|
|
|
fp = es_fopen (ctrl->server_local->devicename, "rb");
|
|
|
|
|
if (!fp)
|
|
|
|
|
{
|
|
|
|
|
err = gpg_error_from_syserror ();
|
|
|
|
|
log_error ("error opening '%s': %s\n",
|
|
|
|
|
ctrl->server_local->devicename, gpg_strerror (err));
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
es_fclose (ctrl->server_local->devicefp);
|
|
|
|
|
ctrl->server_local->devicefp = fp;
|
|
|
|
|
fp = NULL;
|
|
|
|
|
ctrl->devti = ti;
|
|
|
|
|
|
2016-02-02 18:18:31 +01:00
|
|
|
|
/* Fixme: Take some kind of lock. */
|
|
|
|
|
|
2015-10-21 08:38:10 +02:00
|
|
|
|
leave:
|
|
|
|
|
es_fclose (fp);
|
|
|
|
|
if (err)
|
|
|
|
|
{
|
|
|
|
|
xfree (ctrl->server_local->devicename);
|
|
|
|
|
ctrl->server_local->devicename = NULL;
|
|
|
|
|
ctrl->devti = NULL;
|
|
|
|
|
}
|
|
|
|
|
return leave_cmd (ctx, err);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static const char hlp_create[] =
|
|
|
|
|
"CREATE <type>\n"
|
|
|
|
|
"\n"
|
|
|
|
|
"Create a new encrypted partition on the current device.\n"
|
|
|
|
|
"<type> must be \"dm-crypt\" for now.";
|
|
|
|
|
static gpg_error_t
|
|
|
|
|
cmd_create (assuan_context_t ctx, char *line)
|
|
|
|
|
{
|
|
|
|
|
ctrl_t ctrl = assuan_get_pointer (ctx);
|
|
|
|
|
gpg_error_t err = 0;
|
2016-02-13 17:01:45 +01:00
|
|
|
|
estream_t fp = NULL;
|
2015-10-21 08:38:10 +02:00
|
|
|
|
|
|
|
|
|
line = skip_options (line);
|
|
|
|
|
if (strcmp (line, "dm-crypt"))
|
|
|
|
|
{
|
|
|
|
|
err = set_error (GPG_ERR_INV_ARG, "Type must be \"dm-crypt\"");
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!ctrl->server_local->devicename
|
|
|
|
|
|| !ctrl->server_local->devicefp
|
|
|
|
|
|| !ctrl->devti)
|
|
|
|
|
{
|
|
|
|
|
err = set_error (GPG_ERR_ENOENT, "No device has been set");
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
err = sh_is_empty_partition (ctrl->server_local->devicename);
|
|
|
|
|
if (err)
|
|
|
|
|
{
|
2016-02-13 17:01:45 +01:00
|
|
|
|
if (gpg_err_code (err) == GPG_ERR_FALSE)
|
|
|
|
|
err = gpg_error (GPG_ERR_CONFLICT);
|
2016-02-02 18:18:31 +01:00
|
|
|
|
err = assuan_set_error (ctx, err, "Partition is not empty");
|
2015-10-21 08:38:10 +02:00
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
|
2016-02-13 17:01:45 +01:00
|
|
|
|
/* We need a writeable stream to create the container. */
|
|
|
|
|
fp = es_fopen (ctrl->server_local->devicename, "r+b");
|
|
|
|
|
if (!fp)
|
|
|
|
|
{
|
|
|
|
|
err = gpg_error_from_syserror ();
|
|
|
|
|
log_error ("error opening '%s': %s\n",
|
|
|
|
|
ctrl->server_local->devicename, gpg_strerror (err));
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
if (es_setvbuf (fp, NULL, _IONBF, 0))
|
|
|
|
|
{
|
|
|
|
|
err = gpg_error_from_syserror ();
|
|
|
|
|
log_error ("error setting '%s' to _IONBF: %s\n",
|
|
|
|
|
ctrl->server_local->devicename, gpg_strerror (err));
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
|
2015-10-21 08:38:10 +02:00
|
|
|
|
err = sh_dmcrypt_create_container (ctrl,
|
|
|
|
|
ctrl->server_local->devicename,
|
2016-02-13 17:01:45 +01:00
|
|
|
|
fp);
|
|
|
|
|
if (es_fclose (fp))
|
|
|
|
|
{
|
|
|
|
|
gpg_error_t err2 = gpg_error_from_syserror ();
|
|
|
|
|
log_error ("error closing '%s': %s\n",
|
|
|
|
|
ctrl->server_local->devicename, gpg_strerror (err2));
|
|
|
|
|
if (!err)
|
|
|
|
|
err = err2;
|
|
|
|
|
}
|
|
|
|
|
fp = NULL;
|
2015-10-21 08:38:10 +02:00
|
|
|
|
|
2016-02-13 17:01:45 +01:00
|
|
|
|
leave:
|
|
|
|
|
es_fclose (fp);
|
|
|
|
|
return leave_cmd (ctx, err);
|
|
|
|
|
}
|
2015-10-21 08:38:10 +02:00
|
|
|
|
|
|
|
|
|
|
2016-08-13 19:27:28 +02:00
|
|
|
|
static const char hlp_getkeyblob[] =
|
|
|
|
|
"GETKEYBLOB\n"
|
|
|
|
|
"\n"
|
|
|
|
|
"Return the encrypted keyblob of the current device.";
|
|
|
|
|
static gpg_error_t
|
|
|
|
|
cmd_getkeyblob (assuan_context_t ctx, char *line)
|
|
|
|
|
{
|
|
|
|
|
ctrl_t ctrl = assuan_get_pointer (ctx);
|
|
|
|
|
gpg_error_t err;
|
|
|
|
|
void *enckeyblob = NULL;
|
|
|
|
|
size_t enckeybloblen;
|
|
|
|
|
|
|
|
|
|
line = skip_options (line);
|
|
|
|
|
|
|
|
|
|
if (!ctrl->server_local->devicename
|
|
|
|
|
|| !ctrl->server_local->devicefp
|
|
|
|
|
|| !ctrl->devti)
|
|
|
|
|
{
|
|
|
|
|
err = set_error (GPG_ERR_ENOENT, "No device has been set");
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
err = sh_is_empty_partition (ctrl->server_local->devicename);
|
|
|
|
|
if (!err)
|
|
|
|
|
{
|
|
|
|
|
err = gpg_error (GPG_ERR_ENODEV);
|
|
|
|
|
assuan_set_error (ctx, err, "Partition is empty");
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
err = 0;
|
|
|
|
|
|
|
|
|
|
err = g13_keyblob_read (ctrl->server_local->devicename,
|
|
|
|
|
&enckeyblob, &enckeybloblen);
|
|
|
|
|
if (err)
|
|
|
|
|
goto leave;
|
|
|
|
|
|
|
|
|
|
err = assuan_send_data (ctx, enckeyblob, enckeybloblen);
|
|
|
|
|
if (!err)
|
|
|
|
|
err = assuan_send_data (ctx, NULL, 0); /* Flush */
|
|
|
|
|
|
|
|
|
|
leave:
|
|
|
|
|
xfree (enckeyblob);
|
|
|
|
|
return leave_cmd (ctx, err);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2016-02-13 17:01:45 +01:00
|
|
|
|
static const char hlp_mount[] =
|
|
|
|
|
"MOUNT <type>\n"
|
|
|
|
|
"\n"
|
|
|
|
|
"Mount an encrypted partition on the current device.\n"
|
|
|
|
|
"<type> must be \"dm-crypt\" for now.";
|
|
|
|
|
static gpg_error_t
|
|
|
|
|
cmd_mount (assuan_context_t ctx, char *line)
|
|
|
|
|
{
|
|
|
|
|
ctrl_t ctrl = assuan_get_pointer (ctx);
|
|
|
|
|
gpg_error_t err = 0;
|
|
|
|
|
unsigned char *keyblob = NULL;
|
|
|
|
|
size_t keybloblen;
|
|
|
|
|
tupledesc_t tuples = NULL;
|
|
|
|
|
|
|
|
|
|
line = skip_options (line);
|
|
|
|
|
|
|
|
|
|
if (strcmp (line, "dm-crypt"))
|
|
|
|
|
{
|
|
|
|
|
err = set_error (GPG_ERR_INV_ARG, "Type must be \"dm-crypt\"");
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!ctrl->server_local->devicename
|
|
|
|
|
|| !ctrl->server_local->devicefp
|
|
|
|
|
|| !ctrl->devti)
|
|
|
|
|
{
|
|
|
|
|
err = set_error (GPG_ERR_ENOENT, "No device has been set");
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
err = sh_is_empty_partition (ctrl->server_local->devicename);
|
|
|
|
|
if (!err)
|
|
|
|
|
{
|
|
|
|
|
err = gpg_error (GPG_ERR_ENODEV);
|
|
|
|
|
assuan_set_error (ctx, err, "Partition is empty");
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
err = 0;
|
|
|
|
|
|
|
|
|
|
/* We expect that the client already decrypted the keyblob.
|
|
|
|
|
* Eventually we should move reading of the keyblob to here and ask
|
|
|
|
|
* the client to decrypt it. */
|
|
|
|
|
assuan_begin_confidential (ctx);
|
|
|
|
|
err = assuan_inquire (ctx, "KEYBLOB",
|
|
|
|
|
&keyblob, &keybloblen, 4 * 1024);
|
|
|
|
|
assuan_end_confidential (ctx);
|
|
|
|
|
if (err)
|
|
|
|
|
{
|
|
|
|
|
log_error (_("assuan_inquire failed: %s\n"), gpg_strerror (err));
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
err = create_tupledesc (&tuples, keyblob, keybloblen);
|
|
|
|
|
if (!err)
|
|
|
|
|
keyblob = NULL;
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
if (gpg_err_code (err) == GPG_ERR_NOT_SUPPORTED)
|
|
|
|
|
log_error ("unknown keyblob version received\n");
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
err = sh_dmcrypt_mount_container (ctrl,
|
|
|
|
|
ctrl->server_local->devicename,
|
|
|
|
|
tuples);
|
2015-10-21 08:38:10 +02:00
|
|
|
|
|
|
|
|
|
leave:
|
2016-02-13 17:01:45 +01:00
|
|
|
|
destroy_tupledesc (tuples);
|
2015-10-21 08:38:10 +02:00
|
|
|
|
return leave_cmd (ctx, err);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2016-08-14 20:17:51 +02:00
|
|
|
|
static const char hlp_umount[] =
|
|
|
|
|
"UMOUNT <type>\n"
|
|
|
|
|
"\n"
|
|
|
|
|
"Unmount an encrypted partition and wipe the key.\n"
|
|
|
|
|
"<type> must be \"dm-crypt\" for now.";
|
|
|
|
|
static gpg_error_t
|
|
|
|
|
cmd_umount (assuan_context_t ctx, char *line)
|
|
|
|
|
{
|
|
|
|
|
ctrl_t ctrl = assuan_get_pointer (ctx);
|
|
|
|
|
gpg_error_t err = 0;
|
|
|
|
|
|
|
|
|
|
line = skip_options (line);
|
|
|
|
|
|
|
|
|
|
if (strcmp (line, "dm-crypt"))
|
|
|
|
|
{
|
|
|
|
|
err = set_error (GPG_ERR_INV_ARG, "Type must be \"dm-crypt\"");
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!ctrl->server_local->devicename
|
|
|
|
|
|| !ctrl->server_local->devicefp
|
|
|
|
|
|| !ctrl->devti)
|
|
|
|
|
{
|
|
|
|
|
err = set_error (GPG_ERR_ENOENT, "No device has been set");
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
err = sh_dmcrypt_umount_container (ctrl, ctrl->server_local->devicename);
|
|
|
|
|
|
|
|
|
|
leave:
|
|
|
|
|
return leave_cmd (ctx, err);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2016-02-23 14:32:46 +01:00
|
|
|
|
static const char hlp_suspend[] =
|
|
|
|
|
"SUSPEND <type>\n"
|
|
|
|
|
"\n"
|
|
|
|
|
"Suspend an encrypted partition and wipe the key.\n"
|
|
|
|
|
"<type> must be \"dm-crypt\" for now.";
|
|
|
|
|
static gpg_error_t
|
|
|
|
|
cmd_suspend (assuan_context_t ctx, char *line)
|
|
|
|
|
{
|
|
|
|
|
ctrl_t ctrl = assuan_get_pointer (ctx);
|
|
|
|
|
gpg_error_t err = 0;
|
|
|
|
|
|
|
|
|
|
line = skip_options (line);
|
|
|
|
|
|
|
|
|
|
if (strcmp (line, "dm-crypt"))
|
|
|
|
|
{
|
|
|
|
|
err = set_error (GPG_ERR_INV_ARG, "Type must be \"dm-crypt\"");
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!ctrl->server_local->devicename
|
|
|
|
|
|| !ctrl->server_local->devicefp
|
|
|
|
|
|| !ctrl->devti)
|
|
|
|
|
{
|
|
|
|
|
err = set_error (GPG_ERR_ENOENT, "No device has been set");
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
err = sh_is_empty_partition (ctrl->server_local->devicename);
|
|
|
|
|
if (!err)
|
|
|
|
|
{
|
|
|
|
|
err = gpg_error (GPG_ERR_ENODEV);
|
|
|
|
|
assuan_set_error (ctx, err, "Partition is empty");
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
err = 0;
|
|
|
|
|
|
|
|
|
|
err = sh_dmcrypt_suspend_container (ctrl, ctrl->server_local->devicename);
|
|
|
|
|
|
|
|
|
|
leave:
|
|
|
|
|
return leave_cmd (ctx, err);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static const char hlp_resume[] =
|
|
|
|
|
"RESUME <type>\n"
|
|
|
|
|
"\n"
|
|
|
|
|
"Resume an encrypted partition and set the key.\n"
|
|
|
|
|
"<type> must be \"dm-crypt\" for now.";
|
|
|
|
|
static gpg_error_t
|
|
|
|
|
cmd_resume (assuan_context_t ctx, char *line)
|
|
|
|
|
{
|
|
|
|
|
ctrl_t ctrl = assuan_get_pointer (ctx);
|
|
|
|
|
gpg_error_t err = 0;
|
|
|
|
|
unsigned char *keyblob = NULL;
|
|
|
|
|
size_t keybloblen;
|
|
|
|
|
tupledesc_t tuples = NULL;
|
|
|
|
|
|
|
|
|
|
line = skip_options (line);
|
|
|
|
|
|
|
|
|
|
if (strcmp (line, "dm-crypt"))
|
|
|
|
|
{
|
|
|
|
|
err = set_error (GPG_ERR_INV_ARG, "Type must be \"dm-crypt\"");
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!ctrl->server_local->devicename
|
|
|
|
|
|| !ctrl->server_local->devicefp
|
|
|
|
|
|| !ctrl->devti)
|
|
|
|
|
{
|
|
|
|
|
err = set_error (GPG_ERR_ENOENT, "No device has been set");
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
err = sh_is_empty_partition (ctrl->server_local->devicename);
|
|
|
|
|
if (!err)
|
|
|
|
|
{
|
|
|
|
|
err = gpg_error (GPG_ERR_ENODEV);
|
|
|
|
|
assuan_set_error (ctx, err, "Partition is empty");
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
err = 0;
|
|
|
|
|
|
|
|
|
|
/* We expect that the client already decrypted the keyblob.
|
|
|
|
|
* Eventually we should move reading of the keyblob to here and ask
|
|
|
|
|
* the client to decrypt it. */
|
|
|
|
|
assuan_begin_confidential (ctx);
|
|
|
|
|
err = assuan_inquire (ctx, "KEYBLOB",
|
|
|
|
|
&keyblob, &keybloblen, 4 * 1024);
|
|
|
|
|
assuan_end_confidential (ctx);
|
|
|
|
|
if (err)
|
|
|
|
|
{
|
|
|
|
|
log_error (_("assuan_inquire failed: %s\n"), gpg_strerror (err));
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
err = create_tupledesc (&tuples, keyblob, keybloblen);
|
|
|
|
|
if (!err)
|
|
|
|
|
keyblob = NULL;
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
if (gpg_err_code (err) == GPG_ERR_NOT_SUPPORTED)
|
|
|
|
|
log_error ("unknown keyblob version received\n");
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
err = sh_dmcrypt_resume_container (ctrl,
|
|
|
|
|
ctrl->server_local->devicename,
|
|
|
|
|
tuples);
|
|
|
|
|
|
|
|
|
|
leave:
|
|
|
|
|
destroy_tupledesc (tuples);
|
|
|
|
|
return leave_cmd (ctx, err);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2015-10-21 08:38:10 +02:00
|
|
|
|
static const char hlp_getinfo[] =
|
|
|
|
|
"GETINFO <what>\n"
|
|
|
|
|
"\n"
|
|
|
|
|
"Multipurpose function to return a variety of information.\n"
|
|
|
|
|
"Supported values for WHAT are:\n"
|
|
|
|
|
"\n"
|
|
|
|
|
" version - Return the version of the program.\n"
|
|
|
|
|
" pid - Return the process id of the server.\n"
|
|
|
|
|
" showtab - Show the table for the user.";
|
|
|
|
|
static gpg_error_t
|
|
|
|
|
cmd_getinfo (assuan_context_t ctx, char *line)
|
|
|
|
|
{
|
|
|
|
|
ctrl_t ctrl = assuan_get_pointer (ctx);
|
|
|
|
|
gpg_error_t err = 0;
|
|
|
|
|
char *buf;
|
|
|
|
|
|
|
|
|
|
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, "getsz", 5))
|
|
|
|
|
{
|
|
|
|
|
unsigned long long nblocks;
|
|
|
|
|
err = sh_blockdev_getsz (line+6, &nblocks);
|
|
|
|
|
if (!err)
|
|
|
|
|
log_debug ("getsz=%llu\n", nblocks);
|
|
|
|
|
}
|
|
|
|
|
else if (!strcmp (line, "showtab"))
|
|
|
|
|
{
|
|
|
|
|
tab_item_t ti;
|
|
|
|
|
|
|
|
|
|
for (ti=ctrl->client.tab; !err && ti; ti = ti->next)
|
|
|
|
|
{
|
|
|
|
|
buf = es_bsprintf ("%s %s%s %s %s%s\n",
|
|
|
|
|
ctrl->client.uname,
|
|
|
|
|
*ti->blockdev=='/'? "":"partuuid=",
|
|
|
|
|
ti->blockdev,
|
|
|
|
|
ti->label? ti->label : "-",
|
|
|
|
|
ti->mountpoint? " ":"",
|
|
|
|
|
ti->mountpoint? ti->mountpoint:"");
|
|
|
|
|
if (!buf)
|
|
|
|
|
err = gpg_error_from_syserror ();
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
err = assuan_send_data (ctx, buf, strlen (buf));
|
|
|
|
|
if (!err)
|
|
|
|
|
err = assuan_send_data (ctx, NULL, 0); /* Flush */
|
|
|
|
|
}
|
|
|
|
|
xfree (buf);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
err = set_error (GPG_ERR_ASS_PARAMETER, "unknown value for WHAT");
|
|
|
|
|
|
|
|
|
|
return leave_cmd (ctx, err);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* This command handler is used for all commands if this process has
|
|
|
|
|
not been started as expected. */
|
|
|
|
|
static gpg_error_t
|
|
|
|
|
fail_command (assuan_context_t ctx, char *line)
|
|
|
|
|
{
|
|
|
|
|
gpg_error_t err;
|
|
|
|
|
const char *name = assuan_get_command_name (ctx);
|
|
|
|
|
|
|
|
|
|
(void)line;
|
|
|
|
|
|
|
|
|
|
if (!name)
|
|
|
|
|
name = "?";
|
|
|
|
|
|
|
|
|
|
err = set_error_fail_cmd ();
|
|
|
|
|
log_error ("command '%s' failed: %s\n", name, gpg_strerror (err));
|
|
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Tell the Assuan library about our commands. */
|
|
|
|
|
static int
|
|
|
|
|
register_commands (assuan_context_t ctx, int fail_all)
|
|
|
|
|
{
|
|
|
|
|
static struct {
|
|
|
|
|
const char *name;
|
|
|
|
|
assuan_handler_t handler;
|
|
|
|
|
const char * const help;
|
|
|
|
|
} table[] = {
|
2016-08-13 12:49:54 +02:00
|
|
|
|
{ "FINDDEVICE", cmd_finddevice, hlp_finddevice },
|
2015-10-21 08:38:10 +02:00
|
|
|
|
{ "DEVICE", cmd_device, hlp_device },
|
|
|
|
|
{ "CREATE", cmd_create, hlp_create },
|
2016-08-13 19:27:28 +02:00
|
|
|
|
{ "GETKEYBLOB", cmd_getkeyblob, hlp_getkeyblob },
|
2016-02-13 17:01:45 +01:00
|
|
|
|
{ "MOUNT", cmd_mount, hlp_mount },
|
2016-08-14 20:17:51 +02:00
|
|
|
|
{ "UMOUNT", cmd_umount, hlp_umount },
|
2016-02-23 14:32:46 +01:00
|
|
|
|
{ "SUSPEND", cmd_suspend,hlp_suspend},
|
|
|
|
|
{ "RESUME", cmd_resume, hlp_resume },
|
2015-10-21 08:38:10 +02:00
|
|
|
|
{ "INPUT", NULL },
|
|
|
|
|
{ "OUTPUT", NULL },
|
|
|
|
|
{ "GETINFO", cmd_getinfo, hlp_getinfo },
|
|
|
|
|
{ NULL }
|
|
|
|
|
};
|
|
|
|
|
gpg_error_t err;
|
|
|
|
|
int i;
|
|
|
|
|
|
|
|
|
|
for (i=0; table[i].name; i++)
|
|
|
|
|
{
|
|
|
|
|
err = assuan_register_command (ctx, table[i].name,
|
|
|
|
|
fail_all ? fail_command : table[i].handler,
|
|
|
|
|
table[i].help);
|
|
|
|
|
if (err)
|
|
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Startup the server. */
|
|
|
|
|
gpg_error_t
|
|
|
|
|
syshelp_server (ctrl_t ctrl)
|
|
|
|
|
{
|
|
|
|
|
gpg_error_t err;
|
|
|
|
|
assuan_fd_t filedes[2];
|
|
|
|
|
assuan_context_t ctx = NULL;
|
|
|
|
|
|
|
|
|
|
/* 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 FILEDES in this case. */
|
|
|
|
|
filedes[0] = assuan_fdopen (0);
|
|
|
|
|
filedes[1] = assuan_fdopen (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, 0 /*FIXME:ctrl->fail_all_cmds*/);
|
|
|
|
|
if (err)
|
|
|
|
|
{
|
|
|
|
|
log_error ("failed to the register commands with Assuan: %s\n",
|
|
|
|
|
gpg_strerror (err));
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
assuan_set_pointer (ctx, ctrl);
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
char *tmp = xtryasprintf ("G13-syshelp %s ready to serve requests "
|
|
|
|
|
"from %lu(%s)",
|
|
|
|
|
PACKAGE_VERSION,
|
|
|
|
|
(unsigned long)ctrl->client.uid,
|
|
|
|
|
ctrl->client.uname);
|
|
|
|
|
if (tmp)
|
|
|
|
|
{
|
|
|
|
|
assuan_set_hello_line (ctx, tmp);
|
|
|
|
|
xfree (tmp);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
|
|
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:
|
|
|
|
|
reset_notify (ctx, NULL); /* Release all items hold by SERVER_LOCAL. */
|
|
|
|
|
if (ctrl->server_local)
|
|
|
|
|
{
|
|
|
|
|
xfree (ctrl->server_local);
|
|
|
|
|
ctrl->server_local = NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
assuan_release (ctx);
|
|
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
gpg_error_t
|
|
|
|
|
sh_encrypt_keyblob (ctrl_t ctrl, const void *keyblob, size_t keybloblen,
|
|
|
|
|
char **r_enckeyblob, size_t *r_enckeybloblen)
|
|
|
|
|
{
|
|
|
|
|
assuan_context_t ctx = ctrl->server_local->assuan_ctx;
|
|
|
|
|
gpg_error_t err;
|
|
|
|
|
unsigned char *enckeyblob;
|
|
|
|
|
size_t enckeybloblen;
|
|
|
|
|
|
|
|
|
|
*r_enckeyblob = NULL;
|
|
|
|
|
|
|
|
|
|
/* Send the plaintext. */
|
|
|
|
|
err = g13_status (ctrl, STATUS_PLAINTEXT_FOLLOWS, NULL);
|
|
|
|
|
if (err)
|
|
|
|
|
return err;
|
|
|
|
|
assuan_begin_confidential (ctx);
|
|
|
|
|
err = assuan_send_data (ctx, keyblob, keybloblen);
|
|
|
|
|
if (!err)
|
|
|
|
|
err = assuan_send_data (ctx, NULL, 0);
|
|
|
|
|
assuan_end_confidential (ctx);
|
|
|
|
|
if (!err)
|
|
|
|
|
err = assuan_write_line (ctx, "END");
|
|
|
|
|
if (err)
|
|
|
|
|
{
|
|
|
|
|
log_error (_("error sending data: %s\n"), gpg_strerror (err));
|
|
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Inquire the ciphertext. */
|
|
|
|
|
err = assuan_inquire (ctx, "ENCKEYBLOB",
|
|
|
|
|
&enckeyblob, &enckeybloblen, 16 * 1024);
|
|
|
|
|
if (err)
|
|
|
|
|
{
|
|
|
|
|
log_error (_("assuan_inquire failed: %s\n"), gpg_strerror (err));
|
|
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
*r_enckeyblob = enckeyblob;
|
|
|
|
|
*r_enckeybloblen = enckeybloblen;
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Send a status line with status ID NO. The arguments are a list of
|
|
|
|
|
strings terminated by a NULL argument. */
|
|
|
|
|
gpg_error_t
|
|
|
|
|
g13_status (ctrl_t ctrl, int no, ...)
|
|
|
|
|
{
|
2018-02-14 12:21:23 +01:00
|
|
|
|
gpg_error_t err;
|
2015-10-21 08:38:10 +02:00
|
|
|
|
va_list arg_ptr;
|
|
|
|
|
|
|
|
|
|
va_start (arg_ptr, no);
|
|
|
|
|
|
2018-02-14 12:21:23 +01:00
|
|
|
|
err = vprint_assuan_status_strings (ctrl->server_local->assuan_ctx,
|
|
|
|
|
get_status_string (no), arg_ptr);
|
2015-10-21 08:38:10 +02:00
|
|
|
|
va_end (arg_ptr);
|
|
|
|
|
return err;
|
|
|
|
|
}
|