2 * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2016 the Claws Mail team
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
21 #include "claws-features.h"
28 #include <glib/gi18n.h>
39 #include <plugins/pgpcore/sgpgme.h>
40 #include <plugins/pgpcore/prefs_gpg.h>
41 #include <plugins/pgpcore/passphrase.h>
42 #include <plugins/pgpcore/pgp_utils.h>
44 #include "prefs_common.h"
45 #include "file-utils.h"
47 typedef struct _PrivacyDataPGP PrivacyDataPGP
;
49 struct _PrivacyDataPGP
53 gboolean done_sigtest
;
55 gpgme_verify_result_t sigstatus
;
59 static PrivacySystem pgpmime_system
;
61 static gint
pgpmime_check_signature(MimeInfo
*mimeinfo
);
63 static PrivacyDataPGP
*pgpmime_new_privacydata()
68 data
= g_new0(PrivacyDataPGP
, 1);
69 data
->data
.system
= &pgpmime_system
;
70 data
->done_sigtest
= FALSE
;
71 data
->is_signed
= FALSE
;
72 data
->sigstatus
= NULL
;
73 if ((err
= gpgme_new(&data
->ctx
)) != GPG_ERR_NO_ERROR
) {
74 g_warning("Couldn't initialize GPG context: %s", gpgme_strerror(err
));
81 static void pgpmime_free_privacydata(PrivacyData
*_data
)
83 PrivacyDataPGP
*data
= (PrivacyDataPGP
*) _data
;
84 gpgme_release(data
->ctx
);
88 static gboolean
pgpmime_is_signed(MimeInfo
*mimeinfo
)
92 const gchar
*protocol
;
93 PrivacyDataPGP
*data
= NULL
;
95 cm_return_val_if_fail(mimeinfo
!= NULL
, FALSE
);
96 if (mimeinfo
->privacy
!= NULL
) {
97 data
= (PrivacyDataPGP
*) mimeinfo
->privacy
;
98 if (data
->done_sigtest
)
99 return data
->is_signed
;
103 parent
= procmime_mimeinfo_parent(mimeinfo
);
106 if ((parent
->type
!= MIMETYPE_MULTIPART
) ||
107 g_ascii_strcasecmp(parent
->subtype
, "signed"))
109 protocol
= procmime_mimeinfo_get_parameter(parent
, "protocol");
110 if ((protocol
== NULL
) ||
111 (g_ascii_strcasecmp(protocol
, "application/pgp-signature")))
114 /* check if mimeinfo is the first child */
115 if (parent
->node
->children
->data
!= mimeinfo
)
118 /* check signature */
119 signature
= parent
->node
->children
->next
!= NULL
?
120 (MimeInfo
*) parent
->node
->children
->next
->data
: NULL
;
121 if (signature
== NULL
)
123 if ((signature
->type
!= MIMETYPE_APPLICATION
) ||
124 (g_ascii_strcasecmp(signature
->subtype
, "pgp-signature")))
128 data
= pgpmime_new_privacydata();
129 mimeinfo
->privacy
= (PrivacyData
*) data
;
132 data
->done_sigtest
= TRUE
;
133 data
->is_signed
= TRUE
;
139 static gchar
*get_canonical_content(FILE *fp
, const gchar
*boundary
)
146 boundary_len
= strlen(boundary
);
147 while (claws_fgets(buf
, sizeof(buf
), fp
) != NULL
)
148 if (IS_BOUNDARY(buf
, boundary
, boundary_len
))
151 textbuffer
= g_string_new("");
152 while (claws_fgets(buf
, sizeof(buf
), fp
) != NULL
) {
155 if (IS_BOUNDARY(buf
, boundary
, boundary_len
))
158 buf2
= canonicalize_str(buf
);
159 g_string_append(textbuffer
, buf2
);
162 g_string_truncate(textbuffer
, textbuffer
->len
- 2);
164 ret
= textbuffer
->str
;
165 g_string_free(textbuffer
, FALSE
);
170 static gint
pgpmime_check_signature(MimeInfo
*mimeinfo
)
172 PrivacyDataPGP
*data
;
173 MimeInfo
*parent
, *signature
;
177 gpgme_data_t sigdata
= NULL
, textdata
= NULL
;
179 cm_return_val_if_fail(mimeinfo
!= NULL
, -1);
180 cm_return_val_if_fail(mimeinfo
->privacy
!= NULL
, -1);
181 data
= (PrivacyDataPGP
*) mimeinfo
->privacy
;
182 if ((err
= gpgme_new(&data
->ctx
)) != GPG_ERR_NO_ERROR
) {
183 debug_print(("Couldn't initialize GPG context, %s\n"), gpgme_strerror(err
));
184 privacy_set_error(_("Couldn't initialize GPG context, %s"), gpgme_strerror(err
));
189 debug_print("Checking PGP/MIME signature\n");
191 err
= gpgme_set_protocol(data
->ctx
, GPGME_PROTOCOL_OpenPGP
);
194 debug_print ("gpgme_set_protocol failed: %s\n",
195 gpgme_strerror (err
));
197 parent
= procmime_mimeinfo_parent(mimeinfo
);
199 fp
= claws_fopen(parent
->data
.filename
, "rb");
200 cm_return_val_if_fail(fp
!= NULL
, SIGNATURE_INVALID
);
202 boundary
= g_hash_table_lookup(parent
->typeparameters
, "boundary");
204 privacy_set_error(_("Signature boundary not found."));
208 textstr
= get_canonical_content(fp
, boundary
);
210 err
= gpgme_data_new_from_mem(&textdata
, textstr
, (size_t)strlen(textstr
), 0);
212 debug_print ("gpgme_data_new_from_mem failed: %s\n",
213 gpgme_strerror (err
));
215 signature
= (MimeInfo
*) mimeinfo
->node
->next
->data
;
216 sigdata
= sgpgme_data_from_mimeinfo(signature
);
219 if (signature
->encoding_type
== ENC_BASE64
) {
220 err
= gpgme_data_set_encoding (sigdata
, GPGME_DATA_ENCODING_BASE64
);
224 debug_print ("gpgme_data_set_encoding failed: %s\n",
225 gpgme_strerror (err
));
229 sgpgme_verify_signature (data
->ctx
, sigdata
, textdata
, NULL
);
231 gpgme_data_release(sigdata
);
232 gpgme_data_release(textdata
);
239 static SignatureStatus
pgpmime_get_sig_status(MimeInfo
*mimeinfo
)
241 PrivacyDataPGP
*data
= (PrivacyDataPGP
*) mimeinfo
->privacy
;
243 cm_return_val_if_fail(data
!= NULL
, SIGNATURE_INVALID
);
245 return sgpgme_sigstat_gpgme_to_privacy(data
->ctx
, data
->sigstatus
);
248 static gchar
*pgpmime_get_sig_info_short(MimeInfo
*mimeinfo
)
250 PrivacyDataPGP
*data
= (PrivacyDataPGP
*) mimeinfo
->privacy
;
252 cm_return_val_if_fail(data
!= NULL
, g_strdup("Error"));
254 return sgpgme_sigstat_info_short(data
->ctx
, data
->sigstatus
);
257 static gchar
*pgpmime_get_sig_info_full(MimeInfo
*mimeinfo
)
259 PrivacyDataPGP
*data
= (PrivacyDataPGP
*) mimeinfo
->privacy
;
261 cm_return_val_if_fail(data
!= NULL
, g_strdup("Error"));
263 return sgpgme_sigstat_info_full(data
->ctx
, data
->sigstatus
);
266 static gboolean
pgpmime_is_encrypted(MimeInfo
*mimeinfo
)
270 const gchar
*begin_indicator
= "-----BEGIN PGP MESSAGE-----";
271 const gchar
*end_indicator
= "-----END PGP MESSAGE-----";
274 if (mimeinfo
->type
!= MIMETYPE_MULTIPART
)
276 if (g_ascii_strcasecmp(mimeinfo
->subtype
, "encrypted"))
278 tmpstr
= procmime_mimeinfo_get_parameter(mimeinfo
, "protocol");
279 if ((tmpstr
== NULL
) || g_ascii_strcasecmp(tmpstr
, "application/pgp-encrypted"))
281 if (g_node_n_children(mimeinfo
->node
) != 2)
284 tmpinfo
= (MimeInfo
*) g_node_nth_child(mimeinfo
->node
, 0)->data
;
285 if (tmpinfo
->type
!= MIMETYPE_APPLICATION
)
287 if (g_ascii_strcasecmp(tmpinfo
->subtype
, "pgp-encrypted"))
290 tmpinfo
= (MimeInfo
*) g_node_nth_child(mimeinfo
->node
, 1)->data
;
291 if (tmpinfo
->type
!= MIMETYPE_APPLICATION
)
293 if (g_ascii_strcasecmp(tmpinfo
->subtype
, "octet-stream"))
296 textdata
= procmime_get_part_as_string(tmpinfo
, TRUE
);
300 if (!pgp_locate_armor_header(textdata
, begin_indicator
)) {
304 if (!pgp_locate_armor_header(textdata
, end_indicator
)) {
314 static MimeInfo
*pgpmime_decrypt(MimeInfo
*mimeinfo
)
316 MimeInfo
*encinfo
, *decinfo
, *parseinfo
;
317 gpgme_data_t cipher
= NULL
, plain
= NULL
;
321 gpgme_verify_result_t sigstat
= NULL
;
322 PrivacyDataPGP
*data
= NULL
;
328 if ((err
= gpgme_new(&ctx
)) != GPG_ERR_NO_ERROR
) {
329 debug_print(("Couldn't initialize GPG context, %s\n"), gpgme_strerror(err
));
330 privacy_set_error(_("Couldn't initialize GPG context, %s"), gpgme_strerror(err
));
334 cm_return_val_if_fail(pgpmime_is_encrypted(mimeinfo
), NULL
);
336 encinfo
= (MimeInfo
*) g_node_nth_child(mimeinfo
->node
, 1)->data
;
338 cipher
= sgpgme_data_from_mimeinfo(encinfo
);
339 plain
= sgpgme_decrypt_verify(cipher
, &sigstat
, ctx
);
341 gpgme_data_release(cipher
);
343 debug_print("plain is null!\n");
348 fname
= g_strdup_printf("%s%cplaintext.%08x",
349 get_mime_tmp_dir(), G_DIR_SEPARATOR
, ++id
);
351 if ((dstfp
= claws_fopen(fname
, "wb")) == NULL
) {
352 FILE_OP_ERROR(fname
, "claws_fopen");
353 privacy_set_error(_("Couldn't open decrypted file %s"), fname
);
355 gpgme_data_release(plain
);
357 debug_print("can't open!\n");
361 if (fprintf(dstfp
, "MIME-Version: 1.0\n") < 0) {
362 FILE_OP_ERROR(fname
, "fprintf");
364 privacy_set_error(_("Couldn't write to decrypted file %s"), fname
);
366 gpgme_data_release(plain
);
368 debug_print("can't open!\n");
372 chars
= sgpgme_data_release_and_get_mem(plain
, &len
);
374 if (claws_fwrite(chars
, 1, len
, dstfp
) < len
) {
375 FILE_OP_ERROR(fname
, "claws_fwrite");
378 privacy_set_error(_("Couldn't write to decrypted file %s"), fname
);
380 gpgme_data_release(plain
);
382 debug_print("can't open!\n");
388 if (claws_safe_fclose(dstfp
) == EOF
) {
389 FILE_OP_ERROR(fname
, "claws_fclose");
390 privacy_set_error(_("Couldn't close decrypted file %s"), fname
);
392 gpgme_data_release(plain
);
394 debug_print("can't open!\n");
398 parseinfo
= procmime_scan_file(fname
);
400 if (parseinfo
== NULL
) {
402 privacy_set_error(_("Couldn't parse decrypted file."));
405 decinfo
= g_node_first_child(parseinfo
->node
) != NULL
?
406 g_node_first_child(parseinfo
->node
)->data
: NULL
;
407 if (decinfo
== NULL
) {
408 privacy_set_error(_("Couldn't parse decrypted file parts."));
413 g_node_unlink(decinfo
->node
);
414 procmime_mimeinfo_free_all(&parseinfo
);
418 if (sigstat
!= NULL
&& sigstat
->signatures
!= NULL
) {
419 if (decinfo
->privacy
!= NULL
) {
420 data
= (PrivacyDataPGP
*) decinfo
->privacy
;
422 data
= pgpmime_new_privacydata();
423 decinfo
->privacy
= (PrivacyData
*) data
;
426 data
->done_sigtest
= TRUE
;
427 data
->is_signed
= TRUE
;
428 data
->sigstatus
= sigstat
;
430 gpgme_release(data
->ctx
);
439 gboolean
pgpmime_sign(MimeInfo
*mimeinfo
, PrefsAccount
*account
, const gchar
*from_addr
)
441 MimeInfo
*msgcontent
, *sigmultipart
, *newinfo
;
442 gchar
*textstr
, *micalg
= NULL
;
444 gchar
*boundary
= NULL
;
447 gpgme_data_t gpgtext
, gpgsig
;
450 struct passphrase_cb_info_s info
;
451 gpgme_sign_result_t result
= NULL
;
456 perror("my_tmpfile");
457 privacy_set_error(_("Couldn't create temporary file: %s"), g_strerror(errno
));
460 procmime_write_mimeinfo(mimeinfo
, fp
);
463 /* read temporary file into memory */
464 test_msg
= file_read_stream_to_str(fp
);
467 memset (&info
, 0, sizeof info
);
469 /* remove content node from message */
470 msgcontent
= (MimeInfo
*) mimeinfo
->node
->children
->data
;
471 g_node_unlink(msgcontent
->node
);
473 /* create temporary multipart for content */
474 sigmultipart
= procmime_mimeinfo_new();
475 sigmultipart
->type
= MIMETYPE_MULTIPART
;
476 sigmultipart
->subtype
= g_strdup("signed");
480 boundary
= generate_mime_boundary("Sig");
481 } while (strstr(test_msg
, boundary
) != NULL
);
485 g_hash_table_insert(sigmultipart
->typeparameters
, g_strdup("boundary"),
487 g_hash_table_insert(sigmultipart
->typeparameters
, g_strdup("protocol"),
488 g_strdup("application/pgp-signature"));
489 g_node_append(sigmultipart
->node
, msgcontent
->node
);
490 g_node_append(mimeinfo
->node
, sigmultipart
->node
);
492 /* write message content to temporary file */
495 perror("my_tmpfile");
496 privacy_set_error(_("Couldn't create temporary file: %s"), g_strerror(errno
));
499 procmime_write_mimeinfo(sigmultipart
, fp
);
502 /* read temporary file into memory */
503 textstr
= get_canonical_content(fp
, boundary
);
508 gpgme_data_new_from_mem(&gpgtext
, textstr
, (size_t)strlen(textstr
), 0);
509 gpgme_data_new(&gpgsig
);
510 if ((err
= gpgme_new(&ctx
)) != GPG_ERR_NO_ERROR
) {
511 debug_print(("Couldn't initialize GPG context, %s\n"), gpgme_strerror(err
));
512 privacy_set_error(_("Couldn't initialize GPG context, %s"), gpgme_strerror(err
));
515 gpgme_set_textmode(ctx
, 1);
516 gpgme_set_armor(ctx
, 1);
517 gpgme_signers_clear (ctx
);
519 if (!sgpgme_setup_signers(ctx
, account
, from_addr
)) {
524 prefs_gpg_enable_agent(prefs_gpg_get_config()->use_gpg_agent
);
525 if (g_getenv("GPG_AGENT_INFO") && prefs_gpg_get_config()->use_gpg_agent
) {
526 debug_print("GPG_AGENT_INFO environment defined, running without passphrase callback\n");
529 gpgme_set_passphrase_cb (ctx
, gpgmegtk_passphrase_cb
, &info
);
532 err
= gpgme_op_sign(ctx
, gpgtext
, gpgsig
, GPGME_SIG_MODE_DETACH
);
533 if (err
!= GPG_ERR_NO_ERROR
) {
534 if (err
== GPG_ERR_CANCELED
) {
535 /* ignore cancelled signing */
536 privacy_reset_error();
537 debug_print("gpgme_op_sign cancelled\n");
539 privacy_set_error(_("Data signing failed, %s"), gpgme_strerror(err
));
540 debug_print("gpgme_op_sign error : %x\n", err
);
545 result
= gpgme_op_sign_result(ctx
);
546 if (result
&& result
->signatures
) {
547 gpgme_new_signature_t sig
= result
->signatures
;
548 if (gpgme_get_protocol(ctx
) == GPGME_PROTOCOL_OpenPGP
) {
549 gchar
*down_algo
= g_ascii_strdown(gpgme_hash_algo_name(
550 result
->signatures
->hash_algo
), -1);
551 micalg
= g_strdup_printf("pgp-%s", down_algo
);
554 micalg
= g_strdup(gpgme_hash_algo_name(
555 result
->signatures
->hash_algo
));
558 debug_print("valid signature: %s\n", sig
->fpr
);
561 } else if (result
&& result
->invalid_signers
) {
562 gpgme_invalid_key_t invalid
= result
->invalid_signers
;
564 g_warning("invalid signer: %s (%s)", invalid
->fpr
,
565 gpgme_strerror(invalid
->reason
));
566 privacy_set_error(_("Data signing failed due to invalid signer: %s"),
567 gpgme_strerror(invalid
->reason
));
568 invalid
= invalid
->next
;
573 /* can't get result (maybe no signing key?) */
574 debug_print("gpgme_op_sign_result error\n");
575 privacy_set_error(_("Data signing failed, no results."));
580 sigcontent
= sgpgme_data_release_and_get_mem(gpgsig
, &len
);
581 gpgme_data_release(gpgtext
);
584 if (sigcontent
== NULL
|| len
<= 0) {
585 g_warning("sgpgme_data_release_and_get_mem failed");
586 privacy_set_error(_("Data signing failed, no contents."));
593 g_hash_table_insert(sigmultipart
->typeparameters
, g_strdup("micalg"),
596 newinfo
= procmime_mimeinfo_new();
597 newinfo
->type
= MIMETYPE_APPLICATION
;
598 newinfo
->subtype
= g_strdup("pgp-signature");
599 newinfo
->description
= g_strdup(_("OpenPGP digital signature"));
600 newinfo
->content
= MIMECONTENT_MEM
;
601 newinfo
->data
.mem
= g_malloc(len
+ 1);
602 memmove(newinfo
->data
.mem
, sigcontent
, len
);
603 newinfo
->data
.mem
[len
] = '\0';
605 g_node_append(sigmultipart
->node
, newinfo
->node
);
612 gchar
*pgpmime_get_encrypt_data(GSList
*recp_names
)
614 return sgpgme_get_encrypt_data(recp_names
, GPGME_PROTOCOL_OpenPGP
);
617 static const gchar
*pgpmime_get_encrypt_warning(void)
619 if (prefs_gpg_should_skip_encryption_warning(pgpmime_system
.id
))
622 return _("Please note that email headers, like Subject, "
623 "are not encrypted by the PGP/Mime system.");
626 static void pgpmime_inhibit_encrypt_warning(gboolean inhibit
)
629 prefs_gpg_add_skip_encryption_warning(pgpmime_system
.id
);
631 prefs_gpg_remove_skip_encryption_warning(pgpmime_system
.id
);
634 gboolean
pgpmime_encrypt(MimeInfo
*mimeinfo
, const gchar
*encrypt_data
)
636 MimeInfo
*msgcontent
, *encmultipart
, *newinfo
;
638 gchar
*boundary
, *enccontent
;
641 gpgme_data_t gpgtext
= NULL
, gpgenc
= NULL
;
642 gpgme_ctx_t ctx
= NULL
;
643 gpgme_key_t
*kset
= NULL
;
644 gchar
**fprs
= g_strsplit(encrypt_data
, " ", -1);
648 while (fprs
[i
] && strlen(fprs
[i
])) {
652 kset
= g_malloc(sizeof(gpgme_key_t
)*(i
+1));
653 memset(kset
, 0, sizeof(gpgme_key_t
)*(i
+1));
654 if ((err
= gpgme_new(&ctx
)) != GPG_ERR_NO_ERROR
) {
655 debug_print(("Couldn't initialize GPG context, %s\n"), gpgme_strerror(err
));
656 privacy_set_error(_("Couldn't initialize GPG context, %s"), gpgme_strerror(err
));
661 while (fprs
[i
] && strlen(fprs
[i
])) {
663 err
= gpgme_get_key(ctx
, fprs
[i
], &key
, 0);
665 debug_print("can't add key '%s'[%d] (%s)\n", fprs
[i
],i
, gpgme_strerror(err
));
666 privacy_set_error(_("Couldn't add GPG key %s, %s"), fprs
[i
], gpgme_strerror(err
));
670 debug_print("found %s at %d\n", fprs
[i
], i
);
675 debug_print("Encrypting message content\n");
677 /* remove content node from message */
678 msgcontent
= (MimeInfo
*) mimeinfo
->node
->children
->data
;
679 g_node_unlink(msgcontent
->node
);
681 /* create temporary multipart for content */
682 encmultipart
= procmime_mimeinfo_new();
683 encmultipart
->type
= MIMETYPE_MULTIPART
;
684 encmultipart
->subtype
= g_strdup("encrypted");
685 boundary
= generate_mime_boundary("Encrypt");
686 g_hash_table_insert(encmultipart
->typeparameters
, g_strdup("boundary"),
688 g_hash_table_insert(encmultipart
->typeparameters
, g_strdup("protocol"),
689 g_strdup("application/pgp-encrypted"));
690 g_node_append(encmultipart
->node
, msgcontent
->node
);
692 /* write message content to temporary file */
695 perror("my_tmpfile");
696 privacy_set_error(_("Couldn't create temporary file, %s"), g_strerror(errno
));
700 procmime_write_mimeinfo(encmultipart
, fp
);
703 /* read temporary file into memory */
704 textstr
= get_canonical_content(fp
, boundary
);
710 gpgme_data_new_from_mem(&gpgtext
, textstr
, (size_t)strlen(textstr
), 0);
711 gpgme_data_new(&gpgenc
);
712 gpgme_set_armor(ctx
, 1);
713 cm_gpgme_data_rewind(gpgtext
);
715 err
= gpgme_op_encrypt(ctx
, kset
, GPGME_ENCRYPT_ALWAYS_TRUST
, gpgtext
, gpgenc
);
717 enccontent
= sgpgme_data_release_and_get_mem(gpgenc
, &len
);
718 gpgme_data_release(gpgtext
);
722 if (enccontent
== NULL
|| len
<= 0) {
723 g_warning("sgpgme_data_release_and_get_mem failed");
724 privacy_set_error(_("Encryption failed, %s"), gpgme_strerror(err
));
730 /* create encrypted multipart */
731 g_node_unlink(msgcontent
->node
);
732 procmime_mimeinfo_free_all(&msgcontent
);
733 g_node_append(mimeinfo
->node
, encmultipart
->node
);
735 newinfo
= procmime_mimeinfo_new();
736 newinfo
->type
= MIMETYPE_APPLICATION
;
737 newinfo
->subtype
= g_strdup("pgp-encrypted");
738 newinfo
->content
= MIMECONTENT_MEM
;
739 newinfo
->data
.mem
= g_strdup("Version: 1\n");
741 g_node_append(encmultipart
->node
, newinfo
->node
);
743 newinfo
= procmime_mimeinfo_new();
744 newinfo
->type
= MIMETYPE_APPLICATION
;
745 newinfo
->subtype
= g_strdup("octet-stream");
746 newinfo
->content
= MIMECONTENT_MEM
;
747 newinfo
->data
.mem
= g_malloc(len
+ 1);
749 memmove(newinfo
->data
.mem
, enccontent
, len
);
750 newinfo
->data
.mem
[len
] = '\0';
751 g_node_append(encmultipart
->node
, newinfo
->node
);
759 static PrivacySystem pgpmime_system
= {
761 "PGP MIME", /* name */
763 pgpmime_free_privacydata
, /* free_privacydata */
765 pgpmime_is_signed
, /* is_signed(MimeInfo *) */
766 pgpmime_check_signature
, /* check_signature(MimeInfo *) */
767 pgpmime_get_sig_status
, /* get_sig_status(MimeInfo *) */
768 pgpmime_get_sig_info_short
, /* get_sig_info_short(MimeInfo *) */
769 pgpmime_get_sig_info_full
, /* get_sig_info_full(MimeInfo *) */
771 pgpmime_is_encrypted
, /* is_encrypted(MimeInfo *) */
772 pgpmime_decrypt
, /* decrypt(MimeInfo *) */
778 pgpmime_get_encrypt_data
,
780 pgpmime_get_encrypt_warning
,
781 pgpmime_inhibit_encrypt_warning
,
782 prefs_gpg_auto_check_signatures
,
787 privacy_register_system(&pgpmime_system
);
792 privacy_unregister_system(&pgpmime_system
);
795 struct PluginFeature
*plugin_provides(void)
797 static struct PluginFeature features
[] =
798 { {PLUGIN_PRIVACY
, N_("PGP/Mime")},
799 {PLUGIN_NOTHING
, NULL
}};
802 #endif /* USE_GPGME */