From ea6569368591c16ec4ef6398016a7d33579a793d Mon Sep 17 00:00:00 2001 From: Jeffrey Stedfast Date: Fri, 21 Jun 2002 19:11:57 +0000 Subject: New source files implementing the CamelCipherContext class for gnupg. 2002-06-21 Jeffrey Stedfast * camel-gpg-context.[c,h]: New source files implementing the CamelCipherContext class for gnupg. * camel-pgp-context.c (camel_pgp_context_new): Return a CamelCipherContext. * camel-pgp-mime.c (camel_pgp_mime_part_decrypt): Take a CamelCipherContext argument rather than a CamelPgpContext since we now have a CamelGpgContext also. (camel_pgp_mime_part_encrypt): Same. (camel_pgp_mime_part_verify): Same. (camel_pgp_mime_part_sign): Same. svn path=/trunk/; revision=17251 --- camel/ChangeLog | 15 + camel/Makefile.am | 2 + camel/camel-gpg-context.c | 1179 +++++++++++++++++++++++++++++++++++++++++++++ camel/camel-gpg-context.h | 68 +++ camel/camel-pgp-context.c | 4 +- camel/camel-pgp-context.h | 15 +- camel/camel-pgp-mime.c | 24 +- camel/camel-pgp-mime.h | 8 +- camel/camel.h | 3 +- 9 files changed, 1286 insertions(+), 32 deletions(-) create mode 100644 camel/camel-gpg-context.c create mode 100644 camel/camel-gpg-context.h diff --git a/camel/ChangeLog b/camel/ChangeLog index 0341cafb34..b50345b9b7 100644 --- a/camel/ChangeLog +++ b/camel/ChangeLog @@ -1,3 +1,18 @@ +2002-06-21 Jeffrey Stedfast + + * camel-gpg-context.[c,h]: New source files implementing the + CamelCipherContext class for gnupg. + + * camel-pgp-context.c (camel_pgp_context_new): Return a + CamelCipherContext. + + * camel-pgp-mime.c (camel_pgp_mime_part_decrypt): Take a + CamelCipherContext argument rather than a CamelPgpContext since we + now have a CamelGpgContext also. + (camel_pgp_mime_part_encrypt): Same. + (camel_pgp_mime_part_verify): Same. + (camel_pgp_mime_part_sign): Same. + 2002-06-20 Jeffrey Stedfast * camel-digest-folder.c: Updated to support searching as well as diff --git a/camel/Makefile.am b/camel/Makefile.am index 000b66c0b0..2c5d77486d 100644 --- a/camel/Makefile.am +++ b/camel/Makefile.am @@ -40,6 +40,7 @@ libcamel_la_SOURCES = \ camel-folder-summary.c \ camel-folder-thread.c \ camel-folder.c \ + camel-gpg-context.c \ camel-html-parser.c \ camel-http-stream.c \ camel-index.c \ @@ -140,6 +141,7 @@ libcamelinclude_HEADERS = \ camel-folder-summary.h \ camel-folder-thread.h \ camel-folder.h \ + camel-gpg-context.h \ camel-http-stream.h \ camel-index.h \ camel-internet-address.h \ diff --git a/camel/camel-gpg-context.c b/camel/camel-gpg-context.c new file mode 100644 index 0000000000..32764374ed --- /dev/null +++ b/camel/camel-gpg-context.c @@ -0,0 +1,1179 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Authors: Jeffrey Stedfast + * + * Copyright 2002 Ximian, Inc. (www.ximian.com) + * + * This program 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. + * + * This program 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 Street #330, Boston, MA 02111-1307, USA. + * + */ + + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "camel-gpg-context.h" +#include "camel-stream-fs.h" +#include "camel-operation.h" + +static void camel_gpg_context_class_init (CamelGpgContextClass *klass); +static void camel_gpg_context_init (CamelGpgContext *obj); +static void camel_gpg_context_finalise (CamelObject *obj); + +static int gpg_sign (CamelCipherContext *ctx, const char *userid, CamelCipherHash hash, + CamelStream *istream, CamelStream *ostream, CamelException *ex); +static int gpg_clearsign (CamelCipherContext *context, const char *userid, + CamelCipherHash hash, CamelStream *istream, + CamelStream *ostream, CamelException *ex); +static CamelCipherValidity *gpg_verify (CamelCipherContext *context, CamelCipherHash hash, + CamelStream *istream, CamelStream *sigstream, + CamelException *ex); +static int gpg_encrypt (CamelCipherContext *context, gboolean sign, const char *userid, + GPtrArray *recipients, CamelStream *istream, CamelStream *ostream, + CamelException *ex); +static int gpg_decrypt (CamelCipherContext *context, CamelStream *istream, + CamelStream *ostream, CamelException *ex); + +static const char *gpg_hash_to_id (CamelCipherContext *context, CamelCipherHash hash); +static CamelCipherHash gpg_id_to_hash (CamelCipherContext *context, const char *id); + + +static CamelCipherContextClass *parent_class = NULL; + + +CamelType +camel_gpg_context_get_type (void) +{ + static CamelType type = CAMEL_INVALID_TYPE; + + if (type == CAMEL_INVALID_TYPE) { + type = camel_type_register (camel_cipher_context_get_type (), + "CamelGpgContext", + sizeof (CamelGpgContext), + sizeof (CamelGpgContextClass), + (CamelObjectClassInitFunc) camel_gpg_context_class_init, + NULL, + (CamelObjectInitFunc) camel_gpg_context_init, + (CamelObjectFinalizeFunc) camel_gpg_context_finalise); + } + + return type; +} + +static void +camel_gpg_context_class_init (CamelGpgContextClass *klass) +{ + CamelCipherContextClass *cipher_class = CAMEL_CIPHER_CONTEXT_CLASS (klass); + + parent_class = CAMEL_CIPHER_CONTEXT_CLASS (camel_type_get_global_classfuncs (camel_cipher_context_get_type ())); + + cipher_class->sign = gpg_sign; + cipher_class->clearsign = gpg_clearsign; + cipher_class->verify = gpg_verify; + cipher_class->encrypt = gpg_encrypt; + cipher_class->decrypt = gpg_decrypt; + cipher_class->hash_to_id = gpg_hash_to_id; + cipher_class->id_to_hash = gpg_id_to_hash; +} + +static void +camel_gpg_context_init (CamelGpgContext *context) +{ + CamelCipherContext *cipher = (CamelCipherContext *) context; + + context->path = NULL; + context->always_trust = FALSE; + + cipher->sign_protocol = "application/pgp-signature"; + cipher->encrypt_protocol = "application/pgp-encrypted"; +} + +static void +camel_gpg_context_finalise (CamelObject *object) +{ + CamelGpgContext *ctx = (CamelGpgContext *) object; + + g_free (ctx->path); +} + + +static const char * +gpg_hash_to_id (CamelCipherContext *context, CamelCipherHash hash) +{ + switch (hash) { + case CAMEL_CIPHER_HASH_MD2: + return "pgp-md2"; + case CAMEL_CIPHER_HASH_MD5: + return "pgp-md5"; + case CAMEL_CIPHER_HASH_SHA1: + case CAMEL_CIPHER_HASH_DEFAULT: + return "pgp-sha1"; + case CAMEL_CIPHER_HASH_RIPEMD160: + return "pgp-ripemd160"; + } + + return NULL; +} + +static CamelCipherHash +gpg_id_to_hash (CamelCipherContext *context, const char *id) +{ + if (!strcmp (id, "pgp-md2")) + return CAMEL_CIPHER_HASH_MD2; + else if (!strcmp (id, "pgp-md5")) + return CAMEL_CIPHER_HASH_MD5; + else if (!strcmp (id, "pgp-sha1")) + return CAMEL_CIPHER_HASH_SHA1; + else if (!strcmp (id, "pgp-ripemd160")) + return CAMEL_CIPHER_HASH_RIPEMD160; + + return CAMEL_CIPHER_HASH_DEFAULT; +} + + +enum _GpgCtxMode { + GPG_CTX_MODE_SIGN, + GPG_CTX_MODE_VERIFY, + GPG_CTX_MODE_ENCRYPT, + GPG_CTX_MODE_DECRYPT, +}; + +struct _GpgCtx { + enum _GpgCtxMode mode; + CamelSession *session; + GHashTable *userid_hint; + gboolean complete; + pid_t pid; + + char *path; + char *userid; + char *sigfile; + GPtrArray *recipients; + CamelCipherHash hash; + gboolean always_trust; + gboolean armor; + + int stdin; + int stdout; + int stderr; + int status_fd; + int passwd_fd; /* only needed for sign/decrypt */ + + FILE *status_fp; + + gboolean need_passwd; + char *passwd; + + CamelStream *istream; + CamelStream *ostream; + + GByteArray *diagnostics; +}; + +static struct _GpgCtx * +gpg_ctx_new (CamelSession *session, const char *path) +{ + struct _GpgCtx *gpg; + + gpg = g_new (struct _GpgCtx, 1); + gpg->mode = GPG_CTX_MODE_SIGN; + gpg->session = session; + camel_object_ref (CAMEL_OBJECT (session)); + gpg->userid_hint = g_hash_table_new (g_str_hash, g_str_equal); + gpg->complete = FALSE; + gpg->pid = (pid_t) -1; + + gpg->path = g_strdup (path); + gpg->userid = NULL; + gpg->sigfile = NULL; + gpg->recipients = NULL; + gpg->hash = CAMEL_CIPHER_HASH_DEFAULT; + gpg->always_trust = FALSE; + gpg->armor = FALSE; + + gpg->stdin = -1; + gpg->stdout = -1; + gpg->stderr = -1; + gpg->status_fd = -1; + gpg->passwd_fd = -1; + + gpg->status_fp = NULL; + + gpg->need_passwd = FALSE; + gpg->passwd = NULL; + + gpg->istream = NULL; + gpg->ostream = NULL; + + gpg->diagnostics = g_byte_array_new (); + + return gpg; +} + +static void +gpg_ctx_set_mode (struct _GpgCtx *gpg, enum _GpgCtxMode mode) +{ + gpg->mode = mode; + gpg->need_passwd = mode == GPG_CTX_MODE_SIGN || mode == GPG_CTX_MODE_DECRYPT; +} + +static void +gpg_ctx_set_hash (struct _GpgCtx *gpg, CamelCipherHash hash) +{ + gpg->hash = hash; +} + +static void +gpg_ctx_set_always_trust (struct _GpgCtx *gpg, gboolean trust) +{ + gpg->always_trust = trust; +} + +static void +gpg_ctx_set_userid (struct _GpgCtx *gpg, const char *userid) +{ + g_free (gpg->userid); + gpg->userid = g_strdup (userid); +} + +static void +gpg_ctx_add_recipient (struct _GpgCtx *gpg, const char *keyid) +{ + if (gpg->mode != GPG_CTX_MODE_ENCRYPT) + return; + + if (!gpg->recipients) + gpg->recipients = g_ptr_array_new (); + + g_ptr_array_add (gpg->recipients, g_strdup (keyid)); +} + +static void +gpg_ctx_set_sigfile (struct _GpgCtx *gpg, const char *sigfile) +{ + g_free (gpg->sigfile); + gpg->sigfile = g_strdup (sigfile); +} + +static void +gpg_ctx_set_armor (struct _GpgCtx *gpg, gboolean armor) +{ + gpg->armor = armor; +} + +static void +gpg_ctx_set_istream (struct _GpgCtx *gpg, CamelStream *istream) +{ + camel_object_ref (CAMEL_OBJECT (istream)); + if (gpg->istream) + camel_object_unref (CAMEL_OBJECT (gpg->istream)); + gpg->istream = istream; +} + +static void +gpg_ctx_set_ostream (struct _GpgCtx *gpg, CamelStream *ostream) +{ + camel_object_ref (CAMEL_OBJECT (ostream)); + if (gpg->ostream) + camel_object_unref (CAMEL_OBJECT (gpg->ostream)); + gpg->istream = ostream; +} + +static char * +gpg_ctx_get_diagnostics (struct _GpgCtx *gpg) +{ + return g_strndup (gpg->diagnostics->data, gpg->diagnostics->len); +} + +static void +userid_hint_free (gpointer key, gpointer value, gpointer user_data) +{ + g_free (key); + g_free (value); +} + +static void +gpg_ctx_free (struct _GpgCtx *gpg) +{ + int i; + + if (gpg->session) + camel_object_unref (CAMEL_OBJECT (gpg->session)); + + g_hash_table_foreach (gpg->userid_hint, userid_hint_free, NULL); + g_hash_table_destroy (gpg->userid_hint); + + g_free (gpg->path); + + g_free (gpg->userid); + + g_free (gpg->sigfile); + + if (gpg->recipients) { + for (i = 0; i < gpg->recipients->len; i++) + g_free (gpg->recipients->pdata[i]); + + g_ptr_array_free (gpg->recipients, TRUE); + } + + if (gpg->stdin != -1) + close (gpg->stdin); + if (gpg->stdout != -1) + close (gpg->stdout); + if (gpg->stderr != -1) + close (gpg->stderr); + if (gpg->status_fd != -1 && gpg->status_fp == NULL) + close (gpg->status_fd); + if (gpg->passwd_fd != -1) + close (gpg->passwd_fd); + + if (gpg->status_fp != NULL) + fclose (gpg->status_fp); + + if (gpg->passwd) + g_free (gpg->passwd); + + if (gpg->istream) + camel_object_unref (CAMEL_OBJECT (gpg->istream)); + + if (gpg->ostream) + camel_object_unref (CAMEL_OBJECT (gpg->ostream)); + + g_byte_array_free (gpg->diagnostics, TRUE); + + g_free (gpg); +} + +static const char * +gpg_hash_str (CamelCipherHash hash) +{ + switch (hash) { + case CAMEL_CIPHER_HASH_MD2: + return "MD2"; + case CAMEL_CIPHER_HASH_MD5: + return "MD5"; + case CAMEL_CIPHER_HASH_SHA1: + return "SHA1"; + case CAMEL_CIPHER_HASH_RIPEMD160: + return "RIPEMD160"; + default: + return NULL; + } +} + +static GPtrArray * +gpg_ctx_get_argv (struct _GpgCtx *gpg, int status_fd, char **sfd, int passwd_fd, char **pfd) +{ + const char *hash_str; + GPtrArray *argv; + char *buf; + int i; + + argv = g_ptr_array_new (); + g_ptr_array_add (argv, "gpg"); + + g_ptr_array_add (argv, "--verbose"); + g_ptr_array_add (argv, "--no-secmem-warning"); + g_ptr_array_add (argv, "--no-greeting"); + g_ptr_array_add (argv, "--batch"); + g_ptr_array_add (argv, "--yes"); + + *sfd = buf = g_strdup_printf ("%d", status_fd); + g_ptr_array_add (argv, "--status-fd"); + g_ptr_array_add (argv, buf); + + switch (gpg->mode) { + case GPG_CTX_MODE_SIGN: + g_ptr_array_add (argv, "--sign"); + g_ptr_array_add (argv, "-b"); + if (gpg->armor) + g_ptr_array_add (argv, "--armor"); + hash_str = gpg_hash_str (gpg->hash); + if (hash_str) { + g_ptr_array_add (argv, "--digest-algo"); + g_ptr_array_add (argv, (char *) hash_str); + } + if (gpg->userid) { + g_ptr_array_add (argv, "-u"); + g_ptr_array_add (argv, (char *) gpg->userid); + } + g_ptr_array_add (argv, "--output"); + g_ptr_array_add (argv, "-"); + break; + case GPG_CTX_MODE_VERIFY: + if (!camel_session_is_online (gpg->session)) + g_ptr_array_add (argv, "--no-auto-key-retrieve"); + g_ptr_array_add (argv, "--no-tty"); + g_ptr_array_add (argv, "--verify"); + if (gpg->sigfile) + g_ptr_array_add (argv, gpg->sigfile); + g_ptr_array_add (argv, "-"); + break; + case GPG_CTX_MODE_ENCRYPT: + g_ptr_array_add (argv, "--encrypt"); + if (gpg->armor) + g_ptr_array_add (argv, "--armor"); + if (gpg->always_trust) + g_ptr_array_add (argv, "--always-trust"); + if (gpg->userid) { + g_ptr_array_add (argv, "-u"); + g_ptr_array_add (argv, (char *) gpg->userid); + } + if (gpg->recipients) { + for (i = 0; i < gpg->recipients->len; i++) { + g_ptr_array_add (argv, "-r"); + g_ptr_array_add (argv, gpg->recipients->pdata[i]); + } + } + g_ptr_array_add (argv, "--output"); + g_ptr_array_add (argv, "-"); + break; + case GPG_CTX_MODE_DECRYPT: + g_ptr_array_add (argv, "--decrypt"); + g_ptr_array_add (argv, "--output"); + g_ptr_array_add (argv, "-"); + break; + } + + if (gpg->need_passwd && passwd_fd != -1) { + *pfd = buf = g_strdup_printf ("%d", passwd_fd); + g_ptr_array_add (argv, "--passphrase-fd"); + g_ptr_array_add (argv, buf); + } + + g_ptr_array_add (argv, NULL); + + return argv; +} + +static int +gpg_ctx_op_start (struct _GpgCtx *gpg) +{ + char *status_fd = NULL, *passwd_fd = NULL; + int i, maxfd, fds[10]; + GPtrArray *argv; + + for (i = 0; i < 10; i++) + fds[i] = -1; + + maxfd = gpg->need_passwd ? 10 : 8; + for (i = 0; i < maxfd; i += 2) { + if (pipe (fds + i) == -1) + goto exception; + } + + argv = gpg_ctx_get_argv (gpg, fds[6], &status_fd, fds[9], &passwd_fd); + + if (!(gpg->pid = fork ())) { + /* child process */ + + if ((dup2 (fds[0], STDIN_FILENO) < 0 ) || + (dup2 (fds[3], STDOUT_FILENO) < 0 ) || + (dup2 (fds[5], STDERR_FILENO) < 0 )) { + _exit (255); + } + + /* Dissociate from camel's controlling terminal so + * that gpg won't be able to read from it. + */ + setsid (); + + maxfd = sysconf (_SC_OPEN_MAX); + if (maxfd > 0) { + /* Loop over all fds. */ + for (i = 0; i < maxfd; i++) { + if ((i != STDIN_FILENO) && + (i != STDOUT_FILENO) && + (i != STDERR_FILENO) && + (i != fds[7]) && /* status fd */ + (i != fds[8])) /* passwd fd */ + close (i); + } + } + + /* run gpg */ + execvp (gpg->path, (char **) argv->pdata); + _exit (255); + } else if (gpg->pid < 0) { + g_ptr_array_free (argv, TRUE); + g_free (status_fd); + g_free (passwd_fd); + goto exception; + } + + g_ptr_array_free (argv, TRUE); + g_free (status_fd); + g_free (passwd_fd); + + /* Parent */ + close (fds[0]); + gpg->stdin = fds[1]; + gpg->stdout = fds[2]; + close (fds[3]); + gpg->stderr = fds[4]; + close (fds[5]); + gpg->status_fd = fds[6]; + close (fds[7]); + if (gpg->need_passwd) { + close (fds[8]); + gpg->passwd_fd = fds[9]; + fcntl (gpg->passwd_fd, F_SETFL, O_NONBLOCK); + } + + fcntl (gpg->stdin, F_SETFL, O_NONBLOCK); + fcntl (gpg->stdout, F_SETFL, O_NONBLOCK); + fcntl (gpg->stderr, F_SETFL, O_NONBLOCK); + + return 0; + + exception: + + for (i = 0; i < 10; i++) { + if (fds[i] != -1) + close (fds[i]); + } + + return -1; +} + +static const char * +next_token (const char *in, char **token) +{ + const char *start, *inptr = in; + + while (*inptr == ' ') + inptr++; + + if (*inptr == '\0' || *inptr == '\n') { + if (token) + *token = NULL; + return inptr; + } + + start = inptr; + while (*inptr && *inptr != ' ' && *inptr != '\n') + inptr++; + + if (token) + *token = g_strndup (start, inptr - start); + + return inptr; +} + +static int +gpg_ctx_parse_status (struct _GpgCtx *gpg, const char *status, CamelException *ex) +{ + if (strncmp (status, "[GNUPG:] ", 9) != 0) + return -1; + + status += 9; + + if (!strncmp (status, "USERID_HINT ", 12)) { + char *hint, *user; + + status += 12; + status = next_token (status, &hint); + if (!hint) { + camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, + _("Failed to parse gpg userid hint.")); + return -1; + } + + if (g_hash_table_lookup (gpg->userid_hint, hint)) { + /* we already have this userid hint... */ + g_free (hint); + return 0; + } + + user = g_strdup (status); + g_strstrip (user); + + g_hash_table_insert (gpg->userid_hint, hint, user); + } else if (!strncmp (status, "NEED_PASSPHRASE ", 16)) { + char *prompt, *userid; + const char *name; + + status += 16; + + status = next_token (status, &userid); + if (!userid) { + camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, + _("Failed to parse gpg passphrase request.")); + return -1; + } + + name = g_hash_table_lookup (gpg->userid_hint, userid); + if (!name) + name = userid; + + prompt = g_strdup_printf (_("You need a passphrase to unlock the key for\n" + "user: \"%s\""), name); + + gpg->passwd = camel_session_get_password (gpg->session, prompt, TRUE, NULL, userid, ex); + g_free (prompt); + g_free (userid); + + if (gpg->passwd == NULL) { + if (!camel_exception_is_set (ex)) + camel_exception_set (ex, CAMEL_EXCEPTION_USER_CANCEL, _("Cancelled.")); + return -1; + } + } else if (!strncmp (status, "GOOD_PASSPHRASE ", 16)) { + g_free (gpg->passwd); + gpg->passwd = NULL; + gpg->need_passwd = FALSE; + } else if (!strncmp (status, "BAD_PASSPHRASE ", 15)) { + g_free (gpg->passwd); + gpg->passwd = NULL; + gpg->need_passwd = TRUE; + } else if (!strncmp (status, "UNEXPECTED ", 11)) { + /* this is an error */ + return -1; + } else { + /* check to see if we are complete */ + switch (gpg->mode) { + case GPG_CTX_MODE_SIGN: + if (!strncmp (status, "SIG_CREATED ", 12)) + gpg->complete = TRUE; + break; + case GPG_CTX_MODE_VERIFY: + if (!strncmp (status, "TRUST_", 6)) + gpg->complete = TRUE; + break; + case GPG_CTX_MODE_ENCRYPT: + if (!strncmp (status, "END_ENCRYPTION", 14)) + gpg->complete = TRUE; + break; + case GPG_CTX_MODE_DECRYPT: + if (!strncmp (status, "END_DECRYPTION", 14)) + gpg->complete = TRUE; + break; + } + } + + return 0; +} + +static int +gpg_ctx_op_step (struct _GpgCtx *gpg, CamelException *ex) +{ + struct timeval timeout; + fd_set rdset, wrset; + const char *mode; + int maxfd = 0; + int ret; + + do { + FD_ZERO (&rdset); + FD_SET (gpg->stdout, &rdset); + FD_SET (gpg->stderr, &rdset); + FD_SET (gpg->status_fd, &rdset); + + maxfd = MAX (gpg->stdout, gpg->stderr); + maxfd = MAX (maxfd, gpg->status_fd); + + FD_ZERO (&wrset); + FD_SET (gpg->stdin, &wrset); + maxfd = MAX (maxfd, gpg->stdin); + if (gpg->passwd_fd != -1) { + FD_SET (gpg->passwd_fd, &wrset); + maxfd = MAX (maxfd, gpg->passwd_fd); + } + + timeout.tv_sec = 10; /* timeout in seconds */ + timeout.tv_usec = 0; + + if ((ret = select (maxfd + 1, &rdset, &wrset, NULL, &timeout)) == 0) + return 0; + + if (ret < 0) { + if (errno == EINTR) + continue; + + return -1; + } + + if (FD_ISSET (gpg->status_fd, &rdset)) { + /* read the status message and decide what to do... */ + char buffer[4096]; + FILE *fp; + + if (gpg->status_fp == NULL) { + gpg->status_fp = fdopen (gpg->status_fd, "r"); + if (gpg->status_fp == NULL) + goto exception; + } + + fp = gpg->status_fp; + + fgets (buffer, sizeof (buffer), fp); + return gpg_ctx_parse_status (gpg, buffer, ex); + } + + if (FD_ISSET (gpg->stdout, &rdset) && gpg->ostream) { + char buffer[4096]; + ssize_t nread; + + nread = read (gpg->stdout, buffer, sizeof (buffer)); + if (nread > 0) + ret = camel_stream_write (gpg->ostream, buffer, (size_t) nread); + + if (ret == -1) + goto exception; + + return 0; + } + + if (FD_ISSET (gpg->stderr, &rdset)) { + char buffer[4096]; + ssize_t nread; + + nread = read (gpg->stdout, buffer, sizeof (buffer)); + if (nread > 0) + g_byte_array_append (gpg->diagnostics, buffer, nread); + + return 0; + } + + if (gpg->passwd_fd != -1 && gpg->need_passwd && gpg->passwd && FD_ISSET (gpg->passwd_fd, &wrset)) { + ssize_t w, nwritten = 0; + size_t n; + + /* send the passphrase to gpg */ + n = strlen (gpg->passwd); + do { + do { + w = write (gpg->passwd_fd, gpg->passwd + nwritten, n - nwritten); + } while (w == -1 && (errno == EINTR || errno == EAGAIN)); + + if (w > 0) + nwritten += w; + } while (nwritten < n && w != -1); + + if (ret == -1) + goto exception; + + return 0; + } + + if (FD_ISSET (gpg->stdin, &wrset) && gpg->istream && !camel_stream_eos (gpg->istream)) { + CamelStream *stream; + + /* write our stream to gpg's stdin */ + stream = camel_stream_fs_new_with_fd (gpg->stdin); + ret = camel_stream_write_to_stream (gpg->istream, stream); + if (ret != -1) + ret = camel_stream_flush (stream); + CAMEL_STREAM_FS (stream)->fd = -1; + camel_object_unref (CAMEL_OBJECT (stream)); + + if (ret == -1) + goto exception; + + return 0; + } + } while (1); + + return 0; + + exception: + + switch (gpg->mode) { + case GPG_CTX_MODE_SIGN: + mode = "sign"; + break; + case GPG_CTX_MODE_VERIFY: + mode = "verify"; + break; + case GPG_CTX_MODE_ENCRYPT: + mode = "encrypt"; + break; + case GPG_CTX_MODE_DECRYPT: + mode = "decrypt"; + break; + default: + g_assert_not_reached (); + mode = NULL; + break; + } + + camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, + _("Failed to GPG %s message: %s\n"), + mode, g_strerror (errno)); + + return -1; +} + +static gboolean +gpg_ctx_op_complete (struct _GpgCtx *gpg) +{ + return gpg->complete; +} + +static void +gpg_ctx_op_cancel (struct _GpgCtx *gpg) +{ + pid_t retval; + int status; + + kill (gpg->pid, SIGTERM); + sleep (1); + retval = waitpid (gpg->pid, &status, WNOHANG); + if (retval == 0) { + /* no more mr nice guy... */ + kill (gpg->pid, SIGKILL); + sleep (1); + waitpid (gpg->pid, &status, WNOHANG); + } +} + +static int +gpg_ctx_op_wait (struct _GpgCtx *gpg) +{ + sigset_t mask, omask; + pid_t retval; + int status; + + sigemptyset (&mask); + sigaddset (&mask, SIGALRM); + sigprocmask (SIG_BLOCK, &mask, &omask); + alarm (1); + retval = waitpid (gpg->pid, &status, 0); + alarm (0); + sigprocmask (SIG_SETMASK, &omask, NULL); + + if (retval == (pid_t) -1 && errno == EINTR) { + /* The child is hanging: send a friendly reminder. */ + kill (gpg->pid, SIGTERM); + sleep (1); + retval = waitpid (gpg->pid, &status, WNOHANG); + if (retval == (pid_t) 0) { + /* Still hanging; use brute force. */ + kill (gpg->pid, SIGKILL); + sleep (1); + retval = waitpid (gpg->pid, &status, WNOHANG); + } + } + + if (retval != (pid_t) -1 && WIFEXITED (status)) + return WEXITSTATUS (status); + else + return -1; +} + + + +static int +gpg_sign (CamelCipherContext *context, const char *userid, CamelCipherHash hash, + CamelStream *istream, CamelStream *ostream, CamelException *ex) +{ + CamelGpgContext *ctx = (CamelGpgContext *) context; + struct _GpgCtx *gpg; + + gpg = gpg_ctx_new (context->session, ctx->path); + gpg_ctx_set_mode (gpg, GPG_CTX_MODE_SIGN); + gpg_ctx_set_hash (gpg, hash); + gpg_ctx_set_armor (gpg, TRUE); + gpg_ctx_set_userid (gpg, userid); + gpg_ctx_set_istream (gpg, istream); + gpg_ctx_set_ostream (gpg, ostream); + + if (gpg_ctx_op_start (gpg) == -1) { + camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, + _("Failed to execute gpg.")); + gpg_ctx_free (gpg); + + return -1; + } + + while (!gpg_ctx_op_complete (gpg)) { + if (camel_operation_cancel_check (NULL)) { + camel_exception_set (ex, CAMEL_EXCEPTION_USER_CANCEL, + _("Cancelled.")); + gpg_ctx_op_cancel (gpg); + gpg_ctx_free (gpg); + + return -1; + } + + if (gpg_ctx_op_step (gpg, ex) == -1) { + gpg_ctx_op_cancel (gpg); + gpg_ctx_free (gpg); + + return -1; + } + } + + if (gpg_ctx_op_wait (gpg) != 0) { + char *diagnostics; + + diagnostics = gpg_ctx_get_diagnostics (gpg); + camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, diagnostics); + g_free (diagnostics); + } + gpg_ctx_free (gpg); + + return 0; +} + + +static int +gpg_clearsign (CamelCipherContext *context, const char *userid, + CamelCipherHash hash, CamelStream *istream, + CamelStream *ostream, CamelException *ex) +{ + return -1; +} + + +static char * +swrite (CamelStream *istream) +{ + CamelStream *ostream; + char *template; + int fd, ret; + + template = g_strdup ("/tmp/evolution-pgp.XXXXXX"); + fd = mkstemp (template); + if (fd == -1) { + g_free (template); + return NULL; + } + + ostream = camel_stream_fs_new_with_fd (fd); + ret = camel_stream_write_to_stream (istream, ostream); + if (ret != -1) { + ret = camel_stream_flush (ostream); + if (ret != -1) + ret = camel_stream_close (ostream); + } + camel_object_unref (CAMEL_OBJECT (ostream)); + + if (ret == -1) { + unlink (template); + g_free (template); + return NULL; + } + + return template; +} + +static CamelCipherValidity * +gpg_verify (CamelCipherContext *context, CamelCipherHash hash, + CamelStream *istream, CamelStream *sigstream, + CamelException *ex) +{ + CamelGpgContext *ctx = (CamelGpgContext *) context; + CamelCipherValidity *validity; + char *diagnostics = NULL; + struct _GpgCtx *gpg; + char *sigfile = NULL; + gboolean valid; + + if (sigstream != NULL) { + /* We are going to verify a detached signature so save + the signature to a temp file. */ + sigfile = swrite (sigstream); + if (sigfile == NULL) { + camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, + _("Cannot verify this message: couldn't create temp file: %s"), + g_strerror (errno)); + return NULL; + } + } + + gpg = gpg_ctx_new (context->session, ctx->path); + gpg_ctx_set_mode (gpg, GPG_CTX_MODE_VERIFY); + gpg_ctx_set_hash (gpg, hash); + gpg_ctx_set_sigfile (gpg, sigfile); + gpg_ctx_set_istream (gpg, istream); + + if (gpg_ctx_op_start (gpg) == -1) { + camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, + _("Failed to execute gpg.")); + gpg_ctx_free (gpg); + goto exception; + } + + while (!gpg_ctx_op_complete (gpg)) { + if (camel_operation_cancel_check (NULL)) { + camel_exception_set (ex, CAMEL_EXCEPTION_USER_CANCEL, + _("Cancelled.")); + gpg_ctx_op_cancel (gpg); + goto exception; + } + + if (gpg_ctx_op_step (gpg, ex) == -1) { + gpg_ctx_op_cancel (gpg); + goto exception; + } + } + + diagnostics = gpg_ctx_get_diagnostics (gpg); + + valid = gpg_ctx_op_wait (gpg) == 0; + gpg_ctx_free (gpg); + + validity = camel_cipher_validity_new (); + camel_cipher_validity_set_valid (validity, valid); + camel_cipher_validity_set_description (validity, diagnostics); + g_free (diagnostics); + + if (sigfile) { + unlink (sigfile); + g_free (sigfile); + } + + return validity; + + exception: + + gpg_ctx_free (gpg); + + if (sigfile) { + unlink (sigfile); + g_free (sigfile); + } + + return NULL; +} + + +static int +gpg_encrypt (CamelCipherContext *context, gboolean sign, const char *userid, + GPtrArray *recipients, CamelStream *istream, CamelStream *ostream, + CamelException *ex) +{ + CamelGpgContext *ctx = (CamelGpgContext *) context; + struct _GpgCtx *gpg; + int i; + + gpg = gpg_ctx_new (context->session, ctx->path); + gpg_ctx_set_mode (gpg, GPG_CTX_MODE_ENCRYPT); + gpg_ctx_set_armor (gpg, TRUE); + gpg_ctx_set_userid (gpg, userid); + gpg_ctx_set_istream (gpg, istream); + gpg_ctx_set_ostream (gpg, ostream); + gpg_ctx_set_always_trust (gpg, TRUE); + + for (i = 0; i < recipients->len; i++) { + gpg_ctx_add_recipient (gpg, recipients->pdata[i]); + } + + if (gpg_ctx_op_start (gpg) == -1) { + camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, + _("Failed to execute gpg.")); + gpg_ctx_free (gpg); + + return -1; + } + + while (!gpg_ctx_op_complete (gpg)) { + if (camel_operation_cancel_check (NULL)) { + camel_exception_set (ex, CAMEL_EXCEPTION_USER_CANCEL, + _("Cancelled.")); + gpg_ctx_op_cancel (gpg); + gpg_ctx_free (gpg); + + return -1; + } + + if (gpg_ctx_op_step (gpg, ex) == -1) { + gpg_ctx_op_cancel (gpg); + gpg_ctx_free (gpg); + + return -1; + } + } + + if (gpg_ctx_op_wait (gpg) != 0) { + char *diagnostics; + + diagnostics = gpg_ctx_get_diagnostics (gpg); + camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, diagnostics); + g_free (diagnostics); + } + + gpg_ctx_free (gpg); + + return 0; +} + + +static int +gpg_decrypt (CamelCipherContext *context, CamelStream *istream, + CamelStream *ostream, CamelException *ex) +{ + CamelGpgContext *ctx = (CamelGpgContext *) context; + struct _GpgCtx *gpg; + + gpg = gpg_ctx_new (context->session, ctx->path); + gpg_ctx_set_mode (gpg, GPG_CTX_MODE_DECRYPT); + gpg_ctx_set_istream (gpg, istream); + gpg_ctx_set_ostream (gpg, ostream); + + if (gpg_ctx_op_start (gpg) == -1) { + camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, + _("Failed to execute gpg.")); + gpg_ctx_free (gpg); + + return -1; + } + + while (!gpg_ctx_op_complete (gpg)) { + if (camel_operation_cancel_check (NULL)) { + camel_exception_set (ex, CAMEL_EXCEPTION_USER_CANCEL, + _("Cancelled.")); + gpg_ctx_op_cancel (gpg); + gpg_ctx_free (gpg); + + return -1; + } + + if (gpg_ctx_op_step (gpg, ex) == -1) { + gpg_ctx_op_cancel (gpg); + gpg_ctx_free (gpg); + + return -1; + } + } + + if (gpg_ctx_op_wait (gpg) != 0) { + char *diagnostics; + + diagnostics = gpg_ctx_get_diagnostics (gpg); + camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, diagnostics); + g_free (diagnostics); + } + + gpg_ctx_free (gpg); + + return 0; +} diff --git a/camel/camel-gpg-context.h b/camel/camel-gpg-context.h new file mode 100644 index 0000000000..263e76fa0b --- /dev/null +++ b/camel/camel-gpg-context.h @@ -0,0 +1,68 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Authors: Jeffrey Stedfast + * + * Copyright 2002 Ximian, Inc. (www.ximian.com) + * + * This program 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. + * + * This program 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 Street #330, Boston, MA 02111-1307, USA. + * + */ + + +#ifndef __CAMEL_GPG_CONTEXT_H__ +#define __CAMEL_GPG_CONTEXT_H__ + +#ifdef __cplusplus +extern "C" { +#pragma } +#endif /* __cplusplus */ + +#include + +#define CAMEL_GPG_CONTEXT_TYPE (camel_gpg_context_get_type ()) +#define CAMEL_GPG_CONTEXT(obj) (CAMEL_CHECK_CAST((obj), CAMEL_GPG_CONTEXT_TYPE, CamelGpgContext)) +#define CAMEL_GPG_CONTEXT_CLASS(k) (CAMEL_CHECK_CLASS_CAST ((k), CAMEL_GPG_CONTEXT_TYPE, CamelGpgContextClass)) +#define CAMEL_IS_GPG_CONTEXT(o) (CAMEL_CHECK_TYPE((o), CAMEL_GPG_CONTEXT_TYPE)) + +typedef struct _CamelGpgContext CamelGpgContext; +typedef struct _CamelGpgContextClass CamelGpgContextClass; + +struct _CamelGpgContext { + CamelCipherContext parent_object; + + char *path; + + gboolean always_trust; +}; + +struct _CamelGpgContextClass { + CamelCipherContextClass parent_class; + +}; + + +CamelType camel_gpg_context_get_type (void); + +CamelCipherContext *camel_gpg_context_new (CamelSession *session, const char *path); + +void camel_gpg_context_set_always_trust (CamelGpgContext *ctx, gboolean trust); +gboolean camel_gpg_context_get_always_trust (CamelGpgContext *ctx); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __CAMEL_GPG_CONTEXT_H__ */ + diff --git a/camel/camel-pgp-context.c b/camel/camel-pgp-context.c index 0b5916bca2..eb315c3a18 100644 --- a/camel/camel-pgp-context.c +++ b/camel/camel-pgp-context.c @@ -155,7 +155,7 @@ camel_pgp_context_get_type (void) * * Return value: the new CamelPgpContext **/ -CamelPgpContext * +CamelCipherContext * camel_pgp_context_new (CamelSession *session, CamelPgpType type, const char *path) { CamelPgpContext *context; @@ -172,7 +172,7 @@ camel_pgp_context_new (CamelSession *session, CamelPgpType type, const char *pat context->priv->type = type; context->priv->path = g_strdup (path); - return context; + return (CamelCipherContext *) context; } diff --git a/camel/camel-pgp-context.h b/camel/camel-pgp-context.h index 0eaa3ec4e3..36d91f7007 100644 --- a/camel/camel-pgp-context.h +++ b/camel/camel-pgp-context.h @@ -60,19 +60,8 @@ typedef struct _CamelPgpContextClass { CamelType camel_pgp_context_get_type (void); -CamelPgpContext *camel_pgp_context_new (CamelSession *session, CamelPgpType type, - const char *path); - -/* PGP routines */ -#define camel_pgp_sign(c, u, h, i, o, e) camel_cipher_sign (CAMEL_CIPHER_CONTEXT (c), u, h, i, o, e) - -#define camel_pgp_clearsign(c, u, h, i, o, e) camel_cipher_clearsign (CAMEL_CIPHER_CONTEXT (c), u, h, i, o, e) - -#define camel_pgp_verify(c, i, s, e) camel_cipher_verify (CAMEL_CIPHER_CONTEXT (c), CAMEL_CIPHER_HASH_DEFAULT, i, s, e) - -#define camel_pgp_encrypt(c, s, u, r, i, o, e) camel_cipher_encrypt (CAMEL_CIPHER_CONTEXT (c), s, u, r, i, o, e) - -#define camel_pgp_decrypt(c, i, o, e) camel_cipher_decrypt (CAMEL_CIPHER_CONTEXT (c), i, o, e) +CamelCipherContext *camel_pgp_context_new (CamelSession *session, CamelPgpType type, + const char *path); #ifdef __cplusplus } diff --git a/camel/camel-pgp-mime.c b/camel/camel-pgp-mime.c index b0e3ab379e..c297194bd3 100644 --- a/camel/camel-pgp-mime.c +++ b/camel/camel-pgp-mime.c @@ -240,7 +240,7 @@ pgp_mime_part_sign_prepare_part (CamelMimePart *mime_part, GSList **encodings) /** * camel_pgp_mime_part_sign: - * @context: PGP Context + * @cipher: PGP Cipher Context * @mime_part: a MIME part that will be replaced by a pgp signed part * @userid: userid to sign with * @hash: one of CAMEL_PGP_HASH_TYPE_MD5 or CAMEL_PGP_HASH_TYPE_SHA1 @@ -251,7 +251,7 @@ pgp_mime_part_sign_prepare_part (CamelMimePart *mime_part, GSList **encodings) * @ex will be set and #part will remain untouched. **/ void -camel_pgp_mime_part_sign (CamelPgpContext *context, CamelMimePart **mime_part, const char *userid, +camel_pgp_mime_part_sign (CamelCipherContext *cipher, CamelMimePart **mime_part, const char *userid, CamelCipherHash hash, CamelException *ex) { CamelMimePart *part, *signed_part; @@ -327,7 +327,7 @@ camel_pgp_mime_part_sign (CamelPgpContext *context, CamelMimePart **mime_part, c } /* get the signature */ - if (camel_pgp_sign (context, userid, hash, stream, sigstream, ex) == -1) { + if (camel_cipher_sign (cipher, userid, hash, stream, sigstream, ex) == -1) { GSList *list; camel_object_unref (CAMEL_OBJECT (stream)); @@ -381,14 +381,14 @@ camel_pgp_mime_part_sign (CamelPgpContext *context, CamelMimePart **mime_part, c /** * camel_pgp_mime_part_verify: - * @context: PGP Context + * @cipher: PGP Cipher Context * @mime_part: a multipart/signed MIME Part * @ex: exception * * Returns a CamelCipherValidity on success or NULL on fail. **/ CamelCipherValidity * -camel_pgp_mime_part_verify (CamelPgpContext *context, CamelMimePart *mime_part, CamelException *ex) +camel_pgp_mime_part_verify (CamelCipherContext *cipher, CamelMimePart *mime_part, CamelException *ex) { CamelDataWrapper *wrapper; CamelMultipart *multipart; @@ -437,7 +437,7 @@ camel_pgp_mime_part_verify (CamelPgpContext *context, CamelMimePart *mime_part, camel_stream_reset (sigstream); /* verify */ - valid = camel_pgp_verify (context, stream, sigstream, ex); + valid = camel_cipher_verify (cipher, CAMEL_CIPHER_HASH_DEFAULT, stream, sigstream, ex); d(printf ("attempted to verify data:\n----- BEGIN SIGNED PART -----\n%.*s----- END SIGNED PART -----\n", CAMEL_STREAM_MEM (stream)->buffer->len, CAMEL_STREAM_MEM (stream)->buffer->data)); @@ -451,7 +451,7 @@ camel_pgp_mime_part_verify (CamelPgpContext *context, CamelMimePart *mime_part, /** * camel_pgp_mime_part_encrypt: - * @context: PGP Context + * @cipher: PGP Cipher Context * @mime_part: a MIME part that will be replaced by a pgp encrypted part * @recipients: list of recipient PGP Key IDs * @ex: exception which will be set if there are any errors. @@ -461,7 +461,7 @@ camel_pgp_mime_part_verify (CamelPgpContext *context, CamelMimePart *mime_part, * #ex will be set and #part will remain untouched. **/ void -camel_pgp_mime_part_encrypt (CamelPgpContext *context, CamelMimePart **mime_part, +camel_pgp_mime_part_encrypt (CamelCipherContext *cipher, CamelMimePart **mime_part, GPtrArray *recipients, CamelException *ex) { CamelMultipart *multipart; @@ -490,7 +490,7 @@ camel_pgp_mime_part_encrypt (CamelPgpContext *context, CamelMimePart **mime_part /* pgp encrypt */ ciphertext = camel_stream_mem_new (); - if (camel_pgp_encrypt (context, FALSE, NULL, recipients, stream, ciphertext, ex) == -1) { + if (camel_cipher_encrypt (cipher, FALSE, NULL, recipients, stream, ciphertext, ex) == -1) { camel_object_unref (CAMEL_OBJECT (stream)); camel_object_unref (CAMEL_OBJECT (ciphertext)); return; @@ -540,14 +540,14 @@ camel_pgp_mime_part_encrypt (CamelPgpContext *context, CamelMimePart **mime_part /** * camel_pgp_mime_part_decrypt: - * @context: PGP Context + * @cipher: PGP Cipher Context * @mime_part: a multipart/encrypted MIME Part * @ex: exception * * Returns the decrypted MIME Part on success or NULL on fail. **/ CamelMimePart * -camel_pgp_mime_part_decrypt (CamelPgpContext *context, CamelMimePart *mime_part, CamelException *ex) +camel_pgp_mime_part_decrypt (CamelCipherContext *cipher, CamelMimePart *mime_part, CamelException *ex) { CamelDataWrapper *wrapper; CamelMultipart *multipart; @@ -580,7 +580,7 @@ camel_pgp_mime_part_decrypt (CamelPgpContext *context, CamelMimePart *mime_part, /* get the cleartext */ stream = camel_stream_mem_new (); - if (camel_pgp_decrypt (context, ciphertext, stream, ex) == -1) { + if (camel_cipher_decrypt (cipher, ciphertext, stream, ex) == -1) { camel_object_unref (CAMEL_OBJECT (ciphertext)); camel_object_unref (CAMEL_OBJECT (stream)); return NULL; diff --git a/camel/camel-pgp-mime.h b/camel/camel-pgp-mime.h index 7f7928967d..e5dec18cce 100644 --- a/camel/camel-pgp-mime.h +++ b/camel/camel-pgp-mime.h @@ -38,22 +38,22 @@ extern "C" { gboolean camel_pgp_mime_is_rfc2015_signed (CamelMimePart *part); gboolean camel_pgp_mime_is_rfc2015_encrypted (CamelMimePart *part); -void camel_pgp_mime_part_sign (CamelPgpContext *context, +void camel_pgp_mime_part_sign (CamelCipherContext *cipher, CamelMimePart **mime_part, const char *userid, CamelCipherHash hash, CamelException *ex); -CamelCipherValidity *camel_pgp_mime_part_verify (CamelPgpContext *context, +CamelCipherValidity *camel_pgp_mime_part_verify (CamelCipherContext *cipher, CamelMimePart *mime_part, CamelException *ex); -void camel_pgp_mime_part_encrypt (CamelPgpContext *context, +void camel_pgp_mime_part_encrypt (CamelCipherContext *cipher, CamelMimePart **mime_part, GPtrArray *recipients, CamelException *ex); -CamelMimePart *camel_pgp_mime_part_decrypt (CamelPgpContext *context, +CamelMimePart *camel_pgp_mime_part_decrypt (CamelCipherContext *cipher, CamelMimePart *mime_part, CamelException *ex); diff --git a/camel/camel.h b/camel/camel.h index 722b6190fd..1deebd1431 100644 --- a/camel/camel.h +++ b/camel/camel.h @@ -30,7 +30,7 @@ #ifdef __cplusplus extern "C" { #pragma } -#endif /* __cplusplus }*/ +#endif /* __cplusplus */ #include #include @@ -59,6 +59,7 @@ extern "C" { #include #include #include +#include #include #include #include -- cgit v1.2.3