0.9.6claws74
[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 #include "alertpanel.h"
50
51 #define DIM(v)     (sizeof(v)/sizeof((v)[0]))
52
53 static char *content_names[] = {
54     "Content-Type",
55     "Content-Disposition",
56     "Content-Transfer-Encoding",
57     NULL
58 };
59
60 static char *mime_version_name[] = {
61     "Mime-Version",
62     NULL
63 };
64
65 static void sig_expiration_check        (GString        *str,
66                                          GpgmeCtx        ctx,
67                                          GpgmeKey        key, 
68                                          GpgmeSigStat    status,
69                                          int             idx);
70 static void sig_expired                 (GString        *str,
71                                          GpgmeCtx        ctx,
72                                          int             idx);
73 static void sig_key_expired             (GString        *str,
74                                          GpgmeKey        key,
75                                          int             idx);
76
77 #if 0
78 static void dump_mimeinfo (const char *text, MimeInfo *x)
79 {
80     debug_print ("MimeInfo[%s] %p  level=%d\n",
81                text, x, x? x->level:0 );
82     if (!x)
83         return;
84
85     debug_print ("      enc=`%s' enc_type=%d mime_type=%d\n",
86                x->encoding, x->encoding_type, x->mime_type );
87     debug_print ("      cont_type=`%s' cs=`%s' name=`%s' bnd=`%s'\n",
88                x->content_type, x->charset, x->name, x->boundary );
89     debug_print ("      cont_disp=`%s' fname=`%s' fpos=%ld size=%u, lvl=%d\n",
90                x->content_disposition, x->filename, x->fpos, x->size,
91                x->level );
92     dump_mimeinfo (".main", x->main );
93     dump_mimeinfo (".sub", x->sub );
94     dump_mimeinfo (".next", x->next );
95     debug_print ("MimeInfo[.parent] %p\n", x ); 
96     dump_mimeinfo (".children", x->children );
97     dump_mimeinfo (".plaintext", x->plaintext );
98 }
99
100 static void dump_part ( MimeInfo *mimeinfo, FILE *fp )
101 {
102     unsigned int size = mimeinfo->size;
103     int c;
104
105     if (fseek (fp, mimeinfo->fpos, SEEK_SET)) {
106         debug_print ("dump_part: fseek error\n");
107         return;
108     }
109
110     debug_print ("--- begin dump_part ----\n");
111     while (size-- && (c = getc (fp)) != EOF) 
112         putc (c, stderr);
113     if (ferror (fp))
114         debug_print ("dump_part: read error\n");
115     debug_print ("--- end dump_part ----\n");
116 }
117 #endif
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 /*
130  * Copy a gpgme data object to a temporary file and
131  * return this filename 
132  */
133 #if 0
134 static char *
135 copy_gpgmedata_to_temp (GpgmeData data, guint *length)
136 {
137     static int id;
138     char *tmp;
139     FILE *fp;
140     char buf[100];
141     size_t nread;
142     GpgmeError err;
143     
144     tmp = g_strdup_printf("%s%cgpgtmp.%08x",
145                           get_mime_tmp_dir(), G_DIR_SEPARATOR, ++id );
146
147     if ((fp = fopen(tmp, "wb")) == NULL) {
148         FILE_OP_ERROR(tmp, "fopen");
149         g_free(tmp);
150         return NULL;
151     }
152
153     err = gpgme_data_rewind ( data );
154     if (err)
155         debug_print ("gpgme_data_rewind failed: %s\n", gpgme_strerror (err));
156
157     while (!(err = gpgme_data_read (data, buf, 100, &nread))) {
158         fwrite ( buf, nread, 1, fp );
159     }
160
161     if (err != GPGME_EOF)
162         debug_print ("gpgme_data_read failed: %s\n", gpgme_strerror (err));
163
164     fclose (fp);
165     *length = nread;
166
167     return tmp;
168 }
169 #endif
170
171 static GpgmeData
172 pgp_decrypt (MimeInfo *partinfo, FILE *fp)
173 {
174     GpgmeCtx ctx = NULL;
175     GpgmeError err;
176     GpgmeData cipher = NULL, plain = NULL;
177     struct passphrase_cb_info_s info;
178
179     memset (&info, 0, sizeof info);
180
181     err = gpgme_new (&ctx);
182     if (err) {
183         debug_print ("gpgme_new failed: %s\n", gpgme_strerror (err));
184         goto leave;
185     }
186
187     err = gpgme_data_new_from_filepart (&cipher, NULL, fp,
188                                         partinfo->offset, partinfo->length);
189     if (err) {
190         debug_print ("gpgme_data_new_from_filepart failed: %s\n",
191                      gpgme_strerror (err));
192         goto leave;
193     }
194
195     err = gpgme_data_new (&plain);
196     if (err) {
197         debug_print ("gpgme_new failed: %s\n", gpgme_strerror (err));
198         goto leave;
199     }
200
201     if (!getenv("GPG_AGENT_INFO")) {
202         info.c = ctx;
203         gpgme_set_passphrase_cb (ctx, gpgmegtk_passphrase_cb, &info);
204     } 
205
206     err = gpgme_op_decrypt (ctx, cipher, plain);
207
208 leave:
209     gpgme_data_release (cipher);
210     if (err) {
211         gpgmegtk_free_passphrase();
212         debug_print ("decryption failed: %s\n", gpgme_strerror (err));
213         gpgme_data_release (plain);
214         plain = NULL;
215     }
216     else
217         debug_print ("** decryption succeeded\n");
218
219     gpgme_release (ctx);
220     return plain;
221 }
222
223 int rfc2015_is_encrypted (MimeInfo *mimeinfo)
224 {
225     if (!mimeinfo || mimeinfo->type != MIMETYPE_MULTIPART)
226         return 0;
227     if (g_strcasecmp (mimeinfo->subtype, "encrypted"))
228         return 0;
229     /* fixme: we should check the protocol parameter */
230     return 1;
231 }
232
233 gboolean rfc2015_msg_is_encrypted (const gchar *file)
234 {
235         FILE *fp;
236         MimeInfo *mimeinfo;
237         int ret = 0;
238
239 #if 0   /* FIXME */
240         if ((fp = fopen(file, "rb")) == NULL)
241                 return FALSE;
242
243         mimeinfo = procmime_scan_mime_header(fp);
244         if(!mimeinfo) {
245                 fclose(fp);
246                 return FALSE;
247         }
248
249         ret = rfc2015_is_encrypted(mimeinfo);
250         procmime_mimeinfo_free_all(mimeinfo);
251 #endif
252         return ret != 0 ? TRUE : FALSE;
253 }
254
255 static int
256 name_cmp(const char *a, const char *b)
257 {
258     for( ; *a && *b; a++, b++) {
259         if(*a != *b
260            && toupper(*(unsigned char *)a) != toupper(*(unsigned char *)b))
261             return 1;
262     }
263
264     return *a != *b;
265 }
266
267 static int
268 headerp(char *p, char **names)
269 {
270     int i, c;
271     char *p2;
272
273     p2 = strchr(p, ':');
274     if(!p2 || p == p2) {
275         return 0;
276     }
277     if(p2[-1] == ' ' || p2[-1] == '\t') {
278         return 0;
279     }
280
281     if(!names[0])
282         return 1;  
283
284     c = *p2;
285     *p2 = 0;
286     for(i = 0 ; names[i] != NULL; i++) {
287         if(!name_cmp (names[i], p))
288             break;
289     }
290     *p2 = c;
291
292     return names[i] != NULL;
293 }
294
295
296 #define DECRYPTION_ABORT() \
297 { \
298     procmime_mimeinfo_free_all(tmpinfo); \
299     msginfo->decryption_failed = 1; \
300     return; \
301 }
302
303 void rfc2015_decrypt_message (MsgInfo *msginfo, MimeInfo *mimeinfo, FILE *fp)
304 {
305     static int id;
306     MimeInfo *tmpinfo, *partinfo;
307     int ver_ok = 0;
308     char *fname;
309     GpgmeData plain;
310     FILE *dstfp;
311     size_t nread;
312     char buf[BUFFSIZE];
313     int in_cline;
314     GpgmeError err;
315
316     g_return_if_fail (msginfo != NULL);
317     g_return_if_fail (mimeinfo != NULL);
318     g_return_if_fail (fp != NULL);
319     g_return_if_fail (mimeinfo->type == MIMETYPE_MULTIPART);
320
321     debug_print ("** decrypting multipart/encrypted message\n");
322
323 #if 0 /* FIXME */
324
325     /* skip headers */
326     if (fseek(fp, mimeinfo->offset, SEEK_SET) < 0)
327         perror("fseek");
328     tmpinfo = procmime_scan_mime_header(fp);
329     if (!tmpinfo || tmpinfo->type != MIMETYPE_MULTIPART) {
330         DECRYPTION_ABORT();
331     }
332
333     procmime_scan_message(tmpinfo);
334
335     /* check that we have the 2 parts */
336     partinfo = tmpinfo->children;
337     if (!partinfo || !partinfo->next) {
338         DECRYPTION_ABORT();
339     }
340     if ((partinfo->type == MIMETYPE_APPLICATION) && !g_strcasecmp(partinfo->subtype, "pgp-encrypted")) {
341         /* Fixme: check that the version is 1 */
342         ver_ok = 1;
343     }
344     partinfo = partinfo->next;
345     if (ver_ok && (partinfo->type == MIMETYPE_APPLICATION) &&
346         !g_strcasecmp (partinfo->subtype, "octet-stream")) {
347         if (partinfo->next)
348             g_warning ("oops: pgp_encrypted with more than 2 parts");
349     }
350     else {
351         DECRYPTION_ABORT();
352     }
353
354     debug_print ("** yep, it is pgp encrypted\n");
355
356     plain = pgp_decrypt (partinfo, fp);
357     if (!plain) {
358         DECRYPTION_ABORT();
359     }
360
361     fname = g_strdup_printf("%s%cplaintext.%08x",
362                             get_mime_tmp_dir(), G_DIR_SEPARATOR, ++id);
363
364     if ((dstfp = fopen(fname, "wb")) == NULL) {
365         FILE_OP_ERROR(fname, "fopen");
366         g_free(fname);
367         DECRYPTION_ABORT();
368     }
369
370     /* write the orginal header to the new file */
371     if (fseek(fp, tmpinfo->offset, SEEK_SET) < 0)
372         perror("fseek");
373
374     in_cline = 0;
375     while (fgets(buf, sizeof(buf), fp)) {
376         if (headerp (buf, content_names)) {
377             in_cline = 1;
378             continue;
379         }
380         if (in_cline) {
381             if (buf[0] == ' ' || buf[0] == '\t')
382                 continue;
383             in_cline = 0;
384         }
385         if (buf[0] == '\r' || buf[0] == '\n')
386             break;
387         fputs (buf, dstfp);
388     }
389
390     err = gpgme_data_rewind (plain);
391     if (err)
392         debug_print ("gpgme_data_rewind failed: %s\n", gpgme_strerror (err));
393
394     while (!(err = gpgme_data_read (plain, buf, sizeof(buf), &nread))) {
395         fwrite (buf, nread, 1, dstfp);
396     }
397
398     if (err != GPGME_EOF) {
399         debug_print ("gpgme_data_read failed: %s\n", gpgme_strerror (err));
400     }
401
402     fclose (dstfp);
403     procmime_mimeinfo_free_all(tmpinfo);
404
405     msginfo->plaintext_file = fname;
406     msginfo->decryption_failed = 0;
407
408 #endif
409 }
410
411 #undef DECRYPTION_ABORT
412
413
414 /*
415  * plain contains an entire mime object.
416  * Encrypt it and return an GpgmeData object with the encrypted version of
417  * the file or NULL in case of error.
418  */
419 static GpgmeData
420 pgp_encrypt ( GpgmeData plain, GpgmeRecipients rset )
421 {
422     GpgmeCtx ctx = NULL;
423     GpgmeError err;
424     GpgmeData cipher = NULL;
425
426     err = gpgme_new (&ctx);
427     if (!err)
428         err = gpgme_data_new (&cipher);
429     if (!err) {
430         gpgme_set_armor (ctx, 1);
431         err = gpgme_op_encrypt (ctx, rset, plain, cipher);
432     }
433
434     if (err) {
435         debug_print ("encryption failed: %s\n", gpgme_strerror (err));
436         gpgme_data_release (cipher);
437         cipher = NULL;
438     }
439     else {
440         debug_print ("** encryption succeeded\n");
441     }
442
443     gpgme_release (ctx);
444     return cipher;
445 }
446
447 /*
448  * Create and return a list of keys matching a key id
449  */
450
451 GSList *rfc2015_create_signers_list (const char *keyid)
452 {
453         GSList *key_list = NULL;
454         GpgmeCtx list_ctx = NULL;
455         GSList *p;
456         GpgmeError err;
457         GpgmeKey key;
458
459         err = gpgme_new (&list_ctx);
460         if (err)
461                 goto leave;
462         err = gpgme_op_keylist_start (list_ctx, keyid, 1);
463         if (err)
464                 goto leave;
465         while ( !(err = gpgme_op_keylist_next (list_ctx, &key)) ) {
466                 key_list = g_slist_append (key_list, key);
467         }
468         if (err != GPGME_EOF)
469                 goto leave;
470         err = 0;
471         if (key_list == NULL) {
472                 debug_print ("no keys found for keyid \"%s\"\n", keyid);
473         }
474
475 leave:
476         if (err) {
477                 debug_print ("rfc2015_create_signers_list failed: %s\n", gpgme_strerror (err));
478                 for (p = key_list; p != NULL; p = p->next)
479                         gpgme_key_unref ((GpgmeKey) p->data);
480                 g_slist_free (key_list);
481         }
482         if (list_ctx)
483                 gpgme_release (list_ctx);
484         return err ? NULL : key_list;
485 }
486
487 /*
488  * Encrypt the file by extracting all recipients and finding the
489  * encryption keys for all of them.  The file content is then replaced
490  * by the encrypted one.  */
491 int
492 rfc2015_encrypt (const char *file, GSList *recp_list, gboolean ascii_armored)
493 {
494     FILE *fp = NULL;
495     char buf[BUFFSIZE];
496     int i, clineidx, saved_last;
497     char *clines[3] = {NULL};
498     GpgmeError err;
499     GpgmeData header = NULL;
500     GpgmeData plain = NULL;
501     GpgmeData cipher = NULL;
502     GpgmeRecipients rset = NULL;
503     size_t nread;
504     int mime_version_seen = 0;
505     char *boundary;
506
507     boundary = generate_mime_boundary ("Encrypt");
508
509     /* Create the list of recipients */
510     rset = gpgmegtk_recipient_selection (recp_list);
511     if (!rset) {
512         debug_print ("error creating recipient list\n" );
513         goto failure;
514     }
515
516     /* Open the source file */
517     if ((fp = fopen(file, "rb")) == NULL) {
518         FILE_OP_ERROR(file, "fopen");
519         goto failure;
520     }
521
522     err = gpgme_data_new (&header);
523     if (!err)
524         err = gpgme_data_new (&plain);
525     if (err) {
526         debug_print ("gpgme_data_new failed: %s\n", gpgme_strerror (err));
527         goto failure;
528     }
529
530     /* get the content header lines from the source */
531     clineidx = 0;
532     saved_last = 0;
533     while (!err && fgets(buf, sizeof(buf), fp)) {
534         /* fixme: check for overlong lines */
535         if (headerp (buf, content_names)) {
536             if (clineidx >= DIM (clines)) {
537                 debug_print ("rfc2015_encrypt: too many content lines\n");
538                 goto failure;
539             }
540             clines[clineidx++] = g_strdup (buf);
541             saved_last = 1;
542             continue;
543         }
544         if (saved_last) {
545             if (*buf == ' ' || *buf == '\t') {
546                 char *last = clines[clineidx - 1];
547                 clines[clineidx - 1] = g_strconcat (last, buf, NULL);
548                 g_free (last);
549                 continue;
550             }
551             saved_last = 0;
552         }
553
554         if (headerp (buf, mime_version_name)) 
555             mime_version_seen = 1;
556
557         if (buf[0] == '\r' || buf[0] == '\n')
558             break;
559         err = gpgme_data_write (header, buf, strlen (buf));
560     }
561     if (ferror (fp)) {
562         FILE_OP_ERROR (file, "fgets");
563         goto failure;
564     }
565
566     /* write them to the temp data and add the rest of the message */
567     for (i = 0; !err && i < clineidx; i++) {
568         debug_print ("%% %s:%d: cline=`%s'", __FILE__ ,__LINE__, clines[i]);
569         err = gpgme_data_write (plain, clines[i], strlen (clines[i]));
570     }
571     if (!err)
572         err = gpgme_data_write (plain, "\r\n", 2);
573     while (!err && fgets(buf, sizeof(buf), fp)) {
574         err = gpgme_data_write (plain, buf, strlen (buf));
575     }
576     if (ferror (fp)) {
577         FILE_OP_ERROR (file, "fgets");
578         goto failure;
579     }
580     if (err) {
581         debug_print ("gpgme_data_write failed: %s\n", gpgme_strerror (err));
582         goto failure;
583     }
584
585     cipher = pgp_encrypt (plain, rset);
586     gpgme_data_release (plain); plain = NULL;
587     gpgme_recipients_release (rset); rset = NULL;
588     if (!cipher)
589         goto failure;
590
591     /* we have the encrypted message available in cipher and now we
592      * are going to rewrite the source file. To be sure that file has
593      * been truncated we use an approach which should work everywhere:
594      * close the file and then reopen it for writing. It is important
595      * that this works, otherwise it may happen that parts of the
596      * plaintext are still in the file (The encrypted stuff is, due to
597      * compression, usually shorter than the plaintext). 
598      * 
599      * Yes, there is a race condition here, but everyone, who is so
600      * stupid to store the temp file with the plaintext in a public
601      * directory has to live with this anyway. */
602     if (fclose (fp)) {
603         FILE_OP_ERROR(file, "fclose");
604         goto failure;
605     }
606     if ((fp = fopen(file, "wb")) == NULL) {
607         FILE_OP_ERROR(file, "fopen");
608         goto failure;
609     }
610
611     /* Write the header, append new content lines, part 1 and part 2 header */
612     err = gpgme_data_rewind (header);
613     if (err) {
614         debug_print ("gpgme_data_rewind failed: %s\n", gpgme_strerror (err));
615         goto failure;
616     }
617     while (!(err = gpgme_data_read (header, buf, BUFFSIZE, &nread))) {
618         fwrite (buf, nread, 1, fp);
619     }
620     if (err != GPGME_EOF) {
621         debug_print ("gpgme_data_read failed: %s\n", gpgme_strerror (err));
622         goto failure;
623     }
624     if (ferror (fp)) {
625         FILE_OP_ERROR (file, "fwrite");
626         goto failure;
627     }
628     gpgme_data_release (header); header = NULL;
629     
630     if (!mime_version_seen) 
631         fputs ("MIME-Version: 1\r\n", fp);
632
633     if (ascii_armored) {
634         fprintf(fp, 
635             "Content-Type: text/plain; charset=US-ASCII\r\n"
636             "Content-Transfer-Encoding: 7bit\r\n"  
637             "\r\n");
638     } else {
639         fprintf (fp,
640                 "Content-Type: multipart/encrypted;"
641                 " protocol=\"application/pgp-encrypted\";\r\n"
642                 " boundary=\"%s\"\r\n"
643                 "\r\n"
644                 "--%s\r\n"
645                 "Content-Type: application/pgp-encrypted\r\n"
646                 "\r\n"
647                 "Version: 1\r\n"
648                 "\r\n"
649                 "--%s\r\n"
650                 "Content-Type: application/octet-stream\r\n"
651                 "\r\n",
652                 boundary, boundary, boundary);
653     }
654
655     /* append the encrypted stuff */
656     err = gpgme_data_rewind (cipher);
657     if (err) {
658         debug_print ("** gpgme_data_rewind on cipher failed: %s\n",
659                    gpgme_strerror (err));
660         goto failure;
661     }
662
663     while (!(err = gpgme_data_read (cipher, buf, BUFFSIZE, &nread))) {
664         fwrite (buf, nread, 1, fp);
665     }
666     if (err != GPGME_EOF) {
667         debug_print ("** gpgme_data_read failed: %s\n", gpgme_strerror (err));
668         goto failure;
669     }
670
671     /* and the final boundary */
672     if (!ascii_armored) {
673         fprintf (fp,
674                  "\r\n"
675                  "--%s--\r\n",
676                  boundary);
677     }
678     fflush (fp);
679     if (ferror (fp)) {
680         FILE_OP_ERROR (file, "fwrite");
681         goto failure;
682     }
683     fclose (fp);
684     gpgme_data_release (cipher);
685     return 0;
686
687 failure:
688     if (fp) 
689         fclose (fp);
690     gpgme_data_release (header);
691     gpgme_data_release (plain);
692     gpgme_data_release (cipher);
693     gpgme_recipients_release (rset);
694     g_free (boundary);
695     return -1; /* error */
696 }
697
698 /* 
699  * plain contains an entire mime object.  Sign it and return an
700  * GpgmeData object with the signature of it or NULL in case of error.
701  * r_siginfo returns an XML object with information about the signature.
702  */
703 static GpgmeData
704 pgp_sign (GpgmeData plain, GSList *key_list, gboolean clearsign,
705           char **r_siginfo)
706 {
707     GSList *p;
708     GpgmeCtx ctx = NULL;
709     GpgmeError err;
710     GpgmeData sig = NULL;
711     struct passphrase_cb_info_s info;
712
713     *r_siginfo = NULL;
714     memset (&info, 0, sizeof info);
715
716     err = gpgme_new (&ctx);
717     if (err)
718         goto leave;
719     err = gpgme_data_new (&sig);
720     if (err)
721         goto leave;
722
723     if (!getenv("GPG_AGENT_INFO")) {
724         info.c = ctx;
725         gpgme_set_passphrase_cb (ctx, gpgmegtk_passphrase_cb, &info);
726     }
727     gpgme_set_textmode (ctx, 1);
728     gpgme_set_armor (ctx, 1);
729     gpgme_signers_clear (ctx);
730     for (p = key_list; p != NULL; p = p->next) {
731         err = gpgme_signers_add (ctx, (GpgmeKey) p->data);
732         if (err)
733             goto leave;
734     }
735     for (p = key_list; p != NULL; p = p->next)
736         gpgme_key_unref ((GpgmeKey) p->data);
737     g_slist_free (key_list);
738
739     if (err)
740         goto leave;
741     err = gpgme_op_sign
742         (ctx, plain, sig,
743          clearsign ? GPGME_SIG_MODE_CLEAR : GPGME_SIG_MODE_DETACH);
744     if (!err)
745         *r_siginfo = gpgme_get_op_info (ctx, 0);
746
747 leave:
748     if (err) {
749         gpgmegtk_free_passphrase();
750         debug_print ("signing failed: %s\n", gpgme_strerror (err));
751         gpgme_data_release (sig);
752         sig = NULL;
753     }
754     else {
755         debug_print ("signing succeeded\n");
756     }
757
758     gpgme_release (ctx);
759     return sig;
760 }
761
762 /*
763  * Find TAG in XML and return a pointer into xml set just behind the
764  * closing angle.  Return NULL if not found. 
765  */
766 static const char *
767 find_xml_tag (const char *xml, const char *tag)
768 {
769     int taglen = strlen (tag);
770     const char *s = xml;
771  
772     while ( (s = strchr (s, '<')) ) {
773         s++;
774         if (!strncmp (s, tag, taglen)) {
775             const char *s2 = s + taglen;
776             if (*s2 == '>' || isspace (*(const unsigned char*)s2) ) {
777                 /* found */
778                 while (*s2 && *s2 != '>') /* skip attributes */
779                     s2++;
780                 /* fixme: do need to handle angles inside attribute vallues? */
781                 return *s2? (s2+1):NULL;
782             }
783         }
784         while (*s && *s != '>') /* skip to end of tag */
785             s++;
786     }
787     return NULL;
788 }
789
790
791 /*
792  * Extract the micalg from an GnupgOperationInfo XML container.
793  */
794 static char *
795 extract_micalg (char *xml)
796 {
797     const char *s;
798
799     s = find_xml_tag (xml, "GnupgOperationInfo");
800     if (s) {
801         const char *s_end = find_xml_tag (s, "/GnupgOperationInfo");
802         s = find_xml_tag (s, "signature");
803         if (s && s_end && s < s_end) {
804             const char *s_end2 = find_xml_tag (s, "/signature");
805             if (s_end2 && s_end2 < s_end) {
806                 s = find_xml_tag (s, "micalg");
807                 if (s && s < s_end2) {
808                     s_end = strchr (s, '<');
809                     if (s_end) {
810                         char *p = g_malloc (s_end - s + 1);
811                         memcpy (p, s, s_end - s);
812                         p[s_end-s] = 0;
813                         return p;
814                     }
815                 }
816             }
817         }
818     }
819     return NULL;
820 }
821
822
823 /*
824  * Sign the file and replace its content with the signed one.
825  */
826 int
827 rfc2015_sign (const char *file, GSList *key_list)
828 {
829     FILE *fp = NULL;
830     char buf[BUFFSIZE];
831     int i, clineidx, saved_last;
832     char *clines[3] = {NULL};
833     GpgmeError err;
834     GpgmeData header = NULL;
835     GpgmeData plain = NULL;
836     GpgmeData sigdata = NULL;
837     size_t nread;
838     int mime_version_seen = 0;
839     char *boundary;
840     char *micalg = NULL;
841     char *siginfo;
842
843     boundary = generate_mime_boundary ("Signature");
844
845     /* Open the source file */
846     if ((fp = fopen(file, "rb")) == NULL) {
847         FILE_OP_ERROR(file, "fopen");
848         goto failure;
849     }
850
851     err = gpgme_data_new (&header);
852     if (!err)
853         err = gpgme_data_new (&plain);
854     if (err) {
855         debug_print ("gpgme_data_new failed: %s\n", gpgme_strerror (err));
856         goto failure;
857     }
858
859     /* get the content header lines from the source */
860     clineidx = 0;
861     saved_last = 0;
862     while (!err && fgets(buf, sizeof(buf), fp)) {
863         /* fixme: check for overlong lines */
864         if (headerp (buf, content_names)) {
865             if (clineidx >= DIM (clines)) {
866                 debug_print ("rfc2015_sign: too many content lines\n");
867                 goto failure;
868             }
869             clines[clineidx++] = g_strdup (buf);
870             saved_last = 1;
871             continue;
872         }
873         if (saved_last) {
874             if (*buf == ' ' || *buf == '\t') {
875                 char *last = clines[clineidx - 1];
876                 clines[clineidx - 1] = g_strconcat (last, buf, NULL);
877                 g_free (last);
878                 continue;
879             }
880             saved_last = 0;
881         }
882
883         if (headerp (buf, mime_version_name)) 
884             mime_version_seen = 1;
885
886         if (buf[0] == '\r' || buf[0] == '\n')
887             break;
888         err = gpgme_data_write (header, buf, strlen (buf));
889     }
890     if (ferror (fp)) {
891         FILE_OP_ERROR (file, "fgets");
892         goto failure;
893     }
894
895     /* write them to the temp data and add the rest of the message */
896     for (i = 0; !err && i < clineidx; i++) {
897         err = gpgme_data_write (plain, clines[i], strlen (clines[i]));
898     }
899     if (!err)
900         err = gpgme_data_write (plain, "\r\n", 2 );
901     while (!err && fgets(buf, sizeof(buf), fp)) {
902         err = gpgme_data_write (plain, buf, strlen (buf));
903     }
904     if (ferror (fp)) {
905         FILE_OP_ERROR (file, "fgets");
906         goto failure;
907     }
908     if (err) {
909         debug_print ("gpgme_data_write failed: %s\n", gpgme_strerror (err));
910         goto failure;
911     }
912
913     sigdata = pgp_sign (plain, key_list, FALSE, &siginfo); 
914     if (siginfo) {
915         micalg = extract_micalg (siginfo);
916         free (siginfo);
917     }
918     if (!sigdata) 
919         goto failure;
920
921     /* we have the signed message available in sigdata and now we are
922      * going to rewrite the original file. To be sure that file has
923      * been truncated we use an approach which should work everywhere:
924      * close the file and then reopen it for writing. */
925     if (fclose (fp)) {
926         FILE_OP_ERROR(file, "fclose");
927         goto failure;
928     }
929     if ((fp = fopen(file, "wb")) == NULL) {
930         FILE_OP_ERROR(file, "fopen");
931         goto failure;
932     }
933
934     /* Write the rfc822 header and add new content lines */
935     err = gpgme_data_rewind (header);
936     if (err)
937         debug_print ("gpgme_data_rewind failed: %s\n", gpgme_strerror (err));
938     while (!(err = gpgme_data_read (header, buf, BUFFSIZE, &nread))) {
939         fwrite (buf, nread, 1, fp);
940     }
941     if (err != GPGME_EOF) {
942         debug_print ("gpgme_data_read failed: %s\n", gpgme_strerror (err));
943         goto failure;
944     }
945     if (ferror (fp)) {
946         FILE_OP_ERROR (file, "fwrite");
947         goto failure;
948     }
949     gpgme_data_release (header);
950     header = NULL;
951
952     if (!mime_version_seen) 
953         fputs ("MIME-Version: 1.0\r\n", fp);
954     fprintf (fp, "Content-Type: multipart/signed; "
955              "protocol=\"application/pgp-signature\";\r\n");
956     if (micalg)
957         fprintf (fp, " micalg=\"%s\";\r\n", micalg);
958     fprintf (fp, " boundary=\"%s\"\r\n", boundary);
959
960     /* Part 1: signed material */
961     fprintf (fp, "\r\n"
962                  "--%s\r\n",
963                  boundary);
964     err = gpgme_data_rewind (plain);
965     if (err) {
966         debug_print ("gpgme_data_rewind on plain failed: %s\n",
967                    gpgme_strerror (err));
968         goto failure;
969     }
970     while (!(err = gpgme_data_read (plain, buf, BUFFSIZE, &nread))) {
971         fwrite (buf, nread, 1, fp);   
972     }
973     if (err != GPGME_EOF) {
974         debug_print ("gpgme_data_read failed: %s\n", gpgme_strerror (err));
975         goto failure;
976     }
977
978     /* Part 2: signature */
979     fprintf (fp, "\r\n"
980                  "--%s\r\n",
981                  boundary);
982     fputs ("Content-Type: application/pgp-signature\r\n"
983            "\r\n", fp);
984
985     err = gpgme_data_rewind (sigdata);
986     if (err) {
987         debug_print ("gpgme_data_rewind on sigdata failed: %s\n",
988                    gpgme_strerror (err));
989         goto failure;
990     }
991
992     while (!(err = gpgme_data_read (sigdata, buf, BUFFSIZE, &nread))) {
993         fwrite (buf, nread, 1, fp);
994     }
995     if (err != GPGME_EOF) {
996         debug_print ("gpgme_data_read failed: %s\n", gpgme_strerror (err));
997         goto failure;
998     }
999
1000     /* Final boundary */
1001     fprintf (fp, "\r\n"
1002                  "--%s--\r\n",
1003                  boundary);
1004     fflush (fp);
1005     if (ferror (fp)) {
1006         FILE_OP_ERROR (file, "fwrite");
1007         goto failure;
1008     }
1009     fclose (fp);
1010     gpgme_data_release (header);
1011     gpgme_data_release (plain);
1012     gpgme_data_release (sigdata);
1013     g_free (boundary);
1014     g_free (micalg);
1015     return 0;
1016
1017 failure:
1018     if (fp) 
1019         fclose (fp);
1020     gpgme_data_release (header);
1021     gpgme_data_release (plain);
1022     gpgme_data_release (sigdata);
1023     g_free (boundary);
1024     g_free (micalg);
1025     return -1; /* error */
1026 }
1027
1028
1029 /*
1030  * Sign the file with clear text and replace its content with the signed one.
1031  */
1032 gint
1033 rfc2015_clearsign (const gchar *file, GSList *key_list)
1034 {
1035     FILE *fp;
1036     gchar buf[BUFFSIZE];
1037     GpgmeError err;
1038     GpgmeData text = NULL;
1039     GpgmeData sigdata = NULL;
1040     size_t nread;
1041     gchar *siginfo;
1042
1043     if ((fp = fopen(file, "rb")) == NULL) {
1044         FILE_OP_ERROR(file, "fopen");
1045         goto failure;
1046     }
1047
1048     err = gpgme_data_new(&text);
1049     if (err) {
1050         debug_print("gpgme_data_new failed: %s\n", gpgme_strerror(err));
1051         goto failure;
1052     }
1053
1054     while (!err && fgets(buf, sizeof(buf), fp)) {
1055         err = gpgme_data_write(text, buf, strlen(buf));
1056     }
1057     if (ferror(fp)) {
1058         FILE_OP_ERROR(file, "fgets");
1059         goto failure;
1060     }
1061     if (err) {
1062         debug_print("gpgme_data_write failed: %s\n", gpgme_strerror(err));
1063         goto failure;
1064     }
1065
1066     sigdata = pgp_sign(text, key_list, TRUE, &siginfo);
1067     if (siginfo) {
1068         g_free(siginfo);
1069     }
1070     if (!sigdata)
1071         goto failure;
1072
1073     if (fclose(fp) == EOF) {
1074         FILE_OP_ERROR(file, "fclose");
1075         fp = NULL;
1076         goto failure;
1077     }
1078     if ((fp = fopen(file, "wb")) == NULL) {
1079         FILE_OP_ERROR(file, "fopen");
1080         goto failure;
1081     }
1082
1083     err = gpgme_data_rewind(sigdata);
1084     if (err) {
1085         debug_print("gpgme_data_rewind on sigdata failed: %s\n",
1086                     gpgme_strerror(err));
1087         goto failure;
1088     }
1089
1090     while (!(err = gpgme_data_read(sigdata, buf, sizeof(buf), &nread))) {
1091         fwrite(buf, nread, 1, fp);
1092     }
1093     if (err != GPGME_EOF) {
1094         debug_print("gpgme_data_read failed: %s\n", gpgme_strerror(err));
1095         goto failure;
1096     }
1097
1098     if (fclose(fp) == EOF) {
1099         FILE_OP_ERROR(file, "fclose");
1100         fp = NULL;
1101         goto failure;
1102     }
1103     gpgme_data_release(text);
1104     gpgme_data_release(sigdata);
1105     return 0;
1106
1107 failure:
1108     if (fp)
1109         fclose(fp);
1110     gpgme_data_release(text);
1111     gpgme_data_release(sigdata);
1112     return -1;
1113 }
1114
1115 #endif /* USE_GPGME */