More sync with sylpheed 0.5.0pre4
[claws.git] / src / rfc2015.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 2001 Werner Koch (dd9jn)
4  *
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.
9  *
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.
14  *
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.
18  */
19
20 #ifdef HAVE_CONFIG_H
21 #  include "config.h"
22 #endif
23
24 #if USE_GPGME
25
26 #include "defs.h"
27
28 #include <glib.h>
29 #include <stdio.h>
30 #include <string.h>
31 #include <locale.h>
32 #include <ctype.h>
33
34 #include <gpgme.h>
35
36 #include "intl.h"
37 #include "procmime.h"
38 #include "procheader.h"
39 #include "base64.h"
40 #include "uuencode.h"
41 #include "unmime.h"
42 #include "codeconv.h"
43 #include "utils.h"
44 #include "prefs_common.h"
45 #include "passphrase.h"
46 #include "select-keys.h"
47 #include "sigstatus.h"
48 #include "rfc2015.h"
49
50 #define DIM(v)     (sizeof(v)/sizeof((v)[0]))
51
52 static char *content_names[] = {
53     "Content-Type",
54     "Content-Disposition",
55     "Content-Transfer-Encoding",
56     NULL
57 };
58
59 static char *mime_version_name[] = {
60     "Mime-Version",
61     NULL
62 };
63
64
65 struct passphrase_cb_info_s {
66     GpgmeCtx c;
67     int did_it;
68 };
69
70 static char *create_boundary (void);
71
72 static void dump_mimeinfo (const char *text, MimeInfo *x)
73 {
74     g_message ("** MimeInfo[%s] %p  level=%d",
75                text, x, x? x->level:0 );
76     if (!x)
77         return;
78
79     g_message ("**      enc=`%s' enc_type=%d mime_type=%d",
80                x->encoding, x->encoding_type, x->mime_type );
81     g_message ("**      cont_type=`%s' cs=`%s' name=`%s' bnd=`%s'",
82                x->content_type, x->charset, x->name, x->boundary );
83     g_message ("**      cont_disp=`%s' fname=`%s' fpos=%ld size=%u, lvl=%d",
84                x->content_disposition, x->filename, x->fpos, x->size,
85                x->level );
86     dump_mimeinfo (".main", x->main );
87     dump_mimeinfo (".sub", x->sub );
88     dump_mimeinfo (".next", x->next );
89     g_message ("** MimeInfo[.parent] %p", x ); 
90     dump_mimeinfo (".children", x->children );
91     dump_mimeinfo (".plaintext", x->plaintext );
92 }
93
94 static void dump_part ( MimeInfo *mimeinfo, FILE *fp )
95 {
96     unsigned int size = mimeinfo->size;
97     int c;
98
99     if (fseek (fp, mimeinfo->fpos, SEEK_SET)) {
100         g_warning ("dump_part: fseek error");
101         return;
102     }
103
104     g_message ("** --- begin dump_part ----");
105     while (size-- && (c = getc (fp)) != EOF) 
106         putc (c, stderr);
107     if (ferror (fp))
108         g_warning ("dump_part: read error");
109     g_message ("** --- end dump_part ----");
110 }
111
112 void
113 rfc2015_disable_all (void)
114 {
115     /* FIXME: set a flag, so that we don't bother the user with failed
116      * gpgme messages */
117 }
118
119
120 void
121 rfc2015_secure_remove (const char *fname)
122 {
123     if (!fname)
124         return;
125     /* fixme: overwrite the file first */
126     remove (fname);
127 }
128
129
130 static const gchar *
131 sig_status_to_string (GpgmeSigStat status)
132 {
133     const gchar *result;
134
135     switch (status) {
136       case GPGME_SIG_STAT_NONE:
137         result = _("Oops: Signature not verified");
138         break;
139       case GPGME_SIG_STAT_NOSIG:
140         result = _("No signature found");
141         break;
142       case GPGME_SIG_STAT_GOOD:
143         result = _("Good signature");
144         break;
145       case GPGME_SIG_STAT_BAD:
146         result = _("BAD signature");
147         break;
148       case GPGME_SIG_STAT_NOKEY:
149         result = _("No public key to verify the signature");
150         break;
151       case GPGME_SIG_STAT_ERROR:
152         result = _("Error verifying the signature");
153         break;
154       case GPGME_SIG_STAT_DIFF:
155         result = _("Different results for signatures");
156         break;
157       default:
158         result = _("Error: Unknown status");
159         break;
160     }
161
162     return result;
163 }
164
165 static const gchar *
166 sig_status_with_name (GpgmeSigStat status)
167 {
168     const gchar *result;
169
170     switch (status) {
171       case GPGME_SIG_STAT_NONE:
172         result = _("Oops: Signature not verified");
173         break;
174       case GPGME_SIG_STAT_NOSIG:
175         result = _("No signature found");
176         break;
177       case GPGME_SIG_STAT_GOOD:
178         result = _("Good signature from \"%s\"");
179         break;
180       case GPGME_SIG_STAT_BAD:
181         result = _("BAD signature  from \"%s\"");
182         break;
183       case GPGME_SIG_STAT_NOKEY:
184         result = _("No public key to verify the signature");
185         break;
186       case GPGME_SIG_STAT_ERROR:
187         result = _("Error verifying the signature");
188         break;
189       case GPGME_SIG_STAT_DIFF:
190         result = _("Different results for signatures");
191         break;
192       default:
193         result = _("Error: Unknown status");
194         break;
195     }
196
197     return result;
198 }
199
200 static void
201 sig_status_for_key(GString *str, GpgmeCtx ctx, GpgmeSigStat status, 
202                    GpgmeKey key, const gchar *fpr)
203 {
204         gint idx = 0;
205         const char *uid;
206
207         uid = gpgme_key_get_string_attr (key, GPGME_ATTR_USERID, NULL, idx);
208         if (uid == NULL) {
209                 g_string_sprintfa (str, "%s\n",
210                                    sig_status_to_string (status));
211                 if ((fpr != NULL) && (*fpr != '\0'))
212                         g_string_sprintfa (str, "Key fingerprint: %s\n", fpr);
213                 g_string_append (str, _("Cannot find user ID for this key."));
214                 return;
215         }
216         g_string_sprintfa (str, sig_status_with_name (status), uid);
217         g_string_append (str, "\n");
218
219         while (1) {
220                 uid = gpgme_key_get_string_attr (key, GPGME_ATTR_USERID,
221                                                  NULL, ++idx);
222                 if (uid == NULL)
223                         break;
224                 g_string_sprintfa (str, _("                aka \"%s\"\n"),
225                                    uid);
226         }
227 }
228
229 static gchar *
230 sig_status_full (GpgmeCtx ctx)
231 {
232         GString *str;
233         gint sig_idx = 0;
234         GpgmeError err;
235         GpgmeSigStat status;
236         GpgmeKey key;
237         const char *fpr;
238         time_t created;
239         struct tm *ctime_val;
240         char ctime_str[80];
241         gchar *retval;
242
243         str = g_string_new ("");
244
245         fpr = gpgme_get_sig_status (ctx, sig_idx, &status, &created);
246         while (fpr != NULL) {
247                 if (created != 0) {
248                         ctime_val = localtime (&created);
249                         strftime (ctime_str, sizeof (ctime_str), "%c", 
250                                   ctime_val);
251                         g_string_sprintfa (str,
252                                            _("Signature made %s\n"),
253                                            ctime_str);
254                 }
255                 err = gpgme_get_sig_key (ctx, sig_idx, &key);
256                 if (err != 0) {
257                         g_string_sprintfa (str, "%s\n",
258                                            sig_status_to_string (status));
259                         if ((fpr != NULL) && (*fpr != '\0'))
260                                 g_string_sprintfa (str, 
261                                                    _("Key fingerprint: %s\n"),
262                                                    fpr);
263                 } else {
264                         sig_status_for_key (str, ctx, status, key, fpr);
265                         gpgme_key_unref (key);
266                 }
267                 g_string_append (str, "\n\n");
268
269                 fpr = gpgme_get_sig_status (ctx, ++sig_idx, &status, &created);
270         }
271
272         retval = str->str;
273         g_string_free (str, FALSE);
274         return retval;
275 }
276
277 static void check_signature (MimeInfo *mimeinfo, MimeInfo *partinfo, FILE *fp)
278 {
279     GpgmeCtx ctx = NULL;
280     GpgmeError err;
281     GpgmeData sig = NULL, text = NULL;
282     GpgmeSigStat status = GPGME_SIG_STAT_NONE;
283     GpgmegtkSigStatus statuswindow = NULL;
284     const char *result = NULL;
285
286     if (prefs_common.gpg_signature_popup)
287         statuswindow = gpgmegtk_sig_status_create ();
288
289     err = gpgme_new (&ctx);
290     if (err) {
291         g_warning ("gpgme_new failed: %s", gpgme_strerror (err));
292         goto leave;
293     }
294
295     /* don't include the last character (LF). It does not belong to the
296      * signed text */
297     err = gpgme_data_new_from_filepart (&text, NULL, fp,
298                                         mimeinfo->children->fpos,
299                                         mimeinfo->children->size ?
300                                         (mimeinfo->children->size - 1) : 0 );
301     if (!err)
302         err = gpgme_data_new_from_filepart (&sig, NULL, fp,
303                                             partinfo->fpos, partinfo->size);
304     if (err) {
305         g_message ("gpgme_data_new_from_filepart failed: %s",
306                    gpgme_strerror (err));
307         goto leave;
308     }
309
310     err = gpgme_op_verify (ctx, sig, text, &status);
311     if (err) 
312         g_message ("gpgme_op_verify failed: %s", gpgme_strerror (err));
313
314     /* FIXME: check what the heck this sig_status_full stuff is.
315      * it should better go into sigstatus.c */
316     g_free (partinfo->sigstatus_full);
317     partinfo->sigstatus_full = sig_status_full (ctx);
318
319 leave:
320     result = gpgmegtk_sig_status_to_string(status);
321     debug_print("verification status: %s\n", result);
322     if (prefs_common.gpg_signature_popup)
323         gpgmegtk_sig_status_update (statuswindow,ctx);
324
325     g_assert (!err); /* FIXME: Hey: this may indeed happen */
326     g_free (partinfo->sigstatus);
327     partinfo->sigstatus = g_strdup (result);
328
329     gpgme_data_release (sig);
330     gpgme_data_release (text);
331     gpgme_release (ctx);
332     if (prefs_common.gpg_signature_popup)
333         gpgmegtk_sig_status_destroy (statuswindow);
334 }
335
336 static const char *
337 passphrase_cb (void *opaque, const char *desc, void *r_hd)
338 {
339     struct passphrase_cb_info_s *info = opaque;
340     GpgmeCtx ctx = info ? info->c : NULL;
341     const char *pass;
342
343     if (!desc) {
344         /* FIXME: cleanup by looking at *r_hd */
345         return NULL;
346     }
347
348     gpgmegtk_set_passphrase_grab (prefs_common.passphrase_grab);
349     g_message ("%% requesting passphrase for `%s': ", desc );
350     pass = gpgmegtk_passphrase_mbox (desc);
351     if (!pass) {
352         g_message ("%% cancel passphrase entry");
353         gpgme_cancel (ctx);
354     }
355     else
356         g_message ("%% sending passphrase");
357
358     return pass;
359 }
360
361 /*
362  * Copy a gpgme data object to a temporary file and
363  * return this filename 
364  */
365 static char *
366 copy_gpgmedata_to_temp (GpgmeData data, guint *length)
367 {
368     static int id;
369     char *tmp;
370     FILE *fp;
371     char buf[100];
372     size_t nread;
373     GpgmeError err;
374     
375     tmp = g_strdup_printf("%s%cgpgtmp.%08x",
376                           get_mime_tmp_dir(), G_DIR_SEPARATOR, ++id );
377
378     if ((fp = fopen(tmp, "w")) == NULL) {
379         FILE_OP_ERROR(tmp, "fopen");
380         g_free(tmp);
381         return NULL;
382     }
383
384     err = gpgme_data_rewind ( data );
385     if (err)
386         g_message ("** gpgme_data_rewind failed: %s", gpgme_strerror (err));
387
388     while (!(err = gpgme_data_read (data, buf, 100, &nread))) {
389         fwrite ( buf, nread, 1, fp );
390     }
391
392     if (err != GPGME_EOF)
393         g_warning ("** gpgme_data_read failed: %s", gpgme_strerror (err));
394
395     fclose (fp);
396     *length = nread;
397
398     return tmp;
399 }
400
401 static GpgmeData
402 pgp_decrypt (MimeInfo *partinfo, FILE *fp)
403 {
404     GpgmeCtx ctx = NULL;
405     GpgmeError err;
406     GpgmeData cipher = NULL, plain = NULL;
407     struct passphrase_cb_info_s info;
408
409     memset (&info, 0, sizeof info);
410
411     err = gpgme_new (&ctx);
412     if (err) {
413         g_message ("gpgme_new failed: %s", gpgme_strerror (err));
414         goto leave;
415     }
416
417     err = gpgme_data_new_from_filepart (&cipher, NULL, fp,
418                                         partinfo->fpos, partinfo->size);
419     if (err) {
420         g_message ("gpgme_data_new_from_filepart failed: %s",
421                    gpgme_strerror (err));
422         goto leave;
423     }
424
425     err = gpgme_data_new (&plain);
426     if (err) {
427         g_message ("gpgme_new failed: %s", gpgme_strerror (err));
428         goto leave;
429     }
430
431     if (!getenv("GPG_AGENT_INFO")) {
432         info.c = ctx;
433         gpgme_set_passphrase_cb (ctx, passphrase_cb, &info);
434     } 
435
436     err = gpgme_op_decrypt (ctx, cipher, plain);
437
438 leave:
439     gpgme_data_release (cipher);
440     if (err) {
441         g_warning ("** decryption failed: %s", gpgme_strerror (err));
442         gpgme_data_release (plain);
443         plain = NULL;
444     }
445     else
446         g_message ("** decryption succeeded");
447
448     gpgme_release (ctx);
449     return plain;
450 }
451
452 MimeInfo * rfc2015_find_signature (MimeInfo *mimeinfo)
453 {
454     MimeInfo *partinfo;
455     int n = 0;
456
457     if (!mimeinfo)
458         return NULL;
459     if (g_strcasecmp (mimeinfo->content_type, "multipart/signed"))
460         return NULL;
461
462     g_message ("** multipart/signed encountered");
463
464     /* check that we have at least 2 parts of the correct type */
465     for (partinfo = mimeinfo->children;
466          partinfo != NULL; partinfo = partinfo->next) {
467         if (++n > 1  && !g_strcasecmp (partinfo->content_type,
468                                        "application/pgp-signature"))
469             break;
470     }
471
472     return partinfo;
473 }
474
475 gboolean rfc2015_has_signature (MimeInfo *mimeinfo)
476 {
477     return rfc2015_find_signature (mimeinfo) != NULL;
478 }
479
480 void rfc2015_check_signature (MimeInfo *mimeinfo, FILE *fp)
481 {
482     MimeInfo *partinfo;
483
484     partinfo = rfc2015_find_signature (mimeinfo);
485     if (!partinfo)
486         return;
487
488 #if 0
489     g_message ("** yep, it is a pgp signature");
490     dump_mimeinfo ("gpg-signature", partinfo );
491     dump_part (partinfo, fp );
492     dump_mimeinfo ("signed text", mimeinfo->children );
493     dump_part (mimeinfo->children, fp);
494 #endif
495
496     check_signature (mimeinfo, partinfo, fp);
497 }
498
499 int rfc2015_is_encrypted (MimeInfo *mimeinfo)
500 {
501     if (!mimeinfo)
502         return 0;
503     if (g_strcasecmp (mimeinfo->content_type, "multipart/encrypted"))
504         return 0;
505     /* fixme: we should schek the protocol parameter */
506     return 1;
507 }
508
509 static int
510 name_cmp(const char *a, const char *b)
511 {
512     for( ; *a && *b; a++, b++) {
513         if(*a != *b
514            && toupper(*(unsigned char *)a) != toupper(*(unsigned char *)b))
515             return 1;
516     }
517
518     return *a != *b;
519 }
520
521 static int
522 headerp(char *p, char **names)
523 {
524     int i, c;
525     char *p2;
526
527     p2 = strchr(p, ':');
528     if(!p2 || p == p2) {
529         return 0;
530     }
531     if(p2[-1] == ' ' || p2[-1] == '\t') {
532         return 0;
533     }
534
535     if(!names[0])
536         return 1;  
537
538     c = *p2;
539     *p2 = 0;
540     for(i = 0 ; names[i] != NULL; i++) {
541         if(!name_cmp (names[i], p))
542             break;
543     }
544     *p2 = c;
545
546     return names[i] != NULL;
547 }
548
549
550 void rfc2015_decrypt_message (MsgInfo *msginfo, MimeInfo *mimeinfo, FILE *fp)
551 {
552     static int id;
553     MimeInfo *partinfo;
554     int n, found;
555     int ver_okay=0;
556     char *fname;
557     GpgmeData plain;
558     FILE *dstfp;
559     size_t nread;
560     char buf[BUFFSIZE];
561     GpgmeError err;
562
563     g_return_if_fail (mimeinfo->mime_type == MIME_MULTIPART);
564
565     g_message ("** multipart/encrypted encountered");
566
567     /* skip headers */
568     if (fseek(fp, mimeinfo->fpos, SEEK_SET) < 0)
569         perror("fseek");
570     while (fgets(buf, sizeof(buf), fp) != NULL)
571         if (buf[0] == '\r' || buf[0] == '\n') break;
572     
573     procmime_scan_multipart_message(mimeinfo, fp);
574
575     /* check that we have the 2 parts */
576     n = found = 0;
577     for (partinfo = mimeinfo->children; partinfo; partinfo = partinfo->next) {
578         if (++n == 1 && !g_strcasecmp (partinfo->content_type,
579                                        "application/pgp-encrypted")) {
580             /* Fixme: check that the version is 1 */
581             ver_okay = 1;
582         }
583         else if (n == 2 && !g_strcasecmp (partinfo->content_type,
584                                           "application/octet-stream")) {
585             if (partinfo->next)
586                 g_warning ("** oops: pgp_encrypted with more than 2 parts");
587             break;
588         }
589     }
590
591     if (!ver_okay || !partinfo) {
592         msginfo->decryption_failed = 1;
593         /* fixme: remove the stuff, that the above procmime_scan_multiparts() 
594          * has appended to mimeino */
595         return;
596     }
597
598     g_message ("** yep, it is pgp encrypted");
599
600     plain = pgp_decrypt (partinfo, fp);
601     if (!plain) {
602         msginfo->decryption_failed = 1;
603         return;
604     }
605     
606     fname = g_strdup_printf("%s%cplaintext.%08x",
607                             get_mime_tmp_dir(), G_DIR_SEPARATOR, ++id);
608
609     if ((dstfp = fopen(fname, "w")) == NULL) {
610         FILE_OP_ERROR(fname, "fopen");
611         g_free(fname);
612         msginfo->decryption_failed = 1;
613         return;
614     }
615
616     /* write the orginal header to the new file */
617     if (fseek(fp, mimeinfo->fpos, SEEK_SET) < 0)
618         perror("fseek");
619
620     while (fgets(buf, sizeof(buf), fp)) {
621         if (headerp (buf, content_names))
622             continue;
623         if (buf[0] == '\r' || buf[0] == '\n')
624             break;
625         fputs (buf, dstfp);
626     }
627
628     err = gpgme_data_rewind (plain);
629     if (err)
630         g_message ("** gpgme_data_rewind failed: %s", gpgme_strerror (err));
631
632     while (!(err = gpgme_data_read (plain, buf, sizeof(buf), &nread))) {
633         fwrite (buf, nread, 1, dstfp);
634     }
635
636     if (err != GPGME_EOF) {
637         g_warning ("** gpgme_data_read failed: %s", gpgme_strerror (err));
638     }
639
640     fclose (dstfp);
641
642     msginfo->plaintext_file = fname;
643     msginfo->decryption_failed = 0;
644 }
645
646
647 /* 
648  * plain contains an entire mime object.
649  * Encrypt it and return an GpgmeData object with the encrypted version of
650  * the file or NULL in case of error.
651  */
652 static GpgmeData
653 pgp_encrypt ( GpgmeData plain, GpgmeRecipients rset )
654 {
655     GpgmeCtx ctx = NULL;
656     GpgmeError err;
657     GpgmeData cipher = NULL;
658
659     err = gpgme_new (&ctx);
660     if (!err)
661         err = gpgme_data_new (&cipher);
662     if (!err) {
663         gpgme_set_armor (ctx, 1);
664         err = gpgme_op_encrypt (ctx, rset, plain, cipher);
665     }
666
667     if (err) {
668         g_warning ("** encryption failed: %s", gpgme_strerror (err));
669         gpgme_data_release (cipher);
670         cipher = NULL;
671     }
672     else {
673         g_message ("** encryption succeeded");
674     }
675
676     gpgme_release (ctx);
677     return cipher;
678 }
679
680
681 /*
682  * Encrypt the file by extracting all recipients and finding the
683  * encryption keys for all of them.  The file content is then replaced
684  * by the encrypted one.  */
685 int
686 rfc2015_encrypt (const char *file, GSList *recp_list)
687 {
688     FILE *fp = NULL;
689     char buf[BUFFSIZE];
690     int i, clineidx, saved_last;
691     char *clines[3] = {NULL};
692     GpgmeError err;
693     GpgmeData header = NULL;
694     GpgmeData plain = NULL;
695     GpgmeData cipher = NULL;
696     GpgmeRecipients rset = NULL;
697     size_t nread;
698     int mime_version_seen = 0;
699     char *boundary = create_boundary ();
700
701     /* Create the list of recipients */
702     rset = gpgmegtk_recipient_selection (recp_list);
703     if (!rset) {
704         g_warning ("error creating recipient list" );
705         goto failure;
706     }
707
708     /* Open the source file */
709     if ((fp = fopen(file, "rb")) == NULL) {
710         FILE_OP_ERROR(file, "fopen");
711         goto failure;
712     }
713
714     err = gpgme_data_new (&header);
715     if (!err)
716         err = gpgme_data_new (&plain);
717     if (err) {
718         g_message ("gpgme_data_new failed: %s", gpgme_strerror (err));
719         goto failure;
720     }
721
722     /* get the content header lines from the source */
723     clineidx=0;
724     saved_last = 0;
725     while (!err && fgets(buf, sizeof(buf), fp)) {
726         /* fixme: check for overlong lines */
727         if (headerp (buf, content_names)) {
728             if (clineidx >= DIM (clines)) {
729                 g_message ("rfc2015_encrypt: too many content lines");
730                 goto failure;
731             }
732             clines[clineidx++] = g_strdup (buf);
733             saved_last = 1;
734             continue;
735         }
736         if (saved_last) {
737             saved_last = 0;
738             if (*buf == ' ' || *buf == '\t') {
739                 char *last = clines[clineidx-1];
740                 clines[clineidx-1] = g_strconcat (last, buf, NULL);
741                 g_free (last);
742                 continue;
743             }
744         }
745
746         if (headerp (buf, mime_version_name)) 
747             mime_version_seen = 1;
748
749         if (buf[0] == '\r' || buf[0] == '\n')
750             break;
751         err = gpgme_data_write (header, buf, strlen (buf));
752     }
753     if (ferror (fp)) {
754         FILE_OP_ERROR (file, "fgets");
755         goto failure;
756     }
757
758     /* write them to the temp data and add the rest of the message */
759     for (i = 0; !err && i < clineidx; i++) {
760         g_message ("%% %s:%d: cline=`%s'", __FILE__ ,__LINE__, clines[i]);
761         err = gpgme_data_write (plain, clines[i], strlen (clines[i]));
762     }
763     if (!err)
764         err = gpgme_data_write (plain, "\r\n", 2);
765     while (!err && fgets(buf, sizeof(buf), fp)) {
766         err = gpgme_data_write (plain, buf, strlen (buf));
767     }
768     if (ferror (fp)) {
769         FILE_OP_ERROR (file, "fgets");
770         goto failure;
771     }
772     if (err) {
773         g_warning ("** gpgme_data_write failed: %s", gpgme_strerror (err));
774         goto failure;
775     }
776
777     cipher = pgp_encrypt (plain, rset);
778     gpgme_data_release (plain); plain = NULL;
779     gpgme_recipients_release (rset); rset = NULL;
780     if (!cipher)
781         goto failure;
782
783     /* we have the encrypted message available in cipher and now we
784      * are going to rewrite the source file. To be sure that file has
785      * been truncated we use an approach which should work everywhere:
786      * close the file and then reopen it for writing. It is important
787      * that this works, otherwise it may happen that parts of the
788      * plaintext are still in the file (The encrypted stuff is, due to
789      * compression, usually shorter than the plaintext). 
790      * 
791      * Yes, there is a race condition here, but everyone, who is so
792      * stupid to store the temp file with the plaintext in a public
793      * directory has to live with this anyway. */
794     if (fclose (fp)) {
795         FILE_OP_ERROR(file, "fclose");
796         goto failure;
797     }
798     if ((fp = fopen(file, "wb")) == NULL) {
799         FILE_OP_ERROR(file, "fopen");
800         goto failure;
801     }
802
803     /* Write the header, append new content lines, part 1 and part 2 header */
804     err = gpgme_data_rewind (header);
805     if (err) {
806         g_message ("gpgme_data_rewind failed: %s", gpgme_strerror (err));
807         goto failure;
808     }
809     while (!(err = gpgme_data_read (header, buf, BUFFSIZE, &nread))) {
810         fwrite (buf, nread, 1, fp);
811     }
812     if (err != GPGME_EOF) {
813         g_warning ("** gpgme_data_read failed: %s", gpgme_strerror (err));
814         goto failure;
815     }
816     if (ferror (fp)) {
817         FILE_OP_ERROR (file, "fwrite");
818         goto failure;
819     }
820     gpgme_data_release (header); header = NULL;
821     
822     if (!mime_version_seen) 
823         fputs ("MIME-Version: 1\r\n", fp);
824
825     fprintf (fp,
826              "Content-Type: multipart/encrypted;"
827              " protocol=\"application/pgp-encrypted\";\r\n"
828              " boundary=\"%s\"\r\n"
829              "\r\n"
830              "--%s\r\n"
831              "Content-Type: application/pgp-encrypted\r\n"
832              "\r\n"
833              "Version: 1\r\n"
834              "\r\n"
835              "--%s\r\n"
836              "Content-Type: application/octet-stream\r\n"
837              "\r\n",
838              boundary, boundary, boundary);
839
840     /* append the encrypted stuff */
841     err = gpgme_data_rewind (cipher);
842     if (err) {
843         g_warning ("** gpgme_data_rewind on cipher failed: %s",
844                    gpgme_strerror (err));
845         goto failure;
846     }
847
848     while (!(err = gpgme_data_read (cipher, buf, BUFFSIZE, &nread))) {
849         fwrite (buf, nread, 1, fp);
850     }
851     if (err != GPGME_EOF) {
852         g_warning ("** gpgme_data_read failed: %s", gpgme_strerror (err));
853         goto failure;
854     }
855
856     /* and the final boundary */
857     fprintf (fp,
858              "\r\n"
859              "--%s--\r\n"
860              "\r\n",
861              boundary);
862     fflush (fp);
863     if (ferror (fp)) {
864         FILE_OP_ERROR (file, "fwrite");
865         goto failure;
866     }
867     fclose (fp);
868     gpgme_data_release (cipher);
869     return 0;
870
871 failure:
872     if (fp) 
873         fclose (fp);
874     gpgme_data_release (header);
875     gpgme_data_release (plain);
876     gpgme_data_release (cipher);
877     gpgme_recipients_release (rset);
878     g_free (boundary);
879     return -1; /* error */
880 }
881
882 int
883 set_signers (GpgmeCtx ctx, PrefsAccount *ac)
884 {
885     GSList *key_list = NULL;
886     GpgmeCtx list_ctx = NULL;
887     const char *keyid = NULL;
888     GSList *p;
889     GpgmeError err;
890     GpgmeKey key;
891
892     if (ac == NULL)
893         return 0;
894
895     switch (ac->sign_key) {
896     case SIGN_KEY_DEFAULT:
897         return 0;               /* nothing to do */
898
899     case SIGN_KEY_BY_FROM:
900         keyid = ac->address;
901         break;
902
903     case SIGN_KEY_CUSTOM:
904         keyid = ac->sign_key_id;
905         break;
906
907     default:
908         g_assert_not_reached ();
909     }
910
911     err = gpgme_new (&list_ctx);
912     if (err)
913         goto leave;
914     err = gpgme_op_keylist_start (list_ctx, keyid, 1);
915     if (err)
916         goto leave;
917     while ( !(err = gpgme_op_keylist_next (list_ctx, &key)) ) {
918         key_list = g_slist_append (key_list, key);
919     }
920     if (err != GPGME_EOF)
921         goto leave;
922     if (key_list == NULL) {
923         g_warning ("no keys found for keyid \"%s\"", keyid);
924     }
925     gpgme_signers_clear (ctx);
926     for (p = key_list; p != NULL; p = p->next) {
927         err = gpgme_signers_add (ctx, (GpgmeKey) p->data);
928         if (err)
929             goto leave;
930     }
931
932 leave:
933     if (err)
934         g_message ("** set_signers failed: %s", gpgme_strerror (err));
935     for (p = key_list; p != NULL; p = p->next)
936         gpgme_key_unref ((GpgmeKey) p->data);
937     g_slist_free (key_list);
938     if (list_ctx)
939         gpgme_release (list_ctx);
940     return err;
941 }
942
943 /* 
944  * plain contains an entire mime object.  Sign it and return an
945  * GpgmeData object with the signature of it or NULL in case of error.
946  */
947 static GpgmeData
948 pgp_sign (GpgmeData plain, PrefsAccount *ac)
949 {
950     GpgmeCtx ctx = NULL;
951     GpgmeError err;
952     GpgmeData sig = NULL;
953     struct passphrase_cb_info_s info;
954
955     memset (&info, 0, sizeof info);
956
957     err = gpgme_new (&ctx);
958     if (err)
959         goto leave;
960     err = gpgme_data_new (&sig);
961     if (err)
962         goto leave;
963
964     if (!getenv("GPG_AGENT_INFO")) {
965         info.c = ctx;
966         gpgme_set_passphrase_cb (ctx, passphrase_cb, &info);
967     }
968     gpgme_set_textmode (ctx, 1);
969     gpgme_set_armor (ctx, 1);
970     err = set_signers (ctx, ac);
971     if (err)
972         goto leave;
973     err = gpgme_op_sign (ctx, plain, sig, GPGME_SIG_MODE_DETACH);
974
975 leave:
976     if (err) {
977         g_message ("** signing failed: %s", gpgme_strerror (err));
978         gpgme_data_release (sig);
979         sig = NULL;
980     }
981     else {
982         g_message ("** signing succeeded");
983     }
984
985     gpgme_release (ctx);
986     return sig;
987 }
988
989 /*
990  * Sign the file and replace its content with the signed one.
991  */
992 int
993 rfc2015_sign (const char *file, PrefsAccount *ac)
994 {
995     FILE *fp = NULL;
996     char buf[BUFFSIZE];
997     int i, clineidx, saved_last;
998     char *clines[3] = {NULL};
999     GpgmeError err;
1000     GpgmeData header = NULL;
1001     GpgmeData plain = NULL;
1002     GpgmeData sigdata = NULL;
1003     size_t nread;
1004     int mime_version_seen = 0;
1005     char *boundary = create_boundary ();
1006
1007     /* Open the source file */
1008     if ((fp = fopen(file, "rb")) == NULL) {
1009         FILE_OP_ERROR(file, "fopen");
1010         goto failure;
1011     }
1012
1013     err = gpgme_data_new (&header);
1014     if (!err)
1015         err = gpgme_data_new (&plain);
1016     if (err) {
1017         g_message ("gpgme_data_new failed: %s", gpgme_strerror (err));
1018         goto failure;
1019     }
1020
1021     /* get the content header lines from the source */
1022     clineidx = 0;
1023     saved_last = 0;
1024     while (!err && fgets(buf, sizeof(buf), fp)) {
1025         /* fixme: check for overlong lines */
1026         if (headerp (buf, content_names)) {
1027             if (clineidx >= DIM (clines)) {
1028                 g_message ("rfc2015_sign: too many content lines");
1029                 goto failure;
1030             }
1031             clines[clineidx++] = g_strdup (buf);
1032             saved_last = 1;
1033             continue;
1034         }
1035         if (saved_last) {
1036             saved_last = 0;
1037             if (*buf == ' ' || *buf == '\t') {
1038                 char *last = clines[clineidx - 1];
1039                 clines[clineidx - 1] = g_strconcat (last, buf, NULL);
1040                 g_free (last);
1041                 continue;
1042             }
1043         }
1044
1045         if (headerp (buf, mime_version_name)) 
1046             mime_version_seen = 1;
1047
1048         if (buf[0] == '\r' || buf[0] == '\n')
1049             break;
1050         err = gpgme_data_write (header, buf, strlen (buf));
1051     }
1052     if (ferror (fp)) {
1053         FILE_OP_ERROR (file, "fgets");
1054         goto failure;
1055     }
1056
1057     /* write them to the temp data and add the rest of the message */
1058     for (i = 0; !err && i < clineidx; i++) {
1059         err = gpgme_data_write (plain, clines[i], strlen (clines[i]));
1060     }
1061     if (!err)
1062         err = gpgme_data_write (plain, "\r\n", 2 );
1063     while (!err && fgets(buf, sizeof(buf), fp)) {
1064         err = gpgme_data_write (plain, buf, strlen (buf));
1065     }
1066     if (ferror (fp)) {
1067         FILE_OP_ERROR (file, "fgets");
1068         goto failure;
1069     }
1070     if (err) {
1071         g_message ("** gpgme_data_write failed: %s", gpgme_strerror (err));
1072         goto failure;
1073     }
1074
1075     sigdata = pgp_sign (plain, ac);
1076     if (!sigdata) 
1077         goto failure;
1078
1079     /* we have the signed message available in sigdata and now we are
1080      * going to rewrite the original file. To be sure that file has
1081      * been truncated we use an approach which should work everywhere:
1082      * close the file and then reopen it for writing. */
1083     if (fclose (fp)) {
1084         FILE_OP_ERROR(file, "fclose");
1085         goto failure;
1086     }
1087     if ((fp = fopen(file, "wb")) == NULL) {
1088         FILE_OP_ERROR(file, "fopen");
1089         goto failure;
1090     }
1091
1092     /* Write the rfc822 header and add new content lines */
1093     err = gpgme_data_rewind (header);
1094     if (err)
1095         g_message ("** gpgme_data_rewind failed: %s", gpgme_strerror (err));
1096     while (!(err = gpgme_data_read (header, buf, BUFFSIZE, &nread))) {
1097         fwrite (buf, nread, 1, fp);
1098     }
1099     if (err != GPGME_EOF) {
1100         g_message ("** gpgme_data_read failed: %s", gpgme_strerror (err));
1101         goto failure;
1102     }
1103     if (ferror (fp)) {
1104         FILE_OP_ERROR (file, "fwrite");
1105         goto failure;
1106     }
1107     gpgme_data_release (header);
1108     header = NULL;
1109
1110     if (!mime_version_seen) 
1111         fputs ("MIME-Version: 1\r\n", fp);
1112     fprintf (fp, "Content-Type: multipart/signed; "
1113                  "protocol=\"application/pgp-signature\";\r\n"
1114                  " boundary=\"%s\"\r\n", boundary );
1115
1116     /* Part 1: signed material */
1117     fprintf (fp, "\r\n--%s\r\n", boundary);
1118     err = gpgme_data_rewind (plain);
1119     if (err) {
1120         g_message ("** gpgme_data_rewind on plain failed: %s",
1121                    gpgme_strerror (err));
1122         goto failure;
1123     }
1124     while (!(err = gpgme_data_read (plain, buf, BUFFSIZE, &nread))) {
1125         fwrite (buf, nread, 1, fp);   
1126     }
1127     if (err != GPGME_EOF) {
1128         g_message ("** gpgme_data_read failed: %s", gpgme_strerror (err));
1129         goto failure;
1130     }
1131
1132     /* Part 2: signature */
1133     fprintf (fp, "\r\n--%s\r\n", boundary);
1134     fputs ("Content-Type: application/pgp-signature\r\n"
1135            "\r\n", fp);
1136
1137     err = gpgme_data_rewind (sigdata);
1138     if (err) {
1139         g_message ("** gpgme_data_rewind on sigdata failed: %s",
1140                    gpgme_strerror (err));
1141         goto failure;
1142     }
1143
1144     while (!(err = gpgme_data_read (sigdata, buf, BUFFSIZE, &nread))) {
1145         fwrite (buf, nread, 1, fp);
1146     }
1147     if (err != GPGME_EOF) {
1148         g_message ("** gpgme_data_read failed: %s", gpgme_strerror (err));
1149         goto failure;
1150     }
1151
1152     /* Final boundary */
1153     fprintf (fp, "\r\n--%s--\r\n\r\n", boundary);
1154     fflush (fp);
1155     if (ferror (fp)) {
1156         FILE_OP_ERROR (file, "fwrite");
1157         goto failure;
1158     }
1159     fclose (fp);
1160     gpgme_data_release (header);
1161     gpgme_data_release (plain);
1162     gpgme_data_release (sigdata);
1163     g_free (boundary);
1164     return 0;
1165
1166 failure:
1167     if (fp) 
1168         fclose (fp);
1169     gpgme_data_release (header);
1170     gpgme_data_release (plain);
1171     gpgme_data_release (sigdata);
1172     g_free (boundary);
1173     return -1; /* error */
1174 }
1175
1176
1177 /****************
1178  * Create a new boundary in a way that it is very unlikely that this
1179  * will occur in the following text.  It would be easy to ensure
1180  * uniqueness if everything is either quoted-printable or base64
1181  * encoded (note that conversion is allowed), but because MIME bodies
1182  * may be nested, it may happen that the same boundary has already
1183  * been used. We avoid scanning the message for conflicts and hope the
1184  * best.
1185  *
1186  *   boundary := 0*69<bchars> bcharsnospace
1187  *   bchars := bcharsnospace / " "
1188  *   bcharsnospace := DIGIT / ALPHA / "'" / "(" / ")" /
1189  *                    "+" / "_" / "," / "-" / "." /
1190  *                    "/" / ":" / "=" / "?"  
1191  */
1192
1193 static char *
1194 create_boundary (void)
1195 {
1196     static char tbl[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
1197                         "abcdefghijklmnopqrstuvwxyz"
1198                         "1234567890'()+_,./:=?";
1199     char buf[17];
1200     int i, equal;
1201     int pid;
1202
1203     pid = getpid();
1204
1205     /* We make the boundary depend on the pid, so that all running
1206      * processed generate different values even when they have been
1207      * started within the same second and srand48(time(NULL)) has been
1208      * used.  I can't see whether this is really an advantage but it
1209      * doesn't do any harm.
1210      */
1211     equal = -1;
1212     for(i = 0; i < sizeof(buf) - 1; i++) {
1213         buf[i] = tbl[(lrand48() ^ pid) % (sizeof(tbl) - 1)]; /* fill with random */
1214         if(buf[i] == '=' && equal == -1)
1215             equal = i;
1216     }
1217     buf[i] = 0;
1218
1219     /* now make sure that we do have the sequence "=." in it which cannot
1220      * be matched by quoted-printable or base64 encoding */
1221     if(equal != -1 && (equal+1) < i)
1222         buf[equal+1] = '.';
1223     else {
1224         buf[0] = '=';
1225         buf[1] = '.';
1226     }
1227
1228     return g_strdup(buf);
1229 }
1230
1231 #endif /* USE_GPGME */