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 * Copyright (C) 2020 R Primus <rprimus@gmail.com>
11 *
12 * @copyright
13 * This program is free software: you can redistribute it and/or modify it under
14 * the terms of the GNU General Public License as published by the Free Software
15 * Foundation, either version 2 of the License, or (at your option) any later
16 * version.
17 *
18 * This program is distributed in the hope that it will be useful, but WITHOUT
19 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
20 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
21 * details.
22 *
23 * You should have received a copy of the GNU General Public License along with
24 * this program. If not, see <http://www.gnu.org/licenses/>.
25 */
26
27 /**
28 * @mainpage Code Docs
29 *
30 * <img style="float: left; padding-right: 0.5em;" src="structs.svg">
31 * <img style="float: left; padding-right: 0.5em;" src="pages.svg">
32 * <img style="float: left; padding-right: 0.5em;" src="globals.svg">
33 * <img style="float: left; padding-right: 0.5em;" src="functions.svg">
34 * <img style="float: left; padding-right: 0.5em;" src="enums.svg">
35 * <img style="float: left; padding-right: 0.5em;" src="members.svg">
36 * <img style="float: left; padding-right: 0.5em;" src="defines.svg">
37 * <br>
38 *
39 * ## Libraries
40 *
41 * [Each library](pages.html) helps to untangle the code by grouping similar
42 * functions and reducing dependencies.
43 *
44 * The goal is that each library is:
45 * - Self-contained (it may rely on other libraries)
46 * - Independently testable (i.e. without using NeoMutt)
47 * - Fully documented
48 * - Robust
49 *
50 * Libraries:
51 * @ref lib_address, @ref lib_alias, @ref lib_attach, @ref lib_autocrypt,
52 * @ref lib_bcache, @ref lib_color, @ref lib_compmbox, @ref lib_compose,
53 * @ref lib_compress, @ref lib_config, @ref lib_conn, @ref lib_core,
54 * @ref lib_email, @ref lib_gui, @ref lib_hcache, @ref lib_helpbar,
55 * @ref lib_history, @ref lib_imap, @ref lib_index, @ref lib_maildir,
56 * @ref lib_mbox, @ref lib_menu, @ref lib_mutt, @ref lib_ncrypt, @ref lib_nntp,
57 * @ref lib_notmuch, @ref lib_pager, @ref lib_pattern, @ref lib_pop,
58 * @ref lib_progress, @ref lib_question, @ref lib_send, @ref lib_sidebar,
59 * @ref lib_store.
60 *
61 * ## Miscellaneous files
62 *
63 * These file form the main body of NeoMutt.
64 *
65 * | File | Description |
66 * | :-------------- | :------------------------- |
67 * | alternates.c | @subpage neo_alternates |
68 * | browser.c | @subpage neo_browser |
69 * | commands.c | @subpage neo_commands |
70 * | command_parse.c | @subpage neo_command_parse |
71 * | complete.c | @subpage neo_complete |
72 * | context.c | @subpage neo_ctx |
73 * | copy.c | @subpage neo_copy |
74 * | dlg_postpone.c | @subpage neo_dlg_postpone |
75 * | editmsg.c | @subpage neo_editmsg |
76 * | enriched.c | @subpage neo_enriched |
77 * | enter.c | @subpage neo_enter |
78 * | flags.c | @subpage neo_flags |
79 * | functions.c | @subpage neo_functions |
80 * | handler.c | @subpage neo_handler |
81 * | hdrline.c | @subpage neo_hdrline |
82 * | help.c | @subpage neo_help |
83 * | hook.c | @subpage neo_hook |
84 * | icommands.c | @subpage neo_icommands |
85 * | init.c | @subpage neo_init |
86 * | keymap.c | @subpage neo_keymap |
87 * | mailcap.c | @subpage neo_mailcap |
88 * | maillist.c | @subpage neo_maillist |
89 * | main.c | @subpage neo_main |
90 * | monitor.c | @subpage neo_monitor |
91 * | muttlib.c | @subpage neo_muttlib |
92 * | mutt_account.c | @subpage neo_mutt_account |
93 * | mutt_body.c | @subpage neo_mutt_body |
94 * | mutt_commands.c | @subpage neo_mutt_commands |
95 * | mutt_config.c | @subpage neo_mutt_config |
96 * | mutt_globals.h | @subpage neo_mutt_globals |
97 * | mutt_header.c | @subpage neo_mutt_header |
98 * | mutt_history.c | @subpage neo_mutt_history |
99 * | mutt_logging.c | @subpage neo_mutt_logging |
100 * | mutt_lua.c | @subpage neo_mutt_lua |
101 * | mutt_mailbox.c | @subpage neo_mutt_mailbox |
102 * | mutt_signal.c | @subpage neo_mutt_signal |
103 * | mutt_socket.c | @subpage neo_mutt_socket |
104 * | mutt_thread.c | @subpage neo_mutt_thread |
105 * | mx.c | @subpage neo_mx |
106 * | myvar.c | @subpage neo_myvar |
107 * | opcodes.c | @subpage neo_opcode |
108 * | options.h | @subpage neo_options |
109 * | postpone.c | @subpage neo_postpone |
110 * | recvcmd.c | @subpage neo_recvcmd |
111 * | remailer.c | @subpage neo_remailer |
112 * | resize.c | @subpage neo_resize |
113 * | rfc3676.c | @subpage neo_rfc3676 |
114 * | score.c | @subpage neo_score |
115 * | sort.c | @subpage neo_sort |
116 * | status.c | @subpage neo_status |
117 * | subjectrx.c | @subpage neo_subjrx |
118 * | system.c | @subpage neo_system |
119 * | version.c | @subpage neo_version |
120 * | wcscasecmp.c | @subpage neo_wcscasecmp |
121 *
122 * ## Building these Docs
123 *
124 * The config for building the docs is in the main source repo.
125 *
126 * Everything possible is turned on in the config file, so you'll need to
127 * install a few dependencies like `dot` from the graphviz package.
128 *
129 * ## Installing the Docs
130 *
131 * These docs aren't in the main website repo -- they weigh in at 100MB.
132 * Instead, they're stored in the [code repo](https://github.com/neomutt/code)
133 */
134
135 /**
136 * @page neo_main Command line processing
137 *
138 * Command line processing
139 */
140
141 #define MAIN_C 1
142 #define GNULIB_defined_setlocale
143
144 #include "config.h"
145 #include <errno.h>
146 #include <getopt.h>
147 #include <limits.h>
148 #include <locale.h>
149 #include <pwd.h>
150 #include <stdbool.h>
151 #include <stdint.h>
152 #include <stdio.h>
153 #include <stdlib.h>
154 #include <string.h>
155 #include <sys/stat.h>
156 #include <unistd.h>
157 #include "mutt/lib.h"
158 #include "address/lib.h"
159 #include "config/lib.h"
160 #include "email/lib.h"
161 #include "core/lib.h"
162 #include "alias/lib.h"
163 #include "conn/lib.h"
164 #include "gui/lib.h"
165 #include "attach/lib.h"
166 #include "color/lib.h"
167 #include "index/lib.h"
168 #include "menu/lib.h"
169 #include "ncrypt/lib.h"
170 #include "question/lib.h"
171 #include "send/lib.h"
172 #include "alternates.h"
173 #include "browser.h"
174 #include "commands.h"
175 #include "context.h"
176 #include "hook.h"
177 #include "init.h"
178 #include "keymap.h"
179 #include "mutt_globals.h"
180 #include "mutt_history.h"
181 #include "mutt_logging.h"
182 #include "mutt_mailbox.h"
183 #include "muttlib.h"
184 #include "mx.h"
185 #include "myvar.h"
186 #include "options.h"
187 #include "protos.h"
188 #include "subjectrx.h"
189 #include "version.h"
190 #ifdef ENABLE_NLS
191 #include <libintl.h>
192 #endif
193 #ifdef USE_IMAP
194 #include "imap/lib.h"
195 #endif
196 #ifdef USE_POP
197 #include "pop/lib.h"
198 #endif
199 #ifdef USE_NNTP
200 #include "nntp/lib.h"
201 #include "nntp/adata.h" // IWYU pragma: keep
202 #include "nntp/mdata.h" // IWYU pragma: keep
203 #endif
204 #ifdef USE_AUTOCRYPT
205 #include "autocrypt/lib.h"
206 #endif
207 #if defined(USE_DEBUG_NOTIFY) || defined(HAVE_LIBUNWIND)
208 #include "debug/lib.h"
209 #endif
210
211 // clang-format off
212 typedef uint8_t CliFlags; ///< Flags for command line options, e.g. #MUTT_CLI_IGNORE
213 #define MUTT_CLI_NO_FLAGS 0 ///< No flags are set
214 #define MUTT_CLI_IGNORE (1 << 0) ///< -z Open first mailbox if it has mail
215 #define MUTT_CLI_MAILBOX (1 << 1) ///< -Z Open first mailbox if is has new mail
216 #define MUTT_CLI_NOSYSRC (1 << 2) ///< -n Do not read the system-wide config file
217 #define MUTT_CLI_RO (1 << 3) ///< -R Open mailbox in read-only mode
218 #define MUTT_CLI_SELECT (1 << 4) ///< -y Start with a list of all mailboxes
219 #ifdef USE_NNTP
220 #define MUTT_CLI_NEWS (1 << 5) ///< -g/-G Start with a list of all newsgroups
221 #endif
222 // clang-format on
223
224 /**
225 * reset_tilde - Temporary measure
226 * @param cs Config Set
227 */
reset_tilde(struct ConfigSet * cs)228 static void reset_tilde(struct ConfigSet *cs)
229 {
230 static const char *names[] = { "folder", "mbox", "postponed", "record" };
231
232 struct Buffer value = mutt_buffer_make(256);
233 for (size_t i = 0; i < mutt_array_size(names); i++)
234 {
235 struct HashElem *he = cs_get_elem(cs, names[i]);
236 if (!he)
237 continue;
238 mutt_buffer_reset(&value);
239 cs_he_initial_get(cs, he, &value);
240 mutt_buffer_expand_path_regex(&value, false);
241 cs_he_initial_set(cs, he, value.data, NULL);
242 cs_he_reset(cs, he, NULL);
243 }
244 mutt_buffer_dealloc(&value);
245 }
246
247 /**
248 * mutt_exit - Leave NeoMutt NOW
249 * @param code Value to return to the calling environment
250 */
mutt_exit(int code)251 void mutt_exit(int code)
252 {
253 mutt_endwin();
254 #ifdef HAVE_LIBUNWIND
255 if (code != 0)
256 show_backtrace();
257 #endif
258 exit(code);
259 }
260
261 /**
262 * usage - Display NeoMutt command line
263 * @retval true Text displayed
264 */
usage(void)265 static bool usage(void)
266 {
267 puts(mutt_make_version());
268
269 // clang-format off
270 /* L10N: Try to limit to 80 columns */
271 puts(_("usage:"));
272 puts(_(" neomutt [-Enx] [-e <command>] [-F <config>] [-H <draft>] [-i <include>]\n"
273 " [-b <address>] [-c <address>] [-s <subject>] [-a <file> [...] --]\n"
274 " <address> [...]"));
275 puts(_(" neomutt [-nx] [-e <command>] [-F <config>] [-b <address>] [-c <address>]\n"
276 " [-s <subject>] [-a <file> [...] --] <address> [...] < message"));
277 puts(_(" neomutt [-nRy] [-e <command>] [-F <config>] [-f <mailbox>] [-m <type>]"));
278 puts(_(" neomutt [-n] [-e <command>] [-F <config>] -A <alias>"));
279 puts(_(" neomutt [-n] [-e <command>] [-F <config>] -B"));
280 puts(_(" neomutt [-n] [-e <command>] [-F <config>] -D [-S] [-O]"));
281 puts(_(" neomutt [-n] [-e <command>] [-F <config>] -d <level> -l <file>"));
282 puts(_(" neomutt [-n] [-e <command>] [-F <config>] -G"));
283 puts(_(" neomutt [-n] [-e <command>] [-F <config>] -g <server>"));
284 puts(_(" neomutt [-n] [-e <command>] [-F <config>] -p"));
285 puts(_(" neomutt [-n] [-e <command>] [-F <config>] -Q <variable> [-O]"));
286 puts(_(" neomutt [-n] [-e <command>] [-F <config>] -Z"));
287 puts(_(" neomutt [-n] [-e <command>] [-F <config>] -z [-f <mailbox>]"));
288 puts(_(" neomutt -v[v]\n"));
289
290 /* L10N: Try to limit to 80 columns. If more space is needed add an indented line */
291 puts(_("options:"));
292 puts(_(" -- Special argument forces NeoMutt to stop option parsing and treat\n"
293 " remaining arguments as addresses even if they start with a dash"));
294 puts(_(" -A <alias> Print an expanded version of the given alias to stdout and exit"));
295 puts(_(" -a <file> Attach one or more files to a message (must be the last option)\n"
296 " Add any addresses after the '--' argument"));
297 puts(_(" -B Run in batch mode (do not start the ncurses UI)"));
298 puts(_(" -b <address> Specify a blind carbon copy (Bcc) recipient"));
299 puts(_(" -c <address> Specify a carbon copy (Cc) recipient"));
300 puts(_(" -D Dump all config variables as 'name=value' pairs to stdout"));
301 puts(_(" -D -O Like -D, but show one-liner documentation"));
302 puts(_(" -D -S Like -D, but hide the value of sensitive variables"));
303 puts(_(" -d <level> Log debugging output to a file (default is \"~/.neomuttdebug0\")\n"
304 " The level can range from 1-5 and affects verbosity"));
305 puts(_(" -E Edit draft (-H) or include (-i) file during message composition"));
306 puts(_(" -e <command> Specify a command to be run after reading the config files"));
307 puts(_(" -F <config> Specify an alternative initialization file to read"));
308 puts(_(" -f <mailbox> Specify a mailbox (as defined with 'mailboxes' command) to load"));
309 puts(_(" -G Start NeoMutt with a listing of subscribed newsgroups"));
310 puts(_(" -g <server> Like -G, but start at specified news server"));
311 puts(_(" -H <draft> Specify a draft file with header and body for message composing"));
312 puts(_(" -h Print this help message and exit"));
313 puts(_(" -i <include> Specify an include file to be embedded in the body of a message"));
314 puts(_(" -l <file> Specify a file for debugging output (default \"~/.neomuttdebug0\")"));
315 puts(_(" -m <type> Specify a default mailbox format type for newly created folders\n"
316 " The type is either MH, MMDF, Maildir or mbox (case-insensitive)"));
317 puts(_(" -n Do not read the system-wide configuration file"));
318 puts(_(" -p Resume a prior postponed message, if any"));
319 puts(_(" -Q <variable> Query a configuration variable and print its value to stdout\n"
320 " (after the config has been read and any commands executed)\n"
321 " Add -O for one-liner documentation"));
322 puts(_(" -R Open mailbox in read-only mode"));
323 puts(_(" -s <subject> Specify a subject (must be enclosed in quotes if it has spaces)"));
324 puts(_(" -v Print the NeoMutt version and compile-time definitions and exit"));
325 puts(_(" -vv Print the NeoMutt license and copyright information and exit"));
326 puts(_(" -y Start NeoMutt with a listing of all defined mailboxes"));
327 puts(_(" -Z Open the first mailbox with new message or exit immediately with\n"
328 " exit code 1 if none is found in all defined mailboxes"));
329 puts(_(" -z Open the first or specified (-f) mailbox if it holds any message\n"
330 " or exit immediately with exit code 1 otherwise"));
331 // clang-format on
332
333 fflush(stdout);
334 return !ferror(stdout);
335 }
336
337 /**
338 * start_curses - Start the Curses UI
339 * @retval 0 Success
340 * @retval 1 Failure
341 */
start_curses(void)342 static int start_curses(void)
343 {
344 km_init(); /* must come before mutt_init */
345
346 /* should come before initscr() so that ncurses 4.2 doesn't try to install
347 * its own SIGWINCH handler */
348 mutt_signal_init();
349
350 if (!initscr())
351 {
352 mutt_error(_("Error initializing terminal"));
353 return 1;
354 }
355 mutt_signal_init();
356 mutt_colors_init();
357 keypad(stdscr, true);
358 cbreak();
359 noecho();
360 nonl();
361 typeahead(-1); /* simulate smooth scrolling */
362 meta(stdscr, true);
363 init_extended_keys();
364 /* Now that curses is set up, we drop back to normal screen mode.
365 * This simplifies displaying error messages to the user.
366 * The first call to refresh() will swap us back to curses screen mode. */
367 endwin();
368 return 0;
369 }
370
371 /**
372 * init_locale - Initialise the Locale/NLS settings
373 */
init_locale(void)374 static void init_locale(void)
375 {
376 setlocale(LC_ALL, "");
377
378 #ifdef ENABLE_NLS
379 const char *domdir = mutt_str_getenv("TEXTDOMAINDIR");
380 if (domdir)
381 bindtextdomain(PACKAGE, domdir);
382 else
383 bindtextdomain(PACKAGE, MUTTLOCALEDIR);
384 textdomain(PACKAGE);
385 #endif
386 #ifndef LOCALES_HACK
387 /* Do we have a locale definition? */
388 if (mutt_str_getenv("LC_ALL") || mutt_str_getenv("LANG") || mutt_str_getenv("LC_CTYPE"))
389 {
390 OptLocales = true;
391 }
392 #endif
393 }
394
395 /**
396 * get_user_info - Find the user's name, home and shell
397 * @param cs Config Set
398 * @retval true Success
399 *
400 * Find the login name, real name, home directory and shell.
401 */
get_user_info(struct ConfigSet * cs)402 static bool get_user_info(struct ConfigSet *cs)
403 {
404 const char *shell = mutt_str_getenv("SHELL");
405 if (shell)
406 cs_str_initial_set(cs, "shell", shell, NULL);
407
408 /* Get some information about the user */
409 struct passwd *pw = getpwuid(getuid());
410 if (pw)
411 {
412 if (!Username)
413 Username = mutt_str_dup(pw->pw_name);
414 if (!HomeDir)
415 HomeDir = mutt_str_dup(pw->pw_dir);
416 if (!shell)
417 cs_str_initial_set(cs, "shell", pw->pw_shell, NULL);
418 }
419
420 if (!Username)
421 {
422 mutt_error(_("unable to determine username"));
423 return false; // TEST05: neomutt (unset $USER, delete user from /etc/passwd)
424 }
425
426 if (!HomeDir)
427 {
428 mutt_error(_("unable to determine home directory"));
429 return false; // TEST06: neomutt (unset $HOME, delete user from /etc/passwd)
430 }
431
432 cs_str_reset(cs, "shell", NULL);
433 return true;
434 }
435
436 /**
437 * log_translation - Log the translation being used
438 *
439 * Read the header info from the translation file.
440 *
441 * @note Call bindtextdomain() first
442 */
log_translation(void)443 static void log_translation(void)
444 {
445 const char *header = ""; // Do not merge these two lines
446 header = _(header); // otherwise the .po files will end up badly ordered
447 const char *lang = strcasestr(header, "Language:");
448 int len = 64;
449 if (lang)
450 {
451 lang += 9; // skip label
452 SKIPWS(lang);
453 char *nl = strchr(lang, '\n');
454 if (nl)
455 len = (nl - lang);
456 }
457 else
458 {
459 lang = "NONE";
460 }
461
462 mutt_debug(LL_DEBUG1, "Translation: %.*s\n", len, lang);
463 }
464
465 /**
466 * main - Start NeoMutt
467 * @param argc Number of command line arguments
468 * @param argv List of command line arguments
469 * @param envp Copy of the environment
470 * @retval 0 Success
471 * @retval 1 Error
472 */
main(int argc,char * argv[],char * envp[])473 int main(int argc, char *argv[], char *envp[])
474 {
475 char *subject = NULL;
476 char *include_file = NULL;
477 char *draft_file = NULL;
478 char *new_type = NULL;
479 char *dlevel = NULL;
480 char *dfile = NULL;
481 #ifdef USE_NNTP
482 const char *cli_nntp = NULL;
483 #endif
484 struct Email *e = NULL;
485 struct ListHead attach = STAILQ_HEAD_INITIALIZER(attach);
486 struct ListHead commands = STAILQ_HEAD_INITIALIZER(commands);
487 struct ListHead queries = STAILQ_HEAD_INITIALIZER(queries);
488 struct ListHead alias_queries = STAILQ_HEAD_INITIALIZER(alias_queries);
489 struct ListHead cc_list = STAILQ_HEAD_INITIALIZER(cc_list);
490 struct ListHead bcc_list = STAILQ_HEAD_INITIALIZER(bcc_list);
491 SendFlags sendflags = SEND_NO_FLAGS;
492 CliFlags flags = MUTT_CLI_NO_FLAGS;
493 int version = 0;
494 int i;
495 bool explicit_folder = false;
496 bool dump_variables = false;
497 bool one_liner = false;
498 bool hide_sensitive = false;
499 bool batch_mode = false;
500 bool edit_infile = false;
501 #ifdef USE_DEBUG_PARSE_TEST
502 bool test_config = false;
503 #endif
504 int double_dash = argc, nargc = 1;
505 int rc = 1;
506 bool repeat_error = false;
507 struct Buffer folder = mutt_buffer_make(0);
508 struct Buffer expanded_infile = mutt_buffer_make(0);
509 struct Buffer tempfile = mutt_buffer_make(0);
510 struct ConfigSet *cs = NULL;
511
512 MuttLogger = log_disp_terminal;
513
514 /* sanity check against stupid administrators */
515 if (getegid() != getgid())
516 {
517 mutt_error("%s: I don't want to run with privileges!", argv[0]);
518 goto main_exit; // TEST01: neomutt (as root, chgrp mail neomutt; chmod +s neomutt)
519 }
520
521 init_locale();
522
523 umask(077);
524
525 mutt_envlist_init(envp);
526 for (optind = 1; optind < double_dash;)
527 {
528 /* We're getopt'ing POSIXLY, so we'll be here every time getopt()
529 * encounters a non-option. That could be a file to attach
530 * (all non-options between -a and --) or it could be an address
531 * (which gets collapsed to the front of argv). */
532 for (; optind < argc; optind++)
533 {
534 if ((argv[optind][0] == '-') && (argv[optind][1] != '\0'))
535 {
536 if ((argv[optind][1] == '-') && (argv[optind][2] == '\0'))
537 double_dash = optind; /* quit outer loop after getopt */
538 break; /* drop through to getopt */
539 }
540
541 /* non-option, either an attachment or address */
542 if (!STAILQ_EMPTY(&attach))
543 mutt_list_insert_tail(&attach, mutt_str_dup(argv[optind]));
544 else
545 argv[nargc++] = argv[optind];
546 }
547
548 /* USE_NNTP 'g:G' */
549 i = getopt(argc, argv, "+A:a:Bb:F:f:c:Dd:l:Ee:g:GH:i:hm:nOpQ:RSs:TvxyzZ");
550 if (i != EOF)
551 {
552 switch (i)
553 {
554 case 'A':
555 mutt_list_insert_tail(&alias_queries, mutt_str_dup(optarg));
556 break;
557 case 'a':
558 mutt_list_insert_tail(&attach, mutt_str_dup(optarg));
559 break;
560 case 'B':
561 batch_mode = true;
562 break;
563 case 'b':
564 mutt_list_insert_tail(&bcc_list, mutt_str_dup(optarg));
565 break;
566 case 'c':
567 mutt_list_insert_tail(&cc_list, mutt_str_dup(optarg));
568 break;
569 case 'D':
570 dump_variables = true;
571 break;
572 case 'd':
573 dlevel = optarg;
574 break;
575 case 'E':
576 edit_infile = true;
577 break;
578 case 'e':
579 mutt_list_insert_tail(&commands, mutt_str_dup(optarg));
580 break;
581 case 'F':
582 mutt_list_insert_tail(&Muttrc, mutt_str_dup(optarg));
583 break;
584 case 'f':
585 mutt_buffer_strcpy(&folder, optarg);
586 explicit_folder = true;
587 break;
588 #ifdef USE_NNTP
589 case 'g': /* Specify a news server */
590 cli_nntp = optarg;
591 /* fallthrough */
592 case 'G': /* List of newsgroups */
593 flags |= MUTT_CLI_SELECT | MUTT_CLI_NEWS;
594 break;
595 #endif
596 case 'H':
597 draft_file = optarg;
598 break;
599 case 'i':
600 include_file = optarg;
601 break;
602 case 'l':
603 dfile = optarg;
604 break;
605 case 'm':
606 new_type = optarg;
607 break;
608 case 'n':
609 flags |= MUTT_CLI_NOSYSRC;
610 break;
611 case 'O':
612 one_liner = true;
613 break;
614 case 'p':
615 sendflags |= SEND_POSTPONED;
616 break;
617 case 'Q':
618 mutt_list_insert_tail(&queries, mutt_str_dup(optarg));
619 break;
620 case 'R':
621 flags |= MUTT_CLI_RO; /* read-only mode */
622 break;
623 case 'S':
624 hide_sensitive = true;
625 break;
626 case 's':
627 subject = optarg;
628 break;
629 #ifdef USE_DEBUG_PARSE_TEST
630 case 'T':
631 test_config = true;
632 break;
633 #endif
634 case 'v':
635 version++;
636 break;
637 case 'y': /* My special hack mode */
638 flags |= MUTT_CLI_SELECT;
639 break;
640 case 'Z':
641 flags |= MUTT_CLI_MAILBOX | MUTT_CLI_IGNORE;
642 break;
643 case 'z':
644 flags |= MUTT_CLI_IGNORE;
645 break;
646 default:
647 OptNoCurses = true;
648 if (usage())
649 goto main_ok; // TEST03: neomutt -9
650 else
651 goto main_curses;
652 }
653 }
654 }
655
656 /* collapse remaining argv */
657 while (optind < argc)
658 argv[nargc++] = argv[optind++];
659 optind = 1;
660 argc = nargc;
661
662 if (version > 0)
663 {
664 log_queue_flush(log_disp_terminal);
665 bool done;
666 if (version == 1)
667 done = print_version(stdout);
668 else
669 done = print_copyright();
670 OptNoCurses = true;
671 if (done)
672 goto main_ok; // TEST04: neomutt -v
673 else
674 goto main_curses;
675 }
676
677 mutt_str_replace(&Username, mutt_str_getenv("USER"));
678 mutt_str_replace(&HomeDir, mutt_str_getenv("HOME"));
679
680 cs = cs_new(500);
681 if (!cs)
682 goto main_curses;
683
684 NeoMutt = neomutt_new(cs);
685 init_config(cs);
686 subjrx_init();
687 attach_init();
688 alternates_init();
689
690 #ifdef USE_DEBUG_NOTIFY
691 notify_observer_add(NeoMutt->notify, NT_ALL, debug_all_observer, NULL);
692 #endif
693
694 if (!get_user_info(cs))
695 goto main_exit;
696
697 #ifdef USE_DEBUG_PARSE_TEST
698 if (test_config)
699 {
700 cs_str_initial_set(cs, "from", "rich@flatcap.org", NULL);
701 cs_str_reset(cs, "from", NULL);
702 myvar_set("my_var", "foo");
703 test_parse_set();
704 goto main_ok;
705 }
706 #endif
707
708 reset_tilde(cs);
709
710 if (dfile)
711 {
712 cs_str_initial_set(cs, "debug_file", dfile, NULL);
713 cs_str_reset(cs, "debug_file", NULL);
714 }
715
716 if (dlevel)
717 {
718 short num = 0;
719 if ((mutt_str_atos(dlevel, &num) < 0) || (num < LL_MESSAGE) || (num >= LL_MAX))
720 {
721 mutt_error(_("Error: value '%s' is invalid for -d"), dlevel);
722 goto main_exit; // TEST07: neomutt -d xyz
723 }
724 cs_str_initial_set(cs, "debug_level", dlevel, NULL);
725 cs_str_reset(cs, "debug_level", NULL);
726 }
727
728 mutt_log_prep();
729 if (dlevel)
730 mutt_log_start();
731
732 MuttLogger = log_disp_queue;
733
734 log_translation();
735
736 if (!STAILQ_EMPTY(&cc_list) || !STAILQ_EMPTY(&bcc_list))
737 {
738 e = email_new();
739 e->env = mutt_env_new();
740
741 struct ListNode *np = NULL;
742 STAILQ_FOREACH(np, &bcc_list, entries)
743 {
744 mutt_addrlist_parse(&e->env->bcc, np->data);
745 }
746
747 STAILQ_FOREACH(np, &cc_list, entries)
748 {
749 mutt_addrlist_parse(&e->env->cc, np->data);
750 }
751
752 mutt_list_free(&bcc_list);
753 mutt_list_free(&cc_list);
754 }
755
756 /* Check for a batch send. */
757 if (!isatty(0) || !STAILQ_EMPTY(&queries) || !STAILQ_EMPTY(&alias_queries) ||
758 dump_variables || batch_mode)
759 {
760 OptNoCurses = true;
761 sendflags = SEND_BATCH;
762 MuttLogger = log_disp_terminal;
763 log_queue_flush(log_disp_terminal);
764 }
765
766 /* Check to make sure stdout is available in curses mode. */
767 if (!OptNoCurses && !isatty(1))
768 goto main_curses;
769
770 /* Always create the mutt_windows because batch mode has some shared code
771 * paths that end up referencing them. */
772 rootwin_new();
773
774 /* This must come before mutt_init() because curses needs to be started
775 * before calling the init_pair() function to set the color scheme. */
776 if (!OptNoCurses)
777 {
778 int crc = start_curses();
779 if (crc != 0)
780 goto main_curses; // TEST08: can't test -- fake term?
781
782 /* check whether terminal status is supported (must follow curses init) */
783 TsSupported = mutt_ts_capability();
784 rootwin_set_size(COLS, LINES);
785 }
786
787 /* set defaults and read init files */
788 int rc2 = mutt_init(cs, flags & MUTT_CLI_NOSYSRC, &commands);
789 mutt_list_free(&commands);
790 if (rc2 != 0)
791 goto main_curses;
792
793 mutt_init_abort_key();
794
795 /* The command line overrides the config */
796 if (dlevel)
797 cs_str_reset(cs, "debug_level", NULL);
798 if (dfile)
799 cs_str_reset(cs, "debug_file", NULL);
800
801 if (mutt_log_start() < 0)
802 {
803 mutt_perror("log file");
804 goto main_exit;
805 }
806
807 #ifdef USE_NNTP
808 {
809 /* "$news_server" precedence: command line, config file, environment, system file */
810 if (!cli_nntp)
811 cli_nntp = cs_subset_string(NeoMutt->sub, "news_server");
812
813 if (!cli_nntp)
814 cli_nntp = mutt_str_getenv("NNTPSERVER");
815
816 char buf[1024] = { 0 };
817 if (!cli_nntp)
818 cli_nntp = mutt_file_read_keyword(SYSCONFDIR "/nntpserver", buf, sizeof(buf));
819
820 if (cli_nntp)
821 {
822 cs_str_initial_set(cs, "news_server", cli_nntp, NULL);
823 cs_str_reset(cs, "news_server", NULL);
824 }
825 }
826 #endif
827
828 /* Initialize crypto backends. */
829 crypt_init();
830
831 if (new_type)
832 {
833 struct Buffer err = mutt_buffer_make(0);
834 int r = cs_str_initial_set(cs, "mbox_type", new_type, &err);
835 if (CSR_RESULT(r) != CSR_SUCCESS)
836 {
837 mutt_error(err.data);
838 mutt_buffer_dealloc(&err);
839 goto main_curses;
840 }
841 cs_str_reset(cs, "mbox_type", NULL);
842 }
843
844 if (!STAILQ_EMPTY(&queries))
845 {
846 rc = mutt_query_variables(&queries, one_liner);
847 goto main_curses;
848 }
849
850 if (dump_variables)
851 {
852 ConfigDumpFlags cdflags = CS_DUMP_NO_FLAGS;
853 if (hide_sensitive)
854 cdflags |= CS_DUMP_HIDE_SENSITIVE;
855 if (one_liner)
856 cdflags |= CS_DUMP_SHOW_DOCS;
857 dump_config(cs, cdflags, stdout);
858 goto main_ok; // TEST18: neomutt -D
859 }
860
861 if (!STAILQ_EMPTY(&alias_queries))
862 {
863 rc = 0;
864 for (; optind < argc; optind++)
865 mutt_list_insert_tail(&alias_queries, mutt_str_dup(argv[optind]));
866 struct ListNode *np = NULL;
867 STAILQ_FOREACH(np, &alias_queries, entries)
868 {
869 struct AddressList *al = alias_lookup(np->data);
870 if (al)
871 {
872 /* output in machine-readable form */
873 mutt_addrlist_to_intl(al, NULL);
874 mutt_addrlist_write_file(al, stdout, 0, false);
875 }
876 else
877 {
878 rc = 1;
879 printf("%s\n", np->data); // TEST19: neomutt -A unknown
880 }
881 }
882 mutt_list_free(&alias_queries);
883 goto main_curses; // TEST20: neomutt -A alias
884 }
885
886 if (!OptNoCurses)
887 {
888 mutt_curses_set_color_by_id(MT_COLOR_NORMAL);
889 clear();
890 MuttLogger = log_disp_curses;
891 log_queue_flush(log_disp_curses);
892 log_queue_set_max_size(100);
893 }
894
895 #ifdef USE_AUTOCRYPT
896 /* Initialize autocrypt after curses messages are working,
897 * because of the initial account setup screens. */
898 const bool c_autocrypt = cs_subset_bool(NeoMutt->sub, "autocrypt");
899 if (c_autocrypt)
900 mutt_autocrypt_init(!(sendflags & SEND_BATCH));
901 #endif
902
903 /* Create the `$folder` directory if it doesn't exist. */
904 const char *const c_folder = cs_subset_string(NeoMutt->sub, "folder");
905 if (!OptNoCurses && c_folder)
906 {
907 struct stat st = { 0 };
908 struct Buffer *fpath = mutt_buffer_pool_get();
909
910 mutt_buffer_strcpy(fpath, c_folder);
911 mutt_buffer_expand_path(fpath);
912 bool skip = false;
913 #ifdef USE_IMAP
914 /* we're not connected yet - skip mail folder creation */
915 skip |= (imap_path_probe(mutt_buffer_string(fpath), NULL) == MUTT_IMAP);
916 #endif
917 #ifdef USE_POP
918 skip |= (pop_path_probe(mutt_buffer_string(fpath), NULL) == MUTT_POP);
919 #endif
920 #ifdef USE_NNTP
921 skip |= (nntp_path_probe(mutt_buffer_string(fpath), NULL) == MUTT_NNTP);
922 #endif
923 if (!skip && (stat(mutt_buffer_string(fpath), &st) == -1) && (errno == ENOENT))
924 {
925 char msg2[256];
926 snprintf(msg2, sizeof(msg2), _("%s does not exist. Create it?"), c_folder);
927 if (mutt_yesorno(msg2, MUTT_YES) == MUTT_YES)
928 {
929 if ((mkdir(mutt_buffer_string(fpath), 0700) == -1) && (errno != EEXIST))
930 mutt_error(_("Can't create %s: %s"), c_folder, strerror(errno)); // TEST21: neomutt -n -F /dev/null (and ~/Mail doesn't exist)
931 }
932 }
933 mutt_buffer_pool_release(&fpath);
934 }
935
936 if (batch_mode)
937 {
938 goto main_ok; // TEST22: neomutt -B
939 }
940
941 notify_observer_add(NeoMutt->notify, NT_CONFIG, main_hist_observer, NULL);
942 notify_observer_add(NeoMutt->notify, NT_CONFIG, main_log_observer, NULL);
943 notify_observer_add(NeoMutt->notify, NT_CONFIG, main_config_observer, NULL);
944
945 if (sendflags & SEND_POSTPONED)
946 {
947 if (!OptNoCurses)
948 mutt_flushinp();
949 if (mutt_send_message(SEND_POSTPONED, NULL, NULL, NULL, NULL, NeoMutt->sub) == 0)
950 rc = 0;
951 // TEST23: neomutt -p (postponed message, cancel)
952 // TEST24: neomutt -p (no postponed message)
953 log_queue_empty();
954 repeat_error = true;
955 goto main_curses;
956 }
957 else if (subject || e || draft_file || include_file ||
958 !STAILQ_EMPTY(&attach) || (optind < argc))
959 {
960 FILE *fp_in = NULL;
961 FILE *fp_out = NULL;
962 char *infile = NULL;
963 char *bodytext = NULL;
964 const char *bodyfile = NULL;
965 int rv = 0;
966
967 if (!OptNoCurses)
968 mutt_flushinp();
969
970 if (!e)
971 e = email_new();
972 if (!e->env)
973 e->env = mutt_env_new();
974
975 for (i = optind; i < argc; i++)
976 {
977 if (url_check_scheme(argv[i]) == U_MAILTO)
978 {
979 if (!mutt_parse_mailto(e->env, &bodytext, argv[i]))
980 {
981 mutt_error(_("Failed to parse mailto: link"));
982 email_free(&e);
983 goto main_curses; // TEST25: neomutt mailto:?
984 }
985 }
986 else
987 mutt_addrlist_parse(&e->env->to, argv[i]);
988 }
989
990 const bool c_auto_edit = cs_subset_bool(NeoMutt->sub, "auto_edit");
991 if (!draft_file && c_auto_edit && TAILQ_EMPTY(&e->env->to) &&
992 TAILQ_EMPTY(&e->env->cc))
993 {
994 mutt_error(_("No recipients specified"));
995 email_free(&e);
996 goto main_curses; // TEST26: neomutt -s test (with auto_edit=yes)
997 }
998
999 if (subject)
1000 e->env->subject = mutt_str_dup(subject);
1001
1002 if (draft_file)
1003 {
1004 infile = draft_file;
1005 include_file = NULL;
1006 }
1007 else if (include_file)
1008 infile = include_file;
1009 else
1010 edit_infile = false;
1011
1012 if (infile || bodytext)
1013 {
1014 /* Prepare fp_in and expanded_infile. */
1015 if (infile)
1016 {
1017 if (mutt_str_equal("-", infile))
1018 {
1019 if (edit_infile)
1020 {
1021 mutt_error(_("Can't use -E flag with stdin"));
1022 email_free(&e);
1023 goto main_curses; // TEST27: neomutt -E -H -
1024 }
1025 fp_in = stdin;
1026 }
1027 else
1028 {
1029 mutt_buffer_strcpy(&expanded_infile, infile);
1030 mutt_buffer_expand_path(&expanded_infile);
1031 fp_in = fopen(mutt_buffer_string(&expanded_infile), "r");
1032 if (!fp_in)
1033 {
1034 mutt_perror(mutt_buffer_string(&expanded_infile));
1035 email_free(&e);
1036 goto main_curses; // TEST28: neomutt -E -H missing
1037 }
1038 }
1039 }
1040
1041 /* Copy input to a tempfile, and re-point fp_in to the tempfile.
1042 * Note: stdin is always copied to a tempfile, ensuring draft_file
1043 * can stat and get the correct st_size below. */
1044 if (!edit_infile)
1045 {
1046 mutt_buffer_mktemp(&tempfile);
1047
1048 fp_out = mutt_file_fopen(mutt_buffer_string(&tempfile), "w");
1049 if (!fp_out)
1050 {
1051 mutt_file_fclose(&fp_in);
1052 mutt_perror(mutt_buffer_string(&tempfile));
1053 email_free(&e);
1054 goto main_curses; // TEST29: neomutt -H existing-file (where tmpdir=/path/to/FILE blocking tmpdir)
1055 }
1056 if (fp_in)
1057 {
1058 mutt_file_copy_stream(fp_in, fp_out);
1059 if (fp_in != stdin)
1060 mutt_file_fclose(&fp_in);
1061 }
1062 else if (bodytext)
1063 fputs(bodytext, fp_out);
1064 mutt_file_fclose(&fp_out);
1065
1066 fp_in = fopen(mutt_buffer_string(&tempfile), "r");
1067 if (!fp_in)
1068 {
1069 mutt_perror(mutt_buffer_string(&tempfile));
1070 email_free(&e);
1071 goto main_curses; // TEST30: can't test
1072 }
1073 }
1074 /* If editing the infile, keep it around afterwards so
1075 * it doesn't get unlinked, and we can rebuild the draft_file */
1076 else
1077 sendflags |= SEND_NO_FREE_HEADER;
1078
1079 /* Parse the draft_file into the full Email/Body structure.
1080 * Set SEND_DRAFT_FILE so mutt_send_message doesn't overwrite
1081 * our e->body. */
1082 if (draft_file)
1083 {
1084 struct Envelope *opts_env = e->env;
1085 struct stat st = { 0 };
1086
1087 sendflags |= SEND_DRAFT_FILE;
1088
1089 /* Set up a tmp Email with just enough information so that
1090 * mutt_prepare_template() can parse the message in fp_in. */
1091 struct Email *e_tmp = email_new();
1092 e_tmp->offset = 0;
1093 e_tmp->body = mutt_body_new();
1094 if (fstat(fileno(fp_in), &st) != 0)
1095 {
1096 mutt_perror(draft_file);
1097 email_free(&e);
1098 email_free(&e_tmp);
1099 goto main_curses; // TEST31: can't test
1100 }
1101 e_tmp->body->length = st.st_size;
1102
1103 if (mutt_prepare_template(fp_in, NULL, e, e_tmp, false) < 0)
1104 {
1105 mutt_error(_("Can't parse message template: %s"), draft_file);
1106 email_free(&e);
1107 email_free(&e_tmp);
1108 goto main_curses;
1109 }
1110
1111 /* Scan for neomutt header to set `$resume_draft_files` */
1112 struct ListNode *np = NULL, *tmp = NULL;
1113 STAILQ_FOREACH_SAFE(np, &e->env->userhdrs, entries, tmp)
1114 {
1115 if (mutt_istr_startswith(np->data, "X-Mutt-Resume-Draft:"))
1116 {
1117 const bool c_resume_edited_draft_files =
1118 cs_subset_bool(NeoMutt->sub, "resume_edited_draft_files");
1119 if (c_resume_edited_draft_files)
1120 cs_str_native_set(cs, "resume_draft_files", true, NULL);
1121
1122 STAILQ_REMOVE(&e->env->userhdrs, np, ListNode, entries);
1123 FREE(&np->data);
1124 FREE(&np);
1125 }
1126 }
1127
1128 mutt_addrlist_copy(&e->env->to, &opts_env->to, false);
1129 mutt_addrlist_copy(&e->env->cc, &opts_env->cc, false);
1130 mutt_addrlist_copy(&e->env->bcc, &opts_env->bcc, false);
1131 if (opts_env->subject)
1132 mutt_str_replace(&e->env->subject, opts_env->subject);
1133
1134 mutt_env_free(&opts_env);
1135 email_free(&e_tmp);
1136 }
1137 /* Editing the include_file: pass it directly in.
1138 * Note that SEND_NO_FREE_HEADER is set above so it isn't unlinked. */
1139 else if (edit_infile)
1140 bodyfile = mutt_buffer_string(&expanded_infile);
1141 // For bodytext and unedited include_file: use the tempfile.
1142 else
1143 bodyfile = mutt_buffer_string(&tempfile);
1144
1145 mutt_file_fclose(&fp_in);
1146 }
1147
1148 FREE(&bodytext);
1149
1150 if (!STAILQ_EMPTY(&attach))
1151 {
1152 struct Body *b = e->body;
1153
1154 while (b && b->next)
1155 b = b->next;
1156
1157 struct ListNode *np = NULL;
1158 STAILQ_FOREACH(np, &attach, entries)
1159 {
1160 if (b)
1161 {
1162 b->next = mutt_make_file_attach(np->data, NeoMutt->sub);
1163 b = b->next;
1164 }
1165 else
1166 {
1167 b = mutt_make_file_attach(np->data, NeoMutt->sub);
1168 e->body = b;
1169 }
1170 if (!b)
1171 {
1172 mutt_error(_("%s: unable to attach file"), np->data);
1173 mutt_list_free(&attach);
1174 email_free(&e);
1175 goto main_curses; // TEST32: neomutt john@example.com -a missing
1176 }
1177 }
1178 mutt_list_free(&attach);
1179 }
1180
1181 rv = mutt_send_message(sendflags, e, bodyfile, NULL, NULL, NeoMutt->sub);
1182 /* We WANT the "Mail sent." and any possible, later error */
1183 log_queue_empty();
1184 if (ErrorBufMessage)
1185 mutt_message("%s", ErrorBuf);
1186
1187 if (edit_infile)
1188 {
1189 if (include_file)
1190 e->body->unlink = false;
1191 else if (draft_file)
1192 {
1193 if (truncate(mutt_buffer_string(&expanded_infile), 0) == -1)
1194 {
1195 mutt_perror(mutt_buffer_string(&expanded_infile));
1196 email_free(&e);
1197 goto main_curses; // TEST33: neomutt -H read-only -s test john@example.com -E
1198 }
1199 fp_out = mutt_file_fopen(mutt_buffer_string(&expanded_infile), "a");
1200 if (!fp_out)
1201 {
1202 mutt_perror(mutt_buffer_string(&expanded_infile));
1203 email_free(&e);
1204 goto main_curses; // TEST34: can't test
1205 }
1206
1207 /* If the message was sent or postponed, these will already
1208 * have been done. */
1209 if (rv < 0)
1210 {
1211 if (e->body->next)
1212 e->body = mutt_make_multipart(e->body);
1213 mutt_encode_descriptions(e->body, true, NeoMutt->sub);
1214 mutt_prepare_envelope(e->env, false, NeoMutt->sub);
1215 mutt_env_to_intl(e->env, NULL, NULL);
1216 }
1217
1218 const bool c_crypt_protected_headers_read =
1219 cs_subset_bool(NeoMutt->sub, "crypt_protected_headers_read");
1220 mutt_rfc822_write_header(
1221 fp_out, e->env, e->body, MUTT_WRITE_HEADER_POSTPONE, false,
1222 c_crypt_protected_headers_read && mutt_should_hide_protected_subject(e),
1223 NeoMutt->sub);
1224 const bool c_resume_edited_draft_files =
1225 cs_subset_bool(NeoMutt->sub, "resume_edited_draft_files");
1226 if (c_resume_edited_draft_files)
1227 fprintf(fp_out, "X-Mutt-Resume-Draft: 1\n");
1228 fputc('\n', fp_out);
1229 if ((mutt_write_mime_body(e->body, fp_out, NeoMutt->sub) == -1))
1230 {
1231 mutt_file_fclose(&fp_out);
1232 email_free(&e);
1233 goto main_curses; // TEST35: can't test
1234 }
1235 mutt_file_fclose(&fp_out);
1236 }
1237
1238 email_free(&e);
1239 }
1240
1241 /* !edit_infile && draft_file will leave the tempfile around */
1242 if (!mutt_buffer_is_empty(&tempfile))
1243 unlink(mutt_buffer_string(&tempfile));
1244
1245 rootwin_free();
1246
1247 if (rv != 0)
1248 goto main_curses; // TEST36: neomutt -H existing -s test john@example.com -E (cancel sending)
1249 }
1250 else if (sendflags & SEND_BATCH)
1251 {
1252 /* This guards against invoking `neomutt < /dev/null` and accidentally
1253 * sending an email due to a my_hdr or other setting. */
1254 mutt_error(_("No recipients specified"));
1255 goto main_curses;
1256 }
1257 else
1258 {
1259 if (flags & MUTT_CLI_MAILBOX)
1260 {
1261 #ifdef USE_IMAP
1262 const bool c_imap_passive = cs_subset_bool(NeoMutt->sub, "imap_passive");
1263 cs_subset_str_native_set(NeoMutt->sub, "imap_passive", false, NULL);
1264 #endif
1265 if (mutt_mailbox_check(NULL, 0) == 0)
1266 {
1267 mutt_message(_("No mailbox with new mail"));
1268 goto main_curses; // TEST37: neomutt -Z (no new mail)
1269 }
1270 mutt_buffer_reset(&folder);
1271 mutt_mailbox_next(NULL, &folder);
1272 #ifdef USE_IMAP
1273 cs_subset_str_native_set(NeoMutt->sub, "imap_passive", c_imap_passive, NULL);
1274 #endif
1275 }
1276 else if (flags & MUTT_CLI_SELECT)
1277 {
1278 #ifdef USE_NNTP
1279 if (flags & MUTT_CLI_NEWS)
1280 {
1281 const char *const c_news_server =
1282 cs_subset_string(NeoMutt->sub, "news_server");
1283 OptNews = true;
1284 CurrentNewsSrv = nntp_select_server(Context ? Context->mailbox : NULL,
1285 c_news_server, false);
1286 if (!CurrentNewsSrv)
1287 goto main_curses; // TEST38: neomutt -G (unset news_server)
1288 }
1289 else
1290 #endif
1291 if (TAILQ_EMPTY(&NeoMutt->accounts))
1292 {
1293 mutt_error(_("No incoming mailboxes defined"));
1294 goto main_curses; // TEST39: neomutt -n -F /dev/null -y
1295 }
1296 mutt_buffer_reset(&folder);
1297 mutt_buffer_select_file(&folder, MUTT_SEL_FOLDER | MUTT_SEL_MAILBOX,
1298 ctx_mailbox(Context), NULL, NULL);
1299 if (mutt_buffer_is_empty(&folder))
1300 {
1301 goto main_ok; // TEST40: neomutt -y (quit selection)
1302 }
1303 }
1304
1305 if (mutt_buffer_is_empty(&folder))
1306 {
1307 const char *const c_spool_file =
1308 cs_subset_string(NeoMutt->sub, "spool_file");
1309 if (c_spool_file)
1310 {
1311 // Check if `$spool_file` corresponds a mailboxes' description.
1312 struct Mailbox *m_desc = mailbox_find_name(c_spool_file);
1313 if (m_desc)
1314 mutt_buffer_strcpy(&folder, m_desc->realpath);
1315 else
1316 mutt_buffer_strcpy(&folder, c_spool_file);
1317 }
1318 else if (c_folder)
1319 mutt_buffer_strcpy(&folder, c_folder);
1320 /* else no folder */
1321 }
1322
1323 #ifdef USE_NNTP
1324 if (OptNews)
1325 {
1326 OptNews = false;
1327 mutt_buffer_alloc(&folder, PATH_MAX);
1328 nntp_expand_path(folder.data, folder.dsize, &CurrentNewsSrv->conn->account);
1329 }
1330 else
1331 #endif
1332 mutt_buffer_expand_path(&folder);
1333
1334 mutt_str_replace(&CurrentFolder, mutt_buffer_string(&folder));
1335 mutt_str_replace(&LastFolder, mutt_buffer_string(&folder));
1336
1337 if (flags & MUTT_CLI_IGNORE)
1338 {
1339 /* check to see if there are any messages in the folder */
1340 switch (mx_path_is_empty(mutt_buffer_string(&folder)))
1341 {
1342 case -1:
1343 mutt_perror(mutt_buffer_string(&folder));
1344 goto main_curses; // TEST41: neomutt -z -f missing
1345 case 1:
1346 mutt_error(_("Mailbox is empty"));
1347 goto main_curses; // TEST42: neomutt -z -f /dev/null
1348 }
1349 }
1350
1351 mutt_folder_hook(mutt_buffer_string(&folder), NULL);
1352 mutt_startup_shutdown_hook(MUTT_STARTUP_HOOK);
1353 mutt_debug(LL_NOTIFY, "NT_GLOBAL_STARTUP\n");
1354 notify_send(NeoMutt->notify, NT_GLOBAL, NT_GLOBAL_STARTUP, NULL);
1355
1356 repeat_error = true;
1357 struct Mailbox *m = mx_resolve(mutt_buffer_string(&folder));
1358 const bool c_read_only = cs_subset_bool(NeoMutt->sub, "read_only");
1359 if (!mx_mbox_open(m, ((flags & MUTT_CLI_RO) || c_read_only) ? MUTT_READONLY : MUTT_OPEN_NO_FLAGS))
1360 {
1361 if (m->account)
1362 account_mailbox_remove(m->account, m);
1363
1364 mailbox_free(&m);
1365 mutt_error(_("Unable to open mailbox %s"), mutt_buffer_string(&folder));
1366 repeat_error = false;
1367 }
1368 if (m || !explicit_folder)
1369 {
1370 struct MuttWindow *dlg = index_pager_init();
1371 dialog_push(dlg);
1372
1373 struct EventMailbox ev_m = { m };
1374 mutt_debug(LL_NOTIFY, "NT_MAILBOX_SWITCH: %p\n", m);
1375 notify_send(dlg->notify, NT_MAILBOX, NT_MAILBOX_SWITCH, &ev_m);
1376
1377 m = mutt_index_menu(dlg, m);
1378 if (m && (m->flags == MB_HIDDEN))
1379 mailbox_free(&m);
1380
1381 dialog_pop();
1382 mutt_window_free(&dlg);
1383 log_queue_empty();
1384 repeat_error = false;
1385 }
1386 #ifdef USE_IMAP
1387 imap_logout_all();
1388 #endif
1389 #ifdef USE_SASL
1390 mutt_sasl_done();
1391 #endif
1392 #ifdef USE_AUTOCRYPT
1393 mutt_autocrypt_cleanup();
1394 #endif
1395 // TEST43: neomutt (no change to mailbox)
1396 // TEST44: neomutt (change mailbox)
1397 }
1398
1399 main_ok:
1400 rc = 0;
1401 main_curses:
1402 mutt_endwin();
1403 mutt_unlink_temp_attachments();
1404 /* Repeat the last message to the user */
1405 if (repeat_error && ErrorBufMessage)
1406 puts(ErrorBuf);
1407 main_exit:
1408 MuttLogger = log_disp_queue;
1409 mutt_buffer_dealloc(&folder);
1410 mutt_buffer_dealloc(&expanded_infile);
1411 mutt_buffer_dealloc(&tempfile);
1412 mutt_list_free(&queries);
1413 crypto_module_free();
1414 rootwin_free();
1415 mutt_buffer_pool_free();
1416 mutt_envlist_free();
1417 mutt_browser_cleanup();
1418 mutt_commands_cleanup();
1419 menu_cleanup();
1420 crypt_cleanup();
1421 mutt_opts_free();
1422 subjrx_free();
1423 attach_free();
1424 alternates_free();
1425 mutt_keys_free();
1426 myvarlist_free(&MyVars);
1427 mutt_prex_free();
1428 neomutt_free(&NeoMutt);
1429 cs_free(&cs);
1430 log_queue_flush(log_disp_terminal);
1431 log_queue_empty();
1432 mutt_log_stop();
1433 return rc;
1434 }
1435