/* engine-gpgsm.c - GpgSM engine. Copyright (C) 2000 Werner Koch (dd9jn) Copyright (C) 2001, 2002, 2003 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 General Public License as published by the Free Software Foundation; either version 2 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 General Public License for more details. You should have received a copy of the GNU General Public License along with GPGME; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #if HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include /* FIXME */ #include "gpgme.h" #include "util.h" #include "ops.h" #include "wait.h" #include "io.h" #include "key.h" #include "sema.h" #include "assuan.h" #include "status-table.h" #include "engine-backend.h" typedef struct { int fd; /* FD we talk about. */ int dir; /* Inbound/Outbound, maybe given implicit? */ void *data; /* Handler-specific data. */ void *tag; /* ID from the user for gpgme_remove_io_callback. */ } iocb_data_t; struct gpgsm_object_s { ASSUAN_CONTEXT assuan_ctx; iocb_data_t status_cb; /* Input, output etc are from the servers perspective. */ iocb_data_t input_cb; int input_fd_server; iocb_data_t output_cb; int output_fd_server; iocb_data_t message_cb; int message_fd_server; struct { EngineStatusHandler fnc; void *fnc_value; } status; struct { EngineColonLineHandler fnc; void *fnc_value; struct { unsigned char *line; int linesize; int linelen; } attic; int any; /* any data line seen */ } colon; struct GpgmeIOCbs io_cbs; }; typedef struct gpgsm_object_s *GpgsmObject; static const char * gpgsm_get_version (void) { static const char *gpgsm_version; DEFINE_STATIC_LOCK (gpgsm_version_lock); LOCK (gpgsm_version_lock); if (!gpgsm_version) gpgsm_version = _gpgme_get_program_version (_gpgme_get_gpgsm_path ()); UNLOCK (gpgsm_version_lock); return gpgsm_version; } static const char * gpgsm_get_req_version (void) { return NEED_GPGSM_VERSION; } static void close_notify_handler (int fd, void *opaque) { GpgsmObject gpgsm = opaque; assert (fd != -1); if (gpgsm->status_cb.fd == fd) { if (gpgsm->status_cb.tag) (*gpgsm->io_cbs.remove) (gpgsm->status_cb.tag); gpgsm->status_cb.fd = -1; } else if (gpgsm->input_cb.fd == fd) { if (gpgsm->input_cb.tag) (*gpgsm->io_cbs.remove) (gpgsm->input_cb.tag); gpgsm->input_cb.fd = -1; } else if (gpgsm->output_cb.fd == fd) { if (gpgsm->output_cb.tag) (*gpgsm->io_cbs.remove) (gpgsm->output_cb.tag); gpgsm->output_cb.fd = -1; } else if (gpgsm->message_cb.fd == fd) { if (gpgsm->message_cb.tag) (*gpgsm->io_cbs.remove) (gpgsm->message_cb.tag); gpgsm->message_cb.fd = -1; } } static GpgmeError map_assuan_error (AssuanError err) { switch (err) { case ASSUAN_No_Error: return GPGME_No_Error; case ASSUAN_General_Error: return GPGME_General_Error; case ASSUAN_Out_Of_Core: return GPGME_Out_Of_Core; case ASSUAN_Invalid_Value: return GPGME_Invalid_Value; case ASSUAN_Read_Error: return GPGME_Read_Error; case ASSUAN_Write_Error: return GPGME_Write_Error; case ASSUAN_Timeout: case ASSUAN_Problem_Starting_Server: case ASSUAN_Not_A_Server: case ASSUAN_Not_A_Client: case ASSUAN_Nested_Commands: case ASSUAN_Invalid_Response: case ASSUAN_No_Data_Callback: case ASSUAN_No_Inquire_Callback: case ASSUAN_Connect_Failed: case ASSUAN_Accept_Failed: return GPGME_General_Error; /* The following error codes are meant as status codes. */ case ASSUAN_Not_Implemented: return GPGME_Not_Implemented; case ASSUAN_Canceled: return GPGME_Canceled; case ASSUAN_Unsupported_Algorithm: return GPGME_Not_Implemented; /* XXX Argh. */ case ASSUAN_No_Data_Available: return GPGME_EOF; /* These are errors internal to GPGME. */ case ASSUAN_No_Input: case ASSUAN_No_Output: case ASSUAN_Invalid_Command: case ASSUAN_Unknown_Command: case ASSUAN_Syntax_Error: case ASSUAN_Parameter_Error: case ASSUAN_Parameter_Conflict: case ASSUAN_Line_Too_Long: case ASSUAN_Line_Not_Terminated: case ASSUAN_Invalid_Data: case ASSUAN_Unexpected_Command: case ASSUAN_Too_Much_Data: case ASSUAN_Inquire_Unknown: case ASSUAN_Inquire_Error: case ASSUAN_Invalid_Option: case ASSUAN_Invalid_Index: case ASSUAN_Unexpected_Status: case ASSUAN_Unexpected_Data: case ASSUAN_Invalid_Status: case ASSUAN_Not_Confirmed: return GPGME_General_Error; /* These are errors in the server. */ case ASSUAN_Server_Fault: case ASSUAN_Server_Resource_Problem: case ASSUAN_Server_IO_Error: case ASSUAN_Server_Bug: case ASSUAN_No_Agent: case ASSUAN_Agent_Error: return GPGME_Invalid_Engine; /* XXX: Need something more useful. */ case ASSUAN_Bad_Certificate: case ASSUAN_Bad_Certificate_Chain: case ASSUAN_Missing_Certificate: case ASSUAN_No_Public_Key: case ASSUAN_No_Secret_Key: case ASSUAN_Invalid_Name: case ASSUAN_Card_Error: /* XXX: Oh well. */ case ASSUAN_Invalid_Card: /* XXX: Oh well. */ case ASSUAN_No_PKCS15_App: /* XXX: Oh well. */ case ASSUAN_Card_Not_Present: /* XXX: Oh well. */ case ASSUAN_Invalid_Id: /* XXX: Oh well. */ return GPGME_Invalid_Key; case ASSUAN_Bad_Signature: return GPGME_Invalid_Key; /* XXX: This is wrong. */ case ASSUAN_Cert_Revoked: case ASSUAN_No_CRL_For_Cert: case ASSUAN_CRL_Too_Old: case ASSUAN_Not_Trusted: return GPGME_Invalid_Key; /* XXX Some more details would be good. */ default: return GPGME_General_Error; } } static void gpgsm_release (void *engine) { GpgsmObject gpgsm = engine; if (!gpgsm) return; if (gpgsm->status_cb.fd != -1) _gpgme_io_close (gpgsm->status_cb.fd); if (gpgsm->input_cb.fd != -1) _gpgme_io_close (gpgsm->input_cb.fd); if (gpgsm->output_cb.fd != -1) _gpgme_io_close (gpgsm->output_cb.fd); if (gpgsm->message_cb.fd != -1) _gpgme_io_close (gpgsm->message_cb.fd); assuan_disconnect (gpgsm->assuan_ctx); free (gpgsm->colon.attic.line); free (gpgsm); } static GpgmeError gpgsm_new (void **engine) { GpgmeError err = 0; GpgsmObject gpgsm; char *argv[3]; int fds[2]; int child_fds[4]; char *dft_display = NULL; char *dft_ttyname = NULL; char *dft_ttytype = NULL; char *old_lc = NULL; char *dft_lc = NULL; char *optstr; int fdlist[5]; int nfds; gpgsm = calloc (1, sizeof *gpgsm); if (!gpgsm) { err = GPGME_Out_Of_Core; return err; } gpgsm->status_cb.fd = -1; gpgsm->status_cb.tag = 0; gpgsm->input_cb.fd = -1; gpgsm->input_cb.tag = 0; gpgsm->input_fd_server = -1; gpgsm->output_cb.fd = -1; gpgsm->output_cb.tag = 0; gpgsm->output_fd_server = -1; gpgsm->message_cb.fd = -1; gpgsm->message_cb.tag = 0; gpgsm->message_fd_server = -1; gpgsm->status.fnc = 0; gpgsm->colon.fnc = 0; gpgsm->colon.attic.line = 0; gpgsm->colon.attic.linesize = 0; gpgsm->colon.attic.linelen = 0; gpgsm->colon.any = 0; gpgsm->io_cbs.add = NULL; gpgsm->io_cbs.add_priv = NULL; gpgsm->io_cbs.remove = NULL; gpgsm->io_cbs.event = NULL; gpgsm->io_cbs.event_priv = NULL; if (_gpgme_io_pipe (fds, 0) < 0) { err = GPGME_Pipe_Error; goto leave; } gpgsm->input_cb.fd = fds[1]; gpgsm->input_cb.dir = 0; gpgsm->input_fd_server = fds[0]; if (_gpgme_io_pipe (fds, 1) < 0) { err = GPGME_Pipe_Error; goto leave; } gpgsm->output_cb.fd = fds[0]; gpgsm->output_cb.dir = 1; gpgsm->output_fd_server = fds[1]; if (_gpgme_io_pipe (fds, 0) < 0) { err = GPGME_Pipe_Error; goto leave; } gpgsm->message_cb.fd = fds[1]; gpgsm->message_cb.dir = 0; gpgsm->message_fd_server = fds[0]; child_fds[0] = gpgsm->input_fd_server; child_fds[1] = gpgsm->output_fd_server; child_fds[2] = gpgsm->message_fd_server; child_fds[3] = -1; argv[0] = "gpgsm"; argv[1] = "--server"; argv[2] = NULL; err = assuan_pipe_connect (&gpgsm->assuan_ctx, _gpgme_get_gpgsm_path (), argv, child_fds); /* We need to know the fd used by assuan for reads. We do this by using the assumption that the first returned fd from assuan_get_active_fds() is always this one. */ nfds = assuan_get_active_fds (gpgsm->assuan_ctx, 0 /* read fds */, fdlist, DIM (fdlist)); if (nfds < 1) { err = GPGME_General_Error; /* FIXME */ goto leave; } /* We duplicate the file descriptor, so we can close it without disturbing assuan. Alternatively, we could special case status_fd and register/unregister it manually as needed, but this increases code duplication and is more complicated as we can not use the close notifications etc. */ gpgsm->status_cb.fd = dup (fdlist[0]); if (gpgsm->status_cb.fd < 0) { err = GPGME_General_Error; /* FIXME */ goto leave; } gpgsm->status_cb.dir = 1; gpgsm->status_cb.data = gpgsm; dft_display = getenv ("DISPLAY"); if (dft_display) { if (asprintf (&optstr, "OPTION display=%s", dft_display) < 0) { err = GPGME_Out_Of_Core; goto leave; } err = assuan_transact (gpgsm->assuan_ctx, optstr, NULL, NULL, NULL, NULL, NULL, NULL); free (optstr); if (err) { err = map_assuan_error (err); goto leave; } } dft_ttyname = ttyname (1); if (dft_ttyname) { if (asprintf (&optstr, "OPTION ttyname=%s", dft_ttyname) < 0) { err = GPGME_Out_Of_Core; goto leave; } err = assuan_transact (gpgsm->assuan_ctx, optstr, NULL, NULL, NULL, NULL, NULL, NULL); free (optstr); if (err) { err = map_assuan_error (err); goto leave; } dft_ttytype = getenv ("TERM"); if (dft_ttytype) { if (asprintf (&optstr, "OPTION ttytype=%s", dft_ttytype) < 0) { err = GPGME_Out_Of_Core; goto leave; } err = assuan_transact (gpgsm->assuan_ctx, optstr, NULL, NULL, NULL, NULL, NULL, NULL); free (optstr); if (err) { err = map_assuan_error (err); goto leave; } } old_lc = setlocale (LC_CTYPE, NULL); if (old_lc) { old_lc = strdup (old_lc); if (!old_lc) { err = GPGME_Out_Of_Core; goto leave; } } dft_lc = setlocale (LC_CTYPE, ""); if (dft_lc) { if (asprintf (&optstr, "OPTION lc-ctype=%s", dft_lc) < 0) err = GPGME_Out_Of_Core; else { err = assuan_transact (gpgsm->assuan_ctx, optstr, NULL, NULL, NULL, NULL, NULL, NULL); free (optstr); if (err) err = map_assuan_error (err); } } if (old_lc) { setlocale (LC_CTYPE, old_lc); free (old_lc); } if (err) goto leave; old_lc = setlocale (LC_MESSAGES, NULL); if (old_lc) { old_lc = strdup (old_lc); if (!old_lc) { err = GPGME_Out_Of_Core; goto leave; } } dft_lc = setlocale (LC_MESSAGES, ""); if (dft_lc) { if (asprintf (&optstr, "OPTION lc-messages=%s", dft_lc) < 0) err = GPGME_Out_Of_Core; else { err = assuan_transact (gpgsm->assuan_ctx, optstr, NULL, NULL, NULL, NULL, NULL, NULL); free (optstr); if (err) err = map_assuan_error (err); } } if (old_lc) { setlocale (LC_MESSAGES, old_lc); free (old_lc); } if (err) goto leave; } if (!err && (_gpgme_io_set_close_notify (gpgsm->status_cb.fd, close_notify_handler, gpgsm) || _gpgme_io_set_close_notify (gpgsm->input_cb.fd, close_notify_handler, gpgsm) || _gpgme_io_set_close_notify (gpgsm->output_cb.fd, close_notify_handler, gpgsm) || _gpgme_io_set_close_notify (gpgsm->message_cb.fd, close_notify_handler, gpgsm))) { err = GPGME_General_Error; goto leave; } leave: /* Close the server ends of the pipes. Our ends are closed in gpgsm_release(). */ if (gpgsm->input_fd_server != -1) _gpgme_io_close (gpgsm->input_fd_server); if (gpgsm->output_fd_server != -1) _gpgme_io_close (gpgsm->output_fd_server); if (gpgsm->message_fd_server != -1) _gpgme_io_close (gpgsm->message_fd_server); if (err) gpgsm_release (gpgsm); else *engine = gpgsm; return err; } /* Forward declaration. */ static GpgmeStatusCode parse_status (const char *name); static GpgmeError gpgsm_assuan_simple_command (ASSUAN_CONTEXT ctx, char *cmd, EngineStatusHandler status_fnc, void *status_fnc_value) { AssuanError err; char *line; size_t linelen; err = assuan_write_line (ctx, cmd); if (err) return map_assuan_error (err); do { err = assuan_read_line (ctx, &line, &linelen); if (err) return map_assuan_error (err); if (*line == '#' || !linelen) continue; if (linelen >= 2 && line[0] == 'O' && line[1] == 'K' && (line[2] == '\0' || line[2] == ' ')) return 0; else if (linelen >= 4 && line[0] == 'E' && line[1] == 'R' && line[2] == 'R' && line[3] == ' ') err = map_assuan_error (atoi (&line[4])); else if (linelen >= 2 && line[0] == 'S' && line[1] == ' ') { char *rest; GpgmeStatusCode r; rest = strchr (line + 2, ' '); if (!rest) rest = line + linelen; /* set to an empty string */ else *(rest++) = 0; r = parse_status (line + 2); if (r >= 0 && status_fnc) status_fnc (status_fnc_value, r, rest); else err = GPGME_General_Error; } else err = GPGME_General_Error; } while (!err); return err; } #define COMMANDLINELEN 40 static GpgmeError gpgsm_set_fd (ASSUAN_CONTEXT ctx, const char *which, int fd, const char *opt) { char line[COMMANDLINELEN]; if (opt) snprintf (line, COMMANDLINELEN, "%s FD=%i %s", which, fd, opt); else snprintf (line, COMMANDLINELEN, "%s FD=%i", which, fd); return gpgsm_assuan_simple_command (ctx, line, NULL, NULL); } static const char * map_input_enc (GpgmeData d) { switch (gpgme_data_get_encoding (d)) { case GPGME_DATA_ENCODING_NONE: break; case GPGME_DATA_ENCODING_BINARY: return "--binary"; case GPGME_DATA_ENCODING_BASE64: return "--base64"; case GPGME_DATA_ENCODING_ARMOR: return "--armor"; default: break; } return NULL; } static int status_cmp (const void *ap, const void *bp) { const struct status_table_s *a = ap; const struct status_table_s *b = bp; return strcmp (a->name, b->name); } static GpgmeStatusCode parse_status (const char *name) { struct status_table_s t, *r; t.name = name; r = bsearch (&t, status_table, DIM(status_table) - 1, sizeof t, status_cmp); return r ? r->code : -1; } static GpgmeError status_handler (void *opaque, int fd) { AssuanError assuan_err; GpgmeError err = 0; GpgsmObject gpgsm = opaque; char *line; size_t linelen; do { assuan_err = assuan_read_line (gpgsm->assuan_ctx, &line, &linelen); if (assuan_err) { /* Try our best to terminate the connection friendly. */ assuan_write_line (gpgsm->assuan_ctx, "BYE"); err = map_assuan_error (assuan_err); } else if (linelen >= 3 && line[0] == 'E' && line[1] == 'R' && line[2] == 'R' && (line[3] == '\0' || line[3] == ' ')) { if (line[3] == ' ') err = map_assuan_error (atoi (&line[4])); else err = GPGME_General_Error; } else if (linelen >= 2 && line[0] == 'O' && line[1] == 'K' && (line[2] == '\0' || line[2] == ' ')) { if (gpgsm->status.fnc) err = gpgsm->status.fnc (gpgsm->status.fnc_value, GPGME_STATUS_EOF, ""); if (!err && gpgsm->colon.fnc && gpgsm->colon.any ) { /* We must tell a colon function about the EOF. We do this only when we have seen any data lines. Note that this inlined use of colon data lines will eventually be changed into using a regular data channel. */ gpgsm->colon.any = 0; err = gpgsm->colon.fnc (gpgsm->colon.fnc_value, NULL); } _gpgme_io_close (gpgsm->status_cb.fd); return err; } else if (linelen > 2 && line[0] == 'D' && line[1] == ' ' && gpgsm->colon.fnc) { /* We are using the colon handler even for plain inline data - strange name for that function but for historic reasons we keep it. */ /* FIXME We can't use this for binary data because we assume this is a string. For the current usage of colon output it is correct. */ unsigned char *src = line + 2; unsigned char *end = line + linelen; unsigned char *dst; unsigned char **aline = &gpgsm->colon.attic.line; int *alinelen = &gpgsm->colon.attic.linelen; if (gpgsm->colon.attic.linesize < *alinelen + linelen + 1) { unsigned char *newline = realloc (*aline, *alinelen + linelen + 1); if (!newline) err = GPGME_Out_Of_Core; else { *aline = newline; gpgsm->colon.attic.linesize += linelen + 1; } } if (!err) { dst = *aline + *alinelen; while (!err && src < end) { if (*src == '%' && src + 2 < end) { /* Handle escaped characters. */ ++src; *dst = (unsigned char) _gpgme_hextobyte (src); (*alinelen)++; src += 2; } else { *dst = *src++; (*alinelen)++; } if (*dst == '\n') { /* Terminate the pending line, pass it to the colon handler and reset it. */ gpgsm->colon.any = 1; if (*alinelen > 1 && *(dst - 1) == '\r') dst--; *dst = '\0'; /* FIXME How should we handle the return code? */ err = gpgsm->colon.fnc (gpgsm->colon.fnc_value, *aline); if (!err) { dst = *aline; *alinelen = 0; } } else dst++; } } } else if (linelen > 2 && line[0] == 'S' && line[1] == ' ') { char *rest; GpgmeStatusCode r; rest = strchr (line + 2, ' '); if (!rest) rest = line + linelen; /* set to an empty string */ else *(rest++) = 0; r = parse_status (line + 2); if (r >= 0) { if (gpgsm->status.fnc) err = gpgsm->status.fnc (gpgsm->status.fnc_value, r, rest); } else fprintf (stderr, "[UNKNOWN STATUS]%s %s", line + 2, rest); } } while (!err && assuan_pending_line (gpgsm->assuan_ctx)); return err; } static GpgmeError add_io_cb (GpgsmObject gpgsm, iocb_data_t *iocbd, GpgmeIOCb handler) { GpgmeError err; err = (*gpgsm->io_cbs.add) (gpgsm->io_cbs.add_priv, iocbd->fd, iocbd->dir, handler, iocbd->data, &iocbd->tag); if (err) return err; if (!iocbd->dir) /* FIXME Kludge around poll() problem. */ err = _gpgme_io_set_nonblocking (iocbd->fd); return err; } static GpgmeError start (GpgsmObject gpgsm, const char *command) { GpgmeError err; err = add_io_cb (gpgsm, &gpgsm->status_cb, status_handler); if (!err && gpgsm->input_cb.fd != -1) err = add_io_cb (gpgsm, &gpgsm->input_cb, _gpgme_data_outbound_handler); if (!err && gpgsm->output_cb.fd != -1) err = add_io_cb (gpgsm, &gpgsm->output_cb, _gpgme_data_inbound_handler); if (!err && gpgsm->message_cb.fd != -1) err = add_io_cb (gpgsm, &gpgsm->message_cb, _gpgme_data_outbound_handler); if (!err) err = assuan_write_line (gpgsm->assuan_ctx, command); if (!err) (*gpgsm->io_cbs.event) (gpgsm->io_cbs.event_priv, GPGME_EVENT_START, NULL); return err; } static GpgmeError gpgsm_decrypt (void *engine, GpgmeData ciph, GpgmeData plain) { GpgsmObject gpgsm = engine; GpgmeError err; if (!gpgsm) return GPGME_Invalid_Value; gpgsm->input_cb.data = ciph; err = gpgsm_set_fd (gpgsm->assuan_ctx, "INPUT", gpgsm->input_fd_server, map_input_enc (gpgsm->input_cb.data)); if (err) return GPGME_General_Error; /* FIXME */ gpgsm->output_cb.data = plain; err = gpgsm_set_fd (gpgsm->assuan_ctx, "OUTPUT", gpgsm->output_fd_server, 0); if (err) return GPGME_General_Error; /* FIXME */ _gpgme_io_close (gpgsm->message_cb.fd); err = start (engine, "DECRYPT"); return err; } static GpgmeError gpgsm_delete (void *engine, GpgmeKey key, int allow_secret) { GpgsmObject gpgsm = engine; GpgmeError err; char *fpr = (char *) gpgme_key_get_string_attr (key, GPGME_ATTR_FPR, NULL, 0); char *linep = fpr; char *line; int length = 8; /* "DELKEYS " */ if (!fpr) return GPGME_Invalid_Key; while (*linep) { length++; if (*linep == '%' || *linep == ' ' || *linep == '+') length += 2; linep++; } length++; line = malloc (length); if (!line) return GPGME_Out_Of_Core; strcpy (line, "DELKEYS "); linep = &line[8]; while (*fpr) { switch (*fpr) { case '%': *(linep++) = '%'; *(linep++) = '2'; *(linep++) = '5'; break; case ' ': *(linep++) = '%'; *(linep++) = '2'; *(linep++) = '0'; break; case '+': *(linep++) = '%'; *(linep++) = '2'; *(linep++) = 'B'; break; default: *(linep++) = *fpr; break; } fpr++; } *linep = '\0'; _gpgme_io_close (gpgsm->output_cb.fd); _gpgme_io_close (gpgsm->input_cb.fd); _gpgme_io_close (gpgsm->message_cb.fd); err = start (gpgsm, line); free (line); return err; } static GpgmeError set_recipients (GpgsmObject gpgsm, GpgmeRecipients recp) { GpgmeError err; ASSUAN_CONTEXT ctx = gpgsm->assuan_ctx; char *line; int linelen; struct user_id_s *r; int valid_recipients = 0; linelen = 10 + 40 + 1; /* "RECIPIENT " + guess + '\0'. */ line = malloc (10 + 40 + 1); if (!line) return GPGME_Out_Of_Core; strcpy (line, "RECIPIENT "); for (r = recp->list; r; r = r->next) { int newlen = 11 + strlen (r->name); if (linelen < newlen) { char *newline = realloc (line, newlen); if (! newline) { free (line); return GPGME_Out_Of_Core; } line = newline; linelen = newlen; } strcpy (&line[10], r->name); err = gpgsm_assuan_simple_command (ctx, line, gpgsm->status.fnc, gpgsm->status.fnc_value); if (!err) valid_recipients = 1; else if (err != GPGME_Invalid_Key) { free (line); return err; } } free (line); if (!valid_recipients && gpgsm->status.fnc) gpgsm->status.fnc (gpgsm->status.fnc_value, GPGME_STATUS_NO_RECP, ""); return 0; } static GpgmeError gpgsm_encrypt (void *engine, GpgmeRecipients recp, GpgmeData plain, GpgmeData ciph, int use_armor) { GpgsmObject gpgsm = engine; GpgmeError err; if (!gpgsm) return GPGME_Invalid_Value; if (!recp) return GPGME_Not_Implemented; gpgsm->input_cb.data = plain; err = gpgsm_set_fd (gpgsm->assuan_ctx, "INPUT", gpgsm->input_fd_server, map_input_enc (gpgsm->input_cb.data)); if (err) return err; gpgsm->output_cb.data = ciph; err = gpgsm_set_fd (gpgsm->assuan_ctx, "OUTPUT", gpgsm->output_fd_server, use_armor ? "--armor" : 0); if (err) return err; _gpgme_io_close (gpgsm->message_cb.fd); err = set_recipients (gpgsm, recp); if (!err) err = start (gpgsm, "ENCRYPT"); return err; } static GpgmeError gpgsm_export (void *engine, GpgmeRecipients recp, GpgmeData keydata, int use_armor) { GpgsmObject gpgsm = engine; GpgmeError err = 0; char *cmd = NULL; int cmdi; int cmdlen = 32; if (!gpgsm) return GPGME_Invalid_Value; cmd = malloc (cmdlen); if (!cmd) return GPGME_Out_Of_Core; strcpy (cmd, "EXPORT"); cmdi = 6; if (recp) { void *ec; const char *s; err = gpgme_recipients_enum_open (recp, &ec); while (!err && (s = gpgme_recipients_enum_read (recp, &ec))) { int slen = strlen (s); /* New string is old string + ' ' + s + '\0'. */ if (cmdlen < cmdi + 1 + slen + 1) { char *newcmd = realloc (cmd, cmdlen * 2); if (!newcmd) { free (cmd); return GPGME_Out_Of_Core; } cmd = newcmd; cmdlen *= 2; } cmd[cmdi++] = ' '; strcpy (cmd + cmdi, s); cmdi += slen; } if (!err) err = gpgme_recipients_enum_close (recp, &ec); if (err) return err; } gpgsm->output_cb.data = keydata; err = gpgsm_set_fd (gpgsm->assuan_ctx, "OUTPUT", gpgsm->output_fd_server, use_armor ? "--armor" : 0); if (err) return err; _gpgme_io_close (gpgsm->input_cb.fd); _gpgme_io_close (gpgsm->message_cb.fd); err = start (gpgsm, cmd); free (cmd); return err; } static GpgmeError gpgsm_genkey (void *engine, GpgmeData help_data, int use_armor, GpgmeData pubkey, GpgmeData seckey) { GpgsmObject gpgsm = engine; GpgmeError err; if (!gpgsm || !pubkey || seckey) return GPGME_Invalid_Value; gpgsm->input_cb.data = help_data; err = gpgsm_set_fd (gpgsm->assuan_ctx, "INPUT", gpgsm->input_fd_server, map_input_enc (gpgsm->input_cb.data)); if (err) return err; gpgsm->output_cb.data = pubkey; err = gpgsm_set_fd (gpgsm->assuan_ctx, "OUTPUT", gpgsm->output_fd_server, use_armor ? "--armor" : 0); if (err) return err; _gpgme_io_close (gpgsm->message_cb.fd); err = start (gpgsm, "GENKEY"); return err; } static GpgmeError gpgsm_import (void *engine, GpgmeData keydata) { GpgsmObject gpgsm = engine; GpgmeError err; if (!gpgsm) return GPGME_Invalid_Value; gpgsm->input_cb.data = keydata; err = gpgsm_set_fd (gpgsm->assuan_ctx, "INPUT", gpgsm->input_fd_server, map_input_enc (gpgsm->input_cb.data)); if (err) return err; _gpgme_io_close (gpgsm->output_cb.fd); _gpgme_io_close (gpgsm->message_cb.fd); err = start (gpgsm, "IMPORT"); return err; } static GpgmeError gpgsm_keylist (void *engine, const char *pattern, int secret_only, int keylist_mode) { GpgsmObject gpgsm = engine; char *line; GpgmeError err; if (!pattern) pattern = ""; if (asprintf (&line, "OPTION list-mode=%d", (keylist_mode & 3)) < 0) return GPGME_Out_Of_Core; err = gpgsm_assuan_simple_command (gpgsm->assuan_ctx, line, NULL, NULL); free (line); if (err) return err; /* Length is "LISTSECRETKEYS " + p + '\0'. */ line = malloc (15 + strlen (pattern) + 1); if (!line) return GPGME_Out_Of_Core; if (secret_only) { strcpy (line, "LISTSECRETKEYS "); strcpy (&line[15], pattern); } else { strcpy (line, "LISTKEYS "); strcpy (&line[9], pattern); } _gpgme_io_close (gpgsm->input_cb.fd); _gpgme_io_close (gpgsm->output_cb.fd); _gpgme_io_close (gpgsm->message_cb.fd); err = start (gpgsm, line); free (line); return err; } static GpgmeError gpgsm_keylist_ext (void *engine, const char *pattern[], int secret_only, int reserved, int keylist_mode) { GpgsmObject gpgsm = engine; char *line; GpgmeError err; /* Length is "LISTSECRETKEYS " + p + '\0'. */ int length = 15 + 1; char *linep; if (reserved) return GPGME_Invalid_Value; if (asprintf (&line, "OPTION list-mode=%d", (keylist_mode & 3)) < 0) return GPGME_Out_Of_Core; err = gpgsm_assuan_simple_command (gpgsm->assuan_ctx, line, NULL, NULL); free (line); if (err) return err; if (pattern && *pattern) { const char **pat = pattern; while (*pat) { const char *patlet = *pat; while (*patlet) { length++; if (*patlet == '%' || *patlet == ' ' || *patlet == '+') length += 2; patlet++; } pat++; /* This will allocate one byte more than necessary. */ length++; } } line = malloc (length); if (!line) return GPGME_Out_Of_Core; if (secret_only) { strcpy (line, "LISTSECRETKEYS "); linep = &line[15]; } else { strcpy (line, "LISTKEYS "); linep = &line[9]; } if (pattern && *pattern) { while (*pattern) { const char *patlet = *pattern; while (*patlet) { switch (*patlet) { case '%': *(linep++) = '%'; *(linep++) = '2'; *(linep++) = '5'; break; case ' ': *(linep++) = '%'; *(linep++) = '2'; *(linep++) = '0'; break; case '+': *(linep++) = '%'; *(linep++) = '2'; *(linep++) = 'B'; break; default: *(linep++) = *patlet; break; } patlet++; } pattern++; } } *linep = '\0'; _gpgme_io_close (gpgsm->input_cb.fd); _gpgme_io_close (gpgsm->output_cb.fd); _gpgme_io_close (gpgsm->message_cb.fd); err = start (gpgsm, line); free (line); return err; } static GpgmeError gpgsm_sign (void *engine, GpgmeData in, GpgmeData out, GpgmeSigMode mode, int use_armor, int use_textmode, int include_certs, GpgmeCtx ctx /* FIXME */) { GpgsmObject gpgsm = engine; GpgmeError err; char *assuan_cmd; int i; GpgmeKey key; if (!gpgsm) return GPGME_Invalid_Value; if (asprintf (&assuan_cmd, "OPTION include-certs %i", include_certs) < 0) return GPGME_Out_Of_Core; err = gpgsm_assuan_simple_command (gpgsm->assuan_ctx, assuan_cmd, NULL,NULL); free (assuan_cmd); if (err) return err; /* We must do a reset becuase we need to reset the list of signers. Note that RESET does not reset OPTION commands. */ err = gpgsm_assuan_simple_command (gpgsm->assuan_ctx, "RESET", NULL, NULL); if (err) return err; for (i = 0; (key = gpgme_signers_enum (ctx, i)); i++) { const char *s = gpgme_key_get_string_attr (key, GPGME_ATTR_FPR, NULL, 0); if (s && strlen (s) < 80) { char buf[100]; strcpy (stpcpy (buf, "SIGNER "), s); err = gpgsm_assuan_simple_command (gpgsm->assuan_ctx, buf, NULL, NULL); } else err = GPGME_Invalid_Key; gpgme_key_unref (key); if (err) return err; } gpgsm->input_cb.data = in; err = gpgsm_set_fd (gpgsm->assuan_ctx, "INPUT", gpgsm->input_fd_server, map_input_enc (gpgsm->input_cb.data)); if (err) return err; gpgsm->output_cb.data = out; err = gpgsm_set_fd (gpgsm->assuan_ctx, "OUTPUT", gpgsm->output_fd_server, use_armor ? "--armor" : 0); if (err) return err; _gpgme_io_close (gpgsm->message_cb.fd); err = start (gpgsm, mode == GPGME_SIG_MODE_DETACH ? "SIGN --detached" : "SIGN"); return err; } static GpgmeError gpgsm_trustlist (void *engine, const char *pattern) { /* FIXME */ return GPGME_Not_Implemented; } static GpgmeError gpgsm_verify (void *engine, GpgmeData sig, GpgmeData signed_text, GpgmeData plaintext) { GpgsmObject gpgsm = engine; GpgmeError err; if (!gpgsm) return GPGME_Invalid_Value; gpgsm->input_cb.data = sig; err = gpgsm_set_fd (gpgsm->assuan_ctx, "INPUT", gpgsm->input_fd_server, map_input_enc (gpgsm->input_cb.data)); if (err) return err; if (plaintext) { /* Normal or cleartext signature. */ gpgsm->output_cb.data = plaintext; err = gpgsm_set_fd (gpgsm->assuan_ctx, "OUTPUT", gpgsm->output_fd_server, 0); _gpgme_io_close (gpgsm->message_cb.fd); } else { /* Detached signature. */ gpgsm->message_cb.data = signed_text; err = gpgsm_set_fd (gpgsm->assuan_ctx, "MESSAGE", gpgsm->message_fd_server, 0); _gpgme_io_close (gpgsm->output_cb.fd); } if (!err) err = start (gpgsm, "VERIFY"); return err; } static void gpgsm_set_status_handler (void *engine, EngineStatusHandler fnc, void *fnc_value) { GpgsmObject gpgsm = engine; gpgsm->status.fnc = fnc; gpgsm->status.fnc_value = fnc_value; } static GpgmeError gpgsm_set_colon_line_handler (void *engine, EngineColonLineHandler fnc, void *fnc_value) { GpgsmObject gpgsm = engine; gpgsm->colon.fnc = fnc; gpgsm->colon.fnc_value = fnc_value; gpgsm->colon.any = 0; return 0; } static void gpgsm_set_io_cbs (void *engine, struct GpgmeIOCbs *io_cbs) { GpgsmObject gpgsm = engine; gpgsm->io_cbs = *io_cbs; } static void gpgsm_io_event (void *engine, GpgmeEventIO type, void *type_data) { GpgsmObject gpgsm = engine; if (gpgsm->io_cbs.event) (*gpgsm->io_cbs.event) (gpgsm->io_cbs.event_priv, type, type_data); } struct engine_ops _gpgme_engine_ops_gpgsm = { /* Static functions. */ _gpgme_get_gpgsm_path, gpgsm_get_version, gpgsm_get_req_version, gpgsm_new, /* Member functions. */ gpgsm_release, gpgsm_set_status_handler, NULL, /* set_command_handler */ gpgsm_set_colon_line_handler, NULL, /* set_verbosity */ gpgsm_decrypt, gpgsm_delete, NULL, /* edit */ gpgsm_encrypt, NULL, gpgsm_export, gpgsm_genkey, gpgsm_import, gpgsm_keylist, gpgsm_keylist_ext, gpgsm_sign, gpgsm_trustlist, gpgsm_verify, gpgsm_set_io_cbs, gpgsm_io_event };