2 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2003 Hiroyuki Yamamoto & the Sylpheed-Claws 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 2 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, write to the Free Software
17 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
36 #include "prefs_common.h"
37 #include "prefs_gpg.h"
38 #include "passphrase.h"
40 typedef struct _PrivacyDataPGP PrivacyDataPGP;
42 struct _PrivacyDataPGP
46 gboolean done_sigtest;
48 GpgmeSigStat sigstatus;
52 PGPMIME pgpmime_system;
54 static PrivacyDataPGP *pgpmime_new_privacydata()
58 data = g_new0(PrivacyDataPGP, 1);
59 data->data.system = &pgpmime_system;
60 data->done_sigtest = FALSE;
61 data->is_signed = FALSE;
62 data->sigstatus = GPGME_SIG_STAT_NONE;
63 gpgme_new(&data->ctx);
72 const gchar *PGPMIME::getId()
77 const gchar *PGPMIME::getName()
82 void PGPMIME::freePrivacyData(PrivacyData *_data)
84 PrivacyDataPGP *data = (PrivacyDataPGP *) _data;
85 gpgme_release(data->ctx);
87 PrivacySystem::freePrivacyData(_data);
90 gboolean PGPMIME::isSigned(MimeInfo *mimeinfo)
94 const gchar *protocol;
95 PrivacyDataPGP *data = NULL;
97 g_return_val_if_fail(mimeinfo != NULL, FALSE);
98 if (mimeinfo->privacy != NULL) {
99 data = (PrivacyDataPGP *) mimeinfo->privacy;
100 if (data->done_sigtest)
101 return data->is_signed;
105 parent = procmime_mimeinfo_parent(mimeinfo);
108 if ((parent->type != MIMETYPE_MULTIPART) ||
109 g_strcasecmp(parent->subtype, "signed"))
111 protocol = procmime_mimeinfo_get_parameter(parent, "protocol");
112 if ((protocol == NULL) || g_strcasecmp(protocol, "application/pgp-signature"))
115 /* check if mimeinfo is the first child */
116 if (parent->node->children->data != mimeinfo)
119 /* check signature */
120 signature = parent->node->children->next != NULL ?
121 (MimeInfo *) parent->node->children->next->data : NULL;
122 if (signature == NULL)
124 if ((signature->type != MIMETYPE_APPLICATION) ||
125 g_strcasecmp(signature->subtype, "pgp-signature"))
129 data = pgpmime_new_privacydata();
130 mimeinfo->privacy = (PrivacyData *) data;
132 data->done_sigtest = TRUE;
133 data->is_signed = TRUE;
138 static gchar *get_canonical_content(FILE *fp, const gchar *boundary)
145 boundary_len = strlen(boundary);
146 while (fgets(buf, sizeof(buf), fp) != NULL)
147 if (IS_BOUNDARY(buf, boundary, boundary_len))
150 textbuffer = g_string_new("");
151 while (fgets(buf, sizeof(buf), fp) != NULL) {
154 if (IS_BOUNDARY(buf, boundary, boundary_len))
157 buf2 = canonicalize_str(buf);
158 g_string_append(textbuffer, buf2);
161 g_string_truncate(textbuffer, textbuffer->len - 2);
163 ret = textbuffer->str;
164 g_string_free(textbuffer, FALSE);
169 gint PGPMIME::checkSignature(MimeInfo *mimeinfo)
171 PrivacyDataPGP *data;
172 MimeInfo *parent, *signature;
176 GpgmeData sigdata, textdata;
178 g_return_val_if_fail(mimeinfo != NULL, -1);
179 g_return_val_if_fail(mimeinfo->privacy != NULL, -1);
180 data = (PrivacyDataPGP *) mimeinfo->privacy;
182 debug_print("Checking PGP/MIME signature\n");
183 parent = procmime_mimeinfo_parent(mimeinfo);
185 fp = fopen(parent->data.filename, "rb");
186 g_return_val_if_fail(fp != NULL, SIGNATURE_INVALID);
188 boundary = (gchar *) g_hash_table_lookup(parent->typeparameters, "boundary");
192 textstr = get_canonical_content(fp, boundary);
194 gpgme_data_new_from_mem(&textdata, textstr, strlen(textstr), 0);
195 signature = (MimeInfo *) mimeinfo->node->next->data;
196 sigdata = sgpgme_data_from_mimeinfo(signature);
199 sgpgme_verify_signature (data->ctx, sigdata, textdata);
201 gpgme_data_release(sigdata);
202 gpgme_data_release(textdata);
209 SignatureStatus PGPMIME::getSigStatus(MimeInfo *mimeinfo)
211 PrivacyDataPGP *data = (PrivacyDataPGP *) mimeinfo->privacy;
213 g_return_val_if_fail(data != NULL, SIGNATURE_INVALID);
215 if (data->sigstatus == GPGME_SIG_STAT_NONE &&
216 prefs_gpg_get_config()->auto_check_signatures)
217 checkSignature(mimeinfo);
219 return sgpgme_sigstat_gpgme_to_privacy(data->ctx, data->sigstatus);
222 gchar *PGPMIME::getSigInfoShort(MimeInfo *mimeinfo)
224 PrivacyDataPGP *data = (PrivacyDataPGP *) mimeinfo->privacy;
226 g_return_val_if_fail(data != NULL, g_strdup("Error"));
228 if (data->sigstatus == GPGME_SIG_STAT_NONE &&
229 prefs_gpg_get_config()->auto_check_signatures)
230 checkSignature(mimeinfo);
232 return sgpgme_sigstat_info_short(data->ctx, data->sigstatus);
235 gchar *PGPMIME::getSigInfoFull(MimeInfo *mimeinfo)
237 PrivacyDataPGP *data = (PrivacyDataPGP *) mimeinfo->privacy;
239 g_return_val_if_fail(data != NULL, g_strdup("Error"));
241 if (data->sigstatus == GPGME_SIG_STAT_NONE &&
242 prefs_gpg_get_config()->auto_check_signatures)
243 checkSignature(mimeinfo);
245 return sgpgme_sigstat_info_full(data->ctx, data->sigstatus);
248 gboolean PGPMIME::isEncrypted(MimeInfo *mimeinfo)
253 if (mimeinfo->type != MIMETYPE_MULTIPART)
255 if (g_strcasecmp(mimeinfo->subtype, "encrypted"))
257 tmpstr = procmime_mimeinfo_get_parameter(mimeinfo, "protocol");
258 if ((tmpstr == NULL) || g_strcasecmp(tmpstr, "application/pgp-encrypted"))
260 if (g_node_n_children(mimeinfo->node) != 2)
263 tmpinfo = (MimeInfo *) g_node_nth_child(mimeinfo->node, 0)->data;
264 if (tmpinfo->type != MIMETYPE_APPLICATION)
266 if (g_strcasecmp(tmpinfo->subtype, "pgp-encrypted"))
269 tmpinfo = (MimeInfo *) g_node_nth_child(mimeinfo->node, 1)->data;
270 if (tmpinfo->type != MIMETYPE_APPLICATION)
272 if (g_strcasecmp(tmpinfo->subtype, "octet-stream"))
278 MimeInfo *PGPMIME::decrypt(MimeInfo *mimeinfo)
280 MimeInfo *encinfo, *decinfo, *parseinfo;
281 GpgmeData cipher, plain;
287 GpgmeSigStat sigstat = GPGME_SIG_STAT_NONE;
288 PrivacyDataPGP *data = NULL;
291 if (gpgme_new(&ctx) != GPGME_No_Error)
295 g_return_val_if_fail(isEncrypted(mimeinfo), NULL);
297 encinfo = (MimeInfo *) g_node_nth_child(mimeinfo->node, 1)->data;
299 cipher = sgpgme_data_from_mimeinfo(encinfo);
300 plain = sgpgme_decrypt_verify(cipher, &sigstat, ctx);
302 gpgme_data_release(cipher);
308 fname = g_strdup_printf("%s%cplaintext.%08x",
309 get_mime_tmp_dir(), G_DIR_SEPARATOR, ++id);
311 if ((dstfp = fopen(fname, "wb")) == NULL) {
312 FILE_OP_ERROR(fname, "fopen");
314 gpgme_data_release(plain);
319 fprintf(dstfp, "MIME-Version: 1.0\n");
320 gpgme_data_rewind (plain);
321 while (gpgme_data_read(plain, buf, sizeof(buf), &nread) == GPGME_No_Error) {
322 fwrite (buf, nread, 1, dstfp);
326 gpgme_data_release(plain);
328 parseinfo = procmime_scan_file(fname);
330 if (parseinfo == NULL) {
334 decinfo = g_node_first_child(parseinfo->node) != NULL ?
335 (MimeInfo *) g_node_first_child(parseinfo->node)->data : NULL;
336 if (decinfo == NULL) {
341 g_node_unlink(decinfo->node);
342 procmime_mimeinfo_free_all(parseinfo);
346 if (sigstat != GPGME_SIG_STAT_NONE) {
347 if (decinfo->privacy != NULL) {
348 data = (PrivacyDataPGP *) decinfo->privacy;
350 data = pgpmime_new_privacydata();
351 decinfo->privacy = (PrivacyData *) data;
353 data->done_sigtest = TRUE;
354 data->is_signed = TRUE;
355 data->sigstatus = sigstat;
357 gpgme_release(data->ctx);
367 * Find TAG in XML and return a pointer into xml set just behind the
368 * closing angle. Return NULL if not found.
371 find_xml_tag (const char *xml, const char *tag)
373 int taglen = strlen (tag);
376 while ( (s = strchr (s, '<')) ) {
378 if (!strncmp (s, tag, taglen)) {
379 const char *s2 = s + taglen;
380 if (*s2 == '>' || isspace (*(const unsigned char*)s2) ) {
382 while (*s2 && *s2 != '>') /* skip attributes */
384 /* fixme: do need to handle angles inside attribute vallues? */
385 return *s2? (s2+1):NULL;
388 while (*s && *s != '>') /* skip to end of tag */
396 * Extract the micalg from an GnupgOperationInfo XML container.
399 extract_micalg (char *xml)
403 s = find_xml_tag (xml, "GnupgOperationInfo");
405 const char *s_end = find_xml_tag (s, "/GnupgOperationInfo");
406 s = find_xml_tag (s, "signature");
407 if (s && s_end && s < s_end) {
408 const char *s_end2 = find_xml_tag (s, "/signature");
409 if (s_end2 && s_end2 < s_end) {
410 s = find_xml_tag (s, "micalg");
411 if (s && s < s_end2) {
412 s_end = strchr (s, '<');
414 char *p = (gchar *) g_malloc (s_end - s + 1);
415 memcpy (p, s, s_end - s);
426 gboolean PGPMIME::sign(MimeInfo *mimeinfo, PrefsAccount *account)
428 MimeInfo *msgcontent, *sigmultipart, *newinfo;
429 gchar *textstr, *opinfo, *micalg;
431 gchar *boundary, *sigcontent;
433 GpgmeData gpgtext, gpgsig;
435 struct passphrase_cb_info_s info;
437 memset (&info, 0, sizeof info);
439 /* remove content node from message */
440 msgcontent = (MimeInfo *) mimeinfo->node->children->data;
441 g_node_unlink(msgcontent->node);
443 /* create temporary multipart for content */
444 sigmultipart = procmime_mimeinfo_new();
445 sigmultipart->type = MIMETYPE_MULTIPART;
446 sigmultipart->subtype = g_strdup("signed");
447 boundary = generate_mime_boundary("Signature");
448 g_hash_table_insert(sigmultipart->typeparameters, g_strdup("boundary"),
450 g_hash_table_insert(sigmultipart->typeparameters, g_strdup("protocol"),
451 g_strdup("application/pgp-signature"));
452 g_node_append(sigmultipart->node, msgcontent->node);
453 g_node_append(mimeinfo->node, sigmultipart->node);
455 /* write message content to temporary file */
457 procmime_write_mimeinfo(sigmultipart, fp);
460 /* read temporary file into memory */
461 textstr = get_canonical_content(fp, boundary);
465 gpgme_data_new_from_mem(&gpgtext, textstr, strlen(textstr), 0);
466 gpgme_data_new(&gpgsig);
468 gpgme_set_textmode(ctx, 1);
469 gpgme_set_armor(ctx, 1);
471 if (!sgpgme_setup_signers(ctx, account)) {
476 if (!getenv("GPG_AGENT_INFO")) {
478 gpgme_set_passphrase_cb (ctx, gpgmegtk_passphrase_cb, &info);
481 if (gpgme_op_sign(ctx, gpgtext, gpgsig, GPGME_SIG_MODE_DETACH) != GPGME_No_Error) {
485 opinfo = gpgme_get_op_info(ctx, 0);
486 micalg = extract_micalg(opinfo);
490 sigcontent = gpgme_data_release_and_get_mem(gpgsig, &len);
491 gpgme_data_release(gpgtext);
495 g_hash_table_insert(sigmultipart->typeparameters, g_strdup("micalg"),
498 newinfo = procmime_mimeinfo_new();
499 newinfo->type = MIMETYPE_APPLICATION;
500 newinfo->subtype = g_strdup("pgp-signature");
501 newinfo->content = MIMECONTENT_MEM;
502 newinfo->data.mem = g_malloc(len + 1);
503 g_memmove(newinfo->data.mem, sigcontent, len);
504 newinfo->data.mem[len] = '\0';
505 g_node_append(sigmultipart->node, newinfo->node);
512 gchar *PGPMIME::getEncryptData(GSList *recp_names)
514 return sgpgme_get_encrypt_data(recp_names);
517 gboolean PGPMIME::encrypt(MimeInfo *mimeinfo, const gchar *encrypt_data)
519 MimeInfo *msgcontent, *encmultipart, *newinfo;
521 gchar *boundary, *enccontent;
524 GpgmeData gpgtext, gpgenc;
525 gchar **recipients, **nextrecp;
526 GpgmeRecipients recp;
529 /* build GpgmeRecipients from encrypt_data */
530 recipients = g_strsplit(encrypt_data, " ", 0);
531 gpgme_recipients_new(&recp);
532 for (nextrecp = recipients; *nextrecp != NULL; nextrecp++) {
533 gpgme_recipients_add_name_with_validity(recp, *nextrecp,
534 GPGME_VALIDITY_FULL);
536 g_strfreev(recipients);
538 debug_print("Encrypting message content\n");
540 /* remove content node from message */
541 msgcontent = (MimeInfo *) mimeinfo->node->children->data;
542 g_node_unlink(msgcontent->node);
544 /* create temporary multipart for content */
545 encmultipart = procmime_mimeinfo_new();
546 encmultipart->type = MIMETYPE_MULTIPART;
547 encmultipart->subtype = g_strdup("encrypted");
548 boundary = generate_mime_boundary("Encrypt");
549 g_hash_table_insert(encmultipart->typeparameters, g_strdup("boundary"),
551 g_hash_table_insert(encmultipart->typeparameters, g_strdup("protocol"),
552 g_strdup("application/pgp-encrypted"));
553 g_node_append(encmultipart->node, msgcontent->node);
555 /* write message content to temporary file */
557 procmime_write_mimeinfo(encmultipart, fp);
560 /* read temporary file into memory */
561 textstr = get_canonical_content(fp, boundary);
566 gpgme_data_new_from_mem(&gpgtext, textstr, strlen(textstr), 0);
567 gpgme_data_new(&gpgenc);
569 gpgme_set_armor(ctx, 1);
571 gpgme_op_encrypt(ctx, recp, gpgtext, gpgenc);
574 enccontent = gpgme_data_release_and_get_mem(gpgenc, &len);
575 gpgme_recipients_release(recp);
576 gpgme_data_release(gpgtext);
579 /* create encrypted multipart */
580 g_node_unlink(msgcontent->node);
581 procmime_mimeinfo_free_all(msgcontent);
582 g_node_append(mimeinfo->node, encmultipart->node);
584 newinfo = procmime_mimeinfo_new();
585 newinfo->type = MIMETYPE_APPLICATION;
586 newinfo->subtype = g_strdup("pgp-encrypted");
587 newinfo->content = MIMECONTENT_MEM;
588 newinfo->data.mem = g_strdup("Version: 1\n");
589 g_node_append(encmultipart->node, newinfo->node);
591 newinfo = procmime_mimeinfo_new();
592 newinfo->type = MIMETYPE_APPLICATION;
593 newinfo->subtype = g_strdup("octet-stream");
594 newinfo->content = MIMECONTENT_MEM;
595 newinfo->data.mem = g_malloc(len + 1);
596 g_memmove(newinfo->data.mem, enccontent, len);
597 newinfo->data.mem[len] = '\0';
598 g_node_append(encmultipart->node, newinfo->node);
609 privacy_register_system(&pgpmime_system);
614 privacy_unregister_system(&pgpmime_system);
617 #endif /* USE_GPGME */