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