]> granicus.if.org Git - neomutt/blob - main.c
Fix mutt_write_mime_body() application/pgp-encrypted handling
[neomutt] / main.c
1 /**
2  * @file
3  * Command line processing
4  *
5  * @authors
6  * Copyright (C) 1996-2007,2010,2013 Michael R. Elkins <me@mutt.org>
7  * Copyright (C) 1999-2007 Thomas Roessler <roessler@does-not-exist.org>
8  * Copyright (C) 2004 g10 Code GmbH
9  * Copyright (C) 2019 Pietro Cerutti <gahr@gahr.ch>
10  *
11  * @copyright
12  * This program is free software: you can redistribute it and/or modify it under
13  * the terms of the GNU General Public License as published by the Free Software
14  * Foundation, either version 2 of the License, or (at your option) any later
15  * version.
16  *
17  * This program is distributed in the hope that it will be useful, but WITHOUT
18  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
19  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
20  * details.
21  *
22  * You should have received a copy of the GNU General Public License along with
23  * this program.  If not, see <http://www.gnu.org/licenses/>.
24  */
25
26 /**
27  * @page main Command line processing
28  *
29  * Command line processing
30  */
31
32 #define MAIN_C 1
33 #define GNULIB_defined_setlocale
34
35 #include "config.h"
36 #include <errno.h>
37 #include <getopt.h>
38 #include <limits.h>
39 #include <locale.h>
40 #include <pwd.h>
41 #include <stdbool.h>
42 #include <stdint.h>
43 #include <stdio.h>
44 #include <stdlib.h>
45 #include <string.h>
46 #include <sys/stat.h>
47 #include <unistd.h>
48 #include "mutt/mutt.h"
49 #include "address/lib.h"
50 #include "config/lib.h"
51 #include "email/lib.h"
52 #include "core/lib.h"
53 #include "conn/conn.h"
54 #include "mutt.h"
55 #include "alias.h"
56 #include "browser.h"
57 #include "color.h"
58 #include "context.h"
59 #include "curs_lib.h"
60 #include "globals.h"
61 #include "hook.h"
62 #include "index.h"
63 #include "keymap.h"
64 #include "mutt_attach.h"
65 #include "mutt_commands.h"
66 #include "mutt_curses.h"
67 #include "mutt_history.h"
68 #include "mutt_logging.h"
69 #include "mutt_mailbox.h"
70 #include "mutt_menu.h"
71 #include "mutt_window.h"
72 #include "muttlib.h"
73 #include "mx.h"
74 #include "myvar.h"
75 #include "ncrypt/ncrypt.h"
76 #include "options.h"
77 #include "protos.h"
78 #include "send.h"
79 #include "sendlib.h"
80 #include "terminal.h"
81 #include "version.h"
82 #ifdef ENABLE_NLS
83 #include <libintl.h>
84 #endif
85 #ifdef USE_SIDEBAR
86 #include "sidebar.h"
87 #endif
88 #ifdef USE_IMAP
89 #include "imap/imap.h"
90 #endif
91 #ifdef USE_NNTP
92 #include "nntp/nntp.h"
93 #endif
94 #ifdef USE_AUTOCRYPT
95 #include "autocrypt/autocrypt.h"
96 #endif
97
98 /* These Config Variables are only used in main.c */
99 bool C_ResumeEditedDraftFiles; ///< Config: Resume editing previously saved draft files
100
101 // clang-format off
102 typedef uint8_t CliFlags;         ///< Flags for command line options, e.g. #MUTT_CLI_IGNORE
103 #define MUTT_CLI_NO_FLAGS      0  ///< No flags are set
104 #define MUTT_CLI_IGNORE  (1 << 0) ///< -z Open first mailbox if it has mail
105 #define MUTT_CLI_MAILBOX (1 << 1) ///< -Z Open first mailbox if is has new mail
106 #define MUTT_CLI_NOSYSRC (1 << 2) ///< -n Do not read the system-wide config file
107 #define MUTT_CLI_RO      (1 << 3) ///< -R Open mailbox in read-only mode
108 #define MUTT_CLI_SELECT  (1 << 4) ///< -y Start with a list of all mailboxes
109 #ifdef USE_NNTP
110 #define MUTT_CLI_NEWS    (1 << 5) ///< -g/-G Start with a list of all newsgroups
111 #endif
112 // clang-format on
113
114 /**
115  * test_parse_set - Test the config parsing
116  */
117 static void test_parse_set(void)
118 {
119   const char *vars[] = {
120     "from",        // ADDRESS
121     "beep",        // BOOL
122     "ispell",      // COMMAND
123     "mbox_type",   // MAGIC
124     "to_chars",    // MBTABLE
125     "net_inc",     // NUMBER
126     "signature",   // PATH
127     "print",       // QUAD
128     "mask",        // REGEX
129     "sort",        // SORT
130     "attribution", // STRING
131     "zzz",         // UNKNOWN
132     "my_var",      // MY_VAR
133   };
134
135   const char *commands[] = {
136     "set",
137     "toggle",
138     "reset",
139     "unset",
140   };
141
142   const char *tests[] = {
143     "%s %s",       "%s %s=42",  "%s %s?",     "%s ?%s",    "%s ?%s=42",
144     "%s ?%s?",     "%s no%s",   "%s no%s=42", "%s no%s?",  "%s inv%s",
145     "%s inv%s=42", "%s inv%s?", "%s &%s",     "%s &%s=42", "%s &%s?",
146   };
147
148   struct Buffer tmp = mutt_buffer_make(256);
149   struct Buffer err = mutt_buffer_make(256);
150   char line[64];
151
152   for (size_t v = 0; v < mutt_array_size(vars); v++)
153   {
154     // printf("--------------------------------------------------------------------------------\n");
155     // printf("VARIABLE %s\n", vars[v]);
156     for (size_t c = 0; c < mutt_array_size(commands); c++)
157     {
158       // printf("----------------------------------------\n");
159       // printf("COMMAND %s\n", commands[c]);
160       for (size_t t = 0; t < mutt_array_size(tests); t++)
161       {
162         mutt_buffer_reset(&tmp);
163         mutt_buffer_reset(&err);
164
165         snprintf(line, sizeof(line), tests[t], commands[c], vars[v]);
166         printf("%-26s", line);
167         enum CommandResult rc = mutt_parse_rc_line(line, &tmp, &err);
168         printf("%2d %s\n", rc, err.data);
169       }
170       printf("\n");
171     }
172     // printf("\n");
173   }
174
175   mutt_buffer_dealloc(&tmp);
176   mutt_buffer_dealloc(&err);
177 }
178
179 /**
180  * reset_tilde - Temporary measure
181  */
182 static void reset_tilde(struct ConfigSet *cs)
183 {
184   static const char *names[] = {
185     "alias_file", "certificate_file", "debug_file",
186     "folder",     "history_file",     "mbox",
187     "newsrc",     "news_cache_dir",   "postponed",
188     "record",     "signature",
189   };
190
191   struct Buffer value = mutt_buffer_make(256);
192   for (size_t i = 0; i < mutt_array_size(names); i++)
193   {
194     struct HashElem *he = cs_get_elem(cs, names[i]);
195     if (!he)
196       continue;
197     mutt_buffer_reset(&value);
198     cs_he_initial_get(cs, he, &value);
199     mutt_expand_path(value.data, value.dsize);
200     cs_he_initial_set(cs, he, value.data, NULL);
201     cs_he_reset(cs, he, NULL);
202   }
203   mutt_buffer_dealloc(&value);
204 }
205
206 /**
207  * mutt_exit - Leave NeoMutt NOW
208  * @param code Value to return to the calling environment
209  */
210 void mutt_exit(int code)
211 {
212   clear();
213   refresh();
214   mutt_endwin();
215   exit(code);
216 }
217
218 // clang-format off
219 /**
220  * usage - Display NeoMutt command line
221  */
222 static void usage(void)
223 {
224   puts(mutt_make_version());
225
226   /* L10N: Try to limit to 80 columns */
227   puts(_("usage:\n"
228          "  neomutt [-Enx] [-e <command>] [-F <config>] [-H <draft>] [-i <include>]\n"
229          "          [-b <address>] [-c <address>] [-s <subject>] [-a <file> [...] --]\n"
230          "          <address> [...]\n"
231          "  neomutt [-nx] [-e <command>] [-F <config>] [-b <address>] [-c <address>]\n"
232          "          [-s <subject>] [-a <file> [...] --] <address> [...] < message\n"
233          "  neomutt [-nRy] [-e <command>] [-F <config>] [-f <mailbox>] [-m <type>]\n"
234          "  neomutt [-n] [-e <command>] [-F <config>] -A <alias>\n"
235          "  neomutt [-n] [-e <command>] [-F <config>] -B\n"
236          "  neomutt [-n] [-e <command>] [-F <config>] -D [-S]\n"
237          "  neomutt [-n] [-e <command>] [-F <config>] -d <level> -l <file>\n"
238          "  neomutt [-n] [-e <command>] [-F <config>] -G\n"
239          "  neomutt [-n] [-e <command>] [-F <config>] -g <server>\n"
240          "  neomutt [-n] [-e <command>] [-F <config>] -p\n"
241          "  neomutt [-n] [-e <command>] [-F <config>] -Q <variable>\n"
242          "  neomutt [-n] [-e <command>] [-F <config>] -Z\n"
243          "  neomutt [-n] [-e <command>] [-F <config>] -z [-f <mailbox>]\n"
244          "  neomutt -v[v]\n"));
245
246   /* L10N: Try to limit to 80 columns.  If more space is needed add an indented line */
247   puts(_("options:\n"
248          "  --            Special argument forces NeoMutt to stop option parsing and treat\n"
249          "                remaining arguments as addresses even if they start with a dash\n"
250          "  -A <alias>    Print an expanded version of the given alias to stdout and exit\n"
251          "  -a <file>     Attach one or more files to a message (must be the last option)\n"
252          "                Add any addresses after the '--' argument\n"
253          "  -B            Run in batch mode (do not start the ncurses UI)\n"
254          "  -b <address>  Specify a blind carbon copy (Bcc) recipient\n"
255          "  -c <address>  Specify a carbon copy (Cc) recipient\n"
256          "  -D            Dump all config variables as 'name=value' pairs to stdout\n"
257          "  -D -S         Like -D, but hide the value of sensitive variables\n"
258          "  -d <level>    Log debugging output to a file (default is \"~/.neomuttdebug0\")\n"
259          "                The level can range from 1-5 and affects verbosity\n"
260          "  -E            Edit draft (-H) or include (-i) file during message composition\n"
261          "  -e <command>  Specify a command to be run after reading the config files\n"
262          "  -F <config>   Specify an alternative initialization file to read\n"
263          "  -f <mailbox>  Specify a mailbox (as defined with 'mailboxes' command) to load\n"
264          "  -G            Start NeoMutt with a listing of subscribed newsgroups\n"
265          "  -g <server>   Like -G, but start at specified news server\n"
266          "  -H <draft>    Specify a draft file with header and body for message composing\n"
267          "  -h            Print this help message and exit\n"
268          "  -i <include>  Specify an include file to be embedded in the body of a message\n"
269          "  -l <file>     Specify a file for debugging output (default \"~/.neomuttdebug0\")\n"
270          "  -m <type>     Specify a default mailbox format type for newly created folders\n"
271          "                The type is either MH, MMDF, Maildir or mbox (case-insensitive)\n"
272          "  -n            Do not read the system-wide configuration file\n"
273          "  -p            Resume a prior postponed message, if any\n"
274          "  -Q <variable> Query a configuration variable and print its value to stdout\n"
275          "                (after the config has been read and any commands executed)\n"
276          "  -R            Open mailbox in read-only mode\n"
277          "  -s <subject>  Specify a subject (must be enclosed in quotes if it has spaces)\n"
278          "  -v            Print the NeoMutt version and compile-time definitions and exit\n"
279          "  -vv           Print the NeoMutt license and copyright information and exit\n"
280          "  -x            Simulate the mailx(1) send mode\n"
281          "  -y            Start NeoMutt with a listing of all defined mailboxes\n"
282          "  -Z            Open the first mailbox with new message or exit immediately with\n"
283          "                exit code 1 if none is found in all defined mailboxes\n"
284          "  -z            Open the first or specified (-f) mailbox if it holds any message\n"
285          "                or exit immediately with exit code 1 otherwise"));
286 }
287 // clang-format on
288
289 /**
290  * start_curses - Start the curses or slang UI
291  * @retval 0 Success
292  * @retval 1 Failure
293  */
294 static int start_curses(void)
295 {
296   km_init(); /* must come before mutt_init */
297
298 #ifdef USE_SLANG_CURSES
299   SLtt_Ignore_Beep = 1; /* don't do that #*$@^! annoying visual beep! */
300   SLsmg_Display_Eight_Bit = 128; /* characters above this are printable */
301   SLtt_set_color(0, NULL, "default", "default");
302 #if SLANG_VERSION >= 20000
303   SLutf8_enable(-1);
304 #endif
305 #else
306   /* should come before initscr() so that ncurses 4.2 doesn't try to install
307    * its own SIGWINCH handler */
308   mutt_signal_init();
309 #endif
310   if (!initscr())
311   {
312     mutt_error(_("Error initializing terminal"));
313     return 1;
314   }
315   /* slang requires the signal handlers to be set after initializing */
316   mutt_signal_init();
317   mutt_color_init();
318   keypad(stdscr, true);
319   cbreak();
320   noecho();
321   nonl();
322 #ifdef HAVE_TYPEAHEAD
323   typeahead(-1); /* simulate smooth scrolling */
324 #endif
325 #ifdef HAVE_META
326   meta(stdscr, true);
327 #endif
328   init_extended_keys();
329   /* Now that curses is set up, we drop back to normal screen mode.
330    * This simplifies displaying error messages to the user.
331    * The first call to refresh() will swap us back to curses screen mode. */
332   endwin();
333   return 0;
334 }
335
336 /**
337  * init_locale - Initialise the Locale/NLS settings
338  */
339 static void init_locale(void)
340 {
341   setlocale(LC_ALL, "");
342
343 #ifdef ENABLE_NLS
344   const char *domdir = mutt_str_getenv("TEXTDOMAINDIR");
345   if (domdir)
346     bindtextdomain(PACKAGE, domdir);
347   else
348     bindtextdomain(PACKAGE, MUTTLOCALEDIR);
349   textdomain(PACKAGE);
350 #endif
351 #ifndef LOCALES_HACK
352   /* Do we have a locale definition? */
353   if (mutt_str_getenv("LC_ALL") || mutt_str_getenv("LANG") || mutt_str_getenv("LC_CTYPE"))
354   {
355     OptLocales = true;
356   }
357 #endif
358 }
359
360 /**
361  * get_user_info - Find the user's name, home and shell
362  * @param cs Config Set
363  * @retval true Success
364  *
365  * Find the login name, real name, home directory and shell.
366  */
367 bool get_user_info(struct ConfigSet *cs)
368 {
369   mutt_str_replace(&Username, mutt_str_getenv("USER"));
370   mutt_str_replace(&HomeDir, mutt_str_getenv("HOME"));
371
372   const char *shell = mutt_str_getenv("SHELL");
373   if (shell)
374     cs_str_initial_set(cs, "shell", shell, NULL);
375
376   /* Get some information about the user */
377   struct passwd *pw = getpwuid(getuid());
378   if (pw)
379   {
380     if (!Username)
381       Username = mutt_str_strdup(pw->pw_name);
382     if (!HomeDir)
383       HomeDir = mutt_str_strdup(pw->pw_dir);
384     if (!shell)
385       cs_str_initial_set(cs, "shell", pw->pw_shell, NULL);
386   }
387
388   if (!Username)
389   {
390     mutt_error(_("unable to determine username"));
391     return false; // TEST05: neomutt (unset $USER, delete user from /etc/passwd)
392   }
393
394   if (!HomeDir)
395   {
396     mutt_error(_("unable to determine home directory"));
397     return false; // TEST06: neomutt (unset $HOME, delete user from /etc/passwd)
398   }
399
400   cs_str_reset(cs, "shell", NULL);
401   return true;
402 }
403
404 /**
405  * main - Start NeoMutt
406  * @param argc Number of command line arguments
407  * @param argv List of command line arguments
408  * @param envp Copy of the environment
409  * @retval 0 Success
410  * @retval 1 Error
411  */
412 int main(int argc, char *argv[], char *envp[])
413 {
414   char *subject = NULL;
415   char *include_file = NULL;
416   char *draft_file = NULL;
417   char *new_magic = NULL;
418   char *dlevel = NULL;
419   char *dfile = NULL;
420 #ifdef USE_NNTP
421   char *cli_nntp = NULL;
422 #endif
423   struct Email *e = NULL;
424   struct ListHead attach = STAILQ_HEAD_INITIALIZER(attach);
425   struct ListHead commands = STAILQ_HEAD_INITIALIZER(commands);
426   struct ListHead queries = STAILQ_HEAD_INITIALIZER(queries);
427   struct ListHead alias_queries = STAILQ_HEAD_INITIALIZER(alias_queries);
428   struct ListHead cc_list = STAILQ_HEAD_INITIALIZER(cc_list);
429   struct ListHead bcc_list = STAILQ_HEAD_INITIALIZER(bcc_list);
430   SendFlags sendflags = SEND_NO_FLAGS;
431   CliFlags flags = MUTT_CLI_NO_FLAGS;
432   int version = 0;
433   int i;
434   bool explicit_folder = false;
435   bool dump_variables = false;
436   bool hide_sensitive = false;
437   bool batch_mode = false;
438   bool edit_infile = false;
439   bool test_config = false;
440   extern char *optarg;
441   extern int optind;
442   int double_dash = argc, nargc = 1;
443   int rc = 1;
444   bool repeat_error = false;
445   struct Buffer folder = mutt_buffer_make(0);
446
447   MuttLogger = log_disp_terminal;
448
449   /* sanity check against stupid administrators */
450   if (getegid() != getgid())
451   {
452     mutt_error("%s: I don't want to run with privileges!", argv[0]);
453     goto main_exit; // TEST01: neomutt (as root, chgrp mail neomutt; chmod +s neomutt)
454   }
455
456   init_locale();
457
458   int out = 0;
459   if (mutt_randbuf(&out, sizeof(out)) < 0)
460     goto main_exit; // TEST02: neomutt (as root on non-Linux OS, rename /dev/urandom)
461
462   umask(077);
463
464   mutt_envlist_init(envp);
465   for (optind = 1; optind < double_dash;)
466   {
467     /* We're getopt'ing POSIXLY, so we'll be here every time getopt()
468      * encounters a non-option.  That could be a file to attach
469      * (all non-options between -a and --) or it could be an address
470      * (which gets collapsed to the front of argv).  */
471     for (; optind < argc; optind++)
472     {
473       if ((argv[optind][0] == '-') && (argv[optind][1] != '\0'))
474       {
475         if ((argv[optind][1] == '-') && (argv[optind][2] == '\0'))
476           double_dash = optind; /* quit outer loop after getopt */
477         break;                  /* drop through to getopt */
478       }
479
480       /* non-option, either an attachment or address */
481       if (!STAILQ_EMPTY(&attach))
482         mutt_list_insert_tail(&attach, mutt_str_strdup(argv[optind]));
483       else
484         argv[nargc++] = argv[optind];
485     }
486
487     /* USE_NNTP 'g:G' */
488     i = getopt(argc, argv, "+A:a:Bb:F:f:c:Dd:l:Ee:g:GH:i:hm:npQ:RSs:TvxyzZ");
489     if (i != EOF)
490     {
491       switch (i)
492       {
493         case 'A':
494           mutt_list_insert_tail(&alias_queries, mutt_str_strdup(optarg));
495           break;
496         case 'a':
497           mutt_list_insert_tail(&attach, mutt_str_strdup(optarg));
498           break;
499         case 'B':
500           batch_mode = true;
501           break;
502         case 'b':
503           mutt_list_insert_tail(&bcc_list, mutt_str_strdup(optarg));
504           break;
505         case 'c':
506           mutt_list_insert_tail(&cc_list, mutt_str_strdup(optarg));
507           break;
508         case 'D':
509           dump_variables = true;
510           break;
511         case 'd':
512           dlevel = optarg;
513           break;
514         case 'E':
515           edit_infile = true;
516           break;
517         case 'e':
518           mutt_list_insert_tail(&commands, mutt_str_strdup(optarg));
519           break;
520         case 'F':
521           mutt_list_insert_tail(&Muttrc, mutt_str_strdup(optarg));
522           break;
523         case 'f':
524           mutt_buffer_strcpy(&folder, optarg);
525           explicit_folder = true;
526           break;
527 #ifdef USE_NNTP
528         case 'g': /* Specify a news server */
529           cli_nntp = optarg;
530           /* fallthrough */
531         case 'G': /* List of newsgroups */
532           flags |= MUTT_CLI_SELECT | MUTT_CLI_NEWS;
533           break;
534 #endif
535         case 'H':
536           draft_file = optarg;
537           break;
538         case 'i':
539           include_file = optarg;
540           break;
541         case 'l':
542           dfile = optarg;
543           break;
544         case 'm':
545           new_magic = optarg;
546           break;
547         case 'n':
548           flags |= MUTT_CLI_NOSYSRC;
549           break;
550         case 'p':
551           sendflags |= SEND_POSTPONED;
552           break;
553         case 'Q':
554           mutt_list_insert_tail(&queries, mutt_str_strdup(optarg));
555           break;
556         case 'R':
557           flags |= MUTT_CLI_RO; /* read-only mode */
558           break;
559         case 'S':
560           hide_sensitive = true;
561           break;
562         case 's':
563           subject = optarg;
564           break;
565         case 'T':
566           test_config = true;
567           break;
568         case 'v':
569           version++;
570           break;
571         case 'x': /* mailx compatible send mode */
572           sendflags |= SEND_MAILX;
573           break;
574         case 'y': /* My special hack mode */
575           flags |= MUTT_CLI_SELECT;
576           break;
577         case 'Z':
578           flags |= MUTT_CLI_MAILBOX | MUTT_CLI_IGNORE;
579           break;
580         case 'z':
581           flags |= MUTT_CLI_IGNORE;
582           break;
583         default:
584           usage();
585           OptNoCurses = true;
586           goto main_ok; // TEST03: neomutt -9
587       }
588     }
589   }
590
591   /* collapse remaining argv */
592   while (optind < argc)
593     argv[nargc++] = argv[optind++];
594   optind = 1;
595   argc = nargc;
596
597   if (version > 0)
598   {
599     log_queue_flush(log_disp_terminal);
600     if (version == 1)
601       print_version(stdout);
602     else
603       print_copyright();
604     OptNoCurses = true;
605     goto main_ok; // TEST04: neomutt -v
606   }
607
608   Config = init_config(500);
609   if (!Config)
610     goto main_curses;
611   NeoMutt = neomutt_new(Config);
612
613   notify_set_parent(Config->notify, NeoMutt->notify);
614
615   if (!get_user_info(Config))
616     goto main_exit;
617
618   if (test_config)
619   {
620     cs_str_initial_set(Config, "from", "rich@flatcap.org", NULL);
621     cs_str_reset(Config, "from", NULL);
622     myvar_set("my_var", "foo");
623     test_parse_set();
624     goto main_ok;
625   }
626
627   reset_tilde(Config);
628
629   if (dfile)
630   {
631     cs_str_initial_set(Config, "debug_file", dfile, NULL);
632     cs_str_reset(Config, "debug_file", NULL);
633   }
634
635   if (dlevel)
636   {
637     short num = 0;
638     if ((mutt_str_atos(dlevel, &num) < 0) || (num < LL_MESSAGE) || (num >= LL_MAX))
639     {
640       mutt_error(_("Error: value '%s' is invalid for -d"), dlevel);
641       goto main_exit; // TEST07: neomutt -d xyz
642     }
643     cs_str_initial_set(Config, "debug_level", dlevel, NULL);
644     cs_str_reset(Config, "debug_level", NULL);
645   }
646
647   mutt_log_prep();
648   if (dlevel)
649     mutt_log_start();
650
651   MuttLogger = log_disp_queue;
652
653   if (!STAILQ_EMPTY(&cc_list) || !STAILQ_EMPTY(&bcc_list))
654   {
655     e = email_new();
656     e->env = mutt_env_new();
657
658     struct ListNode *np = NULL;
659     STAILQ_FOREACH(np, &bcc_list, entries)
660     {
661       mutt_addrlist_parse(&e->env->bcc, np->data);
662     }
663
664     STAILQ_FOREACH(np, &cc_list, entries)
665     {
666       mutt_addrlist_parse(&e->env->cc, np->data);
667     }
668
669     mutt_list_free(&bcc_list);
670     mutt_list_free(&cc_list);
671   }
672
673   /* Check for a batch send. */
674   if (!isatty(0) || !STAILQ_EMPTY(&queries) || !STAILQ_EMPTY(&alias_queries) ||
675       dump_variables || batch_mode)
676   {
677     OptNoCurses = true;
678     sendflags = SEND_BATCH;
679     MuttLogger = log_disp_terminal;
680     log_queue_flush(log_disp_terminal);
681   }
682
683   /* Always create the mutt_windows because batch mode has some shared code
684    * paths that end up referencing them. */
685   mutt_window_init();
686
687   /* This must come before mutt_init() because curses needs to be started
688    * before calling the init_pair() function to set the color scheme.  */
689   if (!OptNoCurses)
690   {
691     int crc = start_curses();
692
693     if (crc != 0)
694       goto main_curses; // TEST08: can't test -- fake term?
695
696     /* check whether terminal status is supported (must follow curses init) */
697     TsSupported = mutt_ts_capability();
698     mutt_window_reflow();
699   }
700
701   /* set defaults and read init files */
702   if (mutt_init(flags & MUTT_CLI_NOSYSRC, &commands) != 0)
703     goto main_curses;
704
705   /* The command line overrides the config */
706   if (dlevel)
707     cs_str_reset(Config, "debug_level", NULL);
708   if (dfile)
709     cs_str_reset(Config, "debug_file", NULL);
710
711   if (mutt_log_start() < 0)
712   {
713     mutt_perror("log file");
714     goto main_exit;
715   }
716
717   mutt_list_free(&commands);
718
719 #ifdef USE_NNTP
720   /* "$news_server" precedence: command line, config file, environment, system file */
721   if (cli_nntp)
722     cs_str_string_set(Config, "news_server", cli_nntp, NULL);
723   if (!C_NewsServer)
724   {
725     const char *env_nntp = mutt_str_getenv("NNTPSERVER");
726     cs_str_string_set(Config, "news_server", env_nntp, NULL);
727   }
728   if (!C_NewsServer)
729   {
730     char buf[1024];
731     char *server = mutt_file_read_keyword(SYSCONFDIR "/nntpserver", buf, sizeof(buf));
732     cs_str_string_set(Config, "news_server", server, NULL);
733   }
734   if (C_NewsServer)
735     cs_str_initial_set(Config, "news_server", C_NewsServer, NULL);
736 #endif
737
738   /* Initialize crypto backends.  */
739   crypt_init();
740
741   if (new_magic)
742   {
743     struct Buffer err = mutt_buffer_make(0);
744     int r = cs_str_initial_set(Config, "mbox_type", new_magic, &err);
745     if (CSR_RESULT(r) != CSR_SUCCESS)
746     {
747       mutt_error(err.data);
748       mutt_buffer_dealloc(&err);
749       goto main_curses;
750     }
751     cs_str_reset(Config, "mbox_type", NULL);
752   }
753
754   if (!STAILQ_EMPTY(&queries))
755   {
756     rc = mutt_query_variables(&queries);
757     goto main_curses;
758   }
759
760   if (dump_variables)
761   {
762     dump_config(Config, hide_sensitive ? CS_DUMP_HIDE_SENSITIVE : CS_DUMP_NO_FLAGS, stdout);
763     goto main_ok; // TEST18: neomutt -D
764   }
765
766   if (!STAILQ_EMPTY(&alias_queries))
767   {
768     rc = 0;
769     for (; optind < argc; optind++)
770       mutt_list_insert_tail(&alias_queries, mutt_str_strdup(argv[optind]));
771     struct ListNode *np = NULL;
772     STAILQ_FOREACH(np, &alias_queries, entries)
773     {
774       struct AddressList *al = mutt_alias_lookup(np->data);
775       if (al)
776       {
777         /* output in machine-readable form */
778         mutt_addrlist_to_intl(al, NULL);
779         mutt_write_addrlist(al, stdout, 0, 0);
780       }
781       else
782       {
783         rc = 1;
784         printf("%s\n", np->data); // TEST19: neomutt -A unknown
785       }
786     }
787     mutt_list_free(&alias_queries);
788     goto main_curses; // TEST20: neomutt -A alias
789   }
790
791   if (!OptNoCurses)
792   {
793     mutt_curses_set_color(MT_COLOR_NORMAL);
794     clear();
795     MuttLogger = log_disp_curses;
796     log_queue_flush(log_disp_curses);
797     log_queue_set_max_size(100);
798   }
799
800   /* Initialize autocrypt after curses messages are working,
801    * because of the initial account setup screens. */
802 #ifdef USE_AUTOCRYPT
803   if (C_Autocrypt)
804     mutt_autocrypt_init(!(sendflags & SEND_BATCH));
805 #endif
806
807   /* Create the C_Folder directory if it doesn't exist. */
808   if (!OptNoCurses && C_Folder)
809   {
810     struct stat sb;
811     char fpath[PATH_MAX];
812
813     mutt_str_strfcpy(fpath, C_Folder, sizeof(fpath));
814     mutt_expand_path(fpath, sizeof(fpath));
815     bool skip = false;
816 #ifdef USE_IMAP
817     /* we're not connected yet - skip mail folder creation */
818     skip |= (imap_path_probe(fpath, NULL) == MUTT_IMAP);
819 #endif
820 #ifdef USE_NNTP
821     skip |= (nntp_path_probe(fpath, NULL) == MUTT_NNTP);
822 #endif
823     if (!skip && (stat(fpath, &sb) == -1) && (errno == ENOENT))
824     {
825       char msg2[256];
826       snprintf(msg2, sizeof(msg2), _("%s does not exist. Create it?"), C_Folder);
827       if (mutt_yesorno(msg2, MUTT_YES) == MUTT_YES)
828       {
829         if ((mkdir(fpath, 0700) == -1) && (errno != EEXIST))
830           mutt_error(_("Can't create %s: %s"), C_Folder, strerror(errno)); // TEST21: neomutt -n -F /dev/null (and ~/Mail doesn't exist)
831       }
832     }
833   }
834
835   if (batch_mode)
836   {
837     goto main_ok; // TEST22: neomutt -B
838   }
839
840   notify_observer_add(Config->notify, NT_CONFIG, 0, mutt_hist_observer, 0);
841   notify_observer_add(Config->notify, NT_CONFIG, 0, mutt_log_observer, 0);
842   notify_observer_add(Config->notify, NT_CONFIG, 0, mutt_menu_observer, 0);
843   notify_observer_add(Config->notify, NT_CONFIG, 0, mutt_reply_observer, 0);
844
845   if (sendflags & SEND_POSTPONED)
846   {
847     if (!OptNoCurses)
848       mutt_flushinp();
849     if (ci_send_message(SEND_POSTPONED, NULL, NULL, NULL, NULL) == 0)
850       rc = 0;
851     // TEST23: neomutt -p (postponed message, cancel)
852     // TEST24: neomutt -p (no postponed message)
853     log_queue_empty();
854     repeat_error = true;
855     goto main_curses;
856   }
857   else if (subject || e || sendflags || draft_file || include_file ||
858            !STAILQ_EMPTY(&attach) || (optind < argc))
859   {
860     FILE *fp_in = NULL;
861     FILE *fp_out = NULL;
862     char *tempfile = NULL, *infile = NULL;
863     char *bodytext = NULL, *bodyfile = NULL;
864     int rv = 0;
865     char expanded_infile[PATH_MAX];
866
867     if (!OptNoCurses)
868       mutt_flushinp();
869
870     if (!e)
871       e = email_new();
872     if (!e->env)
873       e->env = mutt_env_new();
874
875     for (i = optind; i < argc; i++)
876     {
877       if (url_check_scheme(argv[i]) == U_MAILTO)
878       {
879         if (mutt_parse_mailto(e->env, &bodytext, argv[i]) < 0)
880         {
881           mutt_error(_("Failed to parse mailto: link"));
882           goto main_curses; // TEST25: neomutt mailto:
883         }
884       }
885       else
886         mutt_addrlist_parse(&e->env->to, argv[i]);
887     }
888
889     if (!draft_file && C_Autoedit && TAILQ_EMPTY(&e->env->to) &&
890         TAILQ_EMPTY(&e->env->cc))
891     {
892       mutt_error(_("No recipients specified"));
893       goto main_curses; // TEST26: neomutt -s test (with autoedit=yes)
894     }
895
896     if (subject)
897       e->env->subject = mutt_str_strdup(subject);
898
899     if (draft_file)
900     {
901       infile = draft_file;
902       include_file = NULL;
903     }
904     else if (include_file)
905       infile = include_file;
906     else
907       edit_infile = false;
908
909     if (infile || bodytext)
910     {
911       /* Prepare fp_in and expanded_infile. */
912       if (infile)
913       {
914         if (mutt_str_strcmp("-", infile) == 0)
915         {
916           if (edit_infile)
917           {
918             mutt_error(_("Can't use -E flag with stdin"));
919             goto main_curses; // TEST27: neomutt -E -H -
920           }
921           fp_in = stdin;
922         }
923         else
924         {
925           mutt_str_strfcpy(expanded_infile, infile, sizeof(expanded_infile));
926           mutt_expand_path(expanded_infile, sizeof(expanded_infile));
927           fp_in = fopen(expanded_infile, "r");
928           if (!fp_in)
929           {
930             mutt_perror(expanded_infile);
931             goto main_curses; // TEST28: neomutt -E -H missing
932           }
933         }
934       }
935
936       /* Copy input to a tempfile, and re-point fp_in to the tempfile.
937        * Note: stdin is always copied to a tempfile, ensuring draft_file
938        * can stat and get the correct st_size below.  */
939       if (!edit_infile)
940       {
941         char buf[1024];
942         mutt_mktemp(buf, sizeof(buf));
943         tempfile = mutt_str_strdup(buf);
944
945         fp_out = mutt_file_fopen(tempfile, "w");
946         if (!fp_out)
947         {
948           mutt_file_fclose(&fp_in);
949           mutt_perror(tempfile);
950           FREE(&tempfile);
951           goto main_curses; // TEST29: neomutt -H existing-file (where tmpdir=/path/to/FILE blocking tmpdir)
952         }
953         if (fp_in)
954         {
955           mutt_file_copy_stream(fp_in, fp_out);
956           if (fp_in != stdin)
957             mutt_file_fclose(&fp_in);
958         }
959         else if (bodytext)
960           fputs(bodytext, fp_out);
961         mutt_file_fclose(&fp_out);
962
963         fp_in = fopen(tempfile, "r");
964         if (!fp_in)
965         {
966           mutt_perror(tempfile);
967           FREE(&tempfile);
968           goto main_curses; // TEST30: can't test
969         }
970       }
971       /* If editing the infile, keep it around afterwards so
972        * it doesn't get unlinked, and we can rebuild the draft_file */
973       else
974         sendflags |= SEND_NO_FREE_HEADER;
975
976       /* Parse the draft_file into the full Email/Body structure.
977        * Set SEND_DRAFT_FILE so ci_send_message doesn't overwrite
978        * our e->content.  */
979       if (draft_file)
980       {
981         struct Envelope *opts_env = e->env;
982         struct stat st;
983
984         sendflags |= SEND_DRAFT_FILE;
985
986         /* Set up a tmp Email with just enough information so that
987          * mutt_prepare_template() can parse the message in fp_in.  */
988         struct Email *e_tmp = email_new();
989         e_tmp->offset = 0;
990         e_tmp->content = mutt_body_new();
991         if (fstat(fileno(fp_in), &st) != 0)
992         {
993           mutt_perror(draft_file);
994           goto main_curses; // TEST31: can't test
995         }
996         e_tmp->content->length = st.st_size;
997
998         if (mutt_prepare_template(fp_in, NULL, e, e_tmp, false) < 0)
999         {
1000           mutt_error(_("Can't parse message template: %s"), draft_file);
1001           mutt_env_free(&opts_env);
1002           email_free(&e_tmp);
1003           goto main_curses;
1004         }
1005
1006         /* Scan for neomutt header to set C_ResumeDraftFiles */
1007         struct ListNode *np = NULL, *tmp = NULL;
1008         STAILQ_FOREACH_SAFE(np, &e->env->userhdrs, entries, tmp)
1009         {
1010           if (mutt_str_startswith(np->data, "X-Mutt-Resume-Draft:", CASE_IGNORE))
1011           {
1012             if (C_ResumeEditedDraftFiles)
1013               cs_str_native_set(Config, "resume_draft_files", true, NULL);
1014
1015             STAILQ_REMOVE(&e->env->userhdrs, np, ListNode, entries);
1016             FREE(&np->data);
1017             FREE(&np);
1018           }
1019         }
1020
1021         mutt_addrlist_copy(&e->env->to, &opts_env->to, false);
1022         mutt_addrlist_copy(&e->env->cc, &opts_env->cc, false);
1023         mutt_addrlist_copy(&e->env->bcc, &opts_env->bcc, false);
1024         if (opts_env->subject)
1025           mutt_str_replace(&e->env->subject, opts_env->subject);
1026
1027         mutt_env_free(&opts_env);
1028         email_free(&e_tmp);
1029       }
1030       /* Editing the include_file: pass it directly in.
1031        * Note that SEND_NO_FREE_HEADER is set above so it isn't unlinked.  */
1032       else if (edit_infile)
1033         bodyfile = expanded_infile;
1034       /* For bodytext and unedited include_file: use the tempfile.
1035        */
1036       else
1037         bodyfile = tempfile;
1038
1039       mutt_file_fclose(&fp_in);
1040     }
1041
1042     FREE(&bodytext);
1043
1044     if (!STAILQ_EMPTY(&attach))
1045     {
1046       struct Body *b = e->content;
1047
1048       while (b && b->next)
1049         b = b->next;
1050
1051       struct ListNode *np = NULL;
1052       STAILQ_FOREACH(np, &attach, entries)
1053       {
1054         if (b)
1055         {
1056           b->next = mutt_make_file_attach(np->data);
1057           b = b->next;
1058         }
1059         else
1060         {
1061           b = mutt_make_file_attach(np->data);
1062           e->content = b;
1063         }
1064         if (!b)
1065         {
1066           mutt_error(_("%s: unable to attach file"), np->data);
1067           mutt_list_free(&attach);
1068           goto main_curses; // TEST32: neomutt john@example.com -a missing
1069         }
1070       }
1071       mutt_list_free(&attach);
1072     }
1073
1074     rv = ci_send_message(sendflags, e, bodyfile, NULL, NULL);
1075     /* We WANT the "Mail sent." and any possible, later error */
1076     log_queue_empty();
1077     if (ErrorBufMessage)
1078       mutt_message("%s", ErrorBuf);
1079
1080     if (edit_infile)
1081     {
1082       if (include_file)
1083         e->content->unlink = false;
1084       else if (draft_file)
1085       {
1086         if (truncate(expanded_infile, 0) == -1)
1087         {
1088           mutt_perror(expanded_infile);
1089           goto main_curses; // TEST33: neomutt -H read-only -s test john@example.com -E
1090         }
1091         fp_out = mutt_file_fopen(expanded_infile, "a");
1092         if (!fp_out)
1093         {
1094           mutt_perror(expanded_infile);
1095           goto main_curses; // TEST34: can't test
1096         }
1097
1098         /* If the message was sent or postponed, these will already
1099          * have been done.  */
1100         if (rv < 0)
1101         {
1102           if (e->content->next)
1103             e->content = mutt_make_multipart(e->content);
1104           mutt_encode_descriptions(e->content, true);
1105           mutt_prepare_envelope(e->env, false);
1106           mutt_env_to_intl(e->env, NULL, NULL);
1107         }
1108
1109         mutt_rfc822_write_header(
1110             fp_out, e->env, e->content, MUTT_WRITE_HEADER_POSTPONE, false,
1111             C_CryptProtectedHeadersRead && mutt_should_hide_protected_subject(e));
1112         if (C_ResumeEditedDraftFiles)
1113           fprintf(fp_out, "X-Mutt-Resume-Draft: 1\n");
1114         fputc('\n', fp_out);
1115         if ((mutt_write_mime_body(e->content, fp_out) == -1))
1116         {
1117           mutt_file_fclose(&fp_out);
1118           goto main_curses; // TEST35: can't test
1119         }
1120         mutt_file_fclose(&fp_out);
1121       }
1122
1123       email_free(&e);
1124     }
1125
1126     /* !edit_infile && draft_file will leave the tempfile around */
1127     if (tempfile)
1128     {
1129       unlink(tempfile);
1130       FREE(&tempfile);
1131     }
1132
1133     mutt_window_free_all();
1134
1135     if (rv != 0)
1136       goto main_curses; // TEST36: neomutt -H existing -s test john@example.com -E (cancel sending)
1137   }
1138   else
1139   {
1140     if (flags & MUTT_CLI_MAILBOX)
1141     {
1142 #ifdef USE_IMAP
1143       bool passive = C_ImapPassive;
1144       C_ImapPassive = false;
1145 #endif
1146       if (mutt_mailbox_check(Context ? Context->mailbox : NULL, 0) == 0)
1147       {
1148         mutt_message(_("No mailbox with new mail"));
1149         goto main_curses; // TEST37: neomutt -Z (no new mail)
1150       }
1151       mutt_buffer_reset(&folder);
1152       mutt_mailbox_next_buffer(Context ? Context->mailbox : NULL, &folder);
1153 #ifdef USE_IMAP
1154       C_ImapPassive = passive;
1155 #endif
1156     }
1157     else if (flags & MUTT_CLI_SELECT)
1158     {
1159 #ifdef USE_NNTP
1160       if (flags & MUTT_CLI_NEWS)
1161       {
1162         OptNews = true;
1163         CurrentNewsSrv =
1164             nntp_select_server(Context ? Context->mailbox : NULL, C_NewsServer, false);
1165         if (!CurrentNewsSrv)
1166           goto main_curses; // TEST38: neomutt -G (unset news_server)
1167       }
1168       else
1169 #endif
1170           if (TAILQ_EMPTY(&NeoMutt->accounts))
1171       {
1172         mutt_error(_("No incoming mailboxes defined"));
1173         goto main_curses; // TEST39: neomutt -n -F /dev/null -y
1174       }
1175       mutt_buffer_reset(&folder);
1176       mutt_buffer_select_file(&folder, MUTT_SEL_FOLDER | MUTT_SEL_MAILBOX, NULL, NULL);
1177       if (mutt_buffer_is_empty(&folder))
1178       {
1179         goto main_ok; // TEST40: neomutt -y (quit selection)
1180       }
1181     }
1182
1183     if (mutt_buffer_is_empty(&folder))
1184     {
1185       if (C_Spoolfile)
1186       {
1187         // Check if C_Spoolfile corresponds a mailboxes' description.
1188         struct Mailbox *m_desc = mailbox_find_name(C_Spoolfile);
1189         if (m_desc)
1190           mutt_buffer_strcpy(&folder, m_desc->realpath);
1191         else
1192           mutt_buffer_strcpy(&folder, C_Spoolfile);
1193       }
1194       else if (C_Folder)
1195         mutt_buffer_strcpy(&folder, C_Folder);
1196       /* else no folder */
1197     }
1198
1199 #ifdef USE_NNTP
1200     if (OptNews)
1201     {
1202       OptNews = false;
1203       mutt_buffer_alloc(&folder, PATH_MAX);
1204       nntp_expand_path(folder.data, folder.dsize, &CurrentNewsSrv->conn->account);
1205     }
1206     else
1207 #endif
1208       mutt_buffer_expand_path(&folder);
1209
1210     mutt_str_replace(&CurrentFolder, mutt_b2s(&folder));
1211     mutt_str_replace(&LastFolder, mutt_b2s(&folder));
1212
1213     if (flags & MUTT_CLI_IGNORE)
1214     {
1215       /* check to see if there are any messages in the folder */
1216       switch (mx_check_empty(mutt_b2s(&folder)))
1217       {
1218         case -1:
1219           mutt_perror(mutt_b2s(&folder));
1220           goto main_curses; // TEST41: neomutt -z -f missing
1221         case 1:
1222           mutt_error(_("Mailbox is empty"));
1223           goto main_curses; // TEST42: neomutt -z -f /dev/null
1224       }
1225     }
1226
1227     mutt_folder_hook(mutt_b2s(&folder), NULL);
1228     mutt_startup_shutdown_hook(MUTT_STARTUP_HOOK);
1229     notify_send(NeoMutt->notify, NT_GLOBAL, NT_GLOBAL_STARTUP, 0);
1230
1231     repeat_error = true;
1232     struct Mailbox *m = mx_path_resolve(mutt_b2s(&folder));
1233     Context = mx_mbox_open(m, ((flags & MUTT_CLI_RO) || C_ReadOnly) ? MUTT_READONLY : MUTT_OPEN_NO_FLAGS);
1234     if (!Context)
1235     {
1236       if (m->account)
1237       {
1238         account_mailbox_remove(m->account, m);
1239         m = NULL;
1240       }
1241       else
1242         mailbox_free(&m);
1243     }
1244     if (Context || !explicit_folder)
1245     {
1246 #ifdef USE_SIDEBAR
1247       mutt_sb_set_open_mailbox(Context ? Context->mailbox : NULL);
1248 #endif
1249       mutt_index_menu();
1250       ctx_free(&Context);
1251     }
1252 #ifdef USE_IMAP
1253     imap_logout_all();
1254 #endif
1255 #ifdef USE_SASL
1256     mutt_sasl_done();
1257 #endif
1258 #ifdef USE_AUTOCRYPT
1259     mutt_autocrypt_cleanup();
1260 #endif
1261     log_queue_empty();
1262     mutt_log_stop();
1263     // TEST43: neomutt (no change to mailbox)
1264     // TEST44: neomutt (change mailbox)
1265   }
1266
1267 main_ok:
1268   rc = 0;
1269 main_curses:
1270   clear();
1271   refresh();
1272   mutt_endwin();
1273   log_queue_flush(log_disp_terminal);
1274   mutt_unlink_temp_attachments();
1275   mutt_log_stop();
1276   /* Repeat the last message to the user */
1277   if (repeat_error && ErrorBufMessage)
1278     puts(ErrorBuf);
1279 main_exit:
1280   mutt_buffer_dealloc(&folder);
1281   mutt_list_free(&queries);
1282   crypto_module_free();
1283   mutt_window_free_all();
1284   mutt_buffer_pool_free();
1285   mutt_envlist_free();
1286   mutt_browser_cleanup();
1287   mutt_opts_free();
1288   mutt_keys_free();
1289   myvarlist_free(&MyVars);
1290   neomutt_free(&NeoMutt);
1291   cs_free(&Config);
1292   return rc;
1293 }