aboutsummaryrefslogtreecommitdiffstats
path: root/agent/command.c
diff options
context:
space:
mode:
authorRepo Admin <[email protected]>2003-08-05 17:11:04 +0000
committerRepo Admin <[email protected]>2003-08-05 17:11:04 +0000
commit9ca4830a5b8392bc7bb01211407c876fd40b49d4 (patch)
tree5b5eb60b48cea2f86d01b8d9a458a2fa4fc0a2b2 /agent/command.c
parentminor changes to make make distcheck happy (diff)
downloadgnupg-9ca4830a5b8392bc7bb01211407c876fd40b49d4.tar.gz
gnupg-9ca4830a5b8392bc7bb01211407c876fd40b49d4.zip
This commit was manufactured by cvs2svn to create branch
'GNUPG-1-9-BRANCH'.
Diffstat (limited to 'agent/command.c')
-rw-r--r--agent/command.c782
1 files changed, 782 insertions, 0 deletions
diff --git a/agent/command.c b/agent/command.c
new file mode 100644
index 000000000..ed4ea6b02
--- /dev/null
+++ b/agent/command.c
@@ -0,0 +1,782 @@
+/* command.c - gpg-agent command handler
+ * Copyright (C) 2001, 2002, 2003 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 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+/* FIXME: we should not use the default assuan buffering but setup
+ some buffering in secure mempory to protect session keys etc. */
+
+#include <config.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <unistd.h>
+
+#include <assuan.h>
+
+#include "agent.h"
+
+/* maximum allowed size of the inquired ciphertext */
+#define MAXLEN_CIPHERTEXT 4096
+/* maximum allowed size of the key parameters */
+#define MAXLEN_KEYPARAM 1024
+
+#define set_error(e,t) assuan_set_error (ctx, ASSUAN_ ## e, (t))
+
+
+#if MAX_DIGEST_LEN < 20
+#error MAX_DIGEST_LEN shorter than keygrip
+#endif
+
+/* Data used to associate an Assuan context with local server data */
+struct server_local_s {
+ ASSUAN_CONTEXT assuan_ctx;
+ int message_fd;
+ int use_cache_for_signing;
+};
+
+
+
+
+
+static void
+reset_notify (ASSUAN_CONTEXT ctx)
+{
+ CTRL ctrl = assuan_get_pointer (ctx);
+
+ memset (ctrl->keygrip, 0, 20);
+ ctrl->have_keygrip = 0;
+ ctrl->digest.valuelen = 0;
+}
+
+
+/* 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);
+ return (s && (s == line || spacep (s-1)) && (!s[n] || spacep (s+n)));
+}
+
+/* Parse a hex string. Return an Assuan error code or 0 on success and the
+ length of the parsed string in LEN. */
+static int
+parse_hexstring (ASSUAN_CONTEXT ctx, const char *string, size_t *len)
+{
+ const char *p;
+ size_t n;
+
+ /* parse the hash value */
+ for (p=string, n=0; hexdigitp (p); p++, n++)
+ ;
+ if (*p)
+ return set_error (Parameter_Error, "invalid hexstring");
+ if ((n&1))
+ return set_error (Parameter_Error, "odd number of digits");
+ *len = n;
+ return 0;
+}
+
+/* Parse the keygrip in STRING into the provided buffer BUF. BUF must
+ provide space for 20 bytes. BUF is not changed if the fucntions
+ returns an error. */
+static int
+parse_keygrip (ASSUAN_CONTEXT ctx, const char *string, unsigned char *buf)
+{
+ int rc;
+ size_t n;
+ const unsigned char *p;
+
+ rc = parse_hexstring (ctx, string, &n);
+ if (rc)
+ return rc;
+ n /= 2;
+ if (n != 20)
+ return set_error (Parameter_Error, "invalid length of keygrip");
+
+ for (p=string, n=0; n < 20; p += 2, n++)
+ buf[n] = xtoi_2 (p);
+
+ return 0;
+}
+
+
+
+
+/* ISTRUSTED <hexstring_with_fingerprint>
+
+ Return OK when we have an entry with this fingerprint in our
+ trustlist */
+static int
+cmd_istrusted (ASSUAN_CONTEXT ctx, char *line)
+{
+ int rc, n, i;
+ char *p;
+ char fpr[41];
+
+ /* parse the fingerprint value */
+ for (p=line,n=0; hexdigitp (p); p++, n++)
+ ;
+ if (*p || !(n == 40 || n == 32))
+ return set_error (Parameter_Error, "invalid fingerprint");
+ i = 0;
+ if (n==32)
+ {
+ strcpy (fpr, "00000000");
+ i += 8;
+ }
+ for (p=line; i < 40; p++, i++)
+ fpr[i] = *p >= 'a'? (*p & 0xdf): *p;
+ fpr[i] = 0;
+ rc = agent_istrusted (fpr);
+ if (!rc)
+ return 0;
+ else if (rc == -1)
+ return ASSUAN_Not_Trusted;
+ else
+ {
+ log_error ("command is_trusted failed: %s\n", gpg_strerror (rc));
+ return map_to_assuan_status (rc);
+ }
+}
+
+/* LISTTRUSTED
+
+ List all entries from the trustlist */
+static int
+cmd_listtrusted (ASSUAN_CONTEXT ctx, char *line)
+{
+ int rc = agent_listtrusted (ctx);
+ if (rc)
+ log_error ("command listtrusted failed: %s\n", gpg_strerror (rc));
+ return map_to_assuan_status (rc);
+}
+
+
+/* MARKTRUSTED <hexstring_with_fingerprint> <flag> <display_name>
+
+ Store a new key in into the trustlist*/
+static int
+cmd_marktrusted (ASSUAN_CONTEXT ctx, char *line)
+{
+ CTRL ctrl = assuan_get_pointer (ctx);
+ int rc, n, i;
+ char *p;
+ char fpr[41];
+ int flag;
+
+ /* parse the fingerprint value */
+ for (p=line,n=0; hexdigitp (p); p++, n++)
+ ;
+ if (!spacep (p) || !(n == 40 || n == 32))
+ return set_error (Parameter_Error, "invalid fingerprint");
+ i = 0;
+ if (n==32)
+ {
+ strcpy (fpr, "00000000");
+ i += 8;
+ }
+ for (p=line; i < 40; p++, i++)
+ fpr[i] = *p >= 'a'? (*p & 0xdf): *p;
+ fpr[i] = 0;
+
+ while (spacep (p))
+ p++;
+ flag = *p++;
+ if ( (flag != 'S' && flag != 'P') || !spacep (p) )
+ return set_error (Parameter_Error, "invalid flag - must be P or S");
+ while (spacep (p))
+ p++;
+
+ rc = agent_marktrusted (ctrl, p, fpr, flag);
+ if (rc)
+ log_error ("command marktrusted failed: %s\n", gpg_strerror (rc));
+ return map_to_assuan_status (rc);
+}
+
+
+
+
+/* HAVEKEY <hexstring_with_keygrip>
+
+ Return success when the secret key is available */
+static int
+cmd_havekey (ASSUAN_CONTEXT ctx, char *line)
+{
+ int rc;
+ unsigned char buf[20];
+
+ rc = parse_keygrip (ctx, line, buf);
+ if (rc)
+ return rc;
+
+ if (agent_key_available (buf))
+ return ASSUAN_No_Secret_Key;
+
+ return 0;
+}
+
+
+/* SIGKEY <hexstring_with_keygrip>
+ SETKEY <hexstring_with_keygrip>
+
+ Set the key used for a sign or decrypt operation */
+static int
+cmd_sigkey (ASSUAN_CONTEXT ctx, char *line)
+{
+ int rc;
+ CTRL ctrl = assuan_get_pointer (ctx);
+
+ rc = parse_keygrip (ctx, line, ctrl->keygrip);
+ if (rc)
+ return rc;
+ ctrl->have_keygrip = 1;
+ return 0;
+}
+
+
+/* SETHASH <algonumber> <hexstring>
+
+ The client can use this command to tell the server about the data
+ (which usually is a hash) to be signed. */
+static int
+cmd_sethash (ASSUAN_CONTEXT ctx, char *line)
+{
+ int rc;
+ size_t n;
+ char *p;
+ CTRL ctrl = assuan_get_pointer (ctx);
+ unsigned char *buf;
+ char *endp;
+ int algo;
+
+ /* parse the algo number and check it */
+ algo = (int)strtoul (line, &endp, 10);
+ for (line = endp; *line == ' ' || *line == '\t'; line++)
+ ;
+ if (!algo || gcry_md_test_algo (algo))
+ return set_error (Unsupported_Algorithm, NULL);
+ ctrl->digest.algo = algo;
+
+ /* parse the hash value */
+ rc = parse_hexstring (ctx, line, &n);
+ if (rc)
+ return rc;
+ n /= 2;
+ if (n != 16 && n != 20 && n != 24 && n != 32)
+ return set_error (Parameter_Error, "unsupported length of hash");
+ if (n > MAX_DIGEST_LEN)
+ return set_error (Parameter_Error, "hash value to long");
+
+ buf = ctrl->digest.value;
+ ctrl->digest.valuelen = n;
+ for (p=line, n=0; n < ctrl->digest.valuelen; p += 2, n++)
+ buf[n] = xtoi_2 (p);
+ for (; n < ctrl->digest.valuelen; n++)
+ buf[n] = 0;
+ return 0;
+}
+
+
+/* PKSIGN <options>
+
+ Perform the actual sign operation. Neither input nor output are
+ sensitive to eavesdropping */
+static int
+cmd_pksign (ASSUAN_CONTEXT ctx, char *line)
+{
+ int rc;
+ int ignore_cache = 0;
+ CTRL ctrl = assuan_get_pointer (ctx);
+
+ if (opt.ignore_cache_for_signing)
+ ignore_cache = 1;
+ else if (!ctrl->server_local->use_cache_for_signing)
+ ignore_cache = 1;
+
+ rc = agent_pksign (ctrl, assuan_get_data_fp (ctx), ignore_cache);
+ if (rc)
+ log_error ("command pksign failed: %s\n", gpg_strerror (rc));
+ return map_to_assuan_status (rc);
+}
+
+/* PKDECRYPT <options>
+
+ Perform the actual decrypt operation. Input is not
+ sensitive to eavesdropping */
+static int
+cmd_pkdecrypt (ASSUAN_CONTEXT ctx, char *line)
+{
+ int rc;
+ CTRL ctrl = assuan_get_pointer (ctx);
+ char *value;
+ size_t valuelen;
+
+ /* First inquire the data to decrypt */
+ rc = assuan_inquire (ctx, "CIPHERTEXT",
+ &value, &valuelen, MAXLEN_CIPHERTEXT);
+ if (rc)
+ return rc;
+
+ rc = agent_pkdecrypt (ctrl, value, valuelen, assuan_get_data_fp (ctx));
+ xfree (value);
+ if (rc)
+ log_error ("command pkdecrypt failed: %s\n", gpg_strerror (rc));
+ return map_to_assuan_status (rc);
+}
+
+
+/* GENKEY
+
+ Generate a new key, store the secret part and return the public
+ part. Here is an example transaction:
+
+ C: GENKEY
+ S: INQUIRE KEYPARM
+ C: D (genkey (rsa (nbits 1024)))
+ C: END
+ S: D (public-key
+ S: D (rsa (n 326487324683264) (e 10001)))
+ S OK key created
+*/
+
+static int
+cmd_genkey (ASSUAN_CONTEXT ctx, char *line)
+{
+ CTRL ctrl = assuan_get_pointer (ctx);
+ int rc;
+ char *value;
+ size_t valuelen;
+
+ /* First inquire the parameters */
+ rc = assuan_inquire (ctx, "KEYPARAM", &value, &valuelen, MAXLEN_KEYPARAM);
+ if (rc)
+ return rc;
+
+ rc = agent_genkey (ctrl, value, valuelen, assuan_get_data_fp (ctx));
+ xfree (value);
+ if (rc)
+ log_error ("command genkey failed: %s\n", gpg_strerror (rc));
+ return map_to_assuan_status (rc);
+}
+
+
+static void
+plus_to_blank (char *s)
+{
+ for (; *s; s++)
+ {
+ if (*s == '+')
+ *s = ' ';
+ }
+}
+
+/* GET_PASSPHRASE <cache_id> [<error_message> <prompt> <description>]
+
+ This function is usually used to ask for a passphrase to be used
+ for conventional encryption, but may also be used by programs which
+ need specal handling of passphrases. This command uses a syntax
+ which helps clients to use the agent with minimum effort. The
+ agent either returns with an error or with a OK followed by the hex
+ encoded passphrase. Note that the length of the strings is
+ implicitly limited by the maximum length of a command.
+*/
+
+static int
+cmd_get_passphrase (ASSUAN_CONTEXT ctx, char *line)
+{
+ CTRL ctrl = assuan_get_pointer (ctx);
+ int rc;
+ const char *pw;
+ char *response;
+ char *cacheid = NULL, *desc = NULL, *prompt = NULL, *errtext = NULL;
+ char *p;
+ void *cache_marker;
+
+ /* parse the stuff */
+ for (p=line; *p == ' '; p++)
+ ;
+ cacheid = p;
+ p = strchr (cacheid, ' ');
+ if (p)
+ {
+ *p++ = 0;
+ while (*p == ' ')
+ p++;
+ errtext = p;
+ p = strchr (errtext, ' ');
+ if (p)
+ {
+ *p++ = 0;
+ while (*p == ' ')
+ p++;
+ prompt = p;
+ p = strchr (prompt, ' ');
+ if (p)
+ {
+ *p++ = 0;
+ while (*p == ' ')
+ p++;
+ desc = p;
+ p = strchr (desc, ' ');
+ if (p)
+ *p = 0; /* ignore garbage */
+ }
+ }
+ }
+ if (!cacheid || !*cacheid || strlen (cacheid) > 50)
+ return set_error (Parameter_Error, "invalid length of cacheID");
+ if (!desc)
+ return set_error (Parameter_Error, "no description given");
+
+ if (!strcmp (cacheid, "X"))
+ cacheid = NULL;
+ if (!strcmp (errtext, "X"))
+ errtext = NULL;
+ if (!strcmp (prompt, "X"))
+ prompt = NULL;
+ if (!strcmp (desc, "X"))
+ desc = NULL;
+
+ /* Note: we store the hexified versions in the cache. */
+ pw = cacheid ? agent_get_cache (cacheid, &cache_marker) : NULL;
+ if (pw)
+ {
+ assuan_begin_confidential (ctx);
+ rc = assuan_set_okay_line (ctx, pw);
+ agent_unlock_cache_entry (&cache_marker);
+ }
+ else
+ {
+ /* Note, that we only need to replace the + characters and
+ should leave the other escaping in place because the escaped
+ string is send verbatim to the pinentry which does the
+ unescaping (but not the + replacing) */
+ if (errtext)
+ plus_to_blank (errtext);
+ if (prompt)
+ plus_to_blank (prompt);
+ if (desc)
+ plus_to_blank (desc);
+
+ rc = agent_get_passphrase (ctrl, &response, desc, prompt, errtext);
+ if (!rc)
+ {
+ if (cacheid)
+ agent_put_cache (cacheid, response, 0);
+ assuan_begin_confidential (ctx);
+ rc = assuan_set_okay_line (ctx, response);
+ xfree (response);
+ }
+ }
+
+ if (rc)
+ log_error ("command get_passphrase failed: %s\n", gpg_strerror (rc));
+ return map_to_assuan_status (rc);
+}
+
+
+/* CLEAR_PASSPHRASE <cache_id>
+
+ may be used to invalidate the cache entry for a passphrase. The
+ function returns with OK even when there is no cached passphrase.
+*/
+
+static int
+cmd_clear_passphrase (ASSUAN_CONTEXT ctx, char *line)
+{
+ char *cacheid = NULL;
+ char *p;
+
+ /* parse the stuff */
+ for (p=line; *p == ' '; p++)
+ ;
+ cacheid = p;
+ p = strchr (cacheid, ' ');
+ if (p)
+ *p = 0; /* ignore garbage */
+ if (!cacheid || !*cacheid || strlen (cacheid) > 50)
+ return set_error (Parameter_Error, "invalid length of cacheID");
+
+ agent_put_cache (cacheid, NULL, 0);
+ return 0;
+}
+
+
+/* LEARN [--send]
+
+ Learn something about the currently inserted smartcard. With
+ --send the new certificates are send back. */
+static int
+cmd_learn (ASSUAN_CONTEXT ctx, char *line)
+{
+ int rc;
+
+ rc = agent_handle_learn (has_option (line, "--send")? ctx : NULL);
+ if (rc)
+ log_error ("command learn failed: %s\n", gpg_strerror (rc));
+ return map_to_assuan_status (rc);
+}
+
+
+
+/* PASSWD <hexstring_with_keygrip>
+
+ Change the passphrase/PID for the key identified by keygrip in LINE. */
+static int
+cmd_passwd (ASSUAN_CONTEXT ctx, char *line)
+{
+ CTRL ctrl = assuan_get_pointer (ctx);
+ int rc;
+ unsigned char grip[20];
+ gcry_sexp_t s_skey = NULL;
+ unsigned char *shadow_info = NULL;
+
+ rc = parse_keygrip (ctx, line, grip);
+ if (rc)
+ return rc; /* we can't jump to leave because this is already an
+ Assuan error code. */
+
+ s_skey = agent_key_from_file (ctrl, grip, &shadow_info, 1);
+ if (!s_skey && !shadow_info)
+ rc = gpg_error (GPG_ERR_NO_SECKEY);
+ else if (!s_skey)
+ {
+ log_error ("changing a smartcard PIN is not yet supported\n");
+ rc = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
+ }
+ else
+ rc = agent_protect_and_store (ctrl, s_skey);
+
+ gcry_sexp_release (s_skey);
+ xfree (shadow_info);
+ if (rc)
+ log_error ("command passwd failed: %s\n", gpg_strerror (rc));
+ return map_to_assuan_status (rc);
+}
+
+
+/* SCD <commands to pass to the scdaemon>
+
+ This is a general quote command to redirect everything to the
+ SCDAEMON. */
+static int
+cmd_scd (ASSUAN_CONTEXT ctx, char *line)
+{
+ CTRL ctrl = assuan_get_pointer (ctx);
+ int rc;
+
+ rc = divert_generic_cmd (ctrl, line, ctx);
+
+ return map_to_assuan_status (rc);
+}
+
+
+
+static int
+option_handler (ASSUAN_CONTEXT ctx, const char *key, const char *value)
+{
+ CTRL ctrl = assuan_get_pointer (ctx);
+
+ if (!strcmp (key, "display"))
+ {
+ if (ctrl->display)
+ free (ctrl->display);
+ ctrl->display = strdup (value);
+ if (!ctrl->display)
+ return ASSUAN_Out_Of_Core;
+ }
+ else if (!strcmp (key, "ttyname"))
+ {
+ if (!opt.keep_tty)
+ {
+ if (ctrl->ttyname)
+ free (ctrl->ttyname);
+ ctrl->ttyname = strdup (value);
+ if (!ctrl->ttyname)
+ return ASSUAN_Out_Of_Core;
+ }
+ }
+ else if (!strcmp (key, "ttytype"))
+ {
+ if (!opt.keep_tty)
+ {
+ if (ctrl->ttytype)
+ free (ctrl->ttytype);
+ ctrl->ttytype = strdup (value);
+ if (!ctrl->ttytype)
+ return ASSUAN_Out_Of_Core;
+ }
+ }
+ else if (!strcmp (key, "lc-ctype"))
+ {
+ if (ctrl->lc_ctype)
+ free (ctrl->lc_ctype);
+ ctrl->lc_ctype = strdup (value);
+ if (!ctrl->lc_ctype)
+ return ASSUAN_Out_Of_Core;
+ }
+ else if (!strcmp (key, "lc-messages"))
+ {
+ if (ctrl->lc_messages)
+ free (ctrl->lc_messages);
+ ctrl->lc_messages = strdup (value);
+ if (!ctrl->lc_messages)
+ return ASSUAN_Out_Of_Core;
+ }
+ else if (!strcmp (key, "use-cache-for-signing"))
+ ctrl->server_local->use_cache_for_signing = *value? atoi (value) : 0;
+ else
+ return ASSUAN_Invalid_Option;
+
+ return 0;
+}
+
+
+/* Tell the assuan library about our commands */
+static int
+register_commands (ASSUAN_CONTEXT ctx)
+{
+ static struct {
+ const char *name;
+ int (*handler)(ASSUAN_CONTEXT, char *line);
+ } table[] = {
+ { "ISTRUSTED", cmd_istrusted },
+ { "HAVEKEY", cmd_havekey },
+ { "SIGKEY", cmd_sigkey },
+ { "SETKEY", cmd_sigkey },
+ { "SETHASH", cmd_sethash },
+ { "PKSIGN", cmd_pksign },
+ { "PKDECRYPT", cmd_pkdecrypt },
+ { "GENKEY", cmd_genkey },
+ { "GET_PASSPHRASE", cmd_get_passphrase },
+ { "CLEAR_PASSPHRASE", cmd_clear_passphrase },
+ { "LISTTRUSTED", cmd_listtrusted },
+ { "MARKTRUSTED", cmd_marktrusted },
+ { "LEARN", cmd_learn },
+ { "PASSWD", cmd_passwd },
+ { "INPUT", NULL },
+ { "OUTPUT", NULL },
+ { "SCD", cmd_scd },
+ { NULL }
+ };
+ int i, rc;
+
+ for (i=0; table[i].name; i++)
+ {
+ rc = assuan_register_command (ctx, table[i].name, table[i].handler);
+ if (rc)
+ return rc;
+ }
+ assuan_register_reset_notify (ctx, reset_notify);
+ assuan_register_option_handler (ctx, option_handler);
+ return 0;
+}
+
+
+/* Startup the server. If LISTEN_FD and FD is given as -1, this is a simple
+ piper server, otherwise it is a regular server */
+void
+start_command_handler (int listen_fd, int fd)
+{
+ int rc;
+ ASSUAN_CONTEXT ctx;
+ struct server_control_s ctrl;
+
+ memset (&ctrl, 0, sizeof ctrl);
+ agent_init_default_ctrl (&ctrl);
+
+ if (listen_fd == -1 && fd == -1)
+ {
+ int filedes[2];
+
+ filedes[0] = 0;
+ filedes[1] = 1;
+ rc = assuan_init_pipe_server (&ctx, filedes);
+ }
+ else if (listen_fd != -1)
+ {
+ rc = assuan_init_socket_server (&ctx, listen_fd);
+ }
+ else
+ {
+ rc = assuan_init_connected_socket_server (&ctx, fd);
+ }
+ if (rc)
+ {
+ log_error ("failed to initialize the server: %s\n",
+ assuan_strerror(rc));
+ agent_exit (2);
+ }
+ rc = register_commands (ctx);
+ if (rc)
+ {
+ log_error ("failed to register commands with Assuan: %s\n",
+ assuan_strerror(rc));
+ agent_exit (2);
+ }
+
+ assuan_set_pointer (ctx, &ctrl);
+ ctrl.server_local = xcalloc (1, sizeof *ctrl.server_local);
+ ctrl.server_local->assuan_ctx = ctx;
+ ctrl.server_local->message_fd = -1;
+ ctrl.server_local->use_cache_for_signing = 1;
+
+ if (DBG_ASSUAN)
+ assuan_set_log_stream (ctx, log_get_stream ());
+
+ for (;;)
+ {
+ rc = assuan_accept (ctx);
+ if (rc == -1)
+ {
+ break;
+ }
+ else if (rc)
+ {
+ log_info ("Assuan accept problem: %s\n", assuan_strerror (rc));
+ break;
+ }
+
+ rc = assuan_process (ctx);
+ if (rc)
+ {
+ log_info ("Assuan processing failed: %s\n", assuan_strerror (rc));
+ continue;
+ }
+ }
+
+
+ assuan_deinit_server (ctx);
+ if (ctrl.display)
+ free (ctrl.display);
+ if (ctrl.ttyname)
+ free (ctrl.ttyname);
+ if (ctrl.ttytype)
+ free (ctrl.ttytype);
+ if (ctrl.lc_ctype)
+ free (ctrl.lc_ctype);
+ if (ctrl.lc_messages)
+ free (ctrl.lc_messages);
+}
+