2 * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2012 Colin Leroy <colin@colino.net> and
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 3 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
23 #include "claws-features.h"
32 #include <glib/gi18n.h>
39 #include <plugins/pgpcore/sgpgme.h>
40 #include <plugins/pgpcore/prefs_gpg.h>
41 #include <plugins/pgpcore/pgp_utils.h>
42 #include <plugins/pgpcore/passphrase.h>
44 #include "alertpanel.h"
45 #include "prefs_common.h"
49 typedef struct _PrivacyDataPGP PrivacyDataPGP;
51 struct _PrivacyDataPGP
55 gboolean done_sigtest;
57 gpgme_verify_result_t sigstatus;
61 static PrivacySystem smime_system;
63 static gint smime_check_signature(MimeInfo *mimeinfo);
65 static PrivacyDataPGP *smime_new_privacydata()
70 if (gpgme_new(&ctx) != GPG_ERR_NO_ERROR) {
71 debug_print("gpgme_new failed");
75 data = g_new0(PrivacyDataPGP, 1);
76 data->data.system = &smime_system;
77 data->done_sigtest = FALSE;
78 data->is_signed = FALSE;
79 data->sigstatus = NULL;
85 static void smime_free_privacydata(PrivacyData *_data)
87 PrivacyDataPGP *data = (PrivacyDataPGP *) _data;
88 gpgme_release(data->ctx);
92 static gboolean smime_is_signed(MimeInfo *mimeinfo)
96 const gchar *protocol, *tmpstr;
97 PrivacyDataPGP *data = NULL;
99 cm_return_val_if_fail(mimeinfo != NULL, FALSE);
100 if (mimeinfo->privacy != NULL) {
101 data = (PrivacyDataPGP *) mimeinfo->privacy;
102 if (data->done_sigtest)
103 return data->is_signed;
106 if (!g_ascii_strcasecmp(mimeinfo->subtype, "pkcs7-mime") ||
107 !g_ascii_strcasecmp(mimeinfo->subtype, "x-pkcs7-mime")) {
108 tmpstr = procmime_mimeinfo_get_parameter(mimeinfo, "smime-type");
109 if (tmpstr && !g_ascii_strcasecmp(tmpstr, "signed-data")) {
111 data = smime_new_privacydata();
114 mimeinfo->privacy = (PrivacyData *) data;
117 data->done_sigtest = TRUE;
118 data->is_signed = TRUE;
119 smime_check_signature(mimeinfo);
125 parent = procmime_mimeinfo_parent(mimeinfo);
129 if ((parent->type != MIMETYPE_MULTIPART) ||
130 g_ascii_strcasecmp(parent->subtype, "signed"))
132 protocol = procmime_mimeinfo_get_parameter(parent, "protocol");
133 if ((protocol == NULL) ||
134 (g_ascii_strcasecmp(protocol, "application/pkcs7-signature") &&
135 g_ascii_strcasecmp(protocol, "application/x-pkcs7-signature")))
138 /* check if mimeinfo is the first child */
139 if (parent->node->children->data != mimeinfo)
143 /* check signature */
144 signature = parent->node->children->next != NULL ?
145 (MimeInfo *) parent->node->children->next->data : NULL;
146 if (signature == NULL)
148 if ((signature->type != MIMETYPE_APPLICATION) ||
149 (g_ascii_strcasecmp(signature->subtype, "pkcs7-signature") &&
150 g_ascii_strcasecmp(signature->subtype, "x-pkcs7-signature")))
154 data = smime_new_privacydata();
157 mimeinfo->privacy = (PrivacyData *) data;
160 data->done_sigtest = TRUE;
161 data->is_signed = TRUE;
166 static gchar *get_canonical_content(FILE *fp, const gchar *boundary)
170 guint boundary_len = 0;
174 boundary_len = strlen(boundary);
175 while (fgets(buf, sizeof(buf), fp) != NULL)
176 if (IS_BOUNDARY(buf, boundary, boundary_len))
180 textbuffer = g_string_new("");
181 while (fgets(buf, sizeof(buf), fp) != NULL) {
184 if (boundary && IS_BOUNDARY(buf, boundary, boundary_len))
187 buf2 = canonicalize_str(buf);
188 g_string_append(textbuffer, buf2);
191 g_string_truncate(textbuffer, textbuffer->len - 2);
193 ret = textbuffer->str;
194 g_string_free(textbuffer, FALSE);
199 static gint smime_check_signature(MimeInfo *mimeinfo)
201 PrivacyDataPGP *data;
202 MimeInfo *parent, *signature;
205 gchar *textstr = NULL;
207 gpgme_data_t sigdata = NULL, textdata = NULL;
209 EncodingType oldenc = ENC_BINARY;
211 cm_return_val_if_fail(mimeinfo != NULL, -1);
212 cm_return_val_if_fail(mimeinfo->privacy != NULL, -1);
214 data = (PrivacyDataPGP *) mimeinfo->privacy;
217 if ((err = gpgme_new(&data->ctx)) != GPG_ERR_NO_ERROR) {
218 debug_print("gpgme_new failed: %s\n",
219 gpgme_strerror(err));
224 debug_print("Checking S/MIME signature\n");
226 err = gpgme_set_protocol(data->ctx, GPGME_PROTOCOL_CMS);
229 debug_print ("gpgme_set_protocol failed: %s\n",
230 gpgme_strerror (err));
232 parent = procmime_mimeinfo_parent(mimeinfo);
234 fp = g_fopen(parent->data.filename, "rb");
235 cm_return_val_if_fail(fp != NULL, SIGNATURE_INVALID);
237 boundary = g_hash_table_lookup(parent->typeparameters, "boundary");
239 gchar *tmpfile = get_tmp_file();
240 debug_print("no boundary\n");
242 if (mimeinfo->encoding_type != ENC_BASE64) {
243 procmime_encode_content(mimeinfo, ENC_BASE64);
245 oldenc = mimeinfo->encoding_type;
246 if (mimeinfo->encoding_type == ENC_BASE64)
247 mimeinfo->encoding_type = ENC_BINARY;
248 if (procmime_get_part(tmpfile, mimeinfo) == 0) {
249 textstr = file_read_to_str(tmpfile);
253 if (mimeinfo->encoding_type != oldenc)
254 mimeinfo->encoding_type = oldenc;
258 textstr = get_canonical_content(fp, boundary);
260 err = gpgme_data_new_from_mem(&textdata, textstr, textstr?strlen(textstr):0, 0);
263 debug_print ("gpgme_data_new_from_mem failed: %s\n",
264 gpgme_strerror (err));
267 if (!g_ascii_strcasecmp(mimeinfo->subtype, "pkcs7-mime") ||
268 !g_ascii_strcasecmp(mimeinfo->subtype, "x-pkcs7-mime")) {
269 tmpstr = procmime_mimeinfo_get_parameter(mimeinfo, "smime-type");
270 if (tmpstr && !g_ascii_strcasecmp(tmpstr, "signed-data")) {
273 if (oldenc == ENC_BASE64)
274 gpgme_data_set_encoding (textdata, GPGME_DATA_ENCODING_BASE64);
275 gpgme_data_new(&cipher);
277 sgpgme_verify_signature (data->ctx, textdata, NULL, cipher);
278 gpgme_data_release(textdata);
280 cm_gpgme_data_rewind(cipher);
281 textstr = sgpgme_data_release_and_get_mem(cipher, &len);
283 if (textstr && len > 0)
286 if (textstr && len) {
287 gchar *tmp_file = get_tmp_file();
288 MimeInfo *newinfo = NULL, *decinfo = NULL, *parentinfo = NULL;
290 str_write_to_file(textstr, tmp_file);
291 newinfo = procmime_scan_file(tmp_file);
292 decinfo = g_node_first_child(newinfo->node) != NULL ?
293 g_node_first_child(newinfo->node)->data : NULL;
298 g_node_unlink(decinfo->node);
299 procmime_mimeinfo_free_all(newinfo);
301 parentinfo = procmime_mimeinfo_parent(mimeinfo);
303 if (parentinfo->type == MIMETYPE_MESSAGE &&
304 !strcmp(parentinfo->subtype, "rfc822")) {
305 procmime_decode_content(parentinfo);
306 procmime_encode_content(parentinfo, ENC_BASE64);
307 procmime_encode_content(parentinfo, ENC_8BIT);
308 if (parentinfo->content == MIMECONTENT_MEM) {
310 (gint)(strstr(parentinfo->data.mem, "\n\n") - parentinfo->data.mem);
312 parentinfo->length = newlen;
315 g_node_prepend(parentinfo->node, decinfo->node);
324 signature = (MimeInfo *) mimeinfo->node->next->data;
325 sigdata = sgpgme_data_from_mimeinfo(signature);
328 if (signature->encoding_type == ENC_BASE64) {
329 err = gpgme_data_set_encoding (sigdata, GPGME_DATA_ENCODING_BASE64);
333 debug_print ("gpgme_data_set_encoding failed: %s\n",
334 gpgme_strerror (err));
338 sgpgme_verify_signature (data->ctx, sigdata, textdata, NULL);
340 gpgme_data_release(sigdata);
341 gpgme_data_release(textdata);
348 static SignatureStatus smime_get_sig_status(MimeInfo *mimeinfo)
350 PrivacyDataPGP *data = (PrivacyDataPGP *) mimeinfo->privacy;
352 cm_return_val_if_fail(data != NULL, SIGNATURE_INVALID);
354 return sgpgme_sigstat_gpgme_to_privacy(data->ctx, data->sigstatus);
357 static gchar *smime_get_sig_info_short(MimeInfo *mimeinfo)
359 PrivacyDataPGP *data = (PrivacyDataPGP *) mimeinfo->privacy;
361 cm_return_val_if_fail(data != NULL, g_strdup("Error"));
363 return sgpgme_sigstat_info_short(data->ctx, data->sigstatus);
366 static gchar *smime_get_sig_info_full(MimeInfo *mimeinfo)
368 PrivacyDataPGP *data = (PrivacyDataPGP *) mimeinfo->privacy;
370 cm_return_val_if_fail(data != NULL, g_strdup("Error"));
372 return sgpgme_sigstat_info_full(data->ctx, data->sigstatus);
375 static gboolean smime_is_encrypted(MimeInfo *mimeinfo)
379 if (mimeinfo->type != MIMETYPE_APPLICATION)
381 if (!g_ascii_strcasecmp(mimeinfo->subtype, "pkcs7-mime")) {
382 tmpstr = procmime_mimeinfo_get_parameter(mimeinfo, "smime-type");
383 if (tmpstr && g_ascii_strcasecmp(tmpstr, "enveloped-data"))
388 } else if (!g_ascii_strcasecmp(mimeinfo->subtype, "x-pkcs7-mime")) {
389 tmpstr = procmime_mimeinfo_get_parameter(mimeinfo, "smime-type");
390 if (tmpstr && g_ascii_strcasecmp(tmpstr, "enveloped-data"))
398 static MimeInfo *smime_decrypt(MimeInfo *mimeinfo)
400 MimeInfo *encinfo, *decinfo, *parseinfo;
401 gpgme_data_t cipher = NULL, plain = NULL;
405 gpgme_verify_result_t sigstat = NULL;
406 PrivacyDataPGP *data = NULL;
412 cm_return_val_if_fail(smime_is_encrypted(mimeinfo), NULL);
414 if ((err = gpgme_new(&ctx)) != GPG_ERR_NO_ERROR) {
415 privacy_set_error(_("Couldn't initialize GPG context, %s"), gpgme_strerror(err));
419 err = gpgme_set_protocol(ctx, GPGME_PROTOCOL_CMS);
421 debug_print ("gpgme_set_protocol failed: %s\n",
422 gpgme_strerror (err));
423 privacy_set_error(_("Couldn't set GPG protocol, %s"), gpgme_strerror(err));
427 gpgme_set_armor(ctx, TRUE);
431 cipher = sgpgme_data_from_mimeinfo(encinfo);
433 plain = sgpgme_decrypt_verify(cipher, &sigstat, ctx);
435 gpgme_data_release(cipher);
437 debug_print("plain is null!\n");
442 fname = g_strdup_printf("%s%cplaintext.%08x",
443 get_mime_tmp_dir(), G_DIR_SEPARATOR, ++id);
445 if ((dstfp = g_fopen(fname, "wb")) == NULL) {
446 FILE_OP_ERROR(fname, "g_fopen");
448 gpgme_data_release(plain);
450 debug_print("can't open!\n");
451 privacy_set_error(_("Couldn't open temporary file"));
455 if (fprintf(dstfp, "MIME-Version: 1.0\n") < 0) {
456 FILE_OP_ERROR(fname, "fprintf");
459 gpgme_data_release(plain);
461 debug_print("can't close!\n");
462 privacy_set_error(_("Couldn't write to temporary file"));
466 chars = sgpgme_data_release_and_get_mem(plain, &len);
469 if (fwrite(chars, 1, len, dstfp) < len) {
470 FILE_OP_ERROR(fname, "fwrite");
474 gpgme_data_release(plain);
476 debug_print("can't write!\n");
477 privacy_set_error(_("Couldn't write to temporary file"));
481 if (fclose(dstfp) == EOF) {
482 FILE_OP_ERROR(fname, "fclose");
485 gpgme_data_release(plain);
487 debug_print("can't close!\n");
488 privacy_set_error(_("Couldn't close temporary file"));
493 parseinfo = procmime_scan_file(fname);
495 if (parseinfo == NULL) {
496 privacy_set_error(_("Couldn't parse decrypted file."));
500 decinfo = g_node_first_child(parseinfo->node) != NULL ?
501 g_node_first_child(parseinfo->node)->data : NULL;
502 if (decinfo == NULL) {
503 privacy_set_error(_("Couldn't parse decrypted file parts."));
508 g_node_unlink(decinfo->node);
509 procmime_mimeinfo_free_all(parseinfo);
513 if (sigstat != NULL && sigstat->signatures != NULL) {
514 if (decinfo->privacy != NULL) {
515 data = (PrivacyDataPGP *) decinfo->privacy;
517 data = smime_new_privacydata();
522 decinfo->privacy = (PrivacyData *) data;
524 data->done_sigtest = TRUE;
525 data->is_signed = TRUE;
526 data->sigstatus = sigstat;
528 gpgme_release(data->ctx);
538 gboolean smime_sign(MimeInfo *mimeinfo, PrefsAccount *account, const gchar *from_addr)
540 MimeInfo *msgcontent, *sigmultipart, *newinfo;
541 gchar *textstr, *micalg = NULL;
543 gchar *boundary = NULL;
546 gpgme_data_t gpgtext, gpgsig;
549 struct passphrase_cb_info_s info;
550 gpgme_sign_result_t result = NULL;
552 gchar *real_content = NULL;
556 perror("my_tmpfile");
559 procmime_write_mimeinfo(mimeinfo, fp);
562 /* read temporary file into memory */
563 test_msg = file_read_stream_to_str(fp);
566 memset (&info, 0, sizeof info);
568 /* remove content node from message */
569 msgcontent = (MimeInfo *) mimeinfo->node->children->data;
570 g_node_unlink(msgcontent->node);
572 /* create temporary multipart for content */
573 sigmultipart = procmime_mimeinfo_new();
574 sigmultipart->type = MIMETYPE_MULTIPART;
575 sigmultipart->subtype = g_strdup("signed");
580 boundary = generate_mime_boundary("Sig");
581 } while (strstr(test_msg, boundary) != NULL);
585 g_hash_table_insert(sigmultipart->typeparameters, g_strdup("boundary"),
587 g_hash_table_insert(sigmultipart->typeparameters, g_strdup("protocol"),
588 g_strdup("application/pkcs7-signature"));
589 g_node_append(sigmultipart->node, msgcontent->node);
590 g_node_append(mimeinfo->node, sigmultipart->node);
592 /* write message content to temporary file */
595 perror("my_tmpfile");
598 procmime_write_mimeinfo(sigmultipart, fp);
601 /* read temporary file into memory */
602 textstr = get_canonical_content(fp, boundary);
608 gpgme_data_new_from_mem(&gpgtext, textstr, textstr?strlen(textstr):0, 0);
609 gpgme_data_new(&gpgsig);
611 gpgme_set_armor(ctx, TRUE);
612 gpgme_signers_clear (ctx);
614 err = gpgme_set_protocol(ctx, GPGME_PROTOCOL_CMS);
617 debug_print ("gpgme_set_protocol failed: %s\n",
618 gpgme_strerror (err));
619 gpgme_data_release(gpgtext);
624 if (!sgpgme_setup_signers(ctx, account, from_addr)) {
625 debug_print("setup_signers failed\n");
626 gpgme_data_release(gpgtext);
632 prefs_gpg_enable_agent(TRUE);
633 gpgme_set_passphrase_cb (ctx, NULL, &info);
635 err = gpgme_op_sign(ctx, gpgtext, gpgsig, GPGME_SIG_MODE_DETACH);
636 if (err != GPG_ERR_NO_ERROR) {
637 alertpanel_error("S/MIME : Cannot sign, %s (%d)", gpg_strerror(err), gpg_err_code(err));
638 gpgme_data_release(gpgtext);
642 result = gpgme_op_sign_result(ctx);
643 if (result && result->signatures) {
644 if (gpgme_get_protocol(ctx) == GPGME_PROTOCOL_OpenPGP) {
645 micalg = g_strdup_printf("pgp-%s", g_ascii_strdown(gpgme_hash_algo_name(
646 result->signatures->hash_algo),-1));
648 micalg = g_strdup(gpgme_hash_algo_name(
649 result->signatures->hash_algo));
652 /* can't get result (maybe no signing key?) */
653 debug_print("gpgme_op_sign_result error\n");
658 sigcontent = sgpgme_data_release_and_get_mem(gpgsig, &len);
659 gpgme_data_release(gpgtext);
667 real_content = sigcontent+strlen("-----BEGIN SIGNED MESSAGE-----\n");
668 if (!strstr(real_content, "-----END SIGNED MESSAGE-----")) {
669 debug_print("missing end\n");
674 *strstr(real_content, "-----END SIGNED MESSAGE-----") = '\0';
676 g_hash_table_insert(sigmultipart->typeparameters, g_strdup("micalg"),
679 newinfo = procmime_mimeinfo_new();
680 newinfo->type = MIMETYPE_APPLICATION;
681 newinfo->subtype = g_strdup("pkcs7-signature");
682 g_hash_table_insert(newinfo->typeparameters, g_strdup("name"),
683 g_strdup("smime.p7s"));
684 newinfo->content = MIMECONTENT_MEM;
685 newinfo->disposition = DISPOSITIONTYPE_ATTACHMENT;
686 g_hash_table_insert(newinfo->dispositionparameters, g_strdup("filename"),
687 g_strdup("smime.p7s"));
688 newinfo->data.mem = g_malloc(len + 1);
689 g_memmove(newinfo->data.mem, real_content, len);
690 newinfo->data.mem[len] = '\0';
691 newinfo->encoding_type = ENC_BASE64;
692 g_node_append(sigmultipart->node, newinfo->node);
698 gchar *smime_get_encrypt_data(GSList *recp_names)
700 return sgpgme_get_encrypt_data(recp_names, GPGME_PROTOCOL_CMS);
703 static const gchar *smime_get_encrypt_warning(void)
705 if (prefs_gpg_should_skip_encryption_warning(smime_system.id))
708 return _("Please note that email headers, like Subject, "
709 "are not encrypted by the S/MIME system.");
712 static void smime_inhibit_encrypt_warning(gboolean inhibit)
715 prefs_gpg_add_skip_encryption_warning(smime_system.id);
717 prefs_gpg_remove_skip_encryption_warning(smime_system.id);
720 gboolean smime_encrypt(MimeInfo *mimeinfo, const gchar *encrypt_data)
722 MimeInfo *msgcontent, *encmultipart;
726 gchar *textstr = NULL;
727 gpgme_data_t gpgtext = NULL, gpgenc = NULL;
728 gpgme_ctx_t ctx = NULL;
729 gpgme_key_t *kset = NULL;
730 gchar **fprs = g_strsplit(encrypt_data, " ", -1);
733 gchar *tmpfile = NULL;
735 while (fprs[i] && strlen(fprs[i])) {
739 if ((err = gpgme_new(&ctx)) != GPG_ERR_NO_ERROR) {
740 debug_print ("gpgme_new failed: %s\n", gpgme_strerror(err));
744 err = gpgme_set_protocol(ctx, GPGME_PROTOCOL_CMS);
747 debug_print ("gpgme_set_protocol failed: %s\n",
748 gpgme_strerror (err));
752 kset = g_malloc(sizeof(gpgme_key_t)*(i+1));
753 memset(kset, 0, sizeof(gpgme_key_t)*(i+1));
756 while (fprs[i] && strlen(fprs[i])) {
759 err = gpgme_get_key(ctx, fprs[i], &key, 0);
761 debug_print("can't add key '%s'[%d] (%s)\n", fprs[i],i, gpgme_strerror(err));
764 debug_print("found %s at %d\n", fprs[i], i);
769 debug_print("Encrypting message content\n");
771 /* remove content node from message */
772 msgcontent = (MimeInfo *) mimeinfo->node->children->data;
773 g_node_unlink(msgcontent->node);
776 /* create temporary multipart for content */
777 encmultipart = procmime_mimeinfo_new();
778 encmultipart->type = MIMETYPE_APPLICATION;
779 encmultipart->subtype = g_strdup("x-pkcs7-mime");
780 g_hash_table_insert(encmultipart->typeparameters, g_strdup("name"),
781 g_strdup("smime.p7m"));
782 g_hash_table_insert(encmultipart->typeparameters,
783 g_strdup("smime-type"),
784 g_strdup("enveloped-data"));
786 encmultipart->disposition = DISPOSITIONTYPE_ATTACHMENT;
787 g_hash_table_insert(encmultipart->dispositionparameters, g_strdup("filename"),
788 g_strdup("smime.p7m"));
790 g_node_append(encmultipart->node, msgcontent->node);
792 /* write message content to temporary file */
793 tmpfile = get_tmp_file();
794 fp = g_fopen(tmpfile, "wb");
796 perror("get_tmp_file");
800 procmime_decode_content(msgcontent);
801 procmime_write_mime_header(msgcontent, fp);
802 procmime_write_mimeinfo(msgcontent, fp);
804 canonicalize_file_replace(tmpfile);
805 fp = g_fopen(tmpfile, "rb");
807 perror("get_tmp_file");
813 /* read temporary file into memory */
814 textstr = fp_read_noconv(fp);
819 gpgme_data_new_from_mem(&gpgtext, textstr, textstr?strlen(textstr):0, 0);
820 gpgme_data_new(&gpgenc);
821 cm_gpgme_data_rewind(gpgtext);
823 gpgme_op_encrypt(ctx, kset, GPGME_ENCRYPT_ALWAYS_TRUST, gpgtext, gpgenc);
827 enccontent = sgpgme_data_release_and_get_mem(gpgenc, &len);
830 g_warning("no enccontent\n");
834 tmpfile = get_tmp_file();
835 fp = g_fopen(tmpfile, "wb");
837 if (fwrite(enccontent, 1, len, fp) < len) {
838 FILE_OP_ERROR(tmpfile, "fwrite");
840 claws_unlink(tmpfile);
844 if (fclose(fp) == EOF) {
845 FILE_OP_ERROR(tmpfile, "fclose");
846 claws_unlink(tmpfile);
851 perror("get_tmp_file");
856 gpgme_data_release(gpgtext);
859 /* create encrypted multipart */
860 procmime_mimeinfo_free_all(msgcontent);
861 g_node_append(mimeinfo->node, encmultipart->node);
863 encmultipart->content = MIMECONTENT_FILE;
864 encmultipart->data.filename = tmpfile;
865 procmime_encode_content(encmultipart, ENC_BASE64);
872 static PrivacySystem smime_system = {
876 smime_free_privacydata, /* free_privacydata */
878 smime_is_signed, /* is_signed(MimeInfo *) */
879 smime_check_signature, /* check_signature(MimeInfo *) */
880 smime_get_sig_status, /* get_sig_status(MimeInfo *) */
881 smime_get_sig_info_short, /* get_sig_info_short(MimeInfo *) */
882 smime_get_sig_info_full, /* get_sig_info_full(MimeInfo *) */
884 smime_is_encrypted, /* is_encrypted(MimeInfo *) */
885 smime_decrypt, /* decrypt(MimeInfo *) */
891 smime_get_encrypt_data,
893 smime_get_encrypt_warning,
894 smime_inhibit_encrypt_warning,
895 prefs_gpg_auto_check_signatures,
900 privacy_register_system(&smime_system);
905 privacy_unregister_system(&smime_system);
908 struct PluginFeature *plugin_provides(void)
910 static struct PluginFeature features[] =
911 { {PLUGIN_PRIVACY, N_("S/MIME")},
912 {PLUGIN_NOTHING, NULL}};
915 #endif /* USE_GPGME */