/* call-gpg.c - Communication with the GPG * Copyright (C) 2009 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GnuPG is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include #include #include #include #include #include #include #include #include "g13.h" #include #include "i18n.h" #include "call-gpg.h" #include "utils.h" #include "../common/exechelp.h" /* Fire up a new GPG. Handle the server's initial greeting. Returns 0 on success and stores the assuan context at R_CTX. */ static gpg_error_t start_gpg (ctrl_t ctrl, int input_fd, int output_fd, assuan_context_t *r_ctx) { gpg_error_t err; assuan_context_t ctx = NULL; const char *pgmname; const char *argv[6]; int no_close_list[5]; int i; char line[ASSUAN_LINELENGTH]; (void)ctrl; *r_ctx = NULL; err = assuan_new (&ctx); if (err) { log_error ("can't allocate assuan context: %s\n", gpg_strerror (err)); return err; } /* The first time we are used, intialize the gpg_program variable. */ if ( !opt.gpg_program || !*opt.gpg_program ) opt.gpg_program = gnupg_module_name (GNUPG_MODULE_NAME_GPG); if (opt.verbose) log_info (_("no running gpg - starting `%s'\n"), opt.gpg_program); /* Compute argv[0]. */ if ( !(pgmname = strrchr (opt.gpg_program, '/'))) pgmname = opt.gpg_program; else pgmname++; if (fflush (NULL)) { err = gpg_error_from_syserror (); log_error ("error flushing pending output: %s\n", gpg_strerror (err)); return err; } i = 0; argv[i++] = pgmname; argv[i++] = "--server"; if ((opt.debug & 1024)) argv[i++] = "--debug=1024"; argv[i++] = "-z"; argv[i++] = "0"; argv[i++] = NULL; i = 0; if (log_get_fd () != -1) no_close_list[i++] = log_get_fd (); no_close_list[i++] = fileno (stderr); if (input_fd != -1) no_close_list[i++] = input_fd; if (output_fd != -1) no_close_list[i++] = output_fd; no_close_list[i] = -1; /* Connect to GPG and perform initial handshaking. */ err = assuan_pipe_connect (ctx, opt.gpg_program, argv, no_close_list); /* if (!err) */ /* err = assuan_transact (ctx, "OPTION audit-events=1", */ /* NULL, NULL, NULL, NULL, NULL, NULL); */ /* audit_log_ok (ctrl->audit, AUDIT_GPG_READY, err); */ if (err) { assuan_release (ctx); log_error ("can't connect to GPG: %s\n", gpg_strerror (err)); return gpg_error (GPG_ERR_NO_ENGINE); } if (input_fd != -1) { snprintf (line, sizeof line, "INPUT FD=%d", input_fd); err = assuan_transact (ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (err) { assuan_release (ctx); log_error ("error sending INPUT command: %s\n", gpg_strerror (err)); return err; } } if (output_fd != -1) { snprintf (line, sizeof line, "OUTPUT FD=%d", output_fd); err = assuan_transact (ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (err) { assuan_release (ctx); log_error ("error sending OUTPUT command: %s\n", gpg_strerror (err)); return err; } } *r_ctx = ctx; if (DBG_ASSUAN) log_debug ("connection to GPG established\n"); return 0; } /* Release the assuan context created by start_gpg. */ static void release_gpg (assuan_context_t ctx) { assuan_release (ctx); } /* The data passed to the writer_thread. */ struct writer_thread_parms { int fd; const void *data; size_t datalen; gpg_error_t *err_addr; }; /* The thread started by start_writer. */ static void * writer_thread (void *arg) { struct writer_thread_parms *parm = arg; const char *buffer = parm->data; size_t length = parm->datalen; while (length) { ssize_t nwritten; nwritten = pth_write (parm->fd, buffer, length < 4096? length:4096); if (nwritten < 0) { if (errno == EINTR) continue; *parm->err_addr = gpg_error_from_syserror (); break; /* Write error. */ } length -= nwritten; buffer += nwritten; } if (close (parm->fd)) log_error ("closing writer fd %d failed: %s\n", parm->fd, strerror (errno)); xfree (parm); return NULL; } /* Fire up a thread to send (DATA,DATALEN) to the file descriptor FD. On success the thread receives the ownership over FD. The thread ID is stored at R_TID. WRITER_ERR is the address of an gpg_error_t variable to receive a possible write error after the thread has finished. */ static gpg_error_t start_writer (int fd, const void *data, size_t datalen, pth_t *r_tid, gpg_error_t *err_addr) { gpg_error_t err; struct writer_thread_parms *parm; pth_attr_t tattr; pth_t tid; *r_tid = NULL; *err_addr = 0; parm = xtrymalloc (sizeof *parm); if (!parm) return gpg_error_from_syserror (); parm->fd = fd; parm->data = data; parm->datalen = datalen; parm->err_addr = err_addr; tattr = pth_attr_new (); pth_attr_set (tattr, PTH_ATTR_JOINABLE, 1); pth_attr_set (tattr, PTH_ATTR_STACK_SIZE, 64*1024); pth_attr_set (tattr, PTH_ATTR_NAME, "fd-writer"); tid = pth_spawn (tattr, writer_thread, parm); if (!tid) { err = gpg_error_from_syserror (); log_error ("error spawning writer thread: %s\n", gpg_strerror (err)); } else { err = 0; *r_tid = tid; } pth_attr_destroy (tattr); return err; } /* The data passed to the reader_thread. */ struct reader_thread_parms { int fd; membuf_t *mb; gpg_error_t *err_addr; }; /* The thread started by start_reader. */ static void * reader_thread (void *arg) { struct reader_thread_parms *parm = arg; char buffer[4096]; int nread; while ( (nread = pth_read (parm->fd, buffer, sizeof buffer)) ) { if (nread < 0) { if (errno == EINTR) continue; *parm->err_addr = gpg_error_from_syserror (); break; /* Read error. */ } put_membuf (parm->mb, buffer, nread); } if (close (parm->fd)) log_error ("closing reader fd %d failed: %s\n", parm->fd, strerror (errno)); xfree (parm); return NULL; } /* Fire up a thread to receive data from the file descriptor FD. On success the thread receives the ownership over FD. The thread ID is stored at R_TID. After the thread has finished an error from the thread will be stored at ERR_ADDR. */ static gpg_error_t start_reader (int fd, membuf_t *mb, pth_t *r_tid, gpg_error_t *err_addr) { gpg_error_t err; struct reader_thread_parms *parm; pth_attr_t tattr; pth_t tid; *r_tid = NULL; *err_addr = 0; parm = xtrymalloc (sizeof *parm); if (!parm) return gpg_error_from_syserror (); parm->fd = fd; parm->mb = mb; parm->err_addr = err_addr; tattr = pth_attr_new (); pth_attr_set (tattr, PTH_ATTR_JOINABLE, 1); pth_attr_set (tattr, PTH_ATTR_STACK_SIZE, 64*1024); pth_attr_set (tattr, PTH_ATTR_NAME, "fd-reader"); tid = pth_spawn (tattr, reader_thread, parm); if (!tid) { err = gpg_error_from_syserror (); log_error ("error spawning reader thread: %s\n", gpg_strerror (err)); } else { err = 0; *r_tid = tid; } pth_attr_destroy (tattr); return err; } /* Call GPG to encrypt a block of data. */ gpg_error_t gpg_encrypt_blob (ctrl_t ctrl, const void *plain, size_t plainlen, void **r_ciph, size_t *r_ciphlen) { gpg_error_t err; assuan_context_t ctx; int outbound_fds[2] = { -1, -1 }; int inbound_fds[2] = { -1, -1 }; pth_t writer_tid = NULL; pth_t reader_tid = NULL; gpg_error_t writer_err, reader_err; membuf_t reader_mb; *r_ciph = NULL; *r_ciphlen = 0; /* Init the memory buffer to receive the encrypted stuff. */ init_membuf (&reader_mb, 4096); /* Create two pipes. */ err = gnupg_create_outbound_pipe (outbound_fds); if (!err) err = gnupg_create_inbound_pipe (inbound_fds); if (err) { log_error (_("error creating a pipe: %s\n"), gpg_strerror (err)); goto leave; } /* Start GPG and send the INPUT and OUTPUT commands. */ err = start_gpg (ctrl, outbound_fds[0], inbound_fds[1], &ctx); if (err) goto leave; close (outbound_fds[0]); outbound_fds[0] = -1; close (inbound_fds[1]); inbound_fds[1] = -1; /* Start a writer thread to feed the INPUT command of the server. */ err = start_writer (outbound_fds[1], plain, plainlen, &writer_tid, &writer_err); if (err) return err; outbound_fds[1] = -1; /* The thread owns the FD now. */ /* Start a reader thread to eat from the OUTPUT command of the server. */ err = start_reader (inbound_fds[0], &reader_mb, &reader_tid, &reader_err); if (err) return err; outbound_fds[0] = -1; /* The thread owns the FD now. */ /* Run the encryption. */ err = assuan_transact (ctx, "RECIPIENT alpha@example.net", NULL, NULL, NULL, NULL, NULL, NULL); if (err) { log_error ("the engine's RECIPIENT command failed: %s <%s>\n", gpg_strerror (err), gpg_strsource (err)); goto leave; } err = assuan_transact (ctx, "ENCRYPT", NULL, NULL, NULL, NULL, NULL, NULL); if (err) { log_error ("the engine's ENCRYPT command failed: %s <%s>\n", gpg_strerror (err), gpg_strsource (err)); goto leave; } /* Wait for reader and return the data. */ if (!pth_join (reader_tid, NULL)) { err = gpg_error_from_syserror (); log_error ("waiting for reader thread failed: %s\n", gpg_strerror (err)); goto leave; } reader_tid = NULL; if (reader_err) { err = reader_err; log_error ("read error in reader thread: %s\n", gpg_strerror (err)); goto leave; } /* Wait for the writer to catch a writer error. */ if (!pth_join (writer_tid, NULL)) { err = gpg_error_from_syserror (); log_error ("waiting for writer thread failed: %s\n", gpg_strerror (err)); goto leave; } writer_tid = NULL; if (writer_err) { err = writer_err; log_error ("write error in writer thread: %s\n", gpg_strerror (err)); goto leave; } /* Return the data. */ *r_ciph = get_membuf (&reader_mb, r_ciphlen); if (!*r_ciph) { err = gpg_error_from_syserror (); log_error ("error while storing the data in the reader thread: %s\n", gpg_strerror (err)); goto leave; } leave: if (reader_tid) { pth_cancel (reader_tid); pth_join (reader_tid, NULL); } if (writer_tid) { pth_cancel (writer_tid); pth_join (writer_tid, NULL); } if (outbound_fds[0] != -1) close (outbound_fds[0]); if (outbound_fds[1] != -1) close (outbound_fds[1]); if (inbound_fds[0] != -1) close (inbound_fds[0]); if (inbound_fds[1] != -1) close (inbound_fds[1]); release_gpg (ctx); xfree (get_membuf (&reader_mb, NULL)); return err; }