diff options
Diffstat (limited to 'src/gpgme-tool.c')
-rw-r--r-- | src/gpgme-tool.c | 2012 |
1 files changed, 2012 insertions, 0 deletions
diff --git a/src/gpgme-tool.c b/src/gpgme-tool.c new file mode 100644 index 00000000..08cd82a9 --- /dev/null +++ b/src/gpgme-tool.c @@ -0,0 +1,2012 @@ +/* gpgme-tool.c - GnuPG Made Easy. + Copyright (C) 2000 Werner Koch (dd9jn) + Copyright (C) 2001, 2002, 2003, 2004, 2005, 2007 g10 Code GmbH + + This file is part of GPGME. + + GPGME is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of + the License, or (at your option) any later version. + + GPGME 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser 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. */ + +#if HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <getopt.h> +#include <ctype.h> +#include <stdarg.h> +#include <locale.h> +#ifdef HAVE_ARGP_H +#include <argp.h> +#endif + +#include "gpgme.h" + + +#ifndef HAVE_ARGP_H +/* Minimal argp implementation. */ + +/* Differences to ARGP: + argp_program_version: Required. + argp_program_bug_address: Required. + argp_program_version_hook: Not supported. + argp_err_exit_status: Required. + struct argp: Children and help_filter not supported. + argp_domain: Not supported. + struct argp_option: Group not supported. Options are printed in + order given. Flags OPTION_ALIAS, OPTION_DOC and OPTION_NO_USAGE + are not supported. + argp_parse: No flags are supported (ARGP_PARSE_ARGV0, ARGP_NO_ERRS, + ARGP_NO_ARGS, ARGP_IN_ORDER, ARGP_NO_HELP, ARGP_NO_EXIT, + ARGP_LONG_ONLY, ARGP_SILENT). ARGP must not be NULL. + argp_help: Flag ARGP_HELP_LONG_ONLY not supported. + argp_state: argc, argv, next may not be modified and should not be used. */ + +extern const char *argp_program_version; +extern const char *argp_program_bug_address; +extern error_t argp_err_exit_status; + +struct argp_option +{ + const char *name; + int key; + const char *arg; +#define OPTION_ARG_OPTIONAL 0x1 +#define OPTION_HIDDEN 0x2 + int flags; + const char *doc; + int group; +}; + +struct argp; +struct argp_state +{ + const struct argp *const root_argp; + int argc; + char **argv; + int next; + unsigned flags; + unsigned arg_num; + int quoted; + void *input; + void **child_inputs; + void *hook; + char *name; + FILE *err_stream; + FILE *out_stream; + void *pstate; +}; + +#define ARGP_ERR_UNKNOWN E2BIG +#define ARGP_KEY_ARG 0 +#define ARGP_KEY_ARGS 0x1000006 +#define ARGP_KEY_END 0x1000001 +#define ARGP_KEY_NO_ARGS 0x1000002 +#define ARGP_KEY_INIT 0x1000003 +#define ARGP_KEY_FINI 0x1000007 +#define ARGP_KEY_SUCCESS 0x1000004 +#define ARGP_KEY_ERROR 0x1000005 +typedef error_t (*argp_parser_t) (int key, char *arg, struct argp_state *state); + +struct argp +{ + const struct argp_option *options; + argp_parser_t parser; + const char *args_doc; + const char *doc; + + const struct argp_child *children; + char *(*help_filter) (int key, const char *text, void *input); + const char *argp_domain; +}; + +#define ARGP_HELP_USAGE ARGP_HELP_SHORT_USAGE +#define ARGP_HELP_SHORT_USAGE 0x02 +#define ARGP_HELP_SEE 0x04 +#define ARGP_HELP_LONG 0x08 +#define ARGP_HELP_PRE_DOC 0x10 +#define ARGP_HELP_POST_DOC 0x20 +#define ARGP_HELP_DOC (ARGP_HELP_PRE_DOC | ARGP_HELP_POST_DOC) +#define ARGP_HELP_BUG_ADDR 0x40 +#define ARGP_HELP_EXIT_ERR 0x100 +#define ARGP_HELP_EXIT_OK 0x200 +#define ARGP_HELP_STD_ERR (ARGP_HELP_SEE | ARGP_HELP_EXIT_ERR) +#define ARGP_HELP_STD_USAGE \ + (ARGP_HELP_SHORT_USAGE | ARGP_HELP_SEE | ARGP_HELP_EXIT_ERR) +#define ARGP_HELP_STD_HELP \ + (ARGP_HELP_SHORT_USAGE | ARGP_HELP_LONG | ARGP_HELP_EXIT_OK \ + | ARGP_HELP_DOC | ARGP_HELP_BUG_ADDR) + + +char * +_argp_pname (char *name) +{ + char *pname = name; + char *bname = strrchr (pname, '/'); + if (! bname) + bname = strrchr (pname, '\\'); + if (bname) + pname = bname + 1; + return pname; +} + + +void +_argp_state_help (const struct argp *argp, const struct argp_state *state, + FILE *stream, unsigned flags, char *name) +{ + if (state) + name = state->name; + + if (flags & ARGP_HELP_SHORT_USAGE) + fprintf (stream, "Usage: %s [OPTIONS...] %s\n", name, argp->args_doc); + if (flags & ARGP_HELP_SEE) + fprintf (stream, "Try `%s --help' or `%s --usage' for more information.\n", + name, name); + if (flags & ARGP_HELP_PRE_DOC) + { + char buf[1024]; + char *end; + strncpy (buf, argp->doc, sizeof (buf)); + buf[sizeof (buf) - 1] = '\0'; + end = strchr (buf, '\v'); + if (end) + *end = '\0'; + fprintf (stream, "%s\n%s", buf, buf[0] ? "\n" : ""); + } + if (flags & ARGP_HELP_LONG) + { + const struct argp_option *opt = argp->options; + while (opt->key) + { + #define NSPACES 29 + char spaces[NSPACES + 1] = " "; + int len = 0; + fprintf (stream, " "); + len += 2; + if (isascii (opt->key)) + { + fprintf (stream, "-%c", opt->key); + len += 2; + if (opt->name) + { + fprintf (stream, ", "); + len += 2; + } + } + if (opt->name) + { + fprintf (stream, "--%s", opt->name); + len += 2 + strlen (opt->name); + } + if (opt->arg && (opt->flags & OPTION_ARG_OPTIONAL)) + { + fprintf (stream, "[=%s]", opt->arg); + len += 3 + strlen (opt->arg); + } + else if (opt->arg) + { + fprintf (stream, "=%s", opt->arg); + len += 1 + strlen (opt->arg); + } + if (len >= NSPACES) + len = NSPACES - 1; + spaces[NSPACES - len] = '\0'; + fprintf (stream, "%s%s\n", spaces, opt->doc); + opt++; + } + fprintf (stream, " -?, --help Give this help list\n"); + fprintf (stream, " --usage Give a short usage " + "message\n"); + } + if (flags & ARGP_HELP_POST_DOC) + { + char buf[1024]; + char *end; + strncpy (buf, argp->doc, sizeof (buf)); + buf[sizeof (buf) - 1] = '\0'; + end = strchr (buf, '\v'); + if (end) + { + end++; + if (*end) + fprintf (stream, "\n%s\n", end); + } + fprintf (stream, "\nMandatory or optional arguments to long options are also mandatory or optional\n"); + fprintf (stream, "for any corresponding short options.\n"); + } + if (flags & ARGP_HELP_BUG_ADDR) + fprintf (stream, "\nReport bugs to %s.\n", argp_program_bug_address); + + if (flags & ARGP_HELP_EXIT_ERR) + exit (argp_err_exit_status); + if (flags & ARGP_HELP_EXIT_OK) + exit (0); +} + + +void +argp_usage (const struct argp_state *state) +{ + _argp_state_help (state->root_argp, state, state->err_stream, + ARGP_HELP_STD_USAGE, state->name); +} + + +void +argp_state_help (const struct argp_state *state, FILE *stream, unsigned flags) +{ + _argp_state_help (state->root_argp, state, stream, flags, state->name); +} + + +void +argp_error (const struct argp_state *state, const char *fmt, ...) +{ + va_list ap; + + fprintf (state->err_stream, "%s: ", state->name); + va_start (ap, fmt); + vfprintf (state->err_stream, fmt, ap); + va_end (ap); + fprintf (state->err_stream, "\n"); + argp_state_help (state, state->err_stream, ARGP_HELP_STD_ERR); + exit (argp_err_exit_status); +} + + +void +argp_help (const struct argp *argp, FILE *stream, unsigned flags, char *name) +{ + _argp_state_help (argp, NULL, stream, flags, name); +} + + +error_t +argp_parse (const struct argp *argp, int argc, + char **argv, unsigned flags, int *arg_index, void *input) +{ + int rc = 0; + struct argp_state state = { argp, argc, argv, 1, flags, 0, 0, input, + NULL, NULL, _argp_pname (argv[0]), + stderr, stdout, NULL }; + /* All non-option arguments are collected at the beginning of + &argv[1] during processing. This is a counter for their number. */ + int non_opt_args = 0; + + rc = argp->parser (ARGP_KEY_INIT, NULL, &state); + if (rc && rc != ARGP_ERR_UNKNOWN) + goto argperror; + + while (state.next < state.argc - non_opt_args) + { + int idx = state.next; + state.next++; + + if (! strcasecmp (state.argv[idx], "--")) + { + state.quoted = idx; + continue; + } + + if (state.quoted || state.argv[idx][0] != '-') + { + char *arg_saved = state.argv[idx]; + non_opt_args++; + memmove (&state.argv[idx], &state.argv[idx + 1], + (state.argc - 1 - idx) * sizeof (char *)); + state.argv[argc - 1] = arg_saved; + state.next--; + } + else if (! strcasecmp (state.argv[idx], "--help") + || !strcmp (state.argv[idx], "-?")) + { + argp_state_help (&state, state.out_stream, ARGP_HELP_STD_HELP); + } + else if (! strcasecmp (state.argv[idx], "--usage")) + { + argp_state_help (&state, state.out_stream, + ARGP_HELP_USAGE | ARGP_HELP_EXIT_OK); + } + else if (! strcasecmp (state.argv[idx], "--version") + || !strcmp (state.argv[idx], "-V")) + { + fprintf (state.out_stream, "%s\n", argp_program_version); + exit (0); + } + else + { + /* Search for option and call parser with its KEY. */ + int key = ARGP_KEY_ARG; /* Just some dummy value. */ + const struct argp_option *opt = argp->options; + char *arg = NULL; + int found = 0; + + /* Check for --opt=value syntax. */ + arg = strchr (state.argv[idx], '='); + if (arg) + { + *arg = '\0'; + arg++; + } + + if (state.argv[idx][1] != '-') + key = state.argv[idx][1]; + + while (! found && opt->key) + { + if (key == opt->key + || (key == ARGP_KEY_ARG + && ! strcasecmp (&state.argv[idx][2], opt->name))) + { + if (arg && !opt->arg) + argp_error (&state, "Option %s does not take an argument", + state.argv[idx]); + if (opt->arg && state.next < state.argc + && state.argv[idx + 1][0] != '-') + { + arg = state.argv[idx + 1]; + state.next++; + } + if (opt->arg && !(opt->flags & OPTION_ARG_OPTIONAL)) + argp_error (&state, "Option %s requires an argument", + state.argv[idx]); + + rc = argp->parser (opt->key, arg, &state); + if (rc == ARGP_ERR_UNKNOWN) + break; + else if (rc) + goto argperror; + found = 1; + } + opt++; + } + if (! found) + argp_error (&state, "Unknown option %s", state.argv[idx]); + } + } + + while (state.next < state.argc) + { + /* Call parser for all non-option args. */ + int idx = state.next; + state.next++; + rc = argp->parser (ARGP_KEY_ARG, state.argv[idx], &state); + if (rc && rc != ARGP_ERR_UNKNOWN) + goto argperror; + if (rc == ARGP_ERR_UNKNOWN) + { + int old_next = state.next; + rc = argp->parser (ARGP_KEY_ARGS, NULL, &state); + if (rc == ARGP_ERR_UNKNOWN) + { + argp_error (&state, "Too many arguments", state.argv[idx]); + goto argperror; + } + if (! rc && state.next == old_next) + { + state.arg_num += state.argc - state.next; + state.next = state.argc; + } + } + else + state.arg_num++; + } + + if (state.arg_num == 0) + { + rc = argp->parser (ARGP_KEY_NO_ARGS, NULL, &state); + if (rc && rc != ARGP_ERR_UNKNOWN) + goto argperror; + } + if (state.next == state.argc) + { + rc = argp->parser (ARGP_KEY_END, NULL, &state); + if (rc && rc != ARGP_ERR_UNKNOWN) + goto argperror; + } + rc = argp->parser (ARGP_KEY_FINI, NULL, &state); + if (rc && rc != ARGP_ERR_UNKNOWN) + goto argperror; + + rc = 0; + argp->parser (ARGP_KEY_SUCCESS, NULL, &state); + + argperror: + if (rc) + { + argp_error (&state, "unexpected error: %s", strerror (rc)); + argp->parser (ARGP_KEY_ERROR, NULL, &state); + } + + argp->parser (ARGP_KEY_FINI, NULL, &state); + + if (arg_index) + *arg_index = state.next - 1; + + return 0; +} +#endif + + +/* SUPPORT. */ +FILE *log_stream; +char *program_name = "gpgme-tool"; + +void +log_init (void) +{ + log_stream = stderr; +} + + +void +log_error (int status, gpg_error_t errnum, const char *fmt, ...) +{ + va_list ap; + + fprintf (log_stream, "%s: ", program_name); + va_start (ap, fmt); + vfprintf (log_stream, fmt, ap); + va_end (ap); + if (errnum) + fprintf (log_stream, ": %s <%s>", gpg_strerror (errnum), + gpg_strsource (errnum)); + fprintf (log_stream, "\n"); + if (status) + exit (status); +} + + + +typedef enum status + { + STATUS_PROTOCOL, + STATUS_PROGRESS, + STATUS_ENGINE, + STATUS_ARMOR, + STATUS_TEXTMODE, + STATUS_INCLUDE_CERTS, + STATUS_KEYLIST_MODE, + STATUS_ENCRYPT_RESULT + } status_t; + +const char *status_string[] = + { + "PROTOCOL", + "PROGRESS", + "ENGINE", + "ARMOR", + "TEXTMODE", + "INCLUDE_CERTS", + "KEYLIST_MODE", + "ENCRYPT_RESULT" + }; + +struct gpgme_tool +{ + gpgme_ctx_t ctx; +#define MAX_RECIPIENTS 10 + gpgme_key_t recipients[MAX_RECIPIENTS + 1]; + int recipients_nr; + + gpg_error_t (*write_status) (void *hook, const char *status, const char *msg); + void *write_status_hook; +}; +typedef struct gpgme_tool *gpgme_tool_t; + + +/* Forward declaration. */ +void gt_write_status (gpgme_tool_t gt, status_t status, ...); + +void +_gt_progress_cb (void *opaque, const char *what, + int type, int current, int total) +{ + gpgme_tool_t gt = opaque; + char buf[100]; + + snprintf (buf, sizeof (buf), "0x%02x %i %i", type, current, total); + gt_write_status (gt, STATUS_PROGRESS, what, buf); +} + + +gpg_error_t +_gt_gpgme_new (gpgme_tool_t gt, gpgme_ctx_t *ctx) +{ + gpg_error_t err; + + err = gpgme_new (ctx); + if (err) + return err; + gpgme_set_progress_cb (*ctx, _gt_progress_cb, gt); + return 0; +} + + +void +gt_init (gpgme_tool_t gt) +{ + memset (gt, '\0', sizeof (*gt)); + gpg_error_t err; + + err = _gt_gpgme_new (gt, >->ctx); + if (err) + log_error (1, err, "can't create gpgme context"); +} + + +gpg_error_t +gt_signers_add (gpgme_tool_t gt, const char *fpr) +{ + gpg_error_t err; + gpgme_key_t key; + + err = gpgme_get_key (gt->ctx, fpr, &key, 0); + if (err) + return err; + + return gpgme_signers_add (gt->ctx, key); +} + + +gpg_error_t +gt_signers_clear (gpgme_tool_t gt) +{ + gpgme_signers_clear (gt->ctx); + return 0; +} + + +gpg_error_t +gt_recipients_add (gpgme_tool_t gt, const char *fpr) +{ + gpg_error_t err; + gpgme_key_t key; + + if (gt->recipients_nr >= MAX_RECIPIENTS) + return gpg_error_from_errno (ENOMEM); + + err = gpgme_get_key (gt->ctx, fpr, &key, 0); + if (err) + return err; + + gt->recipients[gt->recipients_nr++] = key; + return 0; +} + + +void +gt_recipients_clear (gpgme_tool_t gt) +{ + int idx; + + for (idx = 0; idx < gt->recipients_nr; idx++) + gpgme_key_unref (gt->recipients[idx]); + memset (gt->recipients, '\0', gt->recipients_nr * sizeof (gpgme_key_t)); + gt->recipients_nr = 0; +} + + +gpg_error_t +gt_reset (gpgme_tool_t gt) +{ + gpg_error_t err; + gpgme_ctx_t ctx; + + err = _gt_gpgme_new (gt, &ctx); + if (err) + return err; + + gpgme_release (gt->ctx); + gt->ctx = ctx; + gt_recipients_clear (gt); + return 0; +} + + +void +gt_write_status (gpgme_tool_t gt, status_t status, ...) +{ + va_list ap; + const char *text; + char buf[950]; + char *p; + size_t n; + gpg_error_t err; + + va_start (ap, status); + p = buf; + n = 0; + while ((text = va_arg (ap, const char *))) + { + if (n) + { + *p++ = ' '; + n++; + } + while (*text && n < sizeof (buf) - 2) + { + *p++ = *text++; + n++; + } + } + *p = 0; + va_end (ap); + + err = gt->write_status (gt->write_status_hook, status_string[status], buf); + if (err) + log_error (1, err, "can't write status line"); +} + + +gpg_error_t +gt_get_engine_info (gpgme_tool_t gt, gpgme_protocol_t proto) +{ + gpgme_engine_info_t info; + info = gpgme_ctx_get_engine_info (gt->ctx); + while (info) + { + if (proto == GPGME_PROTOCOL_UNKNOWN || proto == info->protocol) + gt_write_status (gt, STATUS_ENGINE, + gpgme_get_protocol_name (info->protocol), + info->file_name, info->version, + info->req_version, info->home_dir); + info = info->next; + } + return 0; +} + + +gpgme_protocol_t +gt_protocol_from_name (const char *name) +{ + if (! strcasecmp (name, gpgme_get_protocol_name (GPGME_PROTOCOL_OpenPGP))) + return GPGME_PROTOCOL_OpenPGP; + if (! strcasecmp (name, gpgme_get_protocol_name (GPGME_PROTOCOL_CMS))) + return GPGME_PROTOCOL_CMS; + if (! strcasecmp (name,gpgme_get_protocol_name (GPGME_PROTOCOL_GPGCONF))) + return GPGME_PROTOCOL_GPGCONF; + if (! strcasecmp (name, gpgme_get_protocol_name (GPGME_PROTOCOL_ASSUAN))) + return GPGME_PROTOCOL_ASSUAN; + if (! strcasecmp (name, gpgme_get_protocol_name (GPGME_PROTOCOL_G13))) + return GPGME_PROTOCOL_G13; + return GPGME_PROTOCOL_UNKNOWN; +} + + +gpg_error_t +gt_set_protocol (gpgme_tool_t gt, gpgme_protocol_t proto) +{ + return gpgme_set_protocol (gt->ctx, proto); +} + + +gpg_error_t +gt_get_protocol (gpgme_tool_t gt) +{ + gpgme_protocol_t proto = gpgme_get_protocol (gt->ctx); + + gt_write_status (gt, STATUS_PROTOCOL, gpgme_get_protocol_name (proto), + NULL); + + return 0; +} + + +gpg_error_t +gt_set_armor (gpgme_tool_t gt, int armor) +{ + gpgme_set_armor (gt->ctx, armor); + return 0; +} + + +gpg_error_t +gt_get_armor (gpgme_tool_t gt) +{ + gt_write_status (gt, STATUS_ARMOR, + gpgme_get_armor (gt->ctx) ? "true" : "false", NULL); + + return 0; +} + + +gpg_error_t +gt_set_textmode (gpgme_tool_t gt, int textmode) +{ + gpgme_set_textmode (gt->ctx, textmode); + return 0; +} + + +gpg_error_t +gt_get_textmode (gpgme_tool_t gt) +{ + gt_write_status (gt, STATUS_TEXTMODE, + gpgme_get_textmode (gt->ctx) ? "true" : "false", NULL); + + return 0; +} + + +gpg_error_t +gt_set_keylist_mode (gpgme_tool_t gt, gpgme_keylist_mode_t keylist_mode) +{ + gpgme_set_keylist_mode (gt->ctx, keylist_mode); + return 0; +} + + +gpg_error_t +gt_get_keylist_mode (gpgme_tool_t gt) +{ +#define NR_KEYLIST_MODES 6 + const char *modes[NR_KEYLIST_MODES + 1]; + int idx = 0; + gpgme_keylist_mode_t mode = gpgme_get_keylist_mode (gt->ctx); + + if (mode & GPGME_KEYLIST_MODE_LOCAL) + modes[idx++] = "local"; + if (mode & GPGME_KEYLIST_MODE_EXTERN) + modes[idx++] = "extern"; + if (mode & GPGME_KEYLIST_MODE_SIGS) + modes[idx++] = "sigs"; + if (mode & GPGME_KEYLIST_MODE_SIG_NOTATIONS) + modes[idx++] = "sig_notations"; + if (mode & GPGME_KEYLIST_MODE_EPHEMERAL) + modes[idx++] = "ephemeral"; + if (mode & GPGME_KEYLIST_MODE_VALIDATE) + modes[idx++] = "validate"; + modes[idx++] = NULL; + + gt_write_status (gt, STATUS_KEYLIST_MODE, modes[0], modes[1], modes[2], + modes[3], modes[4], modes[5], modes[6]); + + return 0; +} + + +gpg_error_t +gt_set_include_certs (gpgme_tool_t gt, int include_certs) +{ + gpgme_set_include_certs (gt->ctx, include_certs); + return 0; +} + + +gpg_error_t +gt_get_include_certs (gpgme_tool_t gt) +{ + int include_certs = gpgme_get_include_certs (gt->ctx); + char buf[100]; + + if (include_certs == GPGME_INCLUDE_CERTS_DEFAULT) + strcpy (buf, "default"); + else + snprintf (buf, sizeof (buf), "%i", include_certs); + + gt_write_status (gt, STATUS_INCLUDE_CERTS, buf, NULL); + + return 0; +} + + +gpg_error_t +gt_decrypt_verify (gpgme_tool_t gt, gpgme_data_t cipher, gpgme_data_t plain, + int verify) +{ + if (verify) + return gpgme_op_decrypt_verify (gt->ctx, cipher, plain); + else + return gpgme_op_decrypt (gt->ctx, cipher, plain); +} + + +gpg_error_t +gt_sign_encrypt (gpgme_tool_t gt, gpgme_encrypt_flags_t flags, + gpgme_data_t plain, gpgme_data_t cipher, int sign) +{ + gpg_error_t err; + + if (sign) + err = gpgme_op_encrypt (gt->ctx, gt->recipients, flags, plain, cipher); + else + err = gpgme_op_encrypt_sign (gt->ctx, gt->recipients, flags, plain, cipher); + + gt_recipients_clear (gt); + + return err; +} + + +gpg_error_t +gt_sign (gpgme_tool_t gt, gpgme_data_t plain, gpgme_data_t sig, + gpgme_sig_mode_t mode) +{ + return gpgme_op_sign (gt->ctx, plain, sig, mode); +} + + +gpg_error_t +gt_verify (gpgme_tool_t gt, gpgme_data_t sig, gpgme_data_t sig_text, + gpgme_data_t plain) +{ + return gpgme_op_verify (gt->ctx, sig, sig_text, plain); +} + + +gpg_error_t +gt_import (gpgme_tool_t gt, gpgme_data_t data) +{ + return gpgme_op_import (gt->ctx, data); +} + + +gpg_error_t +gt_export (gpgme_tool_t gt, const char *pattern[], gpgme_export_mode_t mode, + gpgme_data_t data) +{ + return gpgme_op_export_ext (gt->ctx, pattern, mode, data); +} + + +gpg_error_t +gt_genkey (gpgme_tool_t gt, const char *parms, gpgme_data_t public, + gpgme_data_t secret) +{ + return gpgme_op_genkey (gt->ctx, parms, public, secret); +} + + +gpg_error_t +gt_import_keys (gpgme_tool_t gt, char *fpr[]) +{ + gpg_error_t err; + int cnt; + int idx; + gpgme_key_t *keys; + + cnt = 0; + while (fpr[cnt]) + cnt++; + + if (! cnt) + return gpg_error (GPG_ERR_INV_VALUE); + + keys = malloc ((cnt + 1) * sizeof (gpgme_key_t)); + if (! keys) + return gpg_error_from_syserror (); + + for (idx = 0; idx < cnt; idx++) + { + err = gpgme_get_key (gt->ctx, fpr[idx], &keys[idx], 0); + if (err) + break; + } + if (! err) + { + keys[cnt] = NULL; + err = gpgme_op_import_keys (gt->ctx, keys); + } + + /* Rollback. */ + while (--idx >= 0) + gpgme_key_unref (keys[idx]); + free (keys); + + return err; +} + + +gpg_error_t +gt_delete (gpgme_tool_t gt, char *fpr, int allow_secret) +{ + gpg_error_t err; + gpgme_key_t key; + + err = gpgme_get_key (gt->ctx, fpr, &key, 0); + if (err) + return err; + + err = gpgme_op_delete (gt->ctx, key, allow_secret); + gpgme_key_unref (key); + return err; +} + + +gpg_error_t +gt_keylist_start (gpgme_tool_t gt, const char *pattern[], int secret_only) +{ + return gpgme_op_keylist_ext_start (gt->ctx, pattern, secret_only, 0); +} + + +gpg_error_t +gt_keylist_next (gpgme_tool_t gt, gpgme_key_t *key) +{ + return gpgme_op_keylist_next (gt->ctx, key); +} + + +gpg_error_t +gt_getauditlog (gpgme_tool_t gt, gpgme_data_t output, unsigned int flags) +{ + return gpgme_op_getauditlog (gt->ctx, output, flags); +} + + +gpg_error_t +gt_vfs_mount (gpgme_tool_t gt, const char *container_file, + const char *mount_dir, int flags) +{ + gpg_error_t err; + gpg_error_t op_err; + err = gpgme_op_vfs_mount (gt->ctx, container_file, mount_dir, flags, &op_err); + return err || op_err; +} + + +// TODO +#define GT_RESULT_ENCRYPT 0x1 +#define GT_RESULT_DECRYPT 0x2 +#define GT_RESULT_SIGN 0x4 +#define GT_RESULT_VERIFY 0x8 +#define GT_RESULT_IMPORT 0x10 +#define GT_RESULT_GENKEY 0x20 +#define GT_RESULT_KEYLIST 0x40 +#define GT_RESULT_VFS_MOUNT 0x80 +#define GT_RESULT_ALL (~0U) + +gpg_error_t +gt_result (gpgme_tool_t gt, unsigned int flags) +{ + if (flags & GT_RESULT_ENCRYPT) + { + gpgme_encrypt_result_t res = gpgme_op_encrypt_result (gt->ctx); + if (res) + { + gpgme_invalid_key_t invrec = res->invalid_recipients; + while (invrec) + { + gt_write_status (gt, STATUS_ENCRYPT_RESULT, "invalid_recipient", + invrec->fpr, invrec->reason); + invrec = invrec->next; + } + } + } + return 0; +} + + +/* GPGME SERVER. */ + +#include <assuan.h> + +struct server +{ + gpgme_tool_t gt; + assuan_context_t assuan_ctx; + + gpgme_data_encoding_t input_enc; + gpgme_data_encoding_t output_enc; + assuan_fd_t message_fd; + gpgme_data_encoding_t message_enc; +}; + + +gpg_error_t +server_write_status (void *hook, const char *status, const char *msg) +{ + struct server *server = hook; + return assuan_write_status (server->assuan_ctx, status, msg); +} + + +static gpgme_data_encoding_t +server_data_encoding (const char *line) +{ + if (strstr (line, "--binary")) + return GPGME_DATA_ENCODING_BINARY; + if (strstr (line, "--base64")) + return GPGME_DATA_ENCODING_BASE64; + if (strstr (line, "--armor")) + return GPGME_DATA_ENCODING_ARMOR; + if (strstr (line, "--url")) + return GPGME_DATA_ENCODING_URL; + if (strstr (line, "--urlesc")) + return GPGME_DATA_ENCODING_URLESC; + if (strstr (line, "--url0")) + return GPGME_DATA_ENCODING_URL0; + return GPGME_DATA_ENCODING_NONE; +} + + +static gpgme_error_t +server_data_obj (assuan_fd_t fd, gpgme_data_encoding_t encoding, + gpgme_data_t *data) +{ + gpgme_error_t err; + + err = gpgme_data_new_from_fd (data, fd); + if (err) + return err; + return gpgme_data_set_encoding (*data, encoding); +} + + +void +server_reset_fds (struct server *server) +{ + /* assuan closes the input and output FDs for us when doing a RESET, + but we use this same function after commands, so repeat it + here. */ + assuan_close_input_fd (server->assuan_ctx); + assuan_close_output_fd (server->assuan_ctx); + if (server->message_fd != -1) + { + /* FIXME: Assuan should provide a close function. */ + close (server->message_fd); + server->message_fd = -1; + } + server->input_enc = GPGME_DATA_ENCODING_NONE; + server->output_enc = GPGME_DATA_ENCODING_NONE; + server->message_enc = GPGME_DATA_ENCODING_NONE; +} + + +static gpg_error_t +reset_notify (assuan_context_t ctx, char *line) +{ + struct server *server = assuan_get_pointer (ctx); + server_reset_fds (server); + gt_reset (server->gt); + return 0; +} + + +static gpg_error_t +cmd_version (assuan_context_t ctx, char *line) +{ + if (line && *line) + { + const char *version = gpgme_check_version (line); + return version ? 0 : gpg_error (GPG_ERR_SELFTEST_FAILED); + } + else + { + const char *version = gpgme_check_version (NULL); + return assuan_send_data (ctx, version, strlen (version)); + } +} + + +static gpg_error_t +cmd_engine (assuan_context_t ctx, char *line) +{ + struct server *server = assuan_get_pointer (ctx); + return gt_get_engine_info (server->gt, gt_protocol_from_name (line)); +} + + +static gpg_error_t +cmd_protocol (assuan_context_t ctx, char *line) +{ + struct server *server = assuan_get_pointer (ctx); + if (line && *line) + return gt_set_protocol (server->gt, gt_protocol_from_name (line)); + else + return gt_get_protocol (server->gt); +} + + +static gpg_error_t +cmd_armor (assuan_context_t ctx, char *line) +{ + struct server *server = assuan_get_pointer (ctx); + if (line && *line) + { + int flag = 0; + + if (! strcasecmp (line, "true") || ! strcasecmp (line, "yes") + || line[0] == '1') + flag = 1; + + return gt_set_armor (server->gt, flag); + } + else + return gt_get_armor (server->gt); +} + + +static gpg_error_t +cmd_textmode (assuan_context_t ctx, char *line) +{ + struct server *server = assuan_get_pointer (ctx); + if (line && *line) + { + int flag = 0; + + if (! strcasecmp (line, "true") || ! strcasecmp (line, "yes") + || line[0] == '1') + flag = 1; + + return gt_set_textmode (server->gt, flag); + } + else + return gt_get_textmode (server->gt); +} + + +static gpg_error_t +cmd_include_certs (assuan_context_t ctx, char *line) +{ + struct server *server = assuan_get_pointer (ctx); + + if (line && *line) + { + int include_certs = 0; + + if (! strcasecmp (line, "default")) + include_certs = GPGME_INCLUDE_CERTS_DEFAULT; + else + include_certs = atoi (line); + + return gt_set_include_certs (server->gt, include_certs); + } + else + return gt_get_include_certs (server->gt); +} + + +static gpg_error_t +cmd_keylist_mode (assuan_context_t ctx, char *line) +{ + struct server *server = assuan_get_pointer (ctx); + + if (line && *line) + { + gpgme_keylist_mode_t mode = 0; + + if (strstr (line, "local")) + mode |= GPGME_KEYLIST_MODE_LOCAL; + if (strstr (line, "extern")) + mode |= GPGME_KEYLIST_MODE_EXTERN; + if (strstr (line, "sigs")) + mode |= GPGME_KEYLIST_MODE_SIGS; + if (strstr (line, "sig_notations")) + mode |= GPGME_KEYLIST_MODE_SIG_NOTATIONS; + if (strstr (line, "ephemeral")) + mode |= GPGME_KEYLIST_MODE_EPHEMERAL; + if (strstr (line, "validate")) + mode |= GPGME_KEYLIST_MODE_VALIDATE; + + return gt_set_keylist_mode (server->gt, mode); + } + else + return gt_get_keylist_mode (server->gt); +} + + +static void +input_notify (assuan_context_t ctx, const char *line) +{ + struct server *server = assuan_get_pointer (ctx); + server->input_enc = server_data_encoding (line); +} + + +static void +output_notify (assuan_context_t ctx, const char *line) +{ + struct server *server = assuan_get_pointer (ctx); + server->output_enc = server_data_encoding (line); +} + + +static gpg_error_t +cmd_message (assuan_context_t ctx, char *line) +{ + struct server *server = assuan_get_pointer (ctx); + gpg_error_t err; + assuan_fd_t sysfd; + + err = assuan_command_parse_fd (ctx, line, &sysfd); + if (err) + return err; + server->message_fd = sysfd; + server->message_enc = server_data_encoding (line); + return 0; +} + + +static gpg_error_t +cmd_recipient (assuan_context_t ctx, char *line) +{ + struct server *server = assuan_get_pointer (ctx); + + return gt_recipients_add (server->gt, line); +} + + +static gpg_error_t +cmd_signer (assuan_context_t ctx, char *line) +{ + struct server *server = assuan_get_pointer (ctx); + + return gt_signers_add (server->gt, line); +} + + +static gpg_error_t +cmd_signers_clear (assuan_context_t ctx, char *line) +{ + struct server *server = assuan_get_pointer (ctx); + + return gt_signers_clear (server->gt); +} + + +static gpg_error_t +_cmd_decrypt_verify (assuan_context_t ctx, char *line, int verify) +{ + struct server *server = assuan_get_pointer (ctx); + gpg_error_t err; + assuan_fd_t inp_fd; + assuan_fd_t out_fd; + gpgme_data_t inp_data; + gpgme_data_t out_data; + + inp_fd = assuan_get_input_fd (ctx); + if (inp_fd == ASSUAN_INVALID_FD) + return GPG_ERR_ASS_NO_INPUT; + out_fd = assuan_get_output_fd (ctx); + if (out_fd == ASSUAN_INVALID_FD) + return GPG_ERR_ASS_NO_OUTPUT; + + err = server_data_obj (inp_fd, server->input_enc, &inp_data); + if (err) + return err; + err = server_data_obj (out_fd, server->output_enc, &out_data); + if (err) + { + gpgme_data_release (inp_data); + return err; + } + + err = gt_decrypt_verify (server->gt, inp_data, out_data, verify); + + gpgme_data_release (inp_data); + gpgme_data_release (out_data); + + server_reset_fds (server); + + return err; +} + + +static gpg_error_t +cmd_decrypt (assuan_context_t ctx, char *line) +{ + return _cmd_decrypt_verify (ctx, line, 0); +} + + +static gpg_error_t +cmd_decrypt_verify (assuan_context_t ctx, char *line) +{ + return _cmd_decrypt_verify (ctx, line, 1); +} + + +static gpg_error_t +_cmd_sign_encrypt (assuan_context_t ctx, char *line, int sign) +{ + struct server *server = assuan_get_pointer (ctx); + gpg_error_t err; + assuan_fd_t inp_fd; + assuan_fd_t out_fd; + gpgme_data_t inp_data; + gpgme_data_t out_data; + gpgme_encrypt_flags_t flags = 0; + + if (strstr (line, "--always-trust")) + flags |= GPGME_ENCRYPT_ALWAYS_TRUST; + if (strstr (line, "--no-encrypt-to")) + flags |= GPGME_ENCRYPT_NO_ENCRYPT_TO; + + inp_fd = assuan_get_input_fd (ctx); + if (inp_fd == ASSUAN_INVALID_FD) + return GPG_ERR_ASS_NO_INPUT; + out_fd = assuan_get_output_fd (ctx); + if (out_fd == ASSUAN_INVALID_FD) + return GPG_ERR_ASS_NO_OUTPUT; + + err = server_data_obj (inp_fd, server->input_enc, &inp_data); + if (err) + return err; + err = server_data_obj (out_fd, server->output_enc, &out_data); + if (err) + { + gpgme_data_release (inp_data); + return err; + } + + err = gt_sign_encrypt (server->gt, flags, inp_data, out_data, sign); + + gpgme_data_release (inp_data); + gpgme_data_release (out_data); + + server_reset_fds (server); + + return err; +} + + +static gpg_error_t +cmd_encrypt (assuan_context_t ctx, char *line) +{ + return _cmd_sign_encrypt (ctx, line, 0); +} + + +static gpg_error_t +cmd_sign_encrypt (assuan_context_t ctx, char *line) +{ + return _cmd_sign_encrypt (ctx, line, 1); +} + + +static gpg_error_t +cmd_sign (assuan_context_t ctx, char *line) +{ + struct server *server = assuan_get_pointer (ctx); + gpg_error_t err; + assuan_fd_t inp_fd; + assuan_fd_t out_fd; + gpgme_data_t inp_data; + gpgme_data_t out_data; + gpgme_sig_mode_t mode = GPGME_SIG_MODE_NORMAL; + + if (strstr (line, "--clear")) + mode = GPGME_SIG_MODE_CLEAR; + if (strstr (line, "--detach")) + mode = GPGME_SIG_MODE_DETACH; + + inp_fd = assuan_get_input_fd (ctx); + if (inp_fd == ASSUAN_INVALID_FD) + return GPG_ERR_ASS_NO_INPUT; + out_fd = assuan_get_output_fd (ctx); + if (out_fd == ASSUAN_INVALID_FD) + return GPG_ERR_ASS_NO_OUTPUT; + + err = server_data_obj (inp_fd, server->input_enc, &inp_data); + if (err) + return err; + err = server_data_obj (out_fd, server->output_enc, &out_data); + if (err) + { + gpgme_data_release (inp_data); + return err; + } + + err = gt_sign (server->gt, inp_data, out_data, mode); + + gpgme_data_release (inp_data); + gpgme_data_release (out_data); + server_reset_fds (server); + + return err; +} + + +static gpg_error_t +cmd_verify (assuan_context_t ctx, char *line) +{ + struct server *server = assuan_get_pointer (ctx); + gpg_error_t err; + assuan_fd_t inp_fd; + assuan_fd_t msg_fd; + assuan_fd_t out_fd; + gpgme_data_t inp_data; + gpgme_data_t msg_data = NULL; + gpgme_data_t out_data = NULL; + + inp_fd = assuan_get_input_fd (ctx); + if (inp_fd == ASSUAN_INVALID_FD) + return GPG_ERR_ASS_NO_INPUT; + msg_fd = server->message_fd; + out_fd = assuan_get_output_fd (ctx); + + err = server_data_obj (inp_fd, server->input_enc, &inp_data); + if (err) + return err; + if (msg_fd != ASSUAN_INVALID_FD) + { + err = server_data_obj (msg_fd, server->message_enc, &msg_data); + if (err) + { + gpgme_data_release (inp_data); + return err; + } + } + if (out_fd != ASSUAN_INVALID_FD) + { + err = server_data_obj (out_fd, server->output_enc, &out_data); + if (err) + { + gpgme_data_release (inp_data); + gpgme_data_release (msg_data); + return err; + } + } + + err = gt_verify (server->gt, inp_data, msg_data, out_data); + + gpgme_data_release (inp_data); + if (msg_data) + gpgme_data_release (msg_data); + if (out_data) + gpgme_data_release (out_data); + + server_reset_fds (server); + + return err; +} + + +static gpg_error_t +cmd_import (assuan_context_t ctx, char *line) +{ + struct server *server = assuan_get_pointer (ctx); + + if (line && *line) + { + char *fprs[2] = { line, NULL }; + + return gt_import_keys (server->gt, fprs); + } + else + { + gpg_error_t err; + assuan_fd_t inp_fd; + gpgme_data_t inp_data; + + inp_fd = assuan_get_input_fd (ctx); + if (inp_fd == ASSUAN_INVALID_FD) + return GPG_ERR_ASS_NO_INPUT; + + err = server_data_obj (inp_fd, server->input_enc, &inp_data); + if (err) + return err; + + err = gt_import (server->gt, inp_data); + + gpgme_data_release (inp_data); + server_reset_fds (server); + + return err; + } +} + + +static gpg_error_t +cmd_export (assuan_context_t ctx, char *line) +{ + struct server *server = assuan_get_pointer (ctx); + gpg_error_t err; + assuan_fd_t out_fd; + gpgme_data_t out_data; + gpgme_export_mode_t mode = 0; + const char *pattern[2]; + const char optstr[] = "--extern "; + + out_fd = assuan_get_output_fd (ctx); + if (out_fd == ASSUAN_INVALID_FD) + return GPG_ERR_ASS_NO_OUTPUT; + err = server_data_obj (out_fd, server->output_enc, &out_data); + if (err) + return err; + + if (strncasecmp (line, optstr, strlen (optstr))) + { + mode |= GPGME_EXPORT_MODE_EXTERN; + line += strlen (optstr); + } + pattern[0] = line; + pattern[1] = NULL; + + err = gt_export (server->gt, pattern, mode, out_data); + + gpgme_data_release (out_data); + server_reset_fds (server); + + return err; +} + + +static gpg_error_t +_cmd_genkey_write (gpgme_data_t data, const void *buf, size_t size) +{ + while (size > 0) + { + ssize_t writen = gpgme_data_write (data, buf, size); + if (writen < 0 && errno != EAGAIN) + return gpg_error_from_syserror (); + else if (writen > 0) + { + buf = (void *) (((char *) buf) + writen); + size -= writen; + } + } + return 0; +} + + +static gpg_error_t +cmd_genkey (assuan_context_t ctx, char *line) +{ + struct server *server = assuan_get_pointer (ctx); + gpg_error_t err; + assuan_fd_t inp_fd; + assuan_fd_t out_fd; + gpgme_data_t inp_data; + gpgme_data_t out_data = NULL; + gpgme_data_t parms_data = NULL; + const char *parms; + + inp_fd = assuan_get_input_fd (ctx); + if (inp_fd == ASSUAN_INVALID_FD) + return GPG_ERR_ASS_NO_INPUT; + out_fd = assuan_get_output_fd (ctx); + + err = server_data_obj (inp_fd, server->input_enc, &inp_data); + if (err) + return err; + if (out_fd != ASSUAN_INVALID_FD) + { + err = server_data_obj (out_fd, server->output_enc, &out_data); + if (err) + { + gpgme_data_release (inp_data); + return err; + } + } + + /* Convert input data. */ + err = gpgme_data_new (&parms_data); + if (err) + goto out; + do + { + char buf[512]; + ssize_t readn = gpgme_data_read (inp_data, buf, sizeof (buf)); + if (readn < 0) + { + err = gpg_error_from_syserror (); + goto out; + } + else if (readn == 0) + break; + + err = _cmd_genkey_write (parms_data, buf, readn); + if (err) + goto out; + } + while (1); + err = _cmd_genkey_write (parms_data, "", 1); + if (err) + goto out; + parms = gpgme_data_release_and_get_mem (parms_data, NULL); + parms_data = NULL; + if (! parms) + { + err = gpg_error (GPG_ERR_GENERAL); + goto out; + } + + err = gt_genkey (server->gt, parms, out_data, NULL); + + server_reset_fds (server); + + out: + gpgme_data_release (inp_data); + if (out_data) + gpgme_data_release (out_data); + if (parms_data) + gpgme_data_release (parms_data); + + return err; +} + + +static gpg_error_t +cmd_delete (assuan_context_t ctx, char *line) +{ + struct server *server = assuan_get_pointer (ctx); + int allow_secret = 0; + const char optstr[] = "--allow-secret "; + + if (strncasecmp (line, optstr, strlen (optstr))) + { + allow_secret = 1; + line += strlen (optstr); + } + return gt_delete (server->gt, line, allow_secret); +} + + +static gpg_error_t +cmd_keylist (assuan_context_t ctx, char *line) +{ + struct server *server = assuan_get_pointer (ctx); + gpg_error_t err; + int secret_only = 0; + const char *pattern[2]; + const char optstr[] = "--secret-only "; + + if (strncasecmp (line, optstr, strlen (optstr))) + { + secret_only = 1; + line += strlen (optstr); + } + pattern[0] = line; + pattern[1] = NULL; + + err = gt_keylist_start (server->gt, pattern, secret_only); + while (! err) + { + gpgme_key_t key; + + err = gt_keylist_next (server->gt, &key); + if (gpg_err_code (err) == GPG_ERR_EOF) + { + err = 0; + break; + } + else if (! err) + { + char buf[100]; + /* FIXME: More data. */ + snprintf (buf, sizeof (buf), "key:%s\n", key->subkeys->fpr); + assuan_send_data (ctx, buf, strlen (buf)); + gpgme_key_unref (key); + } + } + + server_reset_fds (server); + + return err; +} + + +static gpg_error_t +cmd_getauditlog (assuan_context_t ctx, char *line) +{ + struct server *server = assuan_get_pointer (ctx); + gpg_error_t err; + assuan_fd_t out_fd; + gpgme_data_t out_data; + + out_fd = assuan_get_output_fd (ctx); + if (out_fd == ASSUAN_INVALID_FD) + return GPG_ERR_ASS_NO_OUTPUT; + err = server_data_obj (out_fd, server->output_enc, &out_data); + if (err) + return err; + + err = gt_getauditlog (server->gt, out_data, 0); + + gpgme_data_release (out_data); + server_reset_fds (server); + + return err; +} + + +static gpg_error_t +cmd_vfs_mount (assuan_context_t ctx, char *line) +{ + struct server *server = assuan_get_pointer (ctx); + char *mount_dir; + gpg_error_t err; + + mount_dir = strchr (line, ' '); + if (mount_dir) + { + *(mount_dir++) = '\0'; + while (*mount_dir == ' ') + mount_dir++; + } + + err = gt_vfs_mount (server->gt, line, mount_dir, 0); + + return err; +} + + +static gpg_error_t +cmd_result (assuan_context_t ctx, char *line) +{ + struct server *server = assuan_get_pointer (ctx); + return gt_result (server->gt, GT_RESULT_ALL); +} + + +/* STRERROR <err> */ +static gpg_error_t +cmd_strerror (assuan_context_t ctx, char *line) +{ + gpg_error_t err; + char buf[100]; + + err = atoi (line); + snprintf (buf, sizeof (buf), "%s <%s>", gpgme_strerror (err), + gpgme_strsource (err)); + return assuan_send_data (ctx, buf, strlen (buf)); +} + + +static gpg_error_t +cmd_pubkey_algo_name (assuan_context_t ctx, char *line) +{ + gpgme_pubkey_algo_t algo; + char buf[100]; + + algo = atoi (line); + snprintf (buf, sizeof (buf), "%s", gpgme_pubkey_algo_name (algo)); + return assuan_send_data (ctx, buf, strlen (buf)); +} + + +static gpg_error_t +cmd_hash_algo_name (assuan_context_t ctx, char *line) +{ + gpgme_hash_algo_t algo; + char buf[100]; + + algo = atoi (line); + snprintf (buf, sizeof (buf), "%s", gpgme_hash_algo_name (algo)); + return assuan_send_data (ctx, buf, strlen (buf)); +} + + +/* Tell the assuan library about our commands. */ +static gpg_error_t +register_commands (assuan_context_t ctx) +{ + gpg_error_t err; + static struct { + const char *name; + gpg_error_t (*handler)(assuan_context_t, char *line); + } table[] = { + // RESET, BYE are implicit. + { "VERSION", cmd_version }, + // TODO: Set engine info. + { "ENGINE", cmd_engine }, + { "PROTOCOL", cmd_protocol }, + { "ARMOR", cmd_armor }, + { "TEXTMODE", cmd_textmode }, + { "INCLUDE_CERTS", cmd_include_certs }, + { "KEYLIST_MODE", cmd_keylist_mode }, + { "INPUT", NULL }, + { "OUTPUT", NULL }, + { "MESSAGE", cmd_message }, + { "RECIPIENT", cmd_recipient }, + { "SIGNER", cmd_signer }, + { "SIGNERS_CLEAR", cmd_signers_clear }, + // TODO: SIGNOTATION missing. + // TODO: Could add wait interface if we allow more than one context + // and add _START variants. + // TODO: Could add data interfaces if we allow multiple data objects. + { "DECRYPT", cmd_decrypt }, + { "DECRYPT_VERIFY", cmd_decrypt_verify }, + { "ENCRYPT", cmd_encrypt }, + { "ENCRYPT_SIGN", cmd_sign_encrypt }, + { "SIGN_ENCRYPT", cmd_sign_encrypt }, + { "SIGN", cmd_sign }, + { "VERIFY", cmd_verify }, + { "IMPORT", cmd_import }, + { "EXPORT", cmd_export }, + { "GENKEY", cmd_genkey }, + { "DELETE", cmd_delete }, + // TODO: EDIT, CARD_EDIT (with INQUIRE) + { "KEYLIST", cmd_keylist }, + { "LISTKEYS", cmd_keylist }, + // TODO: TRUSTLIST, TRUSTLIST_EXT + { "GETAUDITLOG", cmd_getauditlog }, + // TODO: ASSUAN + { "VFS_MOUNT", cmd_vfs_mount }, + { "MOUNT", cmd_vfs_mount }, + // TODO: GPGCONF + { "RESULT", cmd_result }, + { "STRERROR", cmd_strerror }, + { "PUBKEY_ALGO_NAME", cmd_pubkey_algo_name }, + { "HASH_ALGO_NAME", cmd_hash_algo_name }, + { NULL } + }; + int idx; + + for (idx = 0; table[idx].name; idx++) + { + err = assuan_register_command (ctx, table[idx].name, table[idx].handler); + if (err) + return err; + } + return 0; +} + + +/* TODO: password callback can do INQUIRE. */ +void +gpgme_server (gpgme_tool_t gt) +{ + gpg_error_t err; + int filedes[2]; + struct server server; + static const char hello[] = ("GPGME-Tool " VERSION " ready"); + + memset (&server, 0, sizeof (server)); + server.message_fd = -1; + server.input_enc = GPGME_DATA_ENCODING_NONE; + server.output_enc = GPGME_DATA_ENCODING_NONE; + server.message_enc = GPGME_DATA_ENCODING_NONE; + + server.gt = gt; + gt->write_status = server_write_status; + gt->write_status_hook = &server; + + /* 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 (&server.assuan_ctx); + if (err) + log_error (1, err, "can't create assuan context"); + + assuan_set_pointer (server.assuan_ctx, &server); + + err = assuan_init_pipe_server (server.assuan_ctx, filedes); + if (err) + log_error (1, err, "can't initialize assuan server"); + err = register_commands (server.assuan_ctx); + if (err) + log_error (1, err, "can't register assuan commands"); + assuan_set_hello_line (server.assuan_ctx, hello); + + assuan_register_reset_notify (server.assuan_ctx, reset_notify); + assuan_register_input_notify (server.assuan_ctx, input_notify); + assuan_register_output_notify (server.assuan_ctx, output_notify); + +#define DBG_ASSUAN 0 + if (DBG_ASSUAN) + assuan_set_log_stream (server.assuan_ctx, log_stream); + + for (;;) + { + err = assuan_accept (server.assuan_ctx); + if (err == -1) + break; + else if (err) + { + log_error (0, err, "assuan accept problem"); + break; + } + + err = assuan_process (server.assuan_ctx); + if (err) + log_error (0, err, "assuan processing failed"); + } + + assuan_release (server.assuan_ctx); +} + + + +/* MAIN PROGRAM STARTS HERE. */ + +const char *argp_program_version = VERSION; +const char *argp_program_bug_address = "[email protected]"; +error_t argp_err_exit_status = 1; + +static char doc[] = "GPGME Tool -- invoke GPGME operations"; +static char args_doc[] = "COMMAND [OPTIONS...]"; + +static struct argp_option options[] = { + { "server", 's', 0, 0, "Server mode" }, + { 0 } +}; + +static error_t parse_options (int key, char *arg, struct argp_state *state); +static struct argp argp = { options, parse_options, args_doc, doc }; + +struct args +{ + enum { CMD_DEFAULT, CMD_SERVER } cmd; +}; + +void +args_init (struct args *args) +{ + memset (args, '\0', sizeof (*args)); + args->cmd = CMD_DEFAULT; +} + + +static error_t +parse_options (int key, char *arg, struct argp_state *state) +{ + struct args *args = state->input; + + switch (key) + { + case 's': + args->cmd = CMD_SERVER; + break; +#if 0 + case ARGP_KEY_ARG: + if (state->arg_num >= 2) + argp_usage (state); + printf ("Arg[%i] = %s\n", state->arg_num, arg); + break; + case ARGP_KEY_END: + if (state->arg_num < 2) + argp_usage (state); + break; +#endif + + default: + return ARGP_ERR_UNKNOWN; + } + return 0; +} + + +int +main (int argc, char *argv[]) +{ + struct args args; + struct gpgme_tool gt; + + setlocale (LC_ALL, ""); + gpgme_check_version (NULL); + gpgme_set_locale (NULL, LC_CTYPE, setlocale (LC_CTYPE, NULL)); +#ifdef LC_MESSAGES + gpgme_set_locale (NULL, LC_MESSAGES, setlocale (LC_MESSAGES, NULL)); +#endif + args_init (&args); + + argp_parse (&argp, argc, argv, 0, 0, &args); + log_init (); + + gt_init (>); + + switch (args.cmd) + { + case CMD_DEFAULT: + case CMD_SERVER: + gpgme_server (>); + break; + } + + gpgme_release (gt.ctx); + + return 0; +} + |