1 /**
2  * @file
3  * Handling of email attachments
4  *
5  * @authors
6  * Copyright (C) 1996-2000,2002,2013 Michael R. Elkins <me@mutt.org>
7  * Copyright (C) 1999-2004,2006 Thomas Roessler <roessler@does-not-exist.org>
8  *
9  * @copyright
10  * This program is free software: you can redistribute it and/or modify it under
11  * the terms of the GNU General Public License as published by the Free Software
12  * Foundation, either version 2 of the License, or (at your option) any later
13  * version.
14  *
15  * This program is distributed in the hope that it will be useful, but WITHOUT
16  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
17  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
18  * details.
19  *
20  * You should have received a copy of the GNU General Public License along with
21  * this program.  If not, see <http://www.gnu.org/licenses/>.
22  */
23 
24 /**
25  * @page attach_mutt_attach Shared attachments functions
26  *
27  * Handling of email attachments
28  */
29 
30 #include "config.h"
31 #include <errno.h>
32 #include <fcntl.h>
33 #include <stdbool.h>
34 #include <stdio.h>
35 #include <string.h>
36 #include <sys/stat.h>
37 #include <sys/wait.h>
38 #include <unistd.h>
39 #include "mutt/lib.h"
40 #include "config/lib.h"
41 #include "email/lib.h"
42 #include "core/lib.h"
43 #include "gui/lib.h"
44 #include "mutt_attach.h"
45 #include "ncrypt/lib.h"
46 #include "pager/lib.h"
47 #include "question/lib.h"
48 #include "send/lib.h"
49 #include "copy.h"
50 #include "handler.h"
51 #include "mailcap.h"
52 #include "mutt_globals.h"
53 #include "muttlib.h"
54 #include "mx.h"
55 #include "options.h"
56 #include "protos.h"
57 #include "rfc3676.h"
58 #ifdef USE_IMAP
59 #include "imap/lib.h"
60 #endif
61 
62 /**
63  * mutt_get_tmp_attachment - Get a temporary copy of an attachment
64  * @param a Attachment to copy
65  * @retval  0 Success
66  * @retval -1 Error
67  */
mutt_get_tmp_attachment(struct Body * a)68 int mutt_get_tmp_attachment(struct Body *a)
69 {
70   char type[256];
71   struct stat st = { 0 };
72 
73   if (a->unlink)
74     return 0;
75 
76   struct Buffer *tmpfile = mutt_buffer_pool_get();
77   struct MailcapEntry *entry = mailcap_entry_new();
78   snprintf(type, sizeof(type), "%s/%s", TYPE(a), a->subtype);
79   mailcap_lookup(a, type, sizeof(type), entry, MUTT_MC_NO_FLAGS);
80   mailcap_expand_filename(entry->nametemplate, a->filename, tmpfile);
81 
82   mailcap_entry_free(&entry);
83 
84   if (stat(a->filename, &st) == -1)
85   {
86     mutt_buffer_pool_release(&tmpfile);
87     return -1;
88   }
89 
90   FILE *fp_in = NULL, *fp_out = NULL;
91   if ((fp_in = fopen(a->filename, "r")) &&
92       (fp_out = mutt_file_fopen(mutt_buffer_string(tmpfile), "w")))
93   {
94     mutt_file_copy_stream(fp_in, fp_out);
95     mutt_str_replace(&a->filename, mutt_buffer_string(tmpfile));
96     a->unlink = true;
97 
98     if (a->stamp >= st.st_mtime)
99       mutt_stamp_attachment(a);
100   }
101   else
102     mutt_perror(fp_in ? mutt_buffer_string(tmpfile) : a->filename);
103 
104   mutt_file_fclose(&fp_in);
105   mutt_file_fclose(&fp_out);
106 
107   mutt_buffer_pool_release(&tmpfile);
108 
109   return a->unlink ? 0 : -1;
110 }
111 
112 /**
113  * mutt_compose_attachment - Create an attachment
114  * @param a Body of email
115  * @retval 1 Require full screen redraw
116  * @retval 0 Otherwise
117  */
mutt_compose_attachment(struct Body * a)118 int mutt_compose_attachment(struct Body *a)
119 {
120   char type[256];
121   struct MailcapEntry *entry = mailcap_entry_new();
122   bool unlink_newfile = false;
123   int rc = 0;
124   struct Buffer *cmd = mutt_buffer_pool_get();
125   struct Buffer *newfile = mutt_buffer_pool_get();
126   struct Buffer *tmpfile = mutt_buffer_pool_get();
127 
128   snprintf(type, sizeof(type), "%s/%s", TYPE(a), a->subtype);
129   if (mailcap_lookup(a, type, sizeof(type), entry, MUTT_MC_COMPOSE))
130   {
131     if (entry->composecommand || entry->composetypecommand)
132     {
133       if (entry->composetypecommand)
134         mutt_buffer_strcpy(cmd, entry->composetypecommand);
135       else
136         mutt_buffer_strcpy(cmd, entry->composecommand);
137 
138       mailcap_expand_filename(entry->nametemplate, a->filename, newfile);
139       mutt_debug(LL_DEBUG1, "oldfile: %s     newfile: %s\n", a->filename,
140                  mutt_buffer_string(newfile));
141       if (mutt_file_symlink(a->filename, mutt_buffer_string(newfile)) == -1)
142       {
143         if (mutt_yesorno(_("Can't match 'nametemplate', continue?"), MUTT_YES) != MUTT_YES)
144           goto bailout;
145         mutt_buffer_strcpy(newfile, a->filename);
146       }
147       else
148         unlink_newfile = true;
149 
150       if (mailcap_expand_command(a, mutt_buffer_string(newfile), type, cmd))
151       {
152         /* For now, editing requires a file, no piping */
153         mutt_error(_("Mailcap compose entry requires %%s"));
154       }
155       else
156       {
157         int r;
158 
159         mutt_endwin();
160         r = mutt_system(mutt_buffer_string(cmd));
161         if (r == -1)
162           mutt_error(_("Error running \"%s\""), mutt_buffer_string(cmd));
163 
164         if ((r != -1) && entry->composetypecommand)
165         {
166           struct Body *b = NULL;
167 
168           FILE *fp = mutt_file_fopen(a->filename, "r");
169           if (!fp)
170           {
171             mutt_perror(_("Failure to open file to parse headers"));
172             goto bailout;
173           }
174 
175           b = mutt_read_mime_header(fp, 0);
176           if (b)
177           {
178             if (!TAILQ_EMPTY(&b->parameter))
179             {
180               mutt_param_free(&a->parameter);
181               a->parameter = b->parameter;
182               TAILQ_INIT(&b->parameter);
183             }
184             if (b->description)
185             {
186               FREE(&a->description);
187               a->description = b->description;
188               b->description = NULL;
189             }
190             if (b->form_name)
191             {
192               FREE(&a->form_name);
193               a->form_name = b->form_name;
194               b->form_name = NULL;
195             }
196 
197             /* Remove headers by copying out data to another file, then
198              * copying the file back */
199             fseeko(fp, b->offset, SEEK_SET);
200             mutt_body_free(&b);
201             mutt_buffer_mktemp(tmpfile);
202             FILE *fp_tmp = mutt_file_fopen(mutt_buffer_string(tmpfile), "w");
203             if (!fp_tmp)
204             {
205               mutt_perror(_("Failure to open file to strip headers"));
206               mutt_file_fclose(&fp);
207               goto bailout;
208             }
209             mutt_file_copy_stream(fp, fp_tmp);
210             mutt_file_fclose(&fp);
211             mutt_file_fclose(&fp_tmp);
212             mutt_file_unlink(a->filename);
213             if (mutt_file_rename(mutt_buffer_string(tmpfile), a->filename) != 0)
214             {
215               mutt_perror(_("Failure to rename file"));
216               goto bailout;
217             }
218           }
219         }
220       }
221     }
222   }
223   else
224   {
225     mutt_message(_("No mailcap compose entry for %s, creating empty file"), type);
226     rc = 1;
227     goto bailout;
228   }
229 
230   rc = 1;
231 
232 bailout:
233 
234   if (unlink_newfile)
235     unlink(mutt_buffer_string(newfile));
236 
237   mutt_buffer_pool_release(&cmd);
238   mutt_buffer_pool_release(&newfile);
239   mutt_buffer_pool_release(&tmpfile);
240 
241   mailcap_entry_free(&entry);
242   return rc;
243 }
244 
245 /**
246  * mutt_edit_attachment - Edit an attachment
247  * @param a Email containing attachment
248  * @retval 1 Editor found
249  * @retval 0 Editor not found
250  *
251  * Currently, this only works for send mode, as it assumes that the
252  * Body->filename actually contains the information.  I'm not sure
253  * we want to deal with editing attachments we've already received,
254  * so this should be ok.
255  *
256  * Returning 0 is useful to tell the calling menu to redraw
257  */
mutt_edit_attachment(struct Body * a)258 int mutt_edit_attachment(struct Body *a)
259 {
260   char type[256];
261   struct MailcapEntry *entry = mailcap_entry_new();
262   bool unlink_newfile = false;
263   int rc = 0;
264   struct Buffer *cmd = mutt_buffer_pool_get();
265   struct Buffer *newfile = mutt_buffer_pool_get();
266 
267   snprintf(type, sizeof(type), "%s/%s", TYPE(a), a->subtype);
268   if (mailcap_lookup(a, type, sizeof(type), entry, MUTT_MC_EDIT))
269   {
270     if (entry->editcommand)
271     {
272       mutt_buffer_strcpy(cmd, entry->editcommand);
273       mailcap_expand_filename(entry->nametemplate, a->filename, newfile);
274       mutt_debug(LL_DEBUG1, "oldfile: %s     newfile: %s\n", a->filename,
275                  mutt_buffer_string(newfile));
276       if (mutt_file_symlink(a->filename, mutt_buffer_string(newfile)) == -1)
277       {
278         if (mutt_yesorno(_("Can't match 'nametemplate', continue?"), MUTT_YES) != MUTT_YES)
279           goto bailout;
280         mutt_buffer_strcpy(newfile, a->filename);
281       }
282       else
283         unlink_newfile = true;
284 
285       if (mailcap_expand_command(a, mutt_buffer_string(newfile), type, cmd))
286       {
287         /* For now, editing requires a file, no piping */
288         mutt_error(_("Mailcap Edit entry requires %%s"));
289         goto bailout;
290       }
291       else
292       {
293         mutt_endwin();
294         if (mutt_system(mutt_buffer_string(cmd)) == -1)
295         {
296           mutt_error(_("Error running \"%s\""), mutt_buffer_string(cmd));
297           goto bailout;
298         }
299       }
300     }
301   }
302   else if (a->type == TYPE_TEXT)
303   {
304     /* On text, default to editor */
305     const char *const c_editor = cs_subset_string(NeoMutt->sub, "editor");
306     mutt_edit_file(NONULL(c_editor), a->filename);
307   }
308   else
309   {
310     mutt_error(_("No mailcap edit entry for %s"), type);
311     rc = 0;
312     goto bailout;
313   }
314 
315   rc = 1;
316 
317 bailout:
318 
319   if (unlink_newfile)
320     unlink(mutt_buffer_string(newfile));
321 
322   mutt_buffer_pool_release(&cmd);
323   mutt_buffer_pool_release(&newfile);
324 
325   mailcap_entry_free(&entry);
326   return rc;
327 }
328 
329 /**
330  * mutt_check_lookup_list - Update the mime type
331  * @param b    Message attachment body
332  * @param type Buffer with mime type of attachment in "type/subtype" format
333  * @param len  Buffer length
334  */
mutt_check_lookup_list(struct Body * b,char * type,size_t len)335 void mutt_check_lookup_list(struct Body *b, char *type, size_t len)
336 {
337   struct ListNode *np = NULL;
338   STAILQ_FOREACH(np, &MimeLookupList, entries)
339   {
340     const int i = mutt_str_len(np->data) - 1;
341     if (((i > 0) && (np->data[i - 1] == '/') && (np->data[i] == '*') &&
342          mutt_istrn_equal(type, np->data, i)) ||
343         mutt_istr_equal(type, np->data))
344     {
345       struct Body tmp = { 0 };
346       enum ContentType n;
347       if ((n = mutt_lookup_mime_type(&tmp, b->filename)) != TYPE_OTHER ||
348           (n = mutt_lookup_mime_type(&tmp, b->description)) != TYPE_OTHER)
349       {
350         snprintf(type, len, "%s/%s",
351                  (n == TYPE_AUDIO)       ? "audio" :
352                  (n == TYPE_APPLICATION) ? "application" :
353                  (n == TYPE_IMAGE)       ? "image" :
354                  (n == TYPE_MESSAGE)     ? "message" :
355                  (n == TYPE_MODEL)       ? "model" :
356                  (n == TYPE_MULTIPART)   ? "multipart" :
357                  (n == TYPE_TEXT)        ? "text" :
358                  (n == TYPE_VIDEO)       ? "video" :
359                                            "other",
360                  tmp.subtype);
361         mutt_debug(LL_DEBUG1, "\"%s\" -> %s\n", b->filename, type);
362       }
363       FREE(&tmp.subtype);
364       FREE(&tmp.xtype);
365     }
366   }
367 }
368 
369 /**
370  * wait_interactive_filter - Wait after an interactive filter
371  * @param pid Process id of the process to wait for
372  * @retval num Exit status of the process identified by pid
373  * @retval -1  Error
374  *
375  * This is used for filters that are actually interactive commands
376  * with input piped in: e.g. in mutt_view_attachment(), a mailcap
377  * entry without copiousoutput _and_ without a %s.
378  *
379  * For those cases, we treat it like a blocking system command, and
380  * poll IMAP to keep connections open.
381  */
wait_interactive_filter(pid_t pid)382 static int wait_interactive_filter(pid_t pid)
383 {
384   int rc;
385 
386 #ifdef USE_IMAP
387   rc = imap_wait_keepalive(pid);
388 #else
389   waitpid(pid, &rc, 0);
390 #endif
391   mutt_sig_unblock_system(true);
392   rc = WIFEXITED(rc) ? WEXITSTATUS(rc) : -1;
393 
394   return rc;
395 }
396 
397 /**
398  * mutt_view_attachment - View an attachment
399  * @param fp     Source file stream. Can be NULL
400  * @param a      The message body containing the attachment
401  * @param mode   How the attachment should be viewed, see #ViewAttachMode
402  * @param e      Current Email. Can be NULL
403  * @param actx   Attachment context
404  * @param win    Window
405  * @retval 0   The viewer is run and exited successfully
406  * @retval -1  Error
407  * @retval num Return value of mutt_do_pager() when it is used
408  *
409  * Display a message attachment using the viewer program configured in mailcap.
410  * If there is no mailcap entry for a file type, view the image as text.
411  * Viewer processes are opened and waited on synchronously so viewing an
412  * attachment this way will block the main neomutt process until the viewer process
413  * exits.
414  */
mutt_view_attachment(FILE * fp,struct Body * a,enum ViewAttachMode mode,struct Email * e,struct AttachCtx * actx,struct MuttWindow * win)415 int mutt_view_attachment(FILE *fp, struct Body *a, enum ViewAttachMode mode,
416                          struct Email *e, struct AttachCtx *actx, struct MuttWindow *win)
417 {
418   bool use_mailcap = false;
419   bool use_pipe = false;
420   bool use_pager = true;
421   char type[256];
422   char desc[256];
423   char *fname = NULL;
424   struct MailcapEntry *entry = NULL;
425   int rc = -1;
426   bool has_tempfile = false;
427   bool unlink_pagerfile = false;
428 
429   bool is_message = mutt_is_message_type(a->type, a->subtype);
430   if ((WithCrypto != 0) && is_message && a->email &&
431       (a->email->security & SEC_ENCRYPT) && !crypt_valid_passphrase(a->email->security))
432   {
433     return rc;
434   }
435 
436   struct Buffer *tmpfile = mutt_buffer_pool_get();
437   struct Buffer *pagerfile = mutt_buffer_pool_get();
438   struct Buffer *cmd = mutt_buffer_pool_get();
439 
440   use_mailcap = ((mode == MUTT_VA_MAILCAP) ||
441                  ((mode == MUTT_VA_REGULAR) && mutt_needs_mailcap(a)) ||
442                  (mode == MUTT_VA_PAGER));
443   snprintf(type, sizeof(type), "%s/%s", TYPE(a), a->subtype);
444 
445   char columns[16];
446   snprintf(columns, sizeof(columns), "%d", win->state.cols);
447   mutt_envlist_set("COLUMNS", columns, true);
448 
449   if (use_mailcap)
450   {
451     entry = mailcap_entry_new();
452     enum MailcapLookup mailcap_opt =
453         (mode == MUTT_VA_PAGER) ? MUTT_MC_AUTOVIEW : MUTT_MC_NO_FLAGS;
454     if (!mailcap_lookup(a, type, sizeof(type), entry, mailcap_opt))
455     {
456       if ((mode == MUTT_VA_REGULAR) || (mode == MUTT_VA_PAGER))
457       {
458         /* fallback to view as text */
459         mailcap_entry_free(&entry);
460         mutt_error(_("No matching mailcap entry found.  Viewing as text."));
461         mode = MUTT_VA_AS_TEXT;
462         use_mailcap = false;
463       }
464       else
465         goto return_error;
466     }
467   }
468 
469   if (use_mailcap)
470   {
471     if (!entry->command)
472     {
473       mutt_error(_("MIME type not defined.  Can't view attachment."));
474       goto return_error;
475     }
476     mutt_buffer_strcpy(cmd, entry->command);
477 
478     fname = mutt_str_dup(a->filename);
479     /* In send mode(!fp), we allow slashes because those are part of
480      * the tmpfile.  The path will be removed in expand_filename */
481     mutt_file_sanitize_filename(fname, fp ? true : false);
482     mailcap_expand_filename(entry->nametemplate, fname, tmpfile);
483     FREE(&fname);
484 
485     if (mutt_save_attachment(fp, a, mutt_buffer_string(tmpfile), 0, NULL) == -1)
486       goto return_error;
487     has_tempfile = true;
488 
489     mutt_rfc3676_space_unstuff_attachment(a, mutt_buffer_string(tmpfile));
490 
491     use_pipe = mailcap_expand_command(a, mutt_buffer_string(tmpfile), type, cmd);
492     use_pager = entry->copiousoutput;
493   }
494 
495   if (use_pager)
496   {
497     if (fp && !use_mailcap && a->filename)
498     {
499       /* recv case */
500       mutt_buffer_strcpy(pagerfile, a->filename);
501       mutt_adv_mktemp(pagerfile);
502     }
503     else
504       mutt_buffer_mktemp(pagerfile);
505   }
506 
507   if (use_mailcap)
508   {
509     pid_t pid = 0;
510     int fd_temp = -1, fd_pager = -1;
511 
512     if (!use_pager)
513       mutt_endwin();
514 
515     const bool c_wait_key = cs_subset_bool(NeoMutt->sub, "wait_key");
516     if (use_pager || use_pipe)
517     {
518       if (use_pager && ((fd_pager = mutt_file_open(mutt_buffer_string(pagerfile),
519                                                    O_CREAT | O_EXCL | O_WRONLY)) == -1))
520       {
521         mutt_perror("open");
522         goto return_error;
523       }
524       unlink_pagerfile = true;
525 
526       if (use_pipe && ((fd_temp = open(mutt_buffer_string(tmpfile), 0)) == -1))
527       {
528         if (fd_pager != -1)
529           close(fd_pager);
530         mutt_perror("open");
531         goto return_error;
532       }
533       unlink_pagerfile = true;
534 
535       pid = filter_create_fd(mutt_buffer_string(cmd), NULL, NULL, NULL,
536                              use_pipe ? fd_temp : -1, use_pager ? fd_pager : -1, -1);
537 
538       if (pid == -1)
539       {
540         if (fd_pager != -1)
541           close(fd_pager);
542 
543         if (fd_temp != -1)
544           close(fd_temp);
545 
546         mutt_error(_("Can't create filter"));
547         goto return_error;
548       }
549 
550       if (use_pager)
551       {
552         if (a->description)
553         {
554           snprintf(desc, sizeof(desc), _("---Command: %-20.20s Description: %s"),
555                    mutt_buffer_string(cmd), a->description);
556         }
557         else
558         {
559           snprintf(desc, sizeof(desc), _("---Command: %-30.30s Attachment: %s"),
560                    mutt_buffer_string(cmd), type);
561         }
562         filter_wait(pid);
563       }
564       else
565       {
566         if (wait_interactive_filter(pid) || (entry->needsterminal && c_wait_key))
567           mutt_any_key_to_continue(NULL);
568       }
569 
570       if (fd_temp != -1)
571         close(fd_temp);
572       if (fd_pager != -1)
573         close(fd_pager);
574     }
575     else
576     {
577       /* interactive cmd */
578       int rv = mutt_system(mutt_buffer_string(cmd));
579       if (rv == -1)
580         mutt_debug(LL_DEBUG1, "Error running \"%s\"", cmd->data);
581 
582       if ((rv != 0) || (entry->needsterminal && c_wait_key))
583         mutt_any_key_to_continue(NULL);
584     }
585   }
586   else
587   {
588     /* Don't use mailcap; the attachment is viewed in the pager */
589 
590     if (mode == MUTT_VA_AS_TEXT)
591     {
592       /* just let me see the raw data */
593       if (fp)
594       {
595         /* Viewing from a received message.
596          *
597          * Don't use mutt_save_attachment() because we want to perform charset
598          * conversion since this will be displayed by the internal pager.  */
599         struct State decode_state = { 0 };
600 
601         decode_state.fp_out = mutt_file_fopen(mutt_buffer_string(pagerfile), "w");
602         if (!decode_state.fp_out)
603         {
604           mutt_debug(LL_DEBUG1, "mutt_file_fopen(%s) errno=%d %s\n",
605                      mutt_buffer_string(pagerfile), errno, strerror(errno));
606           mutt_perror(mutt_buffer_string(pagerfile));
607           goto return_error;
608         }
609         decode_state.fp_in = fp;
610         decode_state.flags = MUTT_CHARCONV;
611         mutt_decode_attachment(a, &decode_state);
612         if (mutt_file_fclose(&decode_state.fp_out) == EOF)
613         {
614           mutt_debug(LL_DEBUG1, "fclose(%s) errno=%d %s\n",
615                      mutt_buffer_string(pagerfile), errno, strerror(errno));
616         }
617       }
618       else
619       {
620         /* in compose mode, just copy the file.  we can't use
621          * mutt_decode_attachment() since it assumes the content-encoding has
622          * already been applied */
623         if (mutt_save_attachment(fp, a, mutt_buffer_string(pagerfile), MUTT_SAVE_NO_FLAGS, NULL))
624           goto return_error;
625         unlink_pagerfile = true;
626       }
627       mutt_rfc3676_space_unstuff_attachment(a, mutt_buffer_string(pagerfile));
628     }
629     else
630     {
631       /* Use built-in handler */
632       OptViewAttach = true; /* disable the "use 'v' to view this part"
633                              * message in case of error */
634       if (mutt_decode_save_attachment(fp, a, mutt_buffer_string(pagerfile),
635                                       MUTT_DISPLAY, MUTT_SAVE_NO_FLAGS))
636       {
637         OptViewAttach = false;
638         goto return_error;
639       }
640       unlink_pagerfile = true;
641       OptViewAttach = false;
642     }
643 
644     if (a->description)
645       mutt_str_copy(desc, a->description, sizeof(desc));
646     else if (a->filename)
647       snprintf(desc, sizeof(desc), _("---Attachment: %s: %s"), a->filename, type);
648     else
649       snprintf(desc, sizeof(desc), _("---Attachment: %s"), type);
650   }
651 
652   /* We only reach this point if there have been no errors */
653 
654   if (use_pager)
655   {
656     struct PagerData pdata = { 0 };
657     struct PagerView pview = { &pdata };
658 
659     pdata.actx = actx;
660     pdata.body = a;
661     pdata.fname = mutt_buffer_string(pagerfile);
662     pdata.fp = fp;
663 
664     pview.banner = desc;
665     pview.flags = MUTT_PAGER_ATTACHMENT |
666                   (is_message ? MUTT_PAGER_MESSAGE : MUTT_PAGER_NO_FLAGS) |
667                   ((use_mailcap && entry->xneomuttnowrap) ? MUTT_PAGER_NOWRAP :
668                                                             MUTT_PAGER_NO_FLAGS);
669     pview.mode = PAGER_MODE_ATTACH;
670 
671     rc = mutt_do_pager(&pview, e);
672 
673     mutt_buffer_reset(pagerfile);
674     unlink_pagerfile = false;
675   }
676   else
677     rc = 0;
678 
679 return_error:
680 
681   if (!entry || !entry->xneomuttkeep)
682   {
683     if ((fp && !mutt_buffer_is_empty(tmpfile)) || has_tempfile)
684     {
685       /* add temporary file to TempAttachmentsList to be deleted on timeout hook */
686       mutt_add_temp_attachment(mutt_buffer_string(tmpfile));
687     }
688   }
689 
690   mailcap_entry_free(&entry);
691 
692   if (unlink_pagerfile)
693     mutt_file_unlink(mutt_buffer_string(pagerfile));
694 
695   mutt_buffer_pool_release(&tmpfile);
696   mutt_buffer_pool_release(&pagerfile);
697   mutt_buffer_pool_release(&cmd);
698   mutt_envlist_unset("COLUMNS");
699 
700   return rc;
701 }
702 
703 /**
704  * mutt_pipe_attachment - Pipe an attachment to a command
705  * @param fp      File to pipe into the command
706  * @param b       Attachment
707  * @param path    Path to command
708  * @param outfile File to save output to
709  * @retval 1 Success
710  * @retval 0 Error
711  */
mutt_pipe_attachment(FILE * fp,struct Body * b,const char * path,char * outfile)712 int mutt_pipe_attachment(FILE *fp, struct Body *b, const char *path, char *outfile)
713 {
714   pid_t pid = 0;
715   int out = -1, rc = 0;
716   bool is_flowed = false;
717   bool unlink_unstuff = false;
718   FILE *fp_filter = NULL, *fp_unstuff = NULL, *fp_in = NULL;
719   struct Buffer *unstuff_tempfile = NULL;
720 
721   if (outfile && *outfile)
722   {
723     out = mutt_file_open(outfile, O_CREAT | O_EXCL | O_WRONLY);
724     if (out < 0)
725     {
726       mutt_perror("open");
727       return 0;
728     }
729   }
730 
731   if (mutt_rfc3676_is_format_flowed(b))
732   {
733     is_flowed = true;
734     unstuff_tempfile = mutt_buffer_pool_get();
735     mutt_buffer_mktemp(unstuff_tempfile);
736   }
737 
738   mutt_endwin();
739 
740   if (outfile && *outfile)
741     pid = filter_create_fd(path, &fp_filter, NULL, NULL, -1, out, -1);
742   else
743     pid = filter_create(path, &fp_filter, NULL, NULL);
744   if (pid < 0)
745   {
746     mutt_perror(_("Can't create filter"));
747     goto bail;
748   }
749 
750   /* recv case */
751   if (fp)
752   {
753     struct State s = { 0 };
754 
755     /* perform charset conversion on text attachments when piping */
756     s.flags = MUTT_CHARCONV;
757 
758     if (is_flowed)
759     {
760       fp_unstuff = mutt_file_fopen(mutt_buffer_string(unstuff_tempfile), "w");
761       if (fp_unstuff == NULL)
762       {
763         mutt_perror("mutt_file_fopen");
764         goto bail;
765       }
766       unlink_unstuff = true;
767 
768       s.fp_in = fp;
769       s.fp_out = fp_unstuff;
770       mutt_decode_attachment(b, &s);
771       mutt_file_fclose(&fp_unstuff);
772 
773       mutt_rfc3676_space_unstuff_attachment(b, mutt_buffer_string(unstuff_tempfile));
774 
775       fp_unstuff = mutt_file_fopen(mutt_buffer_string(unstuff_tempfile), "r");
776       if (fp_unstuff == NULL)
777       {
778         mutt_perror("mutt_file_fopen");
779         goto bail;
780       }
781       mutt_file_copy_stream(fp_unstuff, fp_filter);
782       mutt_file_fclose(&fp_unstuff);
783     }
784     else
785     {
786       s.fp_in = fp;
787       s.fp_out = fp_filter;
788       mutt_decode_attachment(b, &s);
789     }
790   }
791 
792   /* send case */
793   else
794   {
795     const char *infile = NULL;
796 
797     if (is_flowed)
798     {
799       if (mutt_save_attachment(fp, b, mutt_buffer_string(unstuff_tempfile),
800                                MUTT_SAVE_NO_FLAGS, NULL) == -1)
801       {
802         goto bail;
803       }
804       unlink_unstuff = true;
805       mutt_rfc3676_space_unstuff_attachment(b, mutt_buffer_string(unstuff_tempfile));
806       infile = mutt_buffer_string(unstuff_tempfile);
807     }
808     else
809       infile = b->filename;
810 
811     fp_in = fopen(infile, "r");
812     if (!fp_in)
813     {
814       mutt_perror("fopen");
815       goto bail;
816     }
817 
818     mutt_file_copy_stream(fp_in, fp_filter);
819     mutt_file_fclose(&fp_in);
820   }
821 
822   mutt_file_fclose(&fp_filter);
823   rc = 1;
824 
825 bail:
826   if (outfile && *outfile)
827   {
828     close(out);
829     if (rc == 0)
830       unlink(outfile);
831     else if (is_flowed)
832       mutt_rfc3676_space_stuff_attachment(NULL, outfile);
833   }
834 
835   mutt_file_fclose(&fp_unstuff);
836   mutt_file_fclose(&fp_filter);
837   mutt_file_fclose(&fp_in);
838 
839   if (unlink_unstuff)
840     mutt_file_unlink(mutt_buffer_string(unstuff_tempfile));
841   mutt_buffer_pool_release(&unstuff_tempfile);
842 
843   /* check for error exit from child process */
844   if ((pid > 0) && (filter_wait(pid) != 0))
845     rc = 0;
846 
847   const bool c_wait_key = cs_subset_bool(NeoMutt->sub, "wait_key");
848   if ((rc == 0) || c_wait_key)
849     mutt_any_key_to_continue(NULL);
850   return rc;
851 }
852 
853 /**
854  * save_attachment_open - Open a file to write an attachment to
855  * @param path Path to file to open
856  * @param opt  Save option, see #SaveAttach
857  * @retval ptr File handle to attachment file
858  */
save_attachment_open(const char * path,enum SaveAttach opt)859 static FILE *save_attachment_open(const char *path, enum SaveAttach opt)
860 {
861   if (opt == MUTT_SAVE_APPEND)
862     return fopen(path, "a");
863   if (opt == MUTT_SAVE_OVERWRITE)
864     return fopen(path, "w");
865 
866   return mutt_file_fopen(path, "w");
867 }
868 
869 /**
870  * mutt_save_attachment - Save an attachment
871  * @param fp   Source file stream. Can be NULL
872  * @param m    Email Body
873  * @param path Where to save the attachment
874  * @param opt  Save option, see #SaveAttach
875  * @param e    Current Email. Can be NULL
876  * @retval  0 Success
877  * @retval -1 Error
878  */
mutt_save_attachment(FILE * fp,struct Body * m,const char * path,enum SaveAttach opt,struct Email * e)879 int mutt_save_attachment(FILE *fp, struct Body *m, const char *path,
880                          enum SaveAttach opt, struct Email *e)
881 {
882   if (!m)
883     return -1;
884 
885   if (fp)
886   {
887     /* recv mode */
888 
889     if (e && m->email && (m->encoding != ENC_BASE64) &&
890         (m->encoding != ENC_QUOTED_PRINTABLE) && mutt_is_message_type(m->type, m->subtype))
891     {
892       /* message type attachments are written to mail folders. */
893 
894       char buf[8192];
895       struct Message *msg = NULL;
896       CopyHeaderFlags chflags = CH_NO_FLAGS;
897       int rc = -1;
898 
899       struct Email *e_new = m->email;
900       e_new->msgno = e->msgno; /* required for MH/maildir */
901       e_new->read = true;
902 
903       if (fseeko(fp, m->offset, SEEK_SET) < 0)
904         return -1;
905       if (!fgets(buf, sizeof(buf), fp))
906         return -1;
907       struct Mailbox *m_att = mx_path_resolve(path);
908       if (!mx_mbox_open(m_att, MUTT_APPEND | MUTT_QUIET))
909       {
910         mailbox_free(&m_att);
911         return -1;
912       }
913       msg = mx_msg_open_new(m_att, e_new,
914                             is_from(buf, NULL, 0, NULL) ? MUTT_MSG_NO_FLAGS : MUTT_ADD_FROM);
915       if (!msg)
916       {
917         mx_mbox_close(m_att);
918         return -1;
919       }
920       if ((m_att->type == MUTT_MBOX) || (m_att->type == MUTT_MMDF))
921         chflags = CH_FROM | CH_UPDATE_LEN;
922       chflags |= ((m_att->type == MUTT_MAILDIR) ? CH_NOSTATUS : CH_UPDATE);
923       if ((mutt_copy_message_fp(msg->fp, fp, e_new, MUTT_CM_NO_FLAGS, chflags, 0) == 0) &&
924           (mx_msg_commit(m_att, msg) == 0))
925       {
926         rc = 0;
927       }
928       else
929       {
930         rc = -1;
931       }
932 
933       mx_msg_close(m_att, &msg);
934       mx_mbox_close(m_att);
935       return rc;
936     }
937     else
938     {
939       /* In recv mode, extract from folder and decode */
940 
941       struct State s = { 0 };
942 
943       s.fp_out = save_attachment_open(path, opt);
944       if (!s.fp_out)
945       {
946         mutt_perror("fopen");
947         return -1;
948       }
949       if (fseeko((s.fp_in = fp), m->offset, SEEK_SET) != 0)
950       {
951         mutt_perror("fseeko");
952         mutt_file_fclose(&s.fp_out);
953         return -1;
954       }
955       mutt_decode_attachment(m, &s);
956 
957       if (mutt_file_fsync_close(&s.fp_out) != 0)
958       {
959         mutt_perror("fclose");
960         return -1;
961       }
962     }
963   }
964   else
965   {
966     if (!m->filename)
967       return -1;
968 
969     /* In send mode, just copy file */
970 
971     FILE *fp_old = fopen(m->filename, "r");
972     if (!fp_old)
973     {
974       mutt_perror("fopen");
975       return -1;
976     }
977 
978     FILE *fp_new = save_attachment_open(path, opt);
979     if (!fp_new)
980     {
981       mutt_perror("fopen");
982       mutt_file_fclose(&fp_old);
983       return -1;
984     }
985 
986     if (mutt_file_copy_stream(fp_old, fp_new) == -1)
987     {
988       mutt_error(_("Write fault"));
989       mutt_file_fclose(&fp_old);
990       mutt_file_fclose(&fp_new);
991       return -1;
992     }
993     mutt_file_fclose(&fp_old);
994     if (mutt_file_fsync_close(&fp_new) != 0)
995     {
996       mutt_error(_("Write fault"));
997       return -1;
998     }
999   }
1000 
1001   return 0;
1002 }
1003 
1004 /**
1005  * mutt_decode_save_attachment - Decode, then save an attachment
1006  * @param fp         File to read from (OPTIONAL)
1007  * @param m          Attachment
1008  * @param path       Path to save the Attachment to
1009  * @param displaying Flags, e.g. #MUTT_DISPLAY
1010  * @param opt        Save option, see #SaveAttach
1011  * @retval 0  Success
1012  * @retval -1 Error
1013  */
mutt_decode_save_attachment(FILE * fp,struct Body * m,const char * path,int displaying,enum SaveAttach opt)1014 int mutt_decode_save_attachment(FILE *fp, struct Body *m, const char *path,
1015                                 int displaying, enum SaveAttach opt)
1016 {
1017   struct State s = { 0 };
1018   unsigned int saved_encoding = 0;
1019   struct Body *saved_parts = NULL;
1020   struct Email *e_saved = NULL;
1021   int rc = 0;
1022 
1023   s.flags = displaying;
1024 
1025   if (opt == MUTT_SAVE_APPEND)
1026     s.fp_out = fopen(path, "a");
1027   else if (opt == MUTT_SAVE_OVERWRITE)
1028     s.fp_out = fopen(path, "w");
1029   else
1030     s.fp_out = mutt_file_fopen(path, "w");
1031 
1032   if (!s.fp_out)
1033   {
1034     mutt_perror("fopen");
1035     return -1;
1036   }
1037 
1038   if (!fp)
1039   {
1040     /* When called from the compose menu, the attachment isn't parsed,
1041      * so we need to do it here. */
1042     struct stat st = { 0 };
1043 
1044     if (stat(m->filename, &st) == -1)
1045     {
1046       mutt_perror("stat");
1047       mutt_file_fclose(&s.fp_out);
1048       return -1;
1049     }
1050 
1051     s.fp_in = fopen(m->filename, "r");
1052     if (!s.fp_in)
1053     {
1054       mutt_perror("fopen");
1055       return -1;
1056     }
1057 
1058     saved_encoding = m->encoding;
1059     if (!is_multipart(m))
1060       m->encoding = ENC_8BIT;
1061 
1062     m->length = st.st_size;
1063     m->offset = 0;
1064     saved_parts = m->parts;
1065     e_saved = m->email;
1066     mutt_parse_part(s.fp_in, m);
1067 
1068     if (m->noconv || is_multipart(m))
1069       s.flags |= MUTT_CHARCONV;
1070   }
1071   else
1072   {
1073     s.fp_in = fp;
1074     s.flags |= MUTT_CHARCONV;
1075   }
1076 
1077   mutt_body_handler(m, &s);
1078 
1079   if (mutt_file_fsync_close(&s.fp_out) != 0)
1080   {
1081     mutt_perror("fclose");
1082     rc = -1;
1083   }
1084   if (!fp)
1085   {
1086     m->length = 0;
1087     m->encoding = saved_encoding;
1088     if (saved_parts)
1089     {
1090       email_free(&m->email);
1091       m->parts = saved_parts;
1092       m->email = e_saved;
1093     }
1094     mutt_file_fclose(&s.fp_in);
1095   }
1096 
1097   return rc;
1098 }
1099 
1100 /**
1101  * mutt_print_attachment - Print out an attachment
1102  * @param fp File to write to
1103  * @param a  Attachment
1104  * @retval 1 Success
1105  * @retval 0 Error
1106  *
1107  * Ok, the difference between send and receive:
1108  * recv: Body->filename is a suggested name, and Mailbox|Email points
1109  *       to the attachment in mailbox which is encoded
1110  * send: Body->filename points to the un-encoded file which contains the
1111  *       attachment
1112  */
mutt_print_attachment(FILE * fp,struct Body * a)1113 int mutt_print_attachment(FILE *fp, struct Body *a)
1114 {
1115   char type[256];
1116   pid_t pid;
1117   FILE *fp_in = NULL, *fp_out = NULL;
1118   bool unlink_newfile = false;
1119   struct Buffer *newfile = mutt_buffer_pool_get();
1120   struct Buffer *cmd = mutt_buffer_pool_get();
1121 
1122   int rc = 0;
1123 
1124   snprintf(type, sizeof(type), "%s/%s", TYPE(a), a->subtype);
1125 
1126   if (mailcap_lookup(a, type, sizeof(type), NULL, MUTT_MC_PRINT))
1127   {
1128     mutt_debug(LL_DEBUG2, "Using mailcap\n");
1129 
1130     struct MailcapEntry *entry = mailcap_entry_new();
1131     mailcap_lookup(a, type, sizeof(type), entry, MUTT_MC_PRINT);
1132 
1133     char *sanitized_fname = mutt_str_dup(a->filename);
1134     /* In send mode (!fp), we allow slashes because those are part of
1135      * the tempfile.  The path will be removed in expand_filename */
1136     mutt_file_sanitize_filename(sanitized_fname, fp ? true : false);
1137     mailcap_expand_filename(entry->nametemplate, sanitized_fname, newfile);
1138     FREE(&sanitized_fname);
1139 
1140     if (mutt_save_attachment(fp, a, mutt_buffer_string(newfile),
1141                              MUTT_SAVE_NO_FLAGS, NULL) == -1)
1142     {
1143       goto mailcap_cleanup;
1144     }
1145     unlink_newfile = 1;
1146 
1147     mutt_rfc3676_space_unstuff_attachment(a, mutt_buffer_string(newfile));
1148 
1149     mutt_buffer_strcpy(cmd, entry->printcommand);
1150 
1151     bool piped = mailcap_expand_command(a, mutt_buffer_string(newfile), type, cmd);
1152 
1153     mutt_endwin();
1154 
1155     const bool c_wait_key = cs_subset_bool(NeoMutt->sub, "wait_key");
1156     /* interactive program */
1157     if (piped)
1158     {
1159       fp_in = fopen(mutt_buffer_string(newfile), "r");
1160       if (!fp_in)
1161       {
1162         mutt_perror("fopen");
1163         mailcap_entry_free(&entry);
1164         goto mailcap_cleanup;
1165       }
1166 
1167       pid = filter_create(mutt_buffer_string(cmd), &fp_out, NULL, NULL);
1168       if (pid < 0)
1169       {
1170         mutt_perror(_("Can't create filter"));
1171         mailcap_entry_free(&entry);
1172         mutt_file_fclose(&fp_in);
1173         goto mailcap_cleanup;
1174       }
1175       mutt_file_copy_stream(fp_in, fp_out);
1176       mutt_file_fclose(&fp_out);
1177       mutt_file_fclose(&fp_in);
1178       if (filter_wait(pid) || c_wait_key)
1179         mutt_any_key_to_continue(NULL);
1180     }
1181     else
1182     {
1183       int rc2 = mutt_system(mutt_buffer_string(cmd));
1184       if (rc2 == -1)
1185         mutt_debug(LL_DEBUG1, "Error running \"%s\"", cmd->data);
1186 
1187       if ((rc2 != 0) || c_wait_key)
1188         mutt_any_key_to_continue(NULL);
1189     }
1190 
1191     rc = 1;
1192 
1193   mailcap_cleanup:
1194     if (unlink_newfile)
1195       mutt_file_unlink(mutt_buffer_string(newfile));
1196 
1197     mailcap_entry_free(&entry);
1198     goto out;
1199   }
1200 
1201   const char *const c_print_command =
1202       cs_subset_string(NeoMutt->sub, "print_command");
1203   if (mutt_istr_equal("text/plain", type) ||
1204       mutt_istr_equal("application/postscript", type))
1205   {
1206     rc = (mutt_pipe_attachment(fp, a, NONULL(c_print_command), NULL));
1207     goto out;
1208   }
1209   else if (mutt_can_decode(a))
1210   {
1211     /* decode and print */
1212 
1213     fp_in = NULL;
1214     fp_out = NULL;
1215 
1216     mutt_buffer_mktemp(newfile);
1217     if (mutt_decode_save_attachment(fp, a, mutt_buffer_string(newfile),
1218                                     MUTT_PRINTING, MUTT_SAVE_NO_FLAGS) == 0)
1219     {
1220       unlink_newfile = true;
1221       mutt_debug(LL_DEBUG2, "successfully decoded %s type attachment to %s\n",
1222                  type, mutt_buffer_string(newfile));
1223 
1224       fp_in = fopen(mutt_buffer_string(newfile), "r");
1225       if (!fp_in)
1226       {
1227         mutt_perror("fopen");
1228         goto decode_cleanup;
1229       }
1230 
1231       mutt_debug(LL_DEBUG2, "successfully opened %s read-only\n",
1232                  mutt_buffer_string(newfile));
1233 
1234       mutt_endwin();
1235       pid = filter_create(NONULL(c_print_command), &fp_out, NULL, NULL);
1236       if (pid < 0)
1237       {
1238         mutt_perror(_("Can't create filter"));
1239         goto decode_cleanup;
1240       }
1241 
1242       mutt_debug(LL_DEBUG2, "Filter created\n");
1243 
1244       mutt_file_copy_stream(fp_in, fp_out);
1245 
1246       mutt_file_fclose(&fp_out);
1247       mutt_file_fclose(&fp_in);
1248 
1249       const bool c_wait_key = cs_subset_bool(NeoMutt->sub, "wait_key");
1250       if ((filter_wait(pid) != 0) || c_wait_key)
1251         mutt_any_key_to_continue(NULL);
1252       rc = 1;
1253     }
1254   decode_cleanup:
1255     mutt_file_fclose(&fp_in);
1256     mutt_file_fclose(&fp_out);
1257     if (unlink_newfile)
1258       mutt_file_unlink(mutt_buffer_string(newfile));
1259   }
1260   else
1261   {
1262     mutt_error(_("I don't know how to print that"));
1263     rc = 0;
1264   }
1265 
1266 out:
1267   mutt_buffer_pool_release(&newfile);
1268   mutt_buffer_pool_release(&cmd);
1269 
1270   return rc;
1271 }
1272 
1273 /**
1274  * mutt_add_temp_attachment - Add file to list of temporary attachments
1275  * @param filename filename with full path
1276  */
mutt_add_temp_attachment(const char * filename)1277 void mutt_add_temp_attachment(const char *filename)
1278 {
1279   mutt_list_insert_tail(&TempAttachmentsList, mutt_str_dup(filename));
1280 }
1281 
1282 /**
1283  * mutt_unlink_temp_attachments - Delete all temporary attachments
1284  */
mutt_unlink_temp_attachments(void)1285 void mutt_unlink_temp_attachments(void)
1286 {
1287   struct ListNode *np = NULL;
1288 
1289   STAILQ_FOREACH(np, &TempAttachmentsList, entries)
1290   {
1291     (void) mutt_file_chmod_add(np->data, S_IWUSR);
1292     mutt_file_unlink(np->data);
1293   }
1294 
1295   mutt_list_free(&TempAttachmentsList);
1296 }
1297