mutt-2.1.4/0000755000175000017500000000000014155212551007511 500000000000000mutt-2.1.4/main.c0000644000175000017500000010046014155211517010523 00000000000000/* * Copyright (C) 1996-2007,2010,2013 Michael R. Elkins * Copyright (C) 1999-2007 Thomas Roessler * Copyright (C) 2004 g10 Code GmbH * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #define MAIN_C 1 #if HAVE_CONFIG_H # include "config.h" #endif #include "mutt.h" #include "mutt_curses.h" #include "keymap.h" #include "mailbox.h" #include "url.h" #include "mutt_crypt.h" #include "mutt_idna.h" #include "send.h" #include "background.h" #ifdef USE_SIDEBAR #include "sidebar.h" #endif #ifdef USE_SASL #include "mutt_sasl.h" #endif #ifdef USE_IMAP #include "imap/imap.h" #endif #ifdef USE_HCACHE #include "hcache.h" #endif #ifdef USE_INOTIFY #include "monitor.h" #endif #ifdef USE_AUTOCRYPT #include "autocrypt/autocrypt.h" #endif #include #include #include #include #include #include #include #ifdef HAVE_GETOPT_H #include #endif #ifdef HAVE_STRINGPREP_H #include #elif defined(HAVE_IDN_STRINGPREP_H) #include #endif static const char *ReachingUs = N_("\ To contact the developers, please mail to .\n\ To report a bug, please contact the Mutt maintainers via gitlab:\n\ https://gitlab.com/muttmua/mutt/issues\n"); static const char *Notice = N_("\ Copyright (C) 1996-2021 Michael R. Elkins and others.\n\ Mutt comes with ABSOLUTELY NO WARRANTY; for details type `mutt -vv'.\n\ Mutt is free software, and you are welcome to redistribute it\n\ under certain conditions; type `mutt -vv' for details.\n"); static const char Copyright[] = "\ Copyright (C) 1996-2016 Michael R. Elkins \n\ Copyright (C) 1996-2002 Brandon Long \n\ Copyright (C) 1997-2009 Thomas Roessler \n\ Copyright (C) 1998-2005 Werner Koch \n\ Copyright (C) 1999-2017 Brendan Cully \n\ Copyright (C) 1999-2002 Tommi Komulainen \n\ Copyright (C) 2000-2004 Edmund Grimley Evans \n\ Copyright (C) 2006-2009 Rocco Rutte \n\ Copyright (C) 2014-2021 Kevin J. McCarthy \n"; static const char *Thanks = N_("\ Many others not mentioned here contributed code, fixes,\n\ and suggestions.\n"); static const char *Licence = N_("\ This program is free software; you can redistribute it and/or modify\n\ it under the terms of the GNU General Public License as published by\n\ the Free Software Foundation; either version 2 of the License, or\n\ (at your option) any later version.\n\ \n\ This program is distributed in the hope that it will be useful,\n\ but WITHOUT ANY WARRANTY; without even the implied warranty of\n\ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n\ GNU General Public License for more details.\n"); static const char *Obtaining = N_("\ You should have received a copy of the GNU General Public License\n\ along with this program; if not, write to the Free Software\n\ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.\n\ "); char **envlist; void mutt_exit (int code) { mutt_endwin (NULL); exit (code); } static void mutt_usage (void) { puts (mutt_make_version ()); puts _( "usage: mutt [] [-z] [-f | -yZ]\n\ mutt [] [-Ex] [-Hi ] [-s ] [-bc ] [-a [...] --] [...]\n\ mutt [] [-x] [-s ] [-bc ] [-a [...] --] [...] < message\n\ mutt [] -p\n\ mutt [] -A [...]\n\ mutt [] -Q [...]\n\ mutt [] -D\n\ mutt -v[v]\n"); puts _("\ options:\n\ -A \texpand the given alias\n\ -a [...] --\tattach file(s) to the message\n\ \t\tthe list of files must be terminated with the \"--\" sequence\n\ -b
\tspecify a blind carbon-copy (BCC) address\n\ -c
\tspecify a carbon-copy (CC) address\n\ -D\t\tprint the value of all variables to stdout"); #if DEBUG puts _(" -d \tlog debugging output to ~/.muttdebug0\n\ \t\t0 => no debugging; <0 => do not rotate .muttdebug files"); #endif puts _( " -E\t\tedit the draft (-H) or include (-i) file\n\ -e \tspecify a command to be executed after initialization\n\ -f \tspecify which mailbox to read\n\ -F \tspecify an alternate muttrc file\n\ -H \tspecify a draft file to read header and body from\n\ -i \tspecify a file which Mutt should include in the body\n\ -m \tspecify a default mailbox type\n\ -n\t\tcauses Mutt not to read the system Muttrc\n\ -p\t\trecall a postponed message"); puts _("\ -Q \tquery a configuration variable\n\ -R\t\topen mailbox in read-only mode\n\ -s \tspecify a subject (must be in quotes if it has spaces)\n\ -v\t\tshow version and compile-time definitions\n\ -x\t\tsimulate the mailx send mode\n\ -y\t\tselect a mailbox specified in your `mailboxes' list\n\ -z\t\texit immediately if there are no messages in the mailbox\n\ -Z\t\topen the first folder with new message, exit immediately if none\n\ -h\t\tthis help message"); exit (0); } extern unsigned char cc_version[]; extern unsigned char cc_cflags[]; extern unsigned char configure_options[]; static char * rstrip_in_place(char *s) { char *p; p = &s[strlen(s)]; if (p == s) return s; p--; while (p >= s && (*p == '\n' || *p == '\r')) *p-- = '\0'; return s; } static void show_version (void) { struct utsname uts; puts (mutt_make_version()); puts (_(Notice)); uname (&uts); #ifdef _AIX printf ("System: %s %s.%s", uts.sysname, uts.version, uts.release); #elif defined (SCO) printf ("System: SCO %s", uts.release); #else printf ("System: %s %s", uts.sysname, uts.release); #endif printf (" (%s)", uts.machine); #ifdef NCURSES_VERSION printf ("\nncurses: %s (compiled with %s)", curses_version(), NCURSES_VERSION); #elif defined(USE_SLANG_CURSES) printf ("\nslang: %d", SLANG_VERSION); #endif #ifdef _LIBICONV_VERSION printf ("\nlibiconv: %d.%d", _LIBICONV_VERSION >> 8, _LIBICONV_VERSION & 0xff); #endif #ifdef HAVE_LIBIDN printf ("\nlibidn: %s (compiled with %s)", stringprep_check_version (NULL), STRINGPREP_VERSION); #endif #ifdef HAVE_LIBIDN2 printf ("\nlibidn2: %s (compiled with %s)", idn2_check_version (NULL), IDN2_VERSION); #endif #ifdef USE_HCACHE printf ("\nhcache backend: %s", mutt_hcache_backend ()); #endif puts ("\n\nCompiler:"); rstrip_in_place((char *)cc_version); puts ((char *)cc_version); rstrip_in_place((char *)configure_options); printf ("\nConfigure options: %s\n", (char *)configure_options); rstrip_in_place((char *)cc_cflags); printf ("\nCompilation CFLAGS: %s\n", (char *)cc_cflags); puts (_("\nCompile options:")); #ifdef DOMAIN printf ("DOMAIN=\"%s\"\n", DOMAIN); #else puts ("-DOMAIN"); #endif #ifdef DEBUG puts ("+DEBUG"); #else puts ("-DEBUG"); #endif puts ( #ifdef HOMESPOOL "+HOMESPOOL " #else "-HOMESPOOL " #endif #ifdef USE_SETGID "+USE_SETGID " #else "-USE_SETGID " #endif #ifdef USE_DOTLOCK "+USE_DOTLOCK " #else "-USE_DOTLOCK " #endif #ifdef DL_STANDALONE "+DL_STANDALONE " #else "-DL_STANDALONE " #endif #ifdef USE_FCNTL "+USE_FCNTL " #else "-USE_FCNTL " #endif #ifdef USE_FLOCK "+USE_FLOCK " #else "-USE_FLOCK " #endif ); puts ( #ifdef USE_POP "+USE_POP " #else "-USE_POP " #endif #ifdef USE_IMAP "+USE_IMAP " #else "-USE_IMAP " #endif #ifdef USE_SMTP "+USE_SMTP " #else "-USE_SMTP " #endif "\n" #ifdef USE_SSL_OPENSSL "+USE_SSL_OPENSSL " #else "-USE_SSL_OPENSSL " #endif #ifdef USE_SSL_GNUTLS "+USE_SSL_GNUTLS " #else "-USE_SSL_GNUTLS " #endif #ifdef USE_SASL "+USE_SASL " #else "-USE_SASL " #endif #ifdef USE_GSS "+USE_GSS " #else "-USE_GSS " #endif #if HAVE_GETADDRINFO "+HAVE_GETADDRINFO " #else "-HAVE_GETADDRINFO " #endif ); puts ( #ifdef HAVE_REGCOMP "+HAVE_REGCOMP " #else "-HAVE_REGCOMP " #endif #ifdef USE_GNU_REGEX "+USE_GNU_REGEX " #else "-USE_GNU_REGEX " #endif "\n" #ifdef HAVE_COLOR "+HAVE_COLOR " #else "-HAVE_COLOR " #endif #ifdef HAVE_START_COLOR "+HAVE_START_COLOR " #else "-HAVE_START_COLOR " #endif #ifdef HAVE_TYPEAHEAD "+HAVE_TYPEAHEAD " #else "-HAVE_TYPEAHEAD " #endif #ifdef HAVE_BKGDSET "+HAVE_BKGDSET " #else "-HAVE_BKGDSET " #endif "\n" #ifdef HAVE_CURS_SET "+HAVE_CURS_SET " #else "-HAVE_CURS_SET " #endif #ifdef HAVE_META "+HAVE_META " #else "-HAVE_META " #endif #ifdef HAVE_RESIZETERM "+HAVE_RESIZETERM " #else "-HAVE_RESIZETERM " #endif #ifdef HAVE_FUTIMENS "+HAVE_FUTIMENS " #else "-HAVE_FUTIMENS " #endif ); puts ( #ifdef CRYPT_BACKEND_CLASSIC_PGP "+CRYPT_BACKEND_CLASSIC_PGP " #else "-CRYPT_BACKEND_CLASSIC_PGP " #endif #ifdef CRYPT_BACKEND_CLASSIC_SMIME "+CRYPT_BACKEND_CLASSIC_SMIME " #else "-CRYPT_BACKEND_CLASSIC_SMIME " #endif #ifdef CRYPT_BACKEND_GPGME "+CRYPT_BACKEND_GPGME " #else "-CRYPT_BACKEND_GPGME " #endif ); puts ( #ifdef EXACT_ADDRESS "+EXACT_ADDRESS " #else "-EXACT_ADDRESS " #endif #ifdef SUN_ATTACHMENT "+SUN_ATTACHMENT " #else "-SUN_ATTACHMENT " #endif "\n" #ifdef ENABLE_NLS "+ENABLE_NLS " #else "-ENABLE_NLS " #endif #ifdef LOCALES_HACK "+LOCALES_HACK " #else "-LOCALES_HACK " #endif #ifdef HAVE_WC_FUNCS "+HAVE_WC_FUNCS " #else "-HAVE_WC_FUNCS " #endif #ifdef HAVE_LANGINFO_CODESET "+HAVE_LANGINFO_CODESET " #else "-HAVE_LANGINFO_CODESET " #endif #ifdef HAVE_LANGINFO_YESEXPR "+HAVE_LANGINFO_YESEXPR " #else "-HAVE_LANGINFO_YESEXPR " #endif "\n" #if HAVE_ICONV "+HAVE_ICONV " #else "-HAVE_ICONV " #endif #if ICONV_NONTRANS "+ICONV_NONTRANS " #else "-ICONV_NONTRANS " #endif #if HAVE_LIBIDN "+HAVE_LIBIDN " #else "-HAVE_LIBIDN " #endif #if HAVE_LIBIDN2 "+HAVE_LIBIDN2 " #else "-HAVE_LIBIDN2 " #endif #if HAVE_GETSID "+HAVE_GETSID " #else "-HAVE_GETSID " #endif #if USE_HCACHE "+USE_HCACHE " #else "-USE_HCACHE " #endif "\n" #ifdef USE_SIDEBAR "+USE_SIDEBAR " #else "-USE_SIDEBAR " #endif #ifdef USE_COMPRESSED "+USE_COMPRESSED " #else "-USE_COMPRESSED " #endif #ifdef USE_INOTIFY "+USE_INOTIFY " #else "-USE_INOTIFY " #endif ); #ifdef ISPELL printf ("ISPELL=\"%s\"\n", ISPELL); #else puts ("-ISPELL"); #endif printf ("SENDMAIL=\"%s\"\n", SENDMAIL); printf ("MAILPATH=\"%s\"\n", MAILPATH); printf ("PKGDATADIR=\"%s\"\n", PKGDATADIR); printf ("SYSCONFDIR=\"%s\"\n", SYSCONFDIR); printf ("EXECSHELL=\"%s\"\n", EXECSHELL); #ifdef MIXMASTER printf ("MIXMASTER=\"%s\"\n", MIXMASTER); #else puts ("-MIXMASTER"); #endif putchar ('\n'); puts(_(ReachingUs)); mutt_print_patchlist(); exit (0); } static void start_curses (void) { km_init (); /* must come before mutt_init */ #ifdef USE_SLANG_CURSES SLtt_Ignore_Beep = 1; /* don't do that #*$@^! annoying visual beep! */ SLsmg_Display_Eight_Bit = 128; /* characters above this are printable */ SLtt_set_color(0, NULL, "default", "default"); #if SLANG_VERSION >= 20000 SLutf8_enable(-1); #endif #else /* should come before initscr() so that ncurses 4.2 doesn't try to install its own SIGWINCH handler */ mutt_signal_init (); #endif if (initscr () == NULL) { puts _("Error initializing terminal."); exit (1); } /* slang requires the signal handlers to be set after initializing */ mutt_signal_init (); ci_start_color (); keypad (stdscr, TRUE); cbreak (); noecho (); #if HAVE_TYPEAHEAD typeahead (-1); /* simulate smooth scrolling */ #endif #if HAVE_META meta (stdscr, TRUE); #endif init_extended_keys(); mutt_reflow_windows (); } #define MUTT_IGNORE (1<<0) /* -z */ #define MUTT_BUFFY (1<<1) /* -Z */ #define MUTT_NOSYSRC (1<<2) /* -n */ #define MUTT_RO (1<<3) /* -R */ #define MUTT_SELECT (1<<4) /* -y */ int main (int argc, char **argv, char **environ) { BUFFER *folder = NULL; BUFFER *expanded_infile = NULL; BUFFER *tempfile = NULL; char *subject = NULL; char *includeFile = NULL; char *draftFile = NULL; char *newMagic = NULL; HEADER *msg = NULL; LIST *attach = NULL; LIST *commands = NULL; LIST *queries = NULL; LIST *alias_queries = NULL; int sendflags = 0; int flags = 0; int version = 0; int i; int explicit_folder = 0; int dump_variables = 0; int edit_infile = 0; extern char *optarg; extern int optind; int double_dash = argc, nargc = 1; int exit_code = 1; const char *exit_endwin_msg = NULL; /* sanity check against stupid administrators */ if (getegid() != getgid()) { fprintf(stderr, "%s: I don't want to run with privileges!\n", argv[0]); exit(1); } setlocale (LC_ALL, ""); #ifdef ENABLE_NLS /* FIXME what about the LOCALES_HACK in mutt_init() [init.c] ? */ { char *domdir = getenv ("TEXTDOMAINDIR"); if (domdir && domdir[0]) bindtextdomain (PACKAGE, domdir); else bindtextdomain (PACKAGE, MUTTLOCALEDIR); textdomain (PACKAGE); } #endif mutt_error = mutt_nocurses_error; mutt_message = mutt_nocurses_error; SRAND (time (NULL)); umask (077); memset (Options, 0, sizeof (Options)); memset (QuadOptions, 0, sizeof (QuadOptions)); /* Init envlist */ { char **srcp, **dstp; int count = 0; for (srcp = environ; srcp && *srcp; srcp++) count++; envlist = safe_calloc(count+1, sizeof(char *)); for (srcp = environ, dstp = envlist; srcp && *srcp; srcp++, dstp++) *dstp = safe_strdup(*srcp); } for (optind = 1; optind < double_dash; ) { /* We're getopt'ing POSIXLY, so we'll be here every time getopt() * encounters a non-option. That could be a file to attach * (all non-options between -a and --) or it could be an address * (which gets collapsed to the front of argv). */ for (; optind < argc; optind++) { if (argv[optind][0] == '-' && argv[optind][1] != '\0') { if (argv[optind][1] == '-' && argv[optind][2] == '\0') double_dash = optind; /* quit outer loop after getopt */ break; /* drop through to getopt */ } /* non-option, either an attachment or address */ if (attach) attach = mutt_add_list (attach, argv[optind]); else argv[nargc++] = argv[optind]; } if ((i = getopt (argc, argv, "+A:a:b:F:f:c:Dd:Ee:H:s:i:hm:npQ:RvxyzZ")) != EOF) switch (i) { case 'A': alias_queries = mutt_add_list (alias_queries, optarg); break; case 'a': attach = mutt_add_list (attach, optarg); break; case 'F': mutt_str_replace (&Muttrc, optarg); break; case 'f': if (!folder) folder = mutt_buffer_new (); mutt_buffer_strcpy (folder, optarg); explicit_folder = 1; break; case 'b': case 'c': if (!msg) msg = mutt_new_header (); if (!msg->env) msg->env = mutt_new_envelope (); if (i == 'b') msg->env->bcc = rfc822_parse_adrlist (msg->env->bcc, optarg); else msg->env->cc = rfc822_parse_adrlist (msg->env->cc, optarg); break; case 'D': dump_variables = 1; break; case 'd': #ifdef DEBUG mutt_atoi (optarg, &debuglevel, 0); printf (_("Debugging at level %d.\n"), debuglevel); #else printf ("%s", _("DEBUG was not defined during compilation. Ignored.\n")); #endif break; case 'E': edit_infile = 1; break; case 'e': commands = mutt_add_list (commands, optarg); break; case 'H': draftFile = optarg; break; case 'i': includeFile = optarg; break; case 'm': /* should take precedence over .muttrc setting, so save it for later */ newMagic = optarg; break; case 'n': flags |= MUTT_NOSYSRC; break; case 'p': sendflags |= SENDPOSTPONED; break; case 'Q': queries = mutt_add_list (queries, optarg); break; case 'R': flags |= MUTT_RO; /* read-only mode */ break; case 's': subject = optarg; break; case 'v': version++; break; case 'x': /* mailx compatible send mode */ sendflags |= SENDMAILX; break; case 'y': /* My special hack mode */ flags |= MUTT_SELECT; break; case 'z': flags |= MUTT_IGNORE; break; case 'Z': flags |= MUTT_BUFFY | MUTT_IGNORE; break; default: mutt_usage (); } } /* collapse remaining argv */ while (optind < argc) argv[nargc++] = argv[optind++]; optind = 1; argc = nargc; switch (version) { case 0: break; case 1: show_version (); break; default: puts (mutt_make_version ()); puts (Copyright); puts (_(Thanks)); puts (_(Licence)); puts (_(Obtaining)); puts (_(ReachingUs)); mutt_buffer_free (&folder); exit (0); } /* Check for a batch send. */ if (!isatty (0) || queries || alias_queries || dump_variables) { set_option (OPTNOCURSES); sendflags = SENDBATCH; } /* Always create the mutt_windows because batch mode has some shared code * paths that end up referencing them. */ mutt_init_windows (); /* This must come before mutt_init() because curses needs to be started before calling the init_pair() function to set the color scheme. */ if (!option (OPTNOCURSES)) { start_curses (); /* check whether terminal status is supported (must follow curses init) */ TSSupported = mutt_ts_capability(); } /* set defaults and read init files */ mutt_init (flags & MUTT_NOSYSRC, commands); mutt_free_list (&commands); /* Initialize crypto backends. */ crypt_init (); if (newMagic) mx_set_magic (newMagic); if (queries) { for (; optind < argc; optind++) queries = mutt_add_list (queries, argv[optind]); exit_code = mutt_query_variables (queries); mutt_free_list (&queries); goto cleanup_and_exit; } if (dump_variables) { exit_code = mutt_dump_variables(); goto cleanup_and_exit; } if (alias_queries) { ADDRESS *a; exit_code = 0; for (; optind < argc; optind++) alias_queries = mutt_add_list (alias_queries, argv[optind]); for (; alias_queries; alias_queries = alias_queries->next) { if ((a = mutt_lookup_alias (alias_queries->data))) { /* output in machine-readable form */ mutt_addrlist_to_intl (a, NULL); mutt_write_address_list (a, stdout, 0, 0); } else { exit_code = 1; printf ("%s\n", alias_queries->data); } } mutt_free_list (&alias_queries); goto cleanup_and_exit; } if (!option (OPTNOCURSES)) { SETCOLOR (MT_COLOR_NORMAL); clear (); mutt_error = mutt_curses_error; mutt_message = mutt_curses_message; } /* Initialize autocrypt after curses messages are working, * because of the initial account setup screens. */ #ifdef USE_AUTOCRYPT if (option (OPTAUTOCRYPT)) mutt_autocrypt_init (!(sendflags & SENDBATCH)); #endif /* Create the Maildir directory if it doesn't exist. */ if (!option (OPTNOCURSES) && Maildir) { struct stat sb; BUFFER *fpath; char msg[STRING]; fpath = mutt_buffer_pool_get (); mutt_buffer_strcpy (fpath, Maildir); mutt_buffer_expand_path (fpath); #ifdef USE_IMAP /* we're not connected yet - skip mail folder creation */ if (!mx_is_imap (mutt_b2s (fpath))) #endif if (stat (mutt_b2s (fpath), &sb) == -1 && errno == ENOENT) { snprintf (msg, sizeof (msg), _("%s does not exist. Create it?"), Maildir); if (mutt_yesorno (msg, MUTT_YES) == MUTT_YES) { if (mkdir (mutt_b2s (fpath), 0700) == -1 && errno != EEXIST) mutt_error ( _("Can't create %s: %s."), Maildir, strerror (errno)); } } mutt_buffer_pool_release (&fpath); } if (sendflags & SENDPOSTPONED) { if (!option (OPTNOCURSES)) mutt_flushinp (); mutt_send_message (SENDPOSTPONED, NULL, NULL, NULL, NULL); } else if (subject || msg || (sendflags & SENDMAILX) || draftFile || includeFile || attach || optind < argc) { FILE *fin = NULL; FILE *fout = NULL; char *infile = NULL; char *bodytext = NULL; const char *bodyfile = NULL; int rv = 0; if (!option (OPTNOCURSES)) mutt_flushinp (); if (!msg) msg = mutt_new_header (); if (!msg->env) msg->env = mutt_new_envelope (); for (i = optind; i < argc; i++) { if (url_check_scheme (argv[i]) == U_MAILTO) { if (url_parse_mailto (msg->env, &bodytext, argv[i]) < 0) { if (!option (OPTNOCURSES)) { mutt_endwin (NULL); set_option (OPTNOCURSES); } fputs (_("Failed to parse mailto: link\n"), stderr); goto cleanup_and_exit; } } else msg->env->to = rfc822_parse_adrlist (msg->env->to, argv[i]); } if (!draftFile && option (OPTAUTOEDIT) && !msg->env->to && !msg->env->cc) { if (!option (OPTNOCURSES)) { mutt_endwin (NULL); set_option (OPTNOCURSES); } fputs (_("No recipients specified.\n"), stderr); goto cleanup_and_exit; } if (subject) msg->env->subject = safe_strdup (subject); if (draftFile) { infile = draftFile; includeFile = NULL; } else if (includeFile) infile = includeFile; else edit_infile = 0; if (infile || bodytext) { /* Prepare fin and expanded_infile. */ if (infile) { if (mutt_strcmp ("-", infile) == 0) { if (edit_infile) { fputs (_("Cannot use -E flag with stdin\n"), stderr); goto cleanup_and_exit; } fin = stdin; } else { expanded_infile = mutt_buffer_new (); mutt_buffer_strcpy (expanded_infile, infile); mutt_buffer_expand_path (expanded_infile); if ((fin = fopen (mutt_b2s (expanded_infile), "r")) == NULL) { if (!option (OPTNOCURSES)) { mutt_endwin (NULL); set_option (OPTNOCURSES); } perror (mutt_b2s (expanded_infile)); goto cleanup_and_exit; } } } /* Copy input to a tempfile, and re-point fin to the tempfile. * Note: stdin is always copied to a tempfile, ensuring draftFile * can stat and get the correct st_size below. */ if (!edit_infile) { tempfile = mutt_buffer_new (); mutt_buffer_mktemp (tempfile); if ((fout = safe_fopen (mutt_b2s (tempfile), "w")) == NULL) { if (!option (OPTNOCURSES)) { mutt_endwin (NULL); set_option (OPTNOCURSES); } perror (mutt_b2s (tempfile)); safe_fclose (&fin); goto cleanup_and_exit; } if (fin) { mutt_copy_stream (fin, fout); if (fin != stdin) safe_fclose (&fin); } else if (bodytext) fputs (bodytext, fout); safe_fclose (&fout); if ((fin = fopen (mutt_b2s (tempfile), "r")) == NULL) { if (!option (OPTNOCURSES)) { mutt_endwin (NULL); set_option (OPTNOCURSES); } perror (mutt_b2s (tempfile)); goto cleanup_and_exit; } } /* If editing the infile, keep it around afterwards so * it doesn't get unlinked, and we can rebuild the draftFile */ else sendflags |= SENDNOFREEHEADER; /* Parse the draftFile into the full HEADER/BODY structure. * Set SENDDRAFTFILE so mutt_send_message doesn't overwrite * our msg->content. */ if (draftFile) { HEADER *context_hdr = NULL; ENVELOPE *opts_env = msg->env; struct stat st; LIST *uh, **last_uhp; sendflags |= SENDDRAFTFILE; /* Set up a "context" header with just enough information so that * mutt_prepare_template() can parse the message in fin. */ context_hdr = mutt_new_header (); context_hdr->offset = 0; context_hdr->content = mutt_new_body (); if (fstat (fileno (fin), &st)) { perror (draftFile); goto cleanup_and_exit; } context_hdr->content->length = st.st_size; if (mutt_prepare_template (fin, NULL, msg, context_hdr, 0) < 0) { if (!option (OPTNOCURSES)) { mutt_endwin (NULL); set_option (OPTNOCURSES); } /* L10N: Error when using -H command line argument, but reading the draft file fails for some reason. */ fputs (_("Cannot parse draft file\n"), stderr); goto cleanup_and_exit; } /* Scan for mutt header to set OPTRESUMEDRAFTFILES */ for (last_uhp = &msg->env->userhdrs, uh = *last_uhp; uh; uh = *last_uhp) { if (ascii_strncasecmp ("X-Mutt-Resume-Draft:", uh->data, 20) == 0) { if (option (OPTRESUMEEDITEDDRAFTFILES)) set_option (OPTRESUMEDRAFTFILES); *last_uhp = uh->next; uh->next = NULL; mutt_free_list (&uh); } else last_uhp = &uh->next; } rfc822_append (&msg->env->to, opts_env->to, 0); rfc822_append (&msg->env->cc, opts_env->cc, 0); rfc822_append (&msg->env->bcc, opts_env->bcc, 0); if (opts_env->subject) mutt_str_replace (&msg->env->subject, opts_env->subject); mutt_free_envelope (&opts_env); mutt_free_header (&context_hdr); } /* Editing the includeFile: pass it directly in. * Note that SENDNOFREEHEADER is set above so it isn't unlinked. */ else if (edit_infile) bodyfile = mutt_b2s (expanded_infile); /* For bodytext and unedited includeFile: use the tempfile. */ else bodyfile = mutt_b2s (tempfile); if (fin) safe_fclose (&fin); } FREE (&bodytext); if (attach) { LIST *t = attach; BODY *a = msg->content; while (a && a->next) a = a->next; while (t) { if (a) { a->next = mutt_make_file_attach (t->data); a = a->next; } else msg->content = a = mutt_make_file_attach (t->data); if (!a) { if (!option (OPTNOCURSES)) { mutt_endwin (NULL); set_option (OPTNOCURSES); } fprintf (stderr, _("%s: unable to attach file.\n"), t->data); mutt_free_list (&attach); goto cleanup_and_exit; } t = t->next; } mutt_free_list (&attach); } rv = mutt_send_message (sendflags, msg, bodyfile, NULL, NULL); if (edit_infile) { if (includeFile) msg->content->unlink = 0; else if (draftFile) { if (truncate (mutt_b2s (expanded_infile), 0) == -1) { if (!option (OPTNOCURSES)) { mutt_endwin (NULL); set_option (OPTNOCURSES); } perror (mutt_b2s (expanded_infile)); goto cleanup_and_exit; } if ((fout = safe_fopen (mutt_b2s (expanded_infile), "a")) == NULL) { if (!option (OPTNOCURSES)) { mutt_endwin (NULL); set_option (OPTNOCURSES); } perror (mutt_b2s (expanded_infile)); goto cleanup_and_exit; } /* If the message was sent or postponed, these will already * have been done. */ if (rv < 0) { if (msg->content->next) msg->content = mutt_make_multipart_mixed (msg->content); mutt_encode_descriptions (msg->content, 1); mutt_prepare_envelope (msg->env, 0); mutt_env_to_intl (msg->env, NULL, NULL); } mutt_write_rfc822_header (fout, msg->env, msg->content, NULL, MUTT_WRITE_HEADER_POSTPONE, 0, option (OPTCRYPTPROTHDRSREAD) && mutt_should_hide_protected_subject (msg)); if (option (OPTRESUMEEDITEDDRAFTFILES)) fprintf (fout, "X-Mutt-Resume-Draft: 1\n"); fputc ('\n', fout); if ((mutt_write_mime_body (msg->content, fout) == -1)) { safe_fclose (&fout); goto cleanup_and_exit; } safe_fclose (&fout); } mutt_free_header (&msg); } /* !edit_infile && draftFile will leave the tempfile around */ if (tempfile) unlink (mutt_b2s (tempfile)); if (rv) goto cleanup_and_exit; } /* This guards against invoking `mutt < /dev/null` and accidentally * sending an email due to a my_hdr or other setting. */ else if (sendflags & SENDBATCH) { fputs (_("No recipients specified.\n"), stderr); goto cleanup_and_exit; } else { if (!folder) folder = mutt_buffer_new (); if (flags & MUTT_BUFFY) { #ifdef USE_IMAP int passive = option (OPTIMAPPASSIVE); if (passive) unset_option (OPTIMAPPASSIVE); #endif if (!mutt_buffy_check (0)) { exit_endwin_msg = _("No mailbox with new mail."); goto cleanup_and_exit; } mutt_buffer_clear (folder); mutt_buffer_buffy (folder); #ifdef USE_IMAP if (passive) set_option (OPTIMAPPASSIVE); #endif } else if (flags & MUTT_SELECT) { if (!Incoming) { exit_endwin_msg = _("No incoming mailboxes defined."); goto cleanup_and_exit; } mutt_buffer_clear (folder); mutt_buffer_select_file (folder, MUTT_SEL_FOLDER | MUTT_SEL_BUFFY); if (!mutt_buffer_len (folder)) { exit_code = 0; goto cleanup_and_exit; } } if (!mutt_buffer_len (folder)) mutt_buffer_strcpy (folder, NONULL(Spoolfile)); mutt_buffer_expand_path (folder); mutt_str_replace (&CurrentFolder, mutt_b2s (folder)); mutt_str_replace (&LastFolder, mutt_b2s (folder)); if (flags & MUTT_IGNORE) { /* check to see if there are any messages in the folder */ switch (mx_check_empty (mutt_b2s (folder))) { case -1: exit_endwin_msg = strerror (errno); goto cleanup_and_exit; case 1: exit_endwin_msg = _("Mailbox is empty."); goto cleanup_and_exit; } } mutt_folder_hook (mutt_b2s (folder)); Context = mx_open_mailbox (mutt_b2s (folder), ((flags & MUTT_RO) || option (OPTREADONLY)) ? MUTT_READONLY : 0, NULL); mutt_buffer_free (&folder); if (Context || !explicit_folder) { #ifdef USE_SIDEBAR mutt_sb_set_open_buffy (); #endif mutt_index_menu (); if (Context) FREE (&Context); } exit_endwin_msg = Errorbuf; } exit_code = 0; cleanup_and_exit: mutt_buffer_free (&folder); mutt_buffer_free (&expanded_infile); mutt_buffer_free (&tempfile); #ifdef USE_IMAP imap_logout_all (); #endif #ifdef USE_SASL mutt_sasl_done (); #endif #ifdef USE_AUTOCRYPT mutt_autocrypt_cleanup (); #endif mutt_browser_cleanup (); mutt_commands_cleanup (); crypt_cleanup (); mutt_signal_cleanup (); mutt_free_opts (); mutt_free_windows (); mutt_buffer_pool_free (); if (!option (OPTNOCURSES)) mutt_endwin (exit_endwin_msg); exit (exit_code); } mutt-2.1.4/README.SECURITY0000644000175000017500000000464713653360550011577 00000000000000$Id$ Recently, there have been reports on security problems induced by the interpretation of shell meta-characters embedded in MIME parameters. These reports were referring to Pine, but the problem also applied when using mutt. More precisely, a mailcap entry like this one would lead to problems: > text/test-mailcap-bug; cat %s; copiousoutput; \ > test=test "`echo %{charset} | tr '[A-Z]' '[a-z]'`" != iso-8859-1 When expanded with a charset parameter of ``touch${IFS}ME``, a file named "ME" would be created in the current directory. While we don't completely agree that this is an actual MUA problem (see below), we have implemented a couple of fixes for this: - Backticks are handled specially when preparing % expandos for mailcap entries. This fix will keep the current problem from occurring, but we are sure there are other possible mailcap entries where this doesn't help. - We have added a configuration variable named $mailcap_sanitize, which is set by default. If set, mutt will restrict possible characters in mailcap % expandos to a well-defined set of safe characters. This is the safe setting, but we are not sure it doesn't break some more advanced MIME stuff. >>> DON'T UNSET THIS OPTION UNLESS YOU KNOW WHAT YOU ARE DOING. Anyway, this problem is not necessarily a problem which should be solved inside the MUA, as it's difficult (maybe impossible) to solve there. Additionally, there is more than one program which parses mailcap. So writing secure mailcap statements is generally a good idea. We encourage you to do this. The most basic rule is this one: >>> KEEP THE %-EXPANDOS AWAY FROM SHELL QUOTING. Don't quote them with single or double quotes. Mutt does this for you, the right way, as should any other program which interprets mailcap. Don't put them into backtick expansions - as you have seen above, this is a recipe for disaster. Be highly careful with eval statements, and avoid them if possible at all. If you have to use the %-expandos' values in context where you need quoting or backtick expansions, put that value into a shell variable and reference the shell variable where necessary (possibly with the proper quoting put around it, like in "$charset"). For example, a safe version of the mailcap statement above could look like this: > text/test-mailcap-bug; cat %s; copiousoutput; test=charset=%{charset} \ > && test "`echo \"$charset\" | tr '[A-Z]' '[a-z]'`" != iso-8859-1 mutt-2.1.4/sendlib.c0000644000175000017500000023551414155211517011230 00000000000000/* * Copyright (C) 1996-2002,2009-2012 Michael R. Elkins * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #define _SENDLIB_C 1 #if HAVE_CONFIG_H # include "config.h" #endif #include "version.h" #include "mutt.h" #include "mutt_curses.h" #include "rfc2047.h" #include "rfc2231.h" #include "mx.h" #include "mime.h" #include "mailbox.h" #include "copy.h" #include "pager.h" #include "charset.h" #include "mutt_crypt.h" #include "mutt_random.h" #include "mutt_idna.h" #include "buffy.h" #include "send.h" #ifdef USE_AUTOCRYPT #include "autocrypt.h" #endif #include #include #include #include #include #include #include #include #include #ifdef HAVE_SYSEXITS_H #include #else /* Make sure EX_OK is defined */ #define EX_OK 0 #endif /* If you are debugging this file, comment out the following line. */ #define NDEBUG #ifdef NDEBUG #define assert(x) #else #include #endif extern char RFC822Specials[]; const char MimeSpecials[] = "@.,;:<>[]\\\"()?/= \t"; const char B64Chars[64] = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/' }; static void transform_to_7bit (BODY *a, FILE *fpin); static void encode_quoted (FGETCONV * fc, FILE *fout, int istext) { int c, linelen = 0; char line[77], savechar; while ((c = fgetconv (fc)) != EOF) { /* Wrap the line if needed. */ if (linelen == 76 && ((istext && c != '\n') || !istext)) { /* If the last character is "quoted", then be sure to move all three * characters to the next line. Otherwise, just move the last * character... */ if (line[linelen-3] == '=') { line[linelen-3] = 0; fputs (line, fout); fputs ("=\n", fout); line[linelen] = 0; line[0] = '='; line[1] = line[linelen-2]; line[2] = line[linelen-1]; linelen = 3; } else { savechar = line[linelen-1]; line[linelen-1] = '='; line[linelen] = 0; fputs (line, fout); fputc ('\n', fout); line[0] = savechar; linelen = 1; } } /* Escape lines that begin with/only contain "the message separator". */ if (linelen == 4 && !mutt_strncmp ("From", line, 4)) { strfcpy (line, "=46rom", sizeof (line)); linelen = 6; } else if (linelen == 4 && !mutt_strncmp ("from", line, 4)) { strfcpy (line, "=66rom", sizeof (line)); linelen = 6; } else if (linelen == 1 && line[0] == '.') { strfcpy (line, "=2E", sizeof (line)); linelen = 3; } if (c == '\n' && istext) { /* Check to make sure there is no trailing space on this line. */ if (linelen > 0 && (line[linelen-1] == ' ' || line[linelen-1] == '\t')) { if (linelen < 74) { sprintf (line+linelen-1, "=%2.2X", (unsigned char) line[linelen-1]); fputs (line, fout); } else { int savechar = line[linelen-1]; line[linelen-1] = '='; line[linelen] = 0; fputs (line, fout); fprintf (fout, "\n=%2.2X", (unsigned char) savechar); } } else { line[linelen] = 0; fputs (line, fout); } fputc ('\n', fout); linelen = 0; } else if (c != 9 && (c < 32 || c > 126 || c == '=')) { /* Check to make sure there is enough room for the quoted character. * If not, wrap to the next line. */ if (linelen > 73) { line[linelen++] = '='; line[linelen] = 0; fputs (line, fout); fputc ('\n', fout); linelen = 0; } sprintf (line+linelen,"=%2.2X", (unsigned char) c); linelen += 3; } else { /* Don't worry about wrapping the line here. That will happen during * the next iteration when I'll also know what the next character is. */ line[linelen++] = c; } } /* Take care of anything left in the buffer */ if (linelen > 0) { if (line[linelen-1] == ' ' || line[linelen-1] == '\t') { /* take care of trailing whitespace */ if (linelen < 74) sprintf (line+linelen-1, "=%2.2X", (unsigned char) line[linelen-1]); else { savechar = line[linelen-1]; line[linelen-1] = '='; line[linelen] = 0; fputs (line, fout); fputc ('\n', fout); sprintf (line, "=%2.2X", (unsigned char) savechar); } } else line[linelen] = 0; fputs (line, fout); } } static char b64_buffer[3]; static short b64_num; static short b64_linelen; static void b64_flush(FILE *fout) { short i; if (!b64_num) return; if (b64_linelen >= 72) { fputc('\n', fout); b64_linelen = 0; } for (i = b64_num; i < 3; i++) b64_buffer[i] = '\0'; fputc(B64Chars[(b64_buffer[0] >> 2) & 0x3f], fout); b64_linelen++; fputc(B64Chars[((b64_buffer[0] & 0x3) << 4) | ((b64_buffer[1] >> 4) & 0xf) ], fout); b64_linelen++; if (b64_num > 1) { fputc(B64Chars[((b64_buffer[1] & 0xf) << 2) | ((b64_buffer[2] >> 6) & 0x3) ], fout); b64_linelen++; if (b64_num > 2) { fputc(B64Chars[b64_buffer[2] & 0x3f], fout); b64_linelen++; } } while (b64_linelen % 4) { fputc('=', fout); b64_linelen++; } b64_num = 0; } static void b64_putc(char c, FILE *fout) { if (b64_num == 3) b64_flush(fout); b64_buffer[b64_num++] = c; } static void encode_base64 (FGETCONV * fc, FILE *fout, int istext) { int ch, ch1 = EOF; b64_num = b64_linelen = 0; while ((ch = fgetconv (fc)) != EOF) { if (istext && ch == '\n' && ch1 != '\r') b64_putc('\r', fout); b64_putc(ch, fout); ch1 = ch; } b64_flush(fout); fputc('\n', fout); } static void encode_8bit (FGETCONV *fc, FILE *fout, int istext) { int ch; while ((ch = fgetconv (fc)) != EOF) fputc (ch, fout); } int mutt_write_mime_header (BODY *a, FILE *f) { PARAMETER *p; PARAMETER *param_conts, *cont; char buffer[STRING]; char *t; char *fn; int len; int tmplen; fprintf (f, "Content-Type: %s/%s", TYPE (a), a->subtype); if (a->parameter) { len = 25 + mutt_strlen (a->subtype); /* approximate len. of content-type */ for (p = a->parameter; p; p = p->next) { if (!(p->attribute && p->value)) continue; param_conts = rfc2231_encode_string (p->attribute, p->value); for (cont = param_conts; cont; cont = cont->next) { fputc (';', f); buffer[0] = 0; rfc822_cat (buffer, sizeof (buffer), cont->value, MimeSpecials); /* Dirty hack to make messages readable by Outlook Express * for the Mac: force quotes around the boundary parameter * even when they aren't needed. */ if (!ascii_strcasecmp (cont->attribute, "boundary") && !mutt_strcmp (buffer, cont->value)) snprintf (buffer, sizeof (buffer), "\"%s\"", cont->value); tmplen = mutt_strlen (buffer) + mutt_strlen (cont->attribute) + 1; if (len + tmplen + 2 > 76) { fputs ("\n\t", f); len = tmplen + 1; } else { fputc (' ', f); len += tmplen + 1; } fprintf (f, "%s=%s", cont->attribute, buffer); } mutt_free_parameter (¶m_conts); } } fputc ('\n', f); if (a->description) fprintf(f, "Content-Description: %s\n", a->description); if (a->disposition != DISPNONE) { const char *dispstr[] = { "inline", "attachment", "form-data" }; if (a->disposition < sizeof(dispstr)/sizeof(char*)) { fprintf (f, "Content-Disposition: %s", dispstr[a->disposition]); len = 21 + mutt_strlen (dispstr[a->disposition]); if (a->use_disp) { if (!(fn = a->d_filename)) fn = a->filename; if (fn) { /* Strip off the leading path... */ if ((t = strrchr (fn, '/'))) t++; else t = fn; param_conts = rfc2231_encode_string ("filename", t); for (cont = param_conts; cont; cont = cont->next) { fputc (';', f); buffer[0] = 0; rfc822_cat (buffer, sizeof (buffer), cont->value, MimeSpecials); tmplen = mutt_strlen (buffer) + mutt_strlen (cont->attribute) + 1; if (len + tmplen + 2 > 76) { fputs ("\n\t", f); len = tmplen + 1; } else { fputc (' ', f); len += tmplen + 1; } fprintf (f, "%s=%s", cont->attribute, buffer); } mutt_free_parameter (¶m_conts); } } fputc ('\n', f); } else { dprint(1, (debugfile, "ERROR: invalid content-disposition %d\n", a->disposition)); } } if (a->encoding != ENC7BIT) fprintf(f, "Content-Transfer-Encoding: %s\n", ENCODING (a->encoding)); if ((option (OPTCRYPTPROTHDRSWRITE) #ifdef USE_AUTOCRYPT || option (OPTAUTOCRYPT) #endif ) && a->mime_headers) { mutt_write_rfc822_header (f, a->mime_headers, NULL, a->mime_headers->date, MUTT_WRITE_HEADER_MIME, 0, 0); } /* Do NOT add the terminator here!!! */ return (ferror (f) ? -1 : 0); } #define write_as_text_part(a) \ (mutt_is_text_part(a) || \ ((WithCrypto & APPLICATION_PGP) && mutt_is_application_pgp(a))) int mutt_write_mime_body (BODY *a, FILE *f) { char *p, boundary[SHORT_STRING]; char send_charset[SHORT_STRING]; FILE *fpin; BODY *t; FGETCONV *fc; if (a->type == TYPEMULTIPART) { /* First, find the boundary to use */ if (!(p = mutt_get_parameter ("boundary", a->parameter))) { dprint (1, (debugfile, "mutt_write_mime_body(): no boundary parameter found!\n")); mutt_error _("No boundary parameter found! [report this error]"); return (-1); } strfcpy (boundary, p, sizeof (boundary)); for (t = a->parts; t ; t = t->next) { fprintf (f, "\n--%s\n", boundary); if (mutt_write_mime_header (t, f) == -1) return -1; fputc ('\n', f); if (mutt_write_mime_body (t, f) == -1) return -1; } fprintf (f, "\n--%s--\n", boundary); return (ferror (f) ? -1 : 0); } /* This is pretty gross, but it's the best solution for now... */ if ((WithCrypto & APPLICATION_PGP) && a->type == TYPEAPPLICATION && mutt_strcmp (a->subtype, "pgp-encrypted") == 0 && !a->filename) { fputs ("Version: 1\n", f); return 0; } if ((fpin = fopen (a->filename, "r")) == NULL) { dprint(1,(debugfile, "write_mime_body: %s no longer exists!\n",a->filename)); mutt_error (_("%s no longer exists!"), a->filename); return -1; } if (a->type == TYPETEXT && (!a->noconv)) fc = fgetconv_open (fpin, a->charset, mutt_get_body_charset (send_charset, sizeof (send_charset), a), 0); else fc = fgetconv_open (fpin, 0, 0, 0); if (a->encoding == ENCQUOTEDPRINTABLE) encode_quoted (fc, f, write_as_text_part (a)); else if (a->encoding == ENCBASE64) encode_base64 (fc, f, write_as_text_part (a)); else if (a->type == TYPETEXT && (!a->noconv)) encode_8bit (fc, f, write_as_text_part (a)); else mutt_copy_stream (fpin, f); fgetconv_close (&fc); safe_fclose (&fpin); return (ferror (f) ? -1 : 0); } #undef write_as_text_part #define BOUNDARYLEN 16 void mutt_generate_boundary (PARAMETER **parm) { char rs[BOUNDARYLEN + 1]; mutt_base64_random96(rs); mutt_set_parameter ("boundary", rs, parm); } typedef struct { int from; int whitespace; int dot; int linelen; int was_cr; } CONTENT_STATE; static void update_content_info (CONTENT *info, CONTENT_STATE *s, char *d, size_t dlen) { int from = s->from; int whitespace = s->whitespace; int dot = s->dot; int linelen = s->linelen; int was_cr = s->was_cr; if (!d) /* This signals EOF */ { if (was_cr) info->binary = 1; if (linelen > info->linemax) info->linemax = linelen; return; } for (; dlen; d++, dlen--) { char ch = *d; if (was_cr) { was_cr = 0; if (ch != '\n') { info->binary = 1; } else { if (whitespace) info->space = 1; if (dot) info->dot = 1; if (linelen > info->linemax) info->linemax = linelen; whitespace = 0; dot = 0; linelen = 0; continue; } } linelen++; if (ch == '\n') { info->crlf++; if (whitespace) info->space = 1; if (dot) info->dot = 1; if (linelen > info->linemax) info->linemax = linelen; whitespace = 0; linelen = 0; dot = 0; } else if (ch == '\r') { info->crlf++; info->cr = 1; was_cr = 1; continue; } else if (ch & 0x80) info->hibin++; else if (ch == '\t' || ch == '\f') { info->ascii++; whitespace++; } else if (ch == 0) { info->nulbin++; info->lobin++; } else if (ch < 32 || ch == 127) info->lobin++; else { if (linelen == 1) { if ((ch == 'F') || (ch == 'f')) from = 1; else from = 0; if (ch == '.') dot = 1; else dot = 0; } else if (from) { if (linelen == 2 && ch != 'r') from = 0; else if (linelen == 3 && ch != 'o') from = 0; else if (linelen == 4) { if (ch == 'm') info->from = 1; from = 0; } } if (ch == ' ') whitespace++; info->ascii++; } if (linelen > 1) dot = 0; if (ch != ' ' && ch != '\t') whitespace = 0; } s->from = from; s->whitespace = whitespace; s->dot = dot; s->linelen = linelen; s->was_cr = was_cr; } /* Define as 1 if iconv sometimes returns -1(EILSEQ) instead of transcribing. */ #define BUGGY_ICONV 1 /* * Find the best charset conversion of the file from fromcode into one * of the tocodes. If successful, set *tocode and CONTENT *info and * return the number of characters converted inexactly. If no * conversion was possible, return -1. * * We convert via UTF-8 in order to avoid the condition -1(EINVAL), * which would otherwise prevent us from knowing the number of inexact * conversions. Where the candidate target charset is UTF-8 we avoid * doing the second conversion because iconv_open("UTF-8", "UTF-8") * fails with some libraries. * * We assume that the output from iconv is never more than 4 times as * long as the input for any pair of charsets we might be interested * in. */ static size_t convert_file_to (FILE *file, const char *fromcode, int ncodes, const char **tocodes, int *tocode, CONTENT *info) { #ifdef HAVE_ICONV iconv_t cd1, *cd; char bufi[256], bufu[512], bufo[4 * sizeof (bufi)]; ICONV_CONST char *ib, *ub; char *ob; size_t ibl, obl, ubl, ubl1, n, ret; int i; CONTENT *infos; CONTENT_STATE *states; size_t *score; cd1 = mutt_iconv_open ("utf-8", fromcode, 0); if (cd1 == (iconv_t)(-1)) return -1; cd = safe_calloc (ncodes, sizeof (iconv_t)); score = safe_calloc (ncodes, sizeof (size_t)); states = safe_calloc (ncodes, sizeof (CONTENT_STATE)); infos = safe_calloc (ncodes, sizeof (CONTENT)); for (i = 0; i < ncodes; i++) if (ascii_strcasecmp (tocodes[i], "utf-8")) cd[i] = mutt_iconv_open (tocodes[i], "utf-8", 0); else /* Special case for conversion to UTF-8 */ cd[i] = (iconv_t)(-1), score[i] = (size_t)(-1); rewind (file); ibl = 0; for (;;) { /* Try to fill input buffer */ n = fread (bufi + ibl, 1, sizeof (bufi) - ibl, file); ibl += n; /* Convert to UTF-8 */ ib = bufi; ob = bufu, obl = sizeof (bufu); n = iconv (cd1, ibl ? &ib : 0, &ibl, &ob, &obl); assert (n == (size_t)(-1) || !n || ICONV_NONTRANS); if (n == (size_t)(-1) && ((errno != EINVAL && errno != E2BIG) || ib == bufi)) { assert (errno == EILSEQ || (errno == EINVAL && ib == bufi && ibl < sizeof (bufi))); ret = (size_t)(-1); break; } ubl1 = ob - bufu; /* Convert from UTF-8 */ for (i = 0; i < ncodes; i++) if (cd[i] != (iconv_t)(-1) && score[i] != (size_t)(-1)) { ub = bufu, ubl = ubl1; ob = bufo, obl = sizeof (bufo); n = iconv (cd[i], (ibl || ubl) ? &ub : 0, &ubl, &ob, &obl); if (n == (size_t)(-1)) { assert (errno == E2BIG || (BUGGY_ICONV && (errno == EILSEQ || errno == ENOENT))); score[i] = (size_t)(-1); } else { score[i] += n; update_content_info (&infos[i], &states[i], bufo, ob - bufo); } } else if (cd[i] == (iconv_t)(-1) && score[i] == (size_t)(-1)) /* Special case for conversion to UTF-8 */ update_content_info (&infos[i], &states[i], bufu, ubl1); if (ibl) /* Save unused input */ memmove (bufi, ib, ibl); else if (!ubl1 && ib < bufi + sizeof (bufi)) { ret = 0; break; } } if (!ret) { /* Find best score */ ret = (size_t)(-1); for (i = 0; i < ncodes; i++) { if (cd[i] == (iconv_t)(-1) && score[i] == (size_t)(-1)) { /* Special case for conversion to UTF-8 */ *tocode = i; ret = 0; break; } else if (cd[i] == (iconv_t)(-1) || score[i] == (size_t)(-1)) continue; else if (ret == (size_t)(-1) || score[i] < ret) { *tocode = i; ret = score[i]; if (!ret) break; } } if (ret != (size_t)(-1)) { memcpy (info, &infos[*tocode], sizeof(CONTENT)); update_content_info (info, &states[*tocode], 0, 0); /* EOF */ } } for (i = 0; i < ncodes; i++) if (cd[i] != (iconv_t)(-1)) iconv_close (cd[i]); iconv_close (cd1); FREE (&cd); FREE (&infos); FREE (&score); FREE (&states); return ret; #else return -1; #endif /* !HAVE_ICONV */ } /* * Find the first of the fromcodes that gives a valid conversion and * the best charset conversion of the file into one of the tocodes. If * successful, set *fromcode and *tocode to dynamically allocated * strings, set CONTENT *info, and return the number of characters * converted inexactly. If no conversion was possible, return -1. * * Both fromcodes and tocodes may be colon-separated lists of charsets. * However, if fromcode is zero then fromcodes is assumed to be the * name of a single charset even if it contains a colon. */ static size_t convert_file_from_to (FILE *file, const char *fromcodes, const char *tocodes, char **fromcode, char **tocode, CONTENT *info) { char *fcode = NULL; char **tcode; const char *c, *c1; size_t ret; int ncodes, i, cn; /* Count the tocodes */ ncodes = 0; for (c = tocodes; c; c = c1 ? c1 + 1 : 0) { if ((c1 = strchr (c, ':')) == c) continue; ++ncodes; } /* Copy them */ tcode = safe_malloc (ncodes * sizeof (char *)); for (c = tocodes, i = 0; c; c = c1 ? c1 + 1 : 0, i++) { if ((c1 = strchr (c, ':')) == c) continue; tcode[i] = mutt_substrdup (c, c1); } ret = (size_t)(-1); if (fromcode) { /* Try each fromcode in turn */ for (c = fromcodes; c; c = c1 ? c1 + 1 : 0) { if ((c1 = strchr (c, ':')) == c) continue; fcode = mutt_substrdup (c, c1); ret = convert_file_to (file, fcode, ncodes, (const char **)tcode, &cn, info); if (ret != (size_t)(-1)) { *fromcode = fcode; *tocode = tcode[cn]; tcode[cn] = 0; break; } FREE (&fcode); } } else { /* There is only one fromcode */ ret = convert_file_to (file, fromcodes, ncodes, (const char **)tcode, &cn, info); if (ret != (size_t)(-1)) { *tocode = tcode[cn]; tcode[cn] = 0; } } /* Free memory */ for (i = 0; i < ncodes; i++) FREE (&tcode[i]); FREE (&tcode); return ret; } /* * Analyze the contents of a file to determine which MIME encoding to use. * Also set the body charset, sometimes, or not. */ CONTENT *mutt_get_content_info (const char *fname, BODY *b) { CONTENT *info; CONTENT_STATE state; FILE *fp = NULL; char *fromcode = NULL; char *tocode; char buffer[100]; char chsbuf[STRING]; size_t r; struct stat sb; if (b && !fname) fname = b->filename; if (stat (fname, &sb) == -1) { mutt_error (_("Can't stat %s: %s"), fname, strerror (errno)); return NULL; } if (!S_ISREG(sb.st_mode)) { mutt_error (_("%s isn't a regular file."), fname); return NULL; } if ((fp = fopen (fname, "r")) == NULL) { dprint (1, (debugfile, "mutt_get_content_info: %s: %s (errno %d).\n", fname, strerror (errno), errno)); return (NULL); } info = safe_calloc (1, sizeof (CONTENT)); memset (&state, 0, sizeof (state)); if (b != NULL && b->type == TYPETEXT && (!b->noconv && !b->force_charset)) { char *chs = mutt_get_parameter ("charset", b->parameter); char *fchs = b->use_disp ? (AttachCharset ? AttachCharset : Charset) : Charset; if (Charset && (chs || SendCharset) && convert_file_from_to (fp, fchs, chs ? chs : SendCharset, &fromcode, &tocode, info) != (size_t)(-1)) { if (!chs) { mutt_canonical_charset (chsbuf, sizeof (chsbuf), tocode); mutt_set_parameter ("charset", chsbuf, &b->parameter); } FREE (&b->charset); b->charset = fromcode; FREE (&tocode); safe_fclose (&fp); return info; } } rewind (fp); while ((r = fread (buffer, 1, sizeof(buffer), fp))) update_content_info (info, &state, buffer, r); update_content_info (info, &state, 0, 0); safe_fclose (&fp); if (b != NULL && b->type == TYPETEXT && (!b->noconv && !b->force_charset)) mutt_set_parameter ("charset", (!info->hibin ? "us-ascii" : Charset && !mutt_is_us_ascii (Charset) ? Charset : "unknown-8bit"), &b->parameter); return info; } /* Given a file with path ``s'', see if there is a registered MIME type. * returns the major MIME type, and copies the subtype to ``d''. First look * for ~/.mime.types, then look in a system mime.types if we can find one. * The longest match is used so that we can match `ps.gz' when `gz' also * exists. */ int mutt_lookup_mime_type (BODY *att, const char *path) { FILE *f; char *p, *q, *ct; char buf[LONG_STRING]; char subtype[STRING], xtype[STRING]; int count; int szf, sze, cur_sze; int type; *subtype = '\0'; *xtype = '\0'; type = TYPEOTHER; cur_sze = 0; szf = mutt_strlen (path); for (count = 0 ; count < 3 ; count++) { /* * can't use strtok() because we use it in an inner loop below, so use * a switch statement here instead. */ switch (count) { case 0: snprintf (buf, sizeof (buf), "%s/.mime.types", NONULL(Homedir)); break; case 1: strfcpy (buf, SYSCONFDIR"/mime.types", sizeof(buf)); break; case 2: strfcpy (buf, PKGDATADIR"/mime.types", sizeof (buf)); break; default: dprint (1, (debugfile, "mutt_lookup_mime_type: Internal error, count = %d.\n", count)); goto bye; /* shouldn't happen */ } if ((f = fopen (buf, "r")) != NULL) { while (fgets (buf, sizeof (buf) - 1, f) != NULL) { /* weed out any comments */ if ((p = strchr (buf, '#'))) *p = 0; /* remove any leading space. */ ct = buf; SKIPWS (ct); /* position on the next field in this line */ if ((p = strpbrk (ct, " \t")) == NULL) continue; *p++ = 0; SKIPWS (p); /* cycle through the file extensions */ while ((p = strtok (p, " \t\n"))) { sze = mutt_strlen (p); if ((sze > cur_sze) && (szf >= sze) && (mutt_strcasecmp (path + szf - sze, p) == 0 || ascii_strcasecmp (path + szf - sze, p) == 0) && (szf == sze || path[szf - sze - 1] == '.')) { /* get the content-type */ if ((p = strchr (ct, '/')) == NULL) { /* malformed line, just skip it. */ break; } *p++ = 0; for (q = p; *q && !ISSPACE (*q); q++) ; mutt_substrcpy (subtype, p, q, sizeof (subtype)); if ((type = mutt_check_mime_type (ct)) == TYPEOTHER) strfcpy (xtype, ct, sizeof (xtype)); cur_sze = sze; } p = NULL; } } safe_fclose (&f); } } bye: if (type != TYPEOTHER || *xtype != '\0') { att->type = type; mutt_str_replace (&att->subtype, subtype); mutt_str_replace (&att->xtype, xtype); } return (type); } void mutt_message_to_7bit (BODY *a, FILE *fp) { BUFFER *temp = NULL; FILE *fpin = NULL; FILE *fpout = NULL; struct stat sb; if (!a->filename && fp) fpin = fp; else if (!a->filename || !(fpin = fopen (a->filename, "r"))) { mutt_error (_("Could not open %s"), a->filename ? a->filename : "(null)"); return; } else { a->offset = 0; if (stat (a->filename, &sb) == -1) { mutt_perror ("stat"); safe_fclose (&fpin); goto cleanup; } a->length = sb.st_size; } /* Avoid buffer pool due to recursion */ temp = mutt_buffer_new (); mutt_buffer_mktemp (temp); if (!(fpout = safe_fopen (mutt_b2s (temp), "w+"))) { mutt_perror ("fopen"); goto cleanup; } fseeko (fpin, a->offset, 0); a->parts = mutt_parse_messageRFC822 (fpin, a); transform_to_7bit (a->parts, fpin); mutt_copy_hdr (fpin, fpout, a->offset, a->offset + a->length, CH_MIME | CH_NONEWLINE | CH_XMIT, NULL); fputs ("MIME-Version: 1.0\n", fpout); mutt_write_mime_header (a->parts, fpout); fputc ('\n', fpout); mutt_write_mime_body (a->parts, fpout); if (fpin != fp) safe_fclose (&fpin); safe_fclose (&fpout); a->encoding = ENC7BIT; FREE (&a->d_filename); a->d_filename = a->filename; if (a->filename && a->unlink) unlink (a->filename); a->filename = safe_strdup (mutt_b2s (temp)); a->unlink = 1; if (stat (a->filename, &sb) == -1) { mutt_perror ("stat"); goto cleanup; } a->length = sb.st_size; mutt_free_body (&a->parts); a->hdr->content = NULL; cleanup: if (fpin && fpin != fp) safe_fclose (&fpin); if (fpout) { safe_fclose (&fpout); mutt_unlink (mutt_b2s (temp)); } mutt_buffer_free (&temp); } static void transform_to_7bit (BODY *a, FILE *fpin) { BUFFER *buff; STATE s; struct stat sb; memset (&s, 0, sizeof (s)); for (; a; a = a->next) { if (a->type == TYPEMULTIPART) { if (a->encoding != ENC7BIT) a->encoding = ENC7BIT; transform_to_7bit (a->parts, fpin); } else if (mutt_is_message_type(a->type, a->subtype)) { mutt_message_to_7bit (a, fpin); } else { a->noconv = 1; a->force_charset = 1; /* Because of the potential recursion in message types, we * restrict the lifetime of the buffer tightly */ buff = mutt_buffer_pool_get (); mutt_buffer_mktemp (buff); if ((s.fpout = safe_fopen (mutt_b2s (buff), "w")) == NULL) { mutt_perror ("fopen"); mutt_buffer_pool_release (&buff); return; } s.fpin = fpin; mutt_decode_attachment (a, &s); safe_fclose (&s.fpout); FREE (&a->d_filename); a->d_filename = a->filename; a->filename = safe_strdup (mutt_b2s (buff)); mutt_buffer_pool_release (&buff); a->unlink = 1; if (stat (a->filename, &sb) == -1) { mutt_perror ("stat"); return; } a->length = sb.st_size; mutt_update_encoding (a); if (a->encoding == ENC8BIT) a->encoding = ENCQUOTEDPRINTABLE; else if (a->encoding == ENCBINARY) a->encoding = ENCBASE64; } } } /* determine which Content-Transfer-Encoding to use */ static void mutt_set_encoding (BODY *b, CONTENT *info) { char send_charset[SHORT_STRING]; if (b->type == TYPETEXT) { char *chsname = mutt_get_body_charset (send_charset, sizeof (send_charset), b); if ((info->lobin && ascii_strncasecmp (chsname, "iso-2022", 8)) || info->linemax > 990 || (info->from && option (OPTENCODEFROM))) b->encoding = ENCQUOTEDPRINTABLE; else if (info->hibin) b->encoding = option (OPTALLOW8BIT) ? ENC8BIT : ENCQUOTEDPRINTABLE; else b->encoding = ENC7BIT; } else if (b->type == TYPEMESSAGE || b->type == TYPEMULTIPART) { if (info->lobin || info->hibin) { if (option (OPTALLOW8BIT) && !info->lobin) b->encoding = ENC8BIT; else mutt_message_to_7bit (b, NULL); } else b->encoding = ENC7BIT; } else if (b->type == TYPEAPPLICATION && ascii_strcasecmp (b->subtype, "pgp-keys") == 0) b->encoding = ENC7BIT; else { /* Determine which encoding is smaller */ if (1.33 * (float)(info->lobin+info->hibin+info->ascii) < 3.0 * (float)(info->lobin + info->hibin) + (float)info->ascii) b->encoding = ENCBASE64; else b->encoding = ENCQUOTEDPRINTABLE; } } void mutt_stamp_attachment(BODY *a) { a->stamp = time(NULL); } /* Get a body's character set */ char *mutt_get_body_charset (char *d, size_t dlen, BODY *b) { char *p = NULL; if (b && b->type != TYPETEXT) return NULL; if (b) p = mutt_get_parameter ("charset", b->parameter); if (p) mutt_canonical_charset (d, dlen, NONULL(p)); else strfcpy (d, "us-ascii", dlen); return d; } /* Assumes called from send mode where BODY->filename points to actual file */ void mutt_update_encoding (BODY *a) { CONTENT *info; char chsbuff[STRING]; /* override noconv when it's us-ascii */ if (mutt_is_us_ascii (mutt_get_body_charset (chsbuff, sizeof (chsbuff), a))) a->noconv = 0; if (!a->force_charset && !a->noconv) mutt_delete_parameter ("charset", &a->parameter); if ((info = mutt_get_content_info (a->filename, a)) == NULL) return; mutt_set_encoding (a, info); mutt_stamp_attachment(a); FREE (&a->content); a->content = info; } BODY *mutt_make_message_attach (CONTEXT *ctx, HEADER *hdr, int attach_msg) { BUFFER *buffer = NULL; BODY *body; FILE *fp; int cmflags, chflags; int pgp = WithCrypto? hdr->security : 0; int copy_rc, try_decode = 0, try_decrypt = 0; /* If we are attaching a message, ignore OPTMIMEFORWDECODE */ if (!attach_msg && option (OPTMIMEFORWDECODE)) try_decode = 1; else if (WithCrypto && (hdr->security & ENCRYPT) && /* L10N: Prompt for $forward_decrypt when attaching or forwarding a message */ (query_quadoption (OPT_FORWDECRYPT, _("Decrypt message attachment?")) == MUTT_YES)) try_decrypt = 1; if (WithCrypto) { if ((hdr->security & ENCRYPT) && (try_decode || try_decrypt)) { if (!crypt_valid_passphrase(hdr->security)) return (NULL); } } retry: buffer = mutt_buffer_pool_get (); mutt_buffer_mktemp (buffer); if ((fp = safe_fopen (mutt_b2s (buffer), "w+")) == NULL) { mutt_buffer_pool_release (&buffer); return NULL; } body = mutt_new_body (); body->type = TYPEMESSAGE; body->subtype = safe_strdup ("rfc822"); body->filename = safe_strdup (mutt_b2s (buffer)); body->unlink = 1; body->use_disp = 0; body->disposition = DISPINLINE; body->noconv = 1; mutt_buffer_pool_release (&buffer); mutt_parse_mime_message (ctx, hdr); chflags = CH_XMIT; cmflags = 0; if (try_decode) { chflags |= CH_MIME | CH_TXTPLAIN; cmflags = MUTT_CM_DECODE | MUTT_CM_CHARCONV; if ((WithCrypto & APPLICATION_PGP)) pgp &= ~PGPENCRYPT; if ((WithCrypto & APPLICATION_SMIME)) pgp &= ~SMIMEENCRYPT; } else if (try_decrypt) { if ((WithCrypto & APPLICATION_PGP) && mutt_is_multipart_encrypted (hdr->content)) { chflags |= CH_MIME | CH_NONEWLINE; cmflags = MUTT_CM_DECODE_PGP; pgp &= ~PGPENCRYPT; } else if ((WithCrypto & APPLICATION_PGP) && ((mutt_is_application_pgp (hdr->content) & PGPENCRYPT) == PGPENCRYPT)) { chflags |= CH_MIME | CH_TXTPLAIN; cmflags = MUTT_CM_DECODE | MUTT_CM_CHARCONV; pgp &= ~PGPENCRYPT; } else if ((WithCrypto & APPLICATION_SMIME) && ((mutt_is_application_smime (hdr->content) & SMIMEENCRYPT) == SMIMEENCRYPT)) { chflags |= CH_MIME | CH_TXTPLAIN; cmflags = MUTT_CM_DECODE | MUTT_CM_CHARCONV; pgp &= ~SMIMEENCRYPT; } } copy_rc = mutt_copy_message (fp, ctx, hdr, cmflags, chflags); if ((copy_rc != 0) && (try_decode || try_decrypt)) { mutt_clear_error (); if ((try_decode && /* L10N: Prompt when forwarding a message with $mime_forward_decode set, and there was a problem decoding the message. If they answer yes the message will be forwarded without decoding. */ (mutt_yesorno (_("There was a problem decoding the message for attachment. Try again with decoding turned off?"), MUTT_YES) == MUTT_YES)) || (try_decrypt && /* L10N: Prompt when attaching or forwarding a message with $forward_decrypt set, and there was a problem decrypting the message. If they answer yes the message will be attached without decrypting it. */ (mutt_yesorno (_("There was a problem decrypting the message for attachment. Try again with decryption turned off?"), MUTT_YES) == MUTT_YES))) { safe_fclose (&fp); mutt_free_body (&body); try_decode = 0; try_decrypt = 0; goto retry; } } if (copy_rc < 0) { safe_fclose (&fp); mutt_free_body (&body); return NULL; } fflush(fp); rewind(fp); body->hdr = mutt_new_header(); body->hdr->offset = 0; /* we don't need the user headers here */ body->hdr->env = mutt_read_rfc822_header(fp, body->hdr, 0, 0); if (WithCrypto) body->hdr->security = pgp; mutt_update_encoding (body); body->parts = body->hdr->content; safe_fclose (&fp); return (body); } BODY *mutt_run_send_alternative_filter (BODY *b) { BUFFER *alt_file = NULL; FILE *b_fp = NULL, *alt_fp = NULL; FILE *filter_in = NULL, *filter_out = NULL, *filter_err = NULL; BODY *alternative = NULL; pid_t thepid = 0; char *mime = NULL; char *buf = NULL; size_t buflen; if (!SendMultipartAltFilter) return NULL; if ((b_fp = safe_fopen (b->filename, "r")) == NULL) { mutt_perror (b->filename); goto cleanup; } alt_file = mutt_buffer_pool_get (); mutt_buffer_mktemp (alt_file); if ((alt_fp = safe_fopen (mutt_b2s (alt_file), "w")) == NULL) { mutt_perror (mutt_b2s (alt_file)); goto cleanup; } if ((thepid = mutt_create_filter (SendMultipartAltFilter, &filter_in, &filter_out, &filter_err)) < 0) { mutt_error (_("Error running \"%s\"!"), SendMultipartAltFilter); goto cleanup; } mutt_copy_stream (b_fp, filter_in); safe_fclose (&b_fp); safe_fclose (&filter_in); mime = mutt_read_line (NULL, &buflen, filter_out, NULL, 0); if (!mime || !strchr (mime, '/')) { /* L10N: The first line of output from $send_multipart_alternative_filter should be a mime type, e.g. text/html. This error is generated if that is missing. */ mutt_error (_("Missing mime type from output of \"%s\"!"), SendMultipartAltFilter); goto cleanup; } buf = mutt_read_line (NULL, &buflen, filter_out, NULL, 0); if (!buf || mutt_strlen (buf)) { /* L10N: The second line of output from $send_multipart_alternative_filter should be a blank line. This error is generated if the blank line is missing. */ mutt_error (_("Missing blank line separator from output of \"%s\"!"), SendMultipartAltFilter); goto cleanup; } mutt_copy_stream (filter_out, alt_fp); safe_fclose (&filter_out); safe_fclose (&filter_err); if (mutt_wait_filter (thepid) != 0) { mutt_error (_("Error running \"%s\"!"), SendMultipartAltFilter); thepid = 0; goto cleanup; } thepid = 0; safe_fclose (&alt_fp); alternative = mutt_new_body (); alternative->filename = safe_strdup (mutt_b2s (alt_file)); alternative->unlink = 1; alternative->use_disp = 0; alternative->disposition = DISPINLINE; mutt_parse_content_type (mime, alternative); if (alternative->type == TYPEMULTIPART) { /* L10N: Some clever people may try to generate a multipart/mixed "alternative" using $send_multipart_alternative_filter. The actual sending for this will not work, because the data structures will not be properly generated. To preempt bug reports, this error is displayed, and the generation is blocked at the filter level. */ mutt_error _("$send_multipart_alternative_filter does not support multipart type generation."); mutt_free_body (&alternative); goto cleanup; } mutt_update_encoding (alternative); cleanup: safe_fclose (&b_fp); if (alt_fp) { safe_fclose (&alt_fp); mutt_unlink (mutt_b2s (alt_file)); } mutt_buffer_pool_release (&alt_file); safe_fclose (&filter_in); safe_fclose (&filter_out); safe_fclose (&filter_err); if (thepid > 0) mutt_wait_filter (thepid); FREE (&buf); FREE (&mime); return alternative; } static void run_mime_type_query (BODY *att) { FILE *fp, *fperr; BUFFER *cmd = NULL; char *buf = NULL; size_t buflen; int dummy = 0; pid_t thepid; cmd = mutt_buffer_pool_get (); mutt_expand_file_fmt (cmd, MimeTypeQueryCmd, att->filename); if ((thepid = mutt_create_filter (mutt_b2s (cmd), NULL, &fp, &fperr)) < 0) { mutt_error (_("Error running \"%s\"!"), mutt_b2s (cmd)); mutt_buffer_pool_release (&cmd); return; } mutt_buffer_pool_release (&cmd); if ((buf = mutt_read_line (buf, &buflen, fp, &dummy, 0)) != NULL) { if (strchr(buf, '/')) mutt_parse_content_type (buf, att); FREE (&buf); } safe_fclose (&fp); safe_fclose (&fperr); mutt_wait_filter (thepid); } BODY *mutt_make_file_attach (const char *path) { BODY *att; CONTENT *info; att = mutt_new_body (); att->filename = safe_strdup (path); if (MimeTypeQueryCmd && option (OPTMIMETYPEQUERYFIRST)) run_mime_type_query (att); /* Attempt to determine the appropriate content-type based on the filename * suffix. */ if (!att->subtype) mutt_lookup_mime_type (att, path); if (!att->subtype && MimeTypeQueryCmd && !option (OPTMIMETYPEQUERYFIRST)) run_mime_type_query (att); if ((info = mutt_get_content_info (path, att)) == NULL) { mutt_free_body (&att); return NULL; } if (!att->subtype) { if ((info->nulbin == 0) && (info->lobin == 0 || (info->lobin + info->hibin + info->ascii)/ info->lobin >= 10)) { /* * Statistically speaking, there should be more than 10% "lobin" * chars if this is really a binary file... */ att->type = TYPETEXT; att->subtype = safe_strdup ("plain"); } else { att->type = TYPEAPPLICATION; att->subtype = safe_strdup ("octet-stream"); } } FREE(&info); mutt_update_encoding (att); return (att); } static int get_toplevel_encoding (BODY *a) { int e = ENC7BIT; for (; a; a = a->next) { if (a->encoding == ENCBINARY) return (ENCBINARY); else if (a->encoding == ENC8BIT) e = ENC8BIT; } return (e); } /* check for duplicate boundary. return 1 if duplicate */ static int mutt_check_boundary (const char* boundary, BODY *b) { char* p; if (b->parts && mutt_check_boundary (boundary, b->parts)) return 1; if (b->next && mutt_check_boundary (boundary, b->next)) return 1; if ((p = mutt_get_parameter ("boundary", b->parameter)) && !ascii_strcmp (p, boundary)) return 1; return 0; } static BODY *mutt_make_multipart (BODY *b, const char *subtype) { BODY *new; new = mutt_new_body (); new->type = TYPEMULTIPART; new->subtype = safe_strdup (subtype); new->encoding = get_toplevel_encoding (b); do { mutt_generate_boundary (&new->parameter); if (mutt_check_boundary (mutt_get_parameter ("boundary", new->parameter), b)) mutt_delete_parameter ("boundary", &new->parameter); } while (!mutt_get_parameter ("boundary", new->parameter)); new->use_disp = 0; new->disposition = DISPINLINE; new->parts = b; return new; } /* remove the multipart body if it exists */ BODY *mutt_remove_multipart (BODY *b) { BODY *t; if (b->parts) { t = b; b = b->parts; t->parts = NULL; mutt_free_body (&t); } return b; } BODY *mutt_make_multipart_mixed (BODY *b) { return mutt_make_multipart (b, "mixed"); } /* remove the multipart/mixed body if it exists */ BODY *mutt_remove_multipart_mixed (BODY *b) { if ((b->type == TYPEMULTIPART) && !ascii_strcasecmp (b->subtype, "mixed")) return mutt_remove_multipart (b); return b; } BODY *mutt_make_multipart_alternative (BODY *b, BODY *alternative) { BODY *attachments, *mp; attachments = b->next; b->next = alternative; mp = mutt_make_multipart (b, "alternative"); mp->next = attachments; return mp; } BODY *mutt_remove_multipart_alternative (BODY *b) { BODY *attachments; if ((b->type != TYPEMULTIPART) || ascii_strcasecmp (b->subtype, "alternative")) return b; attachments = b->next; b->next = NULL; b = mutt_remove_multipart (b); mutt_free_body (&b->next); b->next = attachments; return b; } /* Appends the date to the passed in buffer. * The buffer is not cleared because some callers prepend quotes. */ void mutt_make_date (BUFFER *s) { time_t t = time (NULL); struct tm *l; time_t tz = 0; if (option (OPTLOCALDATEHEADER)) { l = localtime (&t); tz = mutt_local_tz (t); } else { l = gmtime (&t); } tz /= 60; mutt_buffer_add_printf (s, "%s, %d %s %d %02d:%02d:%02d %+03d%02d", Weekdays[l->tm_wday], l->tm_mday, Months[l->tm_mon], l->tm_year + 1900, l->tm_hour, l->tm_min, l->tm_sec, (int) tz / 60, (int) abs ((int) tz) % 60); } /* wrapper around mutt_write_address() so we can handle very large recipient lists without needing a huge temporary buffer in memory */ void mutt_write_address_list (ADDRESS *adr, FILE *fp, int linelen, int display) { ADDRESS *tmp, *prev; char buf[LONG_STRING]; int count = 0; int len; while (adr) { tmp = adr->next; adr->next = NULL; buf[0] = 0; rfc822_write_address (buf, sizeof (buf), adr, display); len = mutt_strlen (buf); if (count && linelen + len > 74) { fputs ("\n\t", fp); linelen = len + 8; /* tab is usually about 8 spaces... */ } else { if (count && !prev->group && adr->mailbox) { fputc (' ', fp); linelen++; } linelen += len; } fputs (buf, fp); adr->next = tmp; if (!adr->group && adr->next && adr->next->mailbox) { linelen++; fputc (',', fp); } prev = adr; adr = adr->next; count++; } fputc ('\n', fp); } /* arbitrary number of elements to grow the array by */ #define REF_INC 16 /* need to write the list in reverse because they are stored in reverse order * when parsed to speed up threading */ void mutt_write_references (LIST *r, FILE *f, int trim) { LIST **ref = NULL; int refcnt = 0, refmax = 0; for ( ; (trim == 0 || refcnt < trim) && r ; r = r->next) { if (refcnt == refmax) safe_realloc (&ref, (refmax += REF_INC) * sizeof (LIST *)); ref[refcnt++] = r; } while (refcnt-- > 0) { fputc (' ', f); fputs (ref[refcnt]->data, f); if (refcnt >= 1) fputc ('\n', f); } FREE (&ref); } static const char *find_word (const char *src) { const char *p = src; while (p && *p && strchr (" \t\n", *p)) p++; while (p && *p && !strchr (" \t\n", *p)) p++; return p; } /* like wcwidth(), but gets const char* not wchar_t* */ static int my_width (const char *str, int col, int flags) { wchar_t wc; int l, w = 0, nl = 0; const char *p = str; while (p && *p) { if (mbtowc (&wc, p, MB_CUR_MAX) >= 0) { l = wcwidth (wc); if (l < 0) l = 1; /* correctly calc tab stop, even for sending as the * line should look pretty on the receiving end */ if (wc == L'\t' || (nl && wc == L' ')) { nl = 0; l = 8 - (col % 8); } /* track newlines for display-case: if we have a space * after a newline, assume 8 spaces as for display we * always tab-fold */ else if ((flags & CH_DISPLAY) && wc == '\n') nl = 1; } else l = 1; w += l; p++; } return w; } static int print_val (FILE *fp, const char *pfx, const char *value, int flags, size_t col) { while (value && *value) { if (fputc (*value, fp) == EOF) return -1; /* corner-case: break words longer than 998 chars by force, * mandated by RfC5322 */ if (!(flags & CH_DISPLAY) && ++col >= 998) { if (fputs ("\n ", fp) < 0) return -1; col = 1; } if (*value == '\n') { if (*(value + 1) && pfx && *pfx && fputs (pfx, fp) == EOF) return -1; /* for display, turn folding spaces into folding tabs */ if ((flags & CH_DISPLAY) && (*(value + 1) == ' ' || *(value + 1) == '\t')) { value++; while (*value && (*value == ' ' || *value == '\t')) value++; if (fputc ('\t', fp) == EOF) return -1; continue; } } value++; } return 0; } static int fold_one_header (FILE *fp, const char *tag, const char *value, const char *pfx, int wraplen, int flags) { const char *p = value, *next, *sp; char buf[HUGE_STRING] = ""; int first = 1, enc, col = 0, w, l = 0, fold; dprint(4,(debugfile,"mwoh: pfx=[%s], tag=[%s], flags=%d value=[%s]\n", NONULL (pfx), tag, flags, value)); if (tag && *tag && fprintf (fp, "%s%s: ", NONULL (pfx), tag) < 0) return -1; col = mutt_strlen (tag) + (tag && *tag ? 2 : 0) + mutt_strlen (pfx); while (p && *p) { fold = 0; /* find the next word and place it in `buf'. it may start with * whitespace we can fold before */ next = find_word (p); l = MIN(sizeof (buf) - 1, next - p); memcpy (buf, p, l); buf[l] = 0; /* determine width: character cells for display, bytes for sending * (we get pure ascii only) */ w = my_width (buf, col, flags); enc = mutt_strncmp (buf, "=?", 2) == 0; dprint(5,(debugfile,"mwoh: word=[%s], col=%d, w=%d, next=[0x0%x]\n", buf, col, w, *next)); /* insert a folding \n before the current word's lwsp except for * header name, first word on a line (word longer than wrap width) * and encoded words */ if (!first && !enc && col && col + w >= wraplen) { col = mutt_strlen (pfx); fold = 1; if (fprintf (fp, "\n%s", NONULL(pfx)) <= 0) return -1; } /* print the actual word; for display, ignore leading ws for word * and fold with tab for readability */ if ((flags & CH_DISPLAY) && fold) { char *p = buf; while (*p && (*p == ' ' || *p == '\t')) { p++; col--; } if (fputc ('\t', fp) == EOF) return -1; if (print_val (fp, pfx, p, flags, col) < 0) return -1; col += 8; } else if (print_val (fp, pfx, buf, flags, col) < 0) return -1; col += w; /* if the current word ends in \n, ignore all its trailing spaces * and reset column; this prevents us from putting only spaces (or * even none) on a line if the trailing spaces are located at our * current line width * XXX this covers ASCII space only, for display we probably * XXX want something like iswspace() here */ sp = next; while (*sp && (*sp == ' ' || *sp == '\t')) sp++; if (*sp == '\n') { next = sp; col = 0; } p = next; first = 0; } /* if we have printed something but didn't \n-terminate it, do it * except the last word we printed ended in \n already */ if (col && (l == 0 || buf[l - 1] != '\n')) if (putc ('\n', fp) == EOF) return -1; return 0; } static char *unfold_header (char *s) { char *p = s, *q = s; while (p && *p) { /* remove CRLF prior to FWSP, turn \t into ' ' */ if (*p == '\r' && *(p + 1) && *(p + 1) == '\n' && *(p + 2) && (*(p + 2) == ' ' || *(p + 2) == '\t')) { *q++ = ' '; p += 3; continue; } /* remove LF prior to FWSP, turn \t into ' ' */ else if (*p == '\n' && *(p + 1) && (*(p + 1) == ' ' || *(p + 1) == '\t')) { *q++ = ' '; p += 2; continue; } *q++ = *p++; } if (q) *q = 0; return s; } static int write_one_header (FILE *fp, int pfxw, int max, int wraplen, const char *pfx, const char *start, const char *end, int flags) { char *tagbuf, *valbuf, *t; int is_from = ((end - start) > 5 && ascii_strncasecmp (start, "from ", 5) == 0); /* only pass through folding machinery if necessary for sending, never wrap From_ headers on sending */ if (!(flags & CH_DISPLAY) && (pfxw + max <= wraplen || is_from)) { valbuf = mutt_substrdup (start, end); dprint(4,(debugfile,"mwoh: buf[%s%s] short enough, " "max width = %d <= %d\n", NONULL(pfx), valbuf, max, wraplen)); if (pfx && *pfx) if (fputs (pfx, fp) == EOF) return -1; if (!(t = strchr (valbuf, ':'))) { dprint (1, (debugfile, "mwoh: warning: header not in " "'key: value' format!\n")); return 0; } if (print_val (fp, pfx, valbuf, flags, mutt_strlen (pfx)) < 0) { FREE(&valbuf); return -1; } FREE(&valbuf); } else { t = strchr (start, ':'); if (!t || t > end) { dprint (1, (debugfile, "mwoh: warning: header not in " "'key: value' format!\n")); return 0; } if (is_from) { tagbuf = NULL; valbuf = mutt_substrdup (start, end); } else { tagbuf = mutt_substrdup (start, t); /* skip over the colon separating the header field name and value */ ++t; /* skip over any leading whitespace (WSP, as defined in RFC5322) * NOTE: skip_email_wsp() does the wrong thing here. * See tickets 3609 and 3716. */ while (*t == ' ' || *t == '\t') t++; valbuf = mutt_substrdup (t, end); } dprint(4,(debugfile,"mwoh: buf[%s%s] too long, " "max width = %d > %d\n", NONULL(pfx), valbuf, max, wraplen)); if (fold_one_header (fp, tagbuf, valbuf, pfx, wraplen, flags) < 0) return -1; FREE (&tagbuf); FREE (&valbuf); } return 0; } /* split several headers into individual ones and call write_one_header * for each one */ int mutt_write_one_header (FILE *fp, const char *tag, const char *value, const char *pfx, int wraplen, int flags) { char *p = (char *)value, *last, *line; int max = 0, w, rc = -1; int pfxw = mutt_strwidth (pfx); char *v = safe_strdup (value); if (!(flags & CH_DISPLAY) || option (OPTWEED)) v = unfold_header (v); /* when not displaying, use sane wrap value */ if (!(flags & CH_DISPLAY)) { if (WrapHeaders < 78 || WrapHeaders > 998) wraplen = 78; else wraplen = WrapHeaders; } else if (wraplen <= 0 || wraplen > MuttIndexWindow->cols) wraplen = MuttIndexWindow->cols; if (tag) { /* if header is short enough, simply print it */ if (!(flags & CH_DISPLAY) && mutt_strwidth (tag) + 2 + pfxw + mutt_strwidth (v) <= wraplen) { dprint(4,(debugfile,"mwoh: buf[%s%s: %s] is short enough\n", NONULL(pfx), tag, v)); if (fprintf (fp, "%s%s: %s\n", NONULL(pfx), tag, v) <= 0) goto out; rc = 0; goto out; } else { rc = fold_one_header (fp, tag, v, pfx, wraplen, flags); goto out; } } p = last = line = (char *)v; while (p && *p) { p = strchr (p, '\n'); /* find maximum line width in current header */ if (p) *p = 0; if ((w = my_width (line, 0, flags)) > max) max = w; if (p) *p = '\n'; if (!p) break; line = ++p; if (*p != ' ' && *p != '\t') { if (write_one_header (fp, pfxw, max, wraplen, pfx, last, p, flags) < 0) goto out; last = p; max = 0; } } if (last && *last) if (write_one_header (fp, pfxw, max, wraplen, pfx, last, p, flags) < 0) goto out; rc = 0; out: FREE (&v); return rc; } /* Note: all RFC2047 encoding should be done outside of this routine, except * for the "real name." This will allow this routine to be used more than * once, if necessary. * * Likewise, all IDN processing should happen outside of this routine. * * date can be NULL, otherwise it should be the output of * mutt_make_date(). This is passed in explicitly to prevent * accidental date setting from "garbage" in env->date. * * mode == MUTT_WRITE_HEADER_EDITHDRS => "lite" mode (used for edit_hdrs) * mode == MUTT_WRITE_HEADER_NORMAL => normal mode. write full header + MIME headers * mode == MUTT_WRITE_HEADER_FCC => fcc mode, like normal mode but for Bcc header * mode == MUTT_WRITE_HEADER_POSTPONE => write just the envelope info * mode == MUTT_WRITE_HEADER_MIME => for writing protected headers and autocrypt * * privacy != 0 => will omit any headers which may identify the user. * Output generated is suitable for being sent through * anonymous remailer chains. * * hide_protected_subject: replaces the Subject header with * $crypt_protected_headers_subject in NORMAL or POSTPONE mode. * */ int mutt_write_rfc822_header (FILE *fp, ENVELOPE *env, BODY *attach, char *date, mutt_write_header_mode mode, int privacy, int hide_protected_subject) { char buffer[LONG_STRING]; char *p, *q; LIST *tmp = env->userhdrs; int has_agent = 0; /* user defined user-agent header field exists */ if ((mode == MUTT_WRITE_HEADER_NORMAL || mode == MUTT_WRITE_HEADER_FCC || mode == MUTT_WRITE_HEADER_POSTPONE) && !privacy) { if (date) fprintf (fp, "Date: %s\n", date); else { BUFFER *datebuf = mutt_buffer_pool_get (); mutt_make_date (datebuf); fprintf (fp, "Date: %s\n", mutt_b2s (datebuf)); mutt_buffer_pool_release (&datebuf); } } /* The MIME header date is only set for protected headers, and * should only be written for that case. That is: don't generate * and print a new date with Autocrypt if protected header writing * is turned off. */ if ((mode == MUTT_WRITE_HEADER_MIME) && !privacy && date) fprintf (fp, "Date: %s\n", date); /* OPTUSEFROM is not consulted here so that we can still write a From: * field if the user sets it with the `my_hdr' command */ if (env->from && !privacy) { buffer[0] = 0; rfc822_write_address (buffer, sizeof (buffer), env->from, 0); fprintf (fp, "From: %s\n", buffer); } if (env->sender && !privacy) { buffer[0] = 0; rfc822_write_address (buffer, sizeof (buffer), env->sender, 0); fprintf (fp, "Sender: %s\n", buffer); } if (env->to) { fputs ("To: ", fp); mutt_write_address_list (env->to, fp, 4, 0); } else if (mode == MUTT_WRITE_HEADER_EDITHDRS) fputs ("To: \n", fp); if (env->cc) { fputs ("Cc: ", fp); mutt_write_address_list (env->cc, fp, 4, 0); } else if (mode == MUTT_WRITE_HEADER_EDITHDRS) fputs ("Cc: \n", fp); if (env->bcc) { if (mode == MUTT_WRITE_HEADER_POSTPONE || mode == MUTT_WRITE_HEADER_EDITHDRS || mode == MUTT_WRITE_HEADER_FCC || (mode == MUTT_WRITE_HEADER_NORMAL && option(OPTWRITEBCC))) { fputs ("Bcc: ", fp); mutt_write_address_list (env->bcc, fp, 5, 0); } } else if (mode == MUTT_WRITE_HEADER_EDITHDRS) fputs ("Bcc: \n", fp); if (env->subject) { if (hide_protected_subject && (mode == MUTT_WRITE_HEADER_NORMAL || mode == MUTT_WRITE_HEADER_FCC || mode == MUTT_WRITE_HEADER_POSTPONE)) mutt_write_one_header (fp, "Subject", ProtHdrSubject, NULL, 0, 0); else mutt_write_one_header (fp, "Subject", env->subject, NULL, 0, 0); } else if (mode == MUTT_WRITE_HEADER_EDITHDRS) fputs ("Subject: \n", fp); /* save message id if the user has set it */ if (env->message_id && !privacy) fprintf (fp, "Message-ID: %s\n", env->message_id); if (env->reply_to) { fputs ("Reply-To: ", fp); mutt_write_address_list (env->reply_to, fp, 10, 0); } else if (mode == MUTT_WRITE_HEADER_EDITHDRS) fputs ("Reply-To: \n", fp); if (env->mail_followup_to) { fputs ("Mail-Followup-To: ", fp); mutt_write_address_list (env->mail_followup_to, fp, 18, 0); } if (mode == MUTT_WRITE_HEADER_NORMAL || mode == MUTT_WRITE_HEADER_FCC || mode == MUTT_WRITE_HEADER_POSTPONE) { if (env->references) { fputs ("References:", fp); mutt_write_references (env->references, fp, 10); fputc('\n', fp); } /* Add the MIME headers */ fputs ("MIME-Version: 1.0\n", fp); mutt_write_mime_header (attach, fp); } if (env->in_reply_to) { fputs ("In-Reply-To:", fp); mutt_write_references (env->in_reply_to, fp, 0); fputc ('\n', fp); } #ifdef USE_AUTOCRYPT if (option (OPTAUTOCRYPT)) { if (mode == MUTT_WRITE_HEADER_NORMAL || mode == MUTT_WRITE_HEADER_FCC) mutt_autocrypt_write_autocrypt_header (env, fp); if (mode == MUTT_WRITE_HEADER_MIME) mutt_autocrypt_write_gossip_headers (env, fp); } #endif /* Add any user defined headers */ for (; tmp; tmp = tmp->next) { if ((p = strchr (tmp->data, ':'))) { q = p; *p = '\0'; p = skip_email_wsp(p + 1); if (!*p) { *q = ':'; continue; /* don't emit empty fields. */ } /* check to see if the user has overridden the user-agent field */ if (!ascii_strncasecmp ("user-agent", tmp->data, 10)) { has_agent = 1; if (privacy) { *q = ':'; continue; } } mutt_write_one_header (fp, tmp->data, p, NULL, 0, 0); *q = ':'; } } if ((mode == MUTT_WRITE_HEADER_NORMAL || mode == MUTT_WRITE_HEADER_FCC) && !privacy && option (OPTXMAILER) && !has_agent) { /* Add a vanity header */ fprintf (fp, "User-Agent: Mutt/%s (%s)\n", MUTT_VERSION, ReleaseDate); } return (ferror (fp) == 0 ? 0 : -1); } static void encode_headers (LIST *h) { char *tmp; char *p; int i; for (; h; h = h->next) { if (!(p = strchr (h->data, ':'))) continue; i = p - h->data; p = skip_email_wsp(p + 1); tmp = safe_strdup (p); if (!tmp) continue; rfc2047_encode_string (&tmp); safe_realloc (&h->data, mutt_strlen (h->data) + 2 + mutt_strlen (tmp) + 1); sprintf (h->data + i, ": %s", NONULL (tmp)); /* __SPRINTF_CHECKED__ */ FREE (&tmp); } } const char *mutt_fqdn(short may_hide_host) { char *p = NULL; if (Fqdn && Fqdn[0] != '@') { p = Fqdn; if (may_hide_host && option(OPTHIDDENHOST)) { if ((p = strchr(Fqdn, '.'))) p++; /* sanity check: don't hide the host if * the fqdn is something like detebe.org. */ if (!p || !strchr(p, '.')) p = Fqdn; } } return p; } static void alarm_handler (int sig) { SigAlrm = 1; } /* invoke sendmail in a subshell path (in) path to program to execute args (in) arguments to pass to program msg (in) temp file containing message to send tempfile (out) if sendmail is put in the background, this points to the temporary file containing the stdout of the child process. If it is NULL, stderr and stdout are not redirected. */ static int send_msg (const char *path, char **args, const char *msg, char **tempfile) { sigset_t set; int fd, st; pid_t pid, ppid; mutt_block_signals_system (); sigemptyset (&set); /* we also don't want to be stopped right now */ sigaddset (&set, SIGTSTP); sigprocmask (SIG_BLOCK, &set, NULL); if (SendmailWait >= 0 && tempfile) { BUFFER *tmp; tmp = mutt_buffer_pool_get (); mutt_buffer_mktemp (tmp); *tempfile = safe_strdup (mutt_b2s (tmp)); mutt_buffer_pool_release (&tmp); } if ((pid = fork ()) == 0) { struct sigaction act, oldalrm; /* save parent's ID before setsid() */ ppid = getppid (); /* we want the delivery to continue even after the main process dies, * so we put ourselves into another session right away */ setsid (); /* next we close all open files */ close (0); #if defined(OPEN_MAX) for (fd = tempfile ? 1 : 3; fd < OPEN_MAX; fd++) close (fd); #elif defined(_POSIX_OPEN_MAX) for (fd = tempfile ? 1 : 3; fd < _POSIX_OPEN_MAX; fd++) close (fd); #else if (tempfile) { close (1); close (2); } #endif /* now the second fork() */ if ((pid = fork ()) == 0) { /* "msg" will be opened as stdin */ if (open (msg, O_RDONLY, 0) < 0) { unlink (msg); _exit (S_ERR); } unlink (msg); if (SendmailWait >= 0 && tempfile && *tempfile) { /* *tempfile will be opened as stdout */ if (open (*tempfile, O_WRONLY | O_APPEND | O_CREAT | O_EXCL, 0600) < 0) _exit (S_ERR); /* redirect stderr to *tempfile too */ if (dup (1) < 0) _exit (S_ERR); } else if (tempfile) { if (open ("/dev/null", O_WRONLY | O_APPEND) < 0) /* stdout */ _exit (S_ERR); if (open ("/dev/null", O_RDWR | O_APPEND) < 0) /* stderr */ _exit (S_ERR); } /* execvpe is a glibc extension */ /* execvpe (path, args, mutt_envlist ()); */ execvp (path, args); _exit (S_ERR); } else if (pid == -1) { unlink (msg); if (tempfile) FREE (tempfile); /* __FREE_CHECKED__ */ _exit (S_ERR); } /* SendmailWait > 0: interrupt waitpid() after SendmailWait seconds * SendmailWait = 0: wait forever * SendmailWait < 0: don't wait */ if (SendmailWait > 0) { SigAlrm = 0; act.sa_handler = alarm_handler; #ifdef SA_INTERRUPT /* need to make sure waitpid() is interrupted on SIGALRM */ act.sa_flags = SA_INTERRUPT; #else act.sa_flags = 0; #endif sigemptyset (&act.sa_mask); sigaction (SIGALRM, &act, &oldalrm); alarm (SendmailWait); } else if (SendmailWait < 0) _exit (0xff & EX_OK); if (waitpid (pid, &st, 0) > 0) { st = WIFEXITED (st) ? WEXITSTATUS (st) : S_ERR; if (SendmailWait && st == (0xff & EX_OK) && tempfile && *tempfile) { unlink (*tempfile); /* no longer needed */ FREE (tempfile); /* __FREE_CHECKED__ */ } } else { st = (SendmailWait > 0 && errno == EINTR && SigAlrm) ? S_BKG : S_ERR; if (SendmailWait > 0 && tempfile && *tempfile) { unlink (*tempfile); FREE (tempfile); /* __FREE_CHECKED__ */ } } /* reset alarm; not really needed, but... */ alarm (0); sigaction (SIGALRM, &oldalrm, NULL); if (kill (ppid, 0) == -1 && errno == ESRCH && tempfile && *tempfile) { /* the parent is already dead */ unlink (*tempfile); FREE (tempfile); /* __FREE_CHECKED__ */ } _exit (st); } sigprocmask (SIG_UNBLOCK, &set, NULL); if (pid != -1 && waitpid (pid, &st, 0) > 0) st = WIFEXITED (st) ? WEXITSTATUS (st) : S_ERR; /* return child status */ else st = S_ERR; /* error */ mutt_unblock_signals_system (1); return (st); } static char ** add_args (char **args, size_t *argslen, size_t *argsmax, ADDRESS *addr) { for (; addr; addr = addr->next) { /* weed out group mailboxes, since those are for display only */ if (addr->mailbox && !addr->group) { if (*argslen == *argsmax) safe_realloc (&args, (*argsmax += 5) * sizeof (char *)); args[(*argslen)++] = addr->mailbox; } } return (args); } static char ** add_option (char **args, size_t *argslen, size_t *argsmax, char *s) { if (*argslen == *argsmax) safe_realloc (&args, (*argsmax += 5) * sizeof (char *)); args[(*argslen)++] = s; return (args); } int mutt_invoke_sendmail (ADDRESS *from, /* the sender */ ADDRESS *to, ADDRESS *cc, ADDRESS *bcc, /* recips */ const char *msg, /* file containing message */ int eightbit) /* message contains 8bit chars */ { char *ps = NULL, *path = NULL, *s = safe_strdup (Sendmail), *childout = NULL; char **args = NULL; size_t argslen = 0, argsmax = 0; char **extra_args = NULL; size_t extra_argslen = 0, extra_argsmax = 0; int i; /* ensure that $sendmail is set to avoid a crash. http://dev.mutt.org/trac/ticket/3548 */ if (!s) { mutt_error(_("$sendmail must be set in order to send mail.")); return -1; } ps = s; i = 0; while ((ps = strtok (ps, " "))) { if (argslen == argsmax) safe_realloc (&args, sizeof (char *) * (argsmax += 5)); if (i) { if (!mutt_strcmp (ps, "--")) break; args[argslen++] = ps; } else { path = safe_strdup (ps); ps = strrchr (ps, '/'); if (ps) ps++; else ps = path; args[argslen++] = ps; } ps = NULL; i++; } /* If Sendmail contained a "--", we save the recipients to append to * args after other possible options added below. */ if (ps) { ps = NULL; while ((ps = strtok (ps, " "))) { if (extra_argslen == extra_argsmax) safe_realloc (&extra_args, sizeof (char *) * (extra_argsmax += 5)); extra_args[extra_argslen++] = ps; ps = NULL; } } if (eightbit && option (OPTUSE8BITMIME)) args = add_option (args, &argslen, &argsmax, "-B8BITMIME"); if (option (OPTENVFROM)) { if (EnvFrom) { args = add_option (args, &argslen, &argsmax, "-f"); args = add_args (args, &argslen, &argsmax, EnvFrom); } else if (from && !from->next) { args = add_option (args, &argslen, &argsmax, "-f"); args = add_args (args, &argslen, &argsmax, from); } } if (DsnNotify) { args = add_option (args, &argslen, &argsmax, "-N"); args = add_option (args, &argslen, &argsmax, DsnNotify); } if (DsnReturn) { args = add_option (args, &argslen, &argsmax, "-R"); args = add_option (args, &argslen, &argsmax, DsnReturn); } args = add_option (args, &argslen, &argsmax, "--"); for (i = 0; i < extra_argslen; i++) args = add_option (args, &argslen, &argsmax, extra_args[i]); args = add_args (args, &argslen, &argsmax, to); args = add_args (args, &argslen, &argsmax, cc); args = add_args (args, &argslen, &argsmax, bcc); if (argslen == argsmax) safe_realloc (&args, sizeof (char *) * (++argsmax)); args[argslen++] = NULL; /* Some user's $sendmail command uses gpg for password decryption, * and is set up to prompt using ncurses pinentry. If we * mutt_endwin() it leaves other users staring at a blank screen. * So instead, just force a hard redraw on the next refresh. */ if (!option (OPTNOCURSES)) mutt_need_hard_redraw (); if ((i = send_msg (path, args, msg, option(OPTNOCURSES) ? NULL : &childout)) != (EX_OK & 0xff)) { if (i != S_BKG) { const char *e; e = mutt_strsysexit (i); mutt_error (_("Error sending message, child exited %d (%s)."), i, NONULL (e)); if (childout) { struct stat st; if (stat (childout, &st) == 0 && st.st_size > 0) mutt_do_pager (_("Output of the delivery process"), childout, 0, NULL); } } } else if (childout) unlink (childout); FREE (&childout); FREE (&path); FREE (&s); FREE (&args); FREE (&extra_args); if (i == (EX_OK & 0xff)) i = 0; else if (i == S_BKG) i = 1; else i = -1; return (i); } /* For postponing (!final) do the necessary encodings only */ void mutt_prepare_envelope (ENVELOPE *env, int final) { char buffer[LONG_STRING]; if (final) { if (env->bcc && !(env->to || env->cc)) { /* some MTA's will put an Apparently-To: header field showing the Bcc: * recipients if there is no To: or Cc: field, so attempt to suppress * it by using an empty To: field. */ env->to = rfc822_new_address (); env->to->group = 1; env->to->next = rfc822_new_address (); buffer[0] = 0; rfc822_cat (buffer, sizeof (buffer), "undisclosed-recipients", RFC822Specials); env->to->mailbox = safe_strdup (buffer); } mutt_set_followup_to (env); if (!env->message_id) env->message_id = mutt_gen_msgid (); } /* Take care of 8-bit => 7-bit conversion. */ rfc2047_encode_envelope (env); encode_headers (env->userhdrs); } void mutt_unprepare_envelope (ENVELOPE *env) { LIST *item; for (item = env->userhdrs; item; item = item->next) rfc2047_decode (&item->data); rfc822_free_address (&env->mail_followup_to); /* back conversions */ rfc2047_decode_envelope (env); } static int _mutt_bounce_message (FILE *fp, HEADER *h, ADDRESS *to, const char *resent_from, ADDRESS *env_from) { int i, ret = 0; FILE *f; BUFFER *tempfile; MESSAGE *msg = NULL; if (!h) { /* Try to bounce each message out, aborting if we get any failures. */ for (i=0; imsgcount; i++) if (Context->hdrs[i]->tagged) ret |= _mutt_bounce_message (fp, Context->hdrs[i], to, resent_from, env_from); return ret; } /* If we failed to open a message, return with error */ if (!fp && (msg = mx_open_message (Context, h->msgno, 0)) == NULL) return -1; if (!fp) fp = msg->fp; tempfile = mutt_buffer_pool_get (); mutt_buffer_mktemp (tempfile); if ((f = safe_fopen (mutt_b2s (tempfile), "w")) != NULL) { int ch_flags = CH_XMIT | CH_NONEWLINE | CH_NOQFROM; char* msgid_str; BUFFER *date; if (!option (OPTBOUNCEDELIVERED)) ch_flags |= CH_WEED_DELIVERED; fseeko (fp, h->offset, 0); fprintf (f, "Resent-From: %s\n", resent_from); date = mutt_buffer_pool_get (); mutt_make_date (date); fprintf (f, "Resent-Date: %s\n", mutt_b2s (date)); mutt_buffer_pool_release (&date); msgid_str = mutt_gen_msgid(); fprintf (f, "Resent-Message-ID: %s\n", msgid_str); fputs ("Resent-To: ", f); mutt_write_address_list (to, f, 11, 0); mutt_copy_header (fp, h, f, ch_flags, NULL); fputc ('\n', f); mutt_copy_bytes (fp, f, h->content->length); safe_fclose (&f); FREE (&msgid_str); #if USE_SMTP if (SmtpUrl) ret = mutt_smtp_send (env_from, to, NULL, NULL, mutt_b2s (tempfile), h->content->encoding == ENC8BIT); else #endif /* USE_SMTP */ ret = mutt_invoke_sendmail (env_from, to, NULL, NULL, mutt_b2s (tempfile), h->content->encoding == ENC8BIT); } mutt_buffer_pool_release (&tempfile); if (msg) mx_close_message (Context, &msg); return ret; } int mutt_bounce_message (FILE *fp, HEADER *h, ADDRESS *to) { ADDRESS *from, *resent_to; const char *fqdn = mutt_fqdn (1); char resent_from[STRING]; int ret; char *err = NULL; resent_from[0] = '\0'; from = mutt_default_from (); /* * mutt_default_from() does not use $realname if the real name is not set * in $from, so we add it here. The reason it is not added in * mutt_default_from() is that during normal sending, we execute * send-hooks and set the realname last so that it can be changed based * upon message criteria. */ if (! from->personal) { from->personal = safe_strdup(Realname); #ifdef EXACT_ADDRESS FREE (&from->val); #endif } if (fqdn) rfc822_qualify (from, fqdn); rfc2047_encode_adrlist (from, "Resent-From"); if (mutt_addrlist_to_intl (from, &err)) { mutt_error (_("Bad IDN %s while preparing resent-from."), err); FREE (&err); rfc822_free_address (&from); return -1; } rfc822_write_address (resent_from, sizeof (resent_from), from, 0); /* * prepare recipient list. idna conversion appears to happen before this * function is called, since the user receives confirmation of the address * list being bounced to. */ resent_to = rfc822_cpy_adr(to, 0); rfc2047_encode_adrlist(resent_to, "Resent-To"); ret = _mutt_bounce_message (fp, h, resent_to, resent_from, from); rfc822_free_address (&resent_to); rfc822_free_address (&from); return ret; } /* given a list of addresses, return a list of unique addresses */ ADDRESS *mutt_remove_duplicates (ADDRESS *addr) { ADDRESS *top = addr; ADDRESS **last = ⊤ ADDRESS *tmp; int dup; while (addr) { for (tmp = top, dup = 0; tmp && tmp != addr; tmp = tmp->next) { if (tmp->mailbox && addr->mailbox && !ascii_strcasecmp (addr->mailbox, tmp->mailbox)) { dup = 1; break; } } if (dup) { dprint (2, (debugfile, "mutt_remove_duplicates: Removing %s\n", addr->mailbox)); *last = addr->next; addr->next = NULL; rfc822_free_address(&addr); addr = *last; } else { last = &addr->next; addr = addr->next; } } return (top); } static void set_noconv_flags (BODY *b, short flag) { for (; b; b = b->next) { if (b->type == TYPEMESSAGE || b->type == TYPEMULTIPART) set_noconv_flags (b->parts, flag); else if (b->type == TYPETEXT && b->noconv) { if (flag) mutt_set_parameter ("x-mutt-noconv", "yes", &b->parameter); else mutt_delete_parameter ("x-mutt-noconv", &b->parameter); } } } int mutt_write_fcc (const char *path, SEND_CONTEXT *sctx, const char *msgid, int post, const char *fcc) { HEADER *hdr; CONTEXT f; MESSAGE *msg; BUFFER *tempfile = NULL; FILE *tempfp = NULL; int r = -1, need_buffy_cleanup = 0; struct stat st; int onm_flags; hdr = sctx->msg; if (post) set_noconv_flags (hdr->content, 1); if (mx_open_mailbox (path, MUTT_APPEND | MUTT_QUIET, &f) == NULL) { dprint (1, (debugfile, "mutt_write_fcc(): unable to open mailbox %s in append-mode, aborting.\n", path)); goto cleanup; } /* We need to add a Content-Length field to avoid problems where a line in * the message body begins with "From " */ if (f.magic == MUTT_MMDF || f.magic == MUTT_MBOX) { tempfile = mutt_buffer_pool_get (); mutt_buffer_mktemp (tempfile); if ((tempfp = safe_fopen (mutt_b2s (tempfile), "w+")) == NULL) { mutt_perror (mutt_b2s (tempfile)); mx_close_mailbox (&f, NULL); goto cleanup; } /* remember new mail status before appending message */ need_buffy_cleanup = 1; stat (path, &st); } hdr->read = !post; /* make sure to put it in the `cur' directory (maildir) */ onm_flags = MUTT_ADD_FROM; if (post) onm_flags |= MUTT_SET_DRAFT; if ((msg = mx_open_new_message (&f, hdr, onm_flags)) == NULL) { mx_close_mailbox (&f, NULL); goto cleanup; } /* post == 1 => postpone message. * post == 0 => fcc mode. * */ mutt_write_rfc822_header (msg->fp, hdr->env, hdr->content, sctx->date_header, post ? MUTT_WRITE_HEADER_POSTPONE : MUTT_WRITE_HEADER_FCC, 0, option (OPTCRYPTPROTHDRSREAD) && mutt_should_hide_protected_subject (hdr)); /* (postponment) if this was a reply of some sort, contains the * Message-ID: of message replied to. Save it using a special X-Mutt- * header so it can be picked up if the message is recalled at a later * point in time. This will allow the message to be marked as replied if * the same mailbox is still open. */ if (post && msgid) fprintf (msg->fp, "X-Mutt-References: %s\n", msgid); /* (postponment) save the Fcc: using a special X-Mutt- header so that * it can be picked up when the message is recalled */ if (post && fcc) fprintf (msg->fp, "X-Mutt-Fcc: %s\n", fcc); if (f.magic == MUTT_MMDF || f.magic == MUTT_MBOX) fprintf (msg->fp, "Status: RO\n"); /* (postponment) if the mail is to be signed or encrypted, save this info */ if ((WithCrypto & APPLICATION_PGP) && post && (hdr->security & APPLICATION_PGP)) { fputs ("X-Mutt-PGP: ", msg->fp); if (hdr->security & ENCRYPT) fputc ('E', msg->fp); if (hdr->security & OPPENCRYPT) fputc ('O', msg->fp); if (hdr->security & SIGN) { fputc ('S', msg->fp); if (sctx->pgp_sign_as) fprintf (msg->fp, "<%s>", sctx->pgp_sign_as); } if (hdr->security & INLINE) fputc ('I', msg->fp); #ifdef USE_AUTOCRYPT if (hdr->security & AUTOCRYPT) fputc ('A', msg->fp); if (hdr->security & AUTOCRYPT_OVERRIDE) fputc ('Z', msg->fp); #endif fputc ('\n', msg->fp); } /* (postponment) if the mail is to be signed or encrypted, save this info */ if ((WithCrypto & APPLICATION_SMIME) && post && (hdr->security & APPLICATION_SMIME)) { fputs ("X-Mutt-SMIME: ", msg->fp); if (hdr->security & ENCRYPT) { fputc ('E', msg->fp); if (sctx->smime_crypt_alg) fprintf (msg->fp, "C<%s>", sctx->smime_crypt_alg); } if (hdr->security & OPPENCRYPT) fputc ('O', msg->fp); if (hdr->security & SIGN) { fputc ('S', msg->fp); if (sctx->smime_sign_as) fprintf (msg->fp, "<%s>", sctx->smime_sign_as); } if (hdr->security & INLINE) fputc ('I', msg->fp); fputc ('\n', msg->fp); } #ifdef MIXMASTER /* (postponement) if the mail is to be sent through a mixmaster * chain, save that information */ if (post && hdr->chain && hdr->chain) { LIST *p; fputs ("X-Mutt-Mix:", msg->fp); for (p = hdr->chain; p; p = p->next) fprintf (msg->fp, " %s", (char *) p->data); fputc ('\n', msg->fp); } #endif if (tempfp) { char sasha[LONG_STRING]; int lines = 0; mutt_write_mime_body (hdr->content, tempfp); /* make sure the last line ends with a newline. Emacs doesn't ensure * this will happen, and it can cause problems parsing the mailbox * later. */ fseek (tempfp, -1, 2); if (fgetc (tempfp) != '\n') { fseek (tempfp, 0, 2); fputc ('\n', tempfp); } fflush (tempfp); if (ferror (tempfp)) { dprint (1, (debugfile, "mutt_write_fcc(): %s: write failed.\n", mutt_b2s (tempfile))); safe_fclose (&tempfp); unlink (mutt_b2s (tempfile)); mx_commit_message (msg, &f); /* XXX - really? */ mx_close_message (&f, &msg); mx_close_mailbox (&f, NULL); goto cleanup; } /* count the number of lines */ rewind (tempfp); while (fgets (sasha, sizeof (sasha), tempfp) != NULL) lines++; fprintf (msg->fp, "Content-Length: " OFF_T_FMT "\n", (LOFF_T) ftello (tempfp)); fprintf (msg->fp, "Lines: %d\n\n", lines); /* copy the body and clean up */ rewind (tempfp); r = mutt_copy_stream (tempfp, msg->fp); if (safe_fclose (&tempfp) != 0) r = -1; /* if there was an error, leave the temp version */ if (!r) unlink (mutt_b2s (tempfile)); } else { fputc ('\n', msg->fp); /* finish off the header */ r = mutt_write_mime_body (hdr->content, msg->fp); } if (mx_commit_message (msg, &f) != 0) r = -1; mx_close_message (&f, &msg); mx_close_mailbox (&f, NULL); if (!post && need_buffy_cleanup) mutt_buffy_cleanup (path, &st); if (post) set_noconv_flags (hdr->content, 0); cleanup: if (tempfp) { safe_fclose (&tempfp); unlink (mutt_b2s (tempfile)); } mutt_buffer_pool_release (&tempfile); return r; } mutt-2.1.4/color.h0000644000175000017500000000247614116114174010730 00000000000000/* * Copyright (C) 2021 Kevin J. McCarthy * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _COLOR_H #define _COLOR_H 1 typedef struct color_attr { short pair; /* parameter to init_pair(). NOT the retval of COLOR_PAIR() */ int attrs; } COLOR_ATTR; COLOR_ATTR mutt_merge_colors (COLOR_ATTR source, COLOR_ATTR overlay); void mutt_attrset_cursor (COLOR_ATTR source, COLOR_ATTR cursor); #ifdef HAVE_COLOR int mutt_alloc_color (int fg, int bg); int mutt_alloc_ansi_color (int fg, int bg); int mutt_alloc_overlay_color (int fg, int bg); void mutt_free_all_ansi_colors (void); #endif #endif mutt-2.1.4/signal.c0000644000175000017500000001771514155211517011066 00000000000000/* * Copyright (C) 1996-2000,2012 Michael R. Elkins * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #if HAVE_CONFIG_H # include "config.h" #endif #include "mutt.h" #include "mutt_curses.h" #include #include #include #include #include static sigset_t Sigset; static sigset_t SigsetSys; static struct sigaction SysOldInt; static struct sigaction SysOldQuit; static int IsEndwin = 0; static char *Caught_Signal_L10N = NULL; static char *Exiting_L10N = NULL; static void exit_print_int_recursive (int n) { char digit; if (n > 9) exit_print_int_recursive (n / 10); digit = '0' + (n % 10); write (1, &digit, 1); } static void exit_print_int (int n) { if (n < 0) { write (1, "-", 1); n = -n; } exit_print_int_recursive (n); } static void exit_print_string (const char *str) { size_t len = 0; if (!str) return; while (str[len]) len++; if (len > 0) write (1, str, len); } /* Attempt to catch "ordinary" signals and shut down gracefully. * Do not do l10n translations as _() can invoke malloc(), (__MEM_CHECKED__) * which may not be re-entrant. */ static void exit_handler (int sig) { curs_set (1); endwin (); /* just to be safe */ exit_print_string (Caught_Signal_L10N ? Caught_Signal_L10N : "Caught signal "); #if SYS_SIGLIST_DECLARED exit_print_string (sys_siglist[sig]); #else #if (__sun__ && __svr4__) exit_print_string (_sys_siglist[sig]); #else #if (__alpha && __osf__) exit_print_string (__sys_siglist[sig]); #else exit_print_int (sig); #endif #endif #endif exit_print_string (Exiting_L10N ? Exiting_L10N : "... Exiting.\n"); exit (0); } /* These are gettext() translated in advance because that * is not guaranteed reentrant. */ static void exit_handler_l10n_init (void) { if (!Caught_Signal_L10N) { /* L10N: This is printed in the exit handler when a signal is caught. The whole string is "Caught signal [XXX]... Exiting\n". This is the first part of the string: note with a trailing space. */ Caught_Signal_L10N = safe_strdup (_("Caught signal ")); } if (!Exiting_L10N) { /* L10N: This is printed in the exit handler when a signal is caught. The whole string is "Caught signal [XXX]... Exiting\n". This is the second part of the string, printed after the signal number or name. */ Exiting_L10N = safe_strdup (_("... Exiting.\n")); } } static void exit_handler_l10n_cleanup (void) { FREE (&Caught_Signal_L10N); FREE (&Exiting_L10N); } static void chld_handler (int sig) { SigChld = 1; } static void sighandler (int sig) { int save_errno = errno; switch (sig) { case SIGTSTP: /* user requested a suspend */ if (!option (OPTSUSPEND)) break; IsEndwin = isendwin (); curs_set (1); if (!IsEndwin) endwin (); kill (0, SIGSTOP); /* fall through */ case SIGCONT: if (!IsEndwin) refresh (); mutt_curs_set (-1); #if defined (USE_SLANG_CURSES) || defined (HAVE_RESIZETERM) /* We don't receive SIGWINCH when suspended; however, no harm is done by * just assuming we received one, and triggering the 'resize' anyway. */ SigWinch = 1; #endif break; #if defined (USE_SLANG_CURSES) || defined (HAVE_RESIZETERM) case SIGWINCH: SigWinch = 1; break; #endif case SIGINT: SigInt = 1; break; } errno = save_errno; } #ifdef USE_SLANG_CURSES int mutt_intr_hook (void) { return (-1); } #endif /* USE_SLANG_CURSES */ void mutt_signal_init (void) { struct sigaction act; exit_handler_l10n_init (); sigemptyset (&act.sa_mask); act.sa_flags = 0; act.sa_handler = SIG_IGN; sigaction (SIGPIPE, &act, NULL); act.sa_handler = exit_handler; sigaction (SIGTERM, &act, NULL); sigaction (SIGHUP, &act, NULL); sigaction (SIGQUIT, &act, NULL); /* we want to avoid race conditions */ sigaddset (&act.sa_mask, SIGTSTP); act.sa_handler = sighandler; /* we want SIGALRM to abort the current syscall, so we do this before * setting the SA_RESTART flag below. currently this is only used to * timeout on a connect() call in a reasonable amount of time. */ sigaction (SIGALRM, &act, NULL); /* we also don't want to mess with interrupted system calls */ #ifdef SA_RESTART act.sa_flags = SA_RESTART; #endif sigaction (SIGCONT, &act, NULL); sigaction (SIGTSTP, &act, NULL); sigaction (SIGINT, &act, NULL); #if defined (USE_SLANG_CURSES) || defined (HAVE_RESIZETERM) sigaction (SIGWINCH, &act, NULL); #endif /* Handle SIGCHLD. Tracked for background editing processes. */ act.sa_handler = chld_handler; /* don't need to block any other signals here */ sigemptyset (&act.sa_mask); /* we don't want to mess with stopped children */ act.sa_flags |= SA_NOCLDSTOP; sigaction (SIGCHLD, &act, NULL); #ifdef USE_SLANG_CURSES /* This bit of code is required because of the implementation of * SLcurses_wgetch(). If a signal is received (like SIGWINCH) when we * are in blocking mode, SLsys_getkey() will not return an error unless * a handler function is defined and it returns -1. This is needed so * that if the user resizes the screen while at a prompt, it will just * abort and go back to the main-menu. */ SLang_getkey_intr_hook = mutt_intr_hook; #endif } void mutt_signal_cleanup (void) { exit_handler_l10n_cleanup (); } /* signals which are important to block while doing critical ops */ void mutt_block_signals (void) { if (!option (OPTSIGNALSBLOCKED)) { sigemptyset (&Sigset); sigaddset (&Sigset, SIGTERM); sigaddset (&Sigset, SIGHUP); sigaddset (&Sigset, SIGTSTP); sigaddset (&Sigset, SIGINT); #if defined (USE_SLANG_CURSES) || defined (HAVE_RESIZETERM) sigaddset (&Sigset, SIGWINCH); #endif sigprocmask (SIG_BLOCK, &Sigset, 0); set_option (OPTSIGNALSBLOCKED); } } /* restore the previous signal mask */ void mutt_unblock_signals (void) { if (option (OPTSIGNALSBLOCKED)) { sigprocmask (SIG_UNBLOCK, &Sigset, 0); unset_option (OPTSIGNALSBLOCKED); } } void mutt_block_signals_system (void) { struct sigaction sa; if (! option (OPTSYSSIGNALSBLOCKED)) { /* POSIX: ignore SIGINT and SIGQUIT & block SIGCHLD before exec */ sa.sa_handler = SIG_IGN; sa.sa_flags = 0; sigemptyset (&sa.sa_mask); sigaction (SIGINT, &sa, &SysOldInt); sigaction (SIGQUIT, &sa, &SysOldQuit); sigemptyset (&SigsetSys); sigaddset (&SigsetSys, SIGCHLD); sigprocmask (SIG_BLOCK, &SigsetSys, 0); set_option (OPTSYSSIGNALSBLOCKED); } } void mutt_unblock_signals_system (int catch) { if (option (OPTSYSSIGNALSBLOCKED)) { sigprocmask (SIG_UNBLOCK, &SigsetSys, NULL); if (catch) { sigaction (SIGQUIT, &SysOldQuit, NULL); sigaction (SIGINT, &SysOldInt, NULL); } else { struct sigaction sa; sa.sa_handler = SIG_DFL; sigemptyset (&sa.sa_mask); sa.sa_flags = 0; sigaction (SIGQUIT, &sa, NULL); sigaction (SIGINT, &sa, NULL); } unset_option (OPTSYSSIGNALSBLOCKED); } } void mutt_allow_interrupt (int disposition) { struct sigaction sa; memset (&sa, 0, sizeof sa); sa.sa_handler = sighandler; #ifdef SA_RESTART if (disposition == 0) sa.sa_flags |= SA_RESTART; #endif sigaction (SIGINT, &sa, NULL); } mutt-2.1.4/OPS.SMIME0000644000175000017500000000115714116114174010671 00000000000000/* This file is used to generate keymap_defs.h and the manual. * * The Mutt parser scripts scan lines that start with 'OP_' * So please ensure multi-line comments have leading whitespace, * or at least don't start with OP_. * * Gettext also scans this file for translation strings, so * help strings should be surrounded by N_("....") * and have a translator comment line above them. * * All OPS* files (but not keymap_defs.h) should be listed * in po/POTFILES.in. */ /* L10N: Help screen description for OP_COMPOSE_SMIME_MENU compose menu: */ OP_COMPOSE_SMIME_MENU N_("show S/MIME options") mutt-2.1.4/monitor.c0000644000175000017500000003003214155211517011263 00000000000000/* * Copyright (C) 2018 Gero Treuner * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #if HAVE_CONFIG_H # include "config.h" #endif #if HAVE_SYS_INOTIFY_H # include # include # include # include #endif #ifndef HAVE_INOTIFY_INIT1 # include #endif #include "mutt.h" #include "buffy.h" #include "monitor.h" #include "mx.h" #include "mutt_curses.h" #include #include typedef struct monitor_t { struct monitor_t *next; char *mh_backup_path; dev_t st_dev; ino_t st_ino; short magic; int descr; } MONITOR; static int INotifyFd = -1; static MONITOR *Monitor = NULL; static size_t PollFdsCount = 0; static size_t PollFdsLen = 0; static struct pollfd *PollFds; static int MonitorContextDescriptor = -1; typedef struct monitorinfo_t { short magic; short isdir; const char *path; dev_t st_dev; ino_t st_ino; MONITOR *monitor; BUFFER *_pathbuf; /* access via path only (maybe not initialized) */ } MONITORINFO; #define INOTIFY_MASK_DIR (IN_MOVED_TO | IN_ATTRIB | IN_CLOSE_WRITE | IN_ISDIR) #define INOTIFY_MASK_FILE IN_CLOSE_WRITE static void mutt_poll_fd_add(int fd, short events) { int i = 0; for (i = 0; i < PollFdsCount && PollFds[i].fd != fd; ++i); if (i == PollFdsCount) { if (PollFdsCount == PollFdsLen) { PollFdsLen += 2; safe_realloc (&PollFds, PollFdsLen * sizeof(struct pollfd)); } ++PollFdsCount; PollFds[i].fd = fd; PollFds[i].events = events; } else PollFds[i].events |= events; } static int mutt_poll_fd_remove(int fd) { int i = 0, d; for (i = 0; i < PollFdsCount && PollFds[i].fd != fd; ++i); if (i == PollFdsCount) return -1; d = PollFdsCount - i - 1; if (d) memmove (&PollFds[i], &PollFds[i + 1], d * sizeof(struct pollfd)); --PollFdsCount; return 0; } static int monitor_init () { if (INotifyFd == -1) { #if HAVE_INOTIFY_INIT1 INotifyFd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC); if (INotifyFd == -1) { dprint (2, (debugfile, "monitor: inotify_init1 failed, errno=%d %s\n", errno, strerror(errno))); return -1; } #else INotifyFd = inotify_init(); if (INotifyFd == -1) { dprint (2, (debugfile, "monitor: inotify_init failed, errno=%d %s\n", errno, strerror(errno))); return -1; } fcntl(INotifyFd, F_SETFL, O_NONBLOCK); fcntl(INotifyFd, F_SETFD, FD_CLOEXEC); #endif mutt_poll_fd_add(0, POLLIN); mutt_poll_fd_add(INotifyFd, POLLIN); } return 0; } static void monitor_check_free () { if (!Monitor && INotifyFd != -1) { mutt_poll_fd_remove(INotifyFd); close (INotifyFd); INotifyFd = -1; MonitorFilesChanged = 0; } } static MONITOR *monitor_create (MONITORINFO *info, int descriptor) { MONITOR *monitor = (MONITOR *) safe_calloc (1, sizeof (MONITOR)); monitor->magic = info->magic; monitor->st_dev = info->st_dev; monitor->st_ino = info->st_ino; monitor->descr = descriptor; monitor->next = Monitor; if (info->magic == MUTT_MH) monitor->mh_backup_path = safe_strdup(info->path); Monitor = monitor; return monitor; } static void monitor_info_init (MONITORINFO *info) { memset (info, 0, sizeof (MONITORINFO)); } static void monitor_info_free (MONITORINFO *info) { mutt_buffer_free (&info->_pathbuf); } static void monitor_delete (MONITOR *monitor) { MONITOR **ptr = &Monitor; if (!monitor) return; FOREVER { if (!*ptr) return; if (*ptr == monitor) break; ptr = &(*ptr)->next; } FREE (&monitor->mh_backup_path); /* __FREE_CHECKED__ */ monitor = monitor->next; FREE (ptr); /* __FREE_CHECKED__ */ *ptr = monitor; } static int monitor_handle_ignore (int descr) { int new_descr = -1; MONITOR *iter = Monitor; struct stat sb; while (iter && iter->descr != descr) iter = iter->next; if (iter) { if (iter->magic == MUTT_MH && stat (iter->mh_backup_path, &sb) == 0) { if ((new_descr = inotify_add_watch (INotifyFd, iter->mh_backup_path, INOTIFY_MASK_FILE)) == -1) dprint (2, (debugfile, "monitor: inotify_add_watch failed for '%s', errno=%d %s\n", iter->mh_backup_path, errno, strerror(errno))); else { dprint (3, (debugfile, "monitor: inotify_add_watch descriptor=%d for '%s'\n", descr, iter->mh_backup_path)); iter->st_dev = sb.st_dev; iter->st_ino = sb.st_ino; iter->descr = new_descr; } } else { dprint (3, (debugfile, "monitor: cleanup watch (implicitly removed) - descriptor=%d\n", descr)); } if (MonitorContextDescriptor == descr) MonitorContextDescriptor = new_descr; if (new_descr == -1) { monitor_delete (iter); monitor_check_free (); } } return new_descr; } #define EVENT_BUFLEN MAX(4096, sizeof(struct inotify_event) + NAME_MAX + 1) /* mutt_monitor_poll: Waits for I/O ready file descriptors or signals. * * return values: * -3 unknown/unexpected events: poll timeout / fds not handled by us * -2 monitor detected changes, no STDIN input * -1 error (see errno) * 0 (1) input ready from STDIN, or (2) monitoring inactive -> no poll() * MonitorFilesChanged also reflects changes to monitored files. * * Only STDIN and INotify file handles currently expected/supported. * More would ask for common infrastructur (sockets?). */ int mutt_monitor_poll (void) { int rc = 0, fds, i, inputReady; char buf[EVENT_BUFLEN] __attribute__ ((aligned(__alignof__(struct inotify_event)))); MonitorFilesChanged = 0; if (INotifyFd != -1) { fds = poll (PollFds, PollFdsLen, MuttGetchTimeout); if (fds == -1) { rc = -1; if (errno != EINTR) { dprint (2, (debugfile, "monitor: poll() failed, errno=%d %s\n", errno, strerror(errno))); } } else { inputReady = 0; for (i = 0; fds && i < PollFdsCount; ++i) { if (PollFds[i].revents) { --fds; if (PollFds[i].fd == 0) { inputReady = 1; } else if (PollFds[i].fd == INotifyFd) { MonitorFilesChanged = 1; dprint (3, (debugfile, "monitor: file change(s) detected\n")); int len; char *ptr = buf; const struct inotify_event *event; FOREVER { len = read (INotifyFd, buf, sizeof(buf)); if (len == -1) { if (errno != EAGAIN) dprint (2, (debugfile, "monitor: read inotify events failed, errno=%d %s\n", errno, strerror(errno))); break; } while (ptr < buf + len) { event = (const struct inotify_event *) ptr; dprint (5, (debugfile, "monitor: + detail: descriptor=%d mask=0x%x\n", event->wd, event->mask)); if (event->mask & IN_IGNORED) monitor_handle_ignore (event->wd); else if (event->wd == MonitorContextDescriptor) MonitorContextChanged = 1; ptr += sizeof(struct inotify_event) + event->len; } } } } } if (!inputReady) rc = MonitorFilesChanged ? -2 : -3; } } return rc; } #define RESOLVERES_OK_NOTEXISTING 0 #define RESOLVERES_OK_EXISTING 1 #define RESOLVERES_FAIL_NOMAILBOX -3 #define RESOLVERES_FAIL_NOMAGIC -2 #define RESOLVERES_FAIL_STAT -1 /* monitor_resolve: resolve monitor entry match by BUFFY, or - if NULL - by Context. * * return values: * >=0 mailbox is valid and locally accessible: * 0: no monitor / 1: preexisting monitor * -3 no mailbox (MONITORINFO: no fields set) * -2 magic not set * -1 stat() failed (see errno; MONITORINFO fields: magic, isdir, path) */ static int monitor_resolve (MONITORINFO *info, BUFFY *buffy) { MONITOR *iter; char *fmt = NULL; struct stat sb; if (buffy) { info->magic = buffy->magic; info->path = buffy->realpath; } else if (Context) { info->magic = Context->magic; info->path = Context->realpath; } else { return RESOLVERES_FAIL_NOMAILBOX; } if (!info->magic) { return RESOLVERES_FAIL_NOMAGIC; } else if (info->magic == MUTT_MAILDIR) { info->isdir = 1; fmt = "%s/new"; } else { info->isdir = 0; if (info->magic == MUTT_MH) fmt = "%s/.mh_sequences"; } if (fmt) { if (!info->_pathbuf) info->_pathbuf = mutt_buffer_new (); mutt_buffer_printf (info->_pathbuf, fmt, info->path); info->path = mutt_b2s (info->_pathbuf); } if (stat (info->path, &sb) != 0) return RESOLVERES_FAIL_STAT; iter = Monitor; while (iter && (iter->st_ino != sb.st_ino || iter->st_dev != sb.st_dev)) iter = iter->next; info->st_dev = sb.st_dev; info->st_ino = sb.st_ino; info->monitor = iter; return iter ? RESOLVERES_OK_EXISTING : RESOLVERES_OK_NOTEXISTING; } /* mutt_monitor_add: add file monitor from BUFFY, or - if NULL - from Context. * * return values: * 0 success: new or already existing monitor * -1 failed: no mailbox, inaccessible file, create monitor/watcher failed */ int mutt_monitor_add (BUFFY *buffy) { MONITORINFO info; uint32_t mask; int descr, rc = 0; monitor_info_init (&info); descr = monitor_resolve (&info, buffy); if (descr != RESOLVERES_OK_NOTEXISTING) { if (!buffy && (descr == RESOLVERES_OK_EXISTING)) MonitorContextDescriptor = info.monitor->descr; rc = descr == RESOLVERES_OK_EXISTING ? 0 : -1; goto cleanup; } mask = info.isdir ? INOTIFY_MASK_DIR : INOTIFY_MASK_FILE; if ((INotifyFd == -1 && monitor_init () == -1) || (descr = inotify_add_watch (INotifyFd, info.path, mask)) == -1) { dprint (2, (debugfile, "monitor: inotify_add_watch failed for '%s', errno=%d %s\n", info.path, errno, strerror(errno))); rc = -1; goto cleanup; } dprint (3, (debugfile, "monitor: inotify_add_watch descriptor=%d for '%s'\n", descr, info.path)); if (!buffy) MonitorContextDescriptor = descr; monitor_create (&info, descr); cleanup: monitor_info_free (&info); return rc; } /* mutt_monitor_remove: remove file monitor from BUFFY, or - if NULL - from Context. * * return values: * 0 monitor removed (not shared) * 1 monitor not removed (shared) * 2 no monitor */ int mutt_monitor_remove (BUFFY *buffy) { MONITORINFO info, info2; int rc = 0; monitor_info_init (&info); monitor_info_init (&info2); if (!buffy) { MonitorContextDescriptor = -1; MonitorContextChanged = 0; } if (monitor_resolve (&info, buffy) != RESOLVERES_OK_EXISTING) { rc = 2; goto cleanup; } if (Context) { if (buffy) { if (monitor_resolve (&info2, NULL) == RESOLVERES_OK_EXISTING && info.st_ino == info2.st_ino && info.st_dev == info2.st_dev) { rc = 1; goto cleanup; } } else { if (mutt_find_mailbox (Context->realpath)) { rc = 1; goto cleanup; } } } inotify_rm_watch(info.monitor->descr, INotifyFd); dprint (3, (debugfile, "monitor: inotify_rm_watch for '%s' descriptor=%d\n", info.path, info.monitor->descr)); monitor_delete (info.monitor); monitor_check_free (); cleanup: monitor_info_free (&info); monitor_info_free (&info2); return rc; } mutt-2.1.4/wcwidth.c0000644000175000017500000001604114116114174011247 00000000000000/* * This is an implementation of wcwidth() and wcswidth() (defined in * IEEE Std 1002.1-2001) for Unicode. * * http://www.opengroup.org/onlinepubs/007904975/functions/wcwidth.html * http://www.opengroup.org/onlinepubs/007904975/functions/wcswidth.html * * Markus Kuhn -- 2007-05-26 (Unicode 5.0) * * Permission to use, copy, modify, and distribute this software * for any purpose and without fee is hereby granted. The author * disclaims all warranties with regard to this software. * * Latest version: http://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c */ /* Changes made for mutt: * - Adapted for Mutt by Edmund Grimley Evans. * - Changed 'first'/'last' members of combined[] to wchar_t from * unsigned short to fix compiler warnings, 2007-11-13, Rocco Rutte */ #if HAVE_CONFIG_H # include "config.h" #endif #ifndef HAVE_WC_FUNCS #include "mutt.h" #include "mbyte.h" #include /* The following two functions define the column width of an ISO 10646 * character as follows: * * - The null character (U+0000) has a column width of 0. * * - Other C0/C1 control characters and DEL will lead to a return * value of -1. * * - Non-spacing and enclosing combining characters (general * category code Mn or Me in the Unicode database) have a * column width of 0. * * - SOFT HYPHEN (U+00AD) has a column width of 1. * * - Other format characters (general category code Cf in the Unicode * database) and ZERO WIDTH SPACE (U+200B) have a column width of 0. * * - Hangul Jamo medial vowels and final consonants (U+1160-U+11FF) * have a column width of 0. * * - Spacing characters in the East Asian Wide (W) or East Asian * Full-width (F) category as defined in Unicode Technical * Report #11 have a column width of 2. * * - All remaining characters (including all printable * ISO 8859-1 and WGL4 characters, Unicode control characters, * etc.) have a column width of 1. * * This implementation assumes that wchar_t characters are encoded * in ISO 10646. */ int wcwidth_ucs(wchar_t ucs) { /* sorted list of non-overlapping intervals of non-spacing characters */ /* generated by "uniset +cat=Me +cat=Mn +cat=Cf -00AD +1160-11FF +200B c" */ static const struct interval { wchar_t first; wchar_t last; } combining[] = { { 0x0300, 0x036f }, { 0x0483, 0x0486 }, { 0x0488, 0x0489 }, { 0x0591, 0x05bd }, { 0x05bf, 0x05bf }, { 0x05c1, 0x05c2 }, { 0x05c4, 0x05c5 }, { 0x05c7, 0x05c7 }, { 0x0600, 0x0603 }, { 0x0610, 0x0615 }, { 0x064b, 0x065e }, { 0x0670, 0x0670 }, { 0x06d6, 0x06e4 }, { 0x06e7, 0x06e8 }, { 0x06ea, 0x06ed }, { 0x070f, 0x070f }, { 0x0711, 0x0711 }, { 0x0730, 0x074a }, { 0x07a6, 0x07b0 }, { 0x07eb, 0x07f3 }, { 0x0901, 0x0902 }, { 0x093c, 0x093c }, { 0x0941, 0x0948 }, { 0x094d, 0x094d }, { 0x0951, 0x0954 }, { 0x0962, 0x0963 }, { 0x0981, 0x0981 }, { 0x09bc, 0x09bc }, { 0x09c1, 0x09c4 }, { 0x09cd, 0x09cd }, { 0x09e2, 0x09e3 }, { 0x0a01, 0x0a02 }, { 0x0a3c, 0x0a3c }, { 0x0a41, 0x0a42 }, { 0x0a47, 0x0a48 }, { 0x0a4b, 0x0a4d }, { 0x0a70, 0x0a71 }, { 0x0a81, 0x0a82 }, { 0x0abc, 0x0abc }, { 0x0ac1, 0x0ac5 }, { 0x0ac7, 0x0ac8 }, { 0x0acd, 0x0acd }, { 0x0ae2, 0x0ae3 }, { 0x0b01, 0x0b01 }, { 0x0b3c, 0x0b3c }, { 0x0b3f, 0x0b3f }, { 0x0b41, 0x0b43 }, { 0x0b4d, 0x0b4d }, { 0x0b56, 0x0b56 }, { 0x0b82, 0x0b82 }, { 0x0bc0, 0x0bc0 }, { 0x0bcd, 0x0bcd }, { 0x0c3e, 0x0c40 }, { 0x0c46, 0x0c48 }, { 0x0c4a, 0x0c4d }, { 0x0c55, 0x0c56 }, { 0x0cbc, 0x0cbc }, { 0x0cbf, 0x0cbf }, { 0x0cc6, 0x0cc6 }, { 0x0ccc, 0x0ccd }, { 0x0ce2, 0x0ce3 }, { 0x0d41, 0x0d43 }, { 0x0d4d, 0x0d4d }, { 0x0dca, 0x0dca }, { 0x0dd2, 0x0dd4 }, { 0x0dd6, 0x0dd6 }, { 0x0e31, 0x0e31 }, { 0x0e34, 0x0e3a }, { 0x0e47, 0x0e4e }, { 0x0eb1, 0x0eb1 }, { 0x0eb4, 0x0eb9 }, { 0x0ebb, 0x0ebc }, { 0x0ec8, 0x0ecd }, { 0x0f18, 0x0f19 }, { 0x0f35, 0x0f35 }, { 0x0f37, 0x0f37 }, { 0x0f39, 0x0f39 }, { 0x0f71, 0x0f7e }, { 0x0f80, 0x0f84 }, { 0x0f86, 0x0f87 }, { 0x0f90, 0x0f97 }, { 0x0f99, 0x0fbc }, { 0x0fc6, 0x0fc6 }, { 0x102d, 0x1030 }, { 0x1032, 0x1032 }, { 0x1036, 0x1037 }, { 0x1039, 0x1039 }, { 0x1058, 0x1059 }, { 0x1160, 0x11ff }, { 0x135f, 0x135f }, { 0x1712, 0x1714 }, { 0x1732, 0x1734 }, { 0x1752, 0x1753 }, { 0x1772, 0x1773 }, { 0x17b4, 0x17b5 }, { 0x17b7, 0x17bd }, { 0x17c6, 0x17c6 }, { 0x17c9, 0x17d3 }, { 0x17dd, 0x17dd }, { 0x180b, 0x180d }, { 0x18a9, 0x18a9 }, { 0x1920, 0x1922 }, { 0x1927, 0x1928 }, { 0x1932, 0x1932 }, { 0x1939, 0x193b }, { 0x1a17, 0x1a18 }, { 0x1b00, 0x1b03 }, { 0x1b34, 0x1b34 }, { 0x1b36, 0x1b3a }, { 0x1b3c, 0x1b3c }, { 0x1b42, 0x1b42 }, { 0x1b6b, 0x1b73 }, { 0x1dc0, 0x1dca }, { 0x1dfe, 0x1dff }, { 0x200b, 0x200f }, { 0x202a, 0x202e }, { 0x2060, 0x2063 }, { 0x206a, 0x206f }, { 0x20d0, 0x20ef }, { 0x302a, 0x302f }, { 0x3099, 0x309a }, { 0xa806, 0xa806 }, { 0xa80b, 0xa80b }, { 0xa825, 0xa826 }, { 0xfb1e, 0xfb1e }, { 0xfe00, 0xfe0f }, { 0xfe20, 0xfe23 }, { 0xfeff, 0xfeff }, { 0xfff9, 0xfffb }, { 0x10a01, 0x10a03 }, { 0x10a05, 0x10a06 }, { 0x10a0c, 0x10a0f }, { 0x10a38, 0x10a3a }, { 0x10a3f, 0x10a3f }, { 0x1d167, 0x1d169 }, { 0x1d173, 0x1d182 }, { 0x1d185, 0x1d18b }, { 0x1d1aa, 0x1d1ad }, { 0x1d242, 0x1d244 }, { 0xe0001, 0xe0001 }, { 0xe0020, 0xe007f }, { 0xe0100, 0xe01ef } }; int min = 0; int max = sizeof(combining) / sizeof(struct interval) - 1; int mid; /* test for 8-bit control characters */ if (ucs == 0) return 0; if (ucs < 32 || (ucs >= 0x7f && ucs < 0xa0)) return -1; /* first quick check for Latin-1 etc. characters */ if (ucs < combining[0].first) return 1; /* binary search in table of non-spacing characters */ while (max >= min) { mid = (min + max) / 2; if (combining[mid].last < ucs) min = mid + 1; else if (combining[mid].first > ucs) max = mid - 1; else if (combining[mid].first <= ucs && combining[mid].last >= ucs) return 0; } /* if we arrive here, ucs is not a combining or C0/C1 control character */ /* fast test for majority of non-wide scripts */ if (ucs < 0x1100) return 1; return 1 + (ucs >= 0x1100 && (ucs <= 0x115f || /* Hangul Jamo init. consonants */ ucs == 0x2329 || ucs == 0x232a || (ucs >= 0x2e80 && ucs <= 0xa4cf && ucs != 0x303f) || /* CJK ... Yi */ (ucs >= 0xac00 && ucs <= 0xd7a3) || /* Hangul Syllables */ (ucs >= 0xf900 && ucs <= 0xfaff) || /* CJK Compatibility Ideographs */ (ucs >= 0xfe10 && ucs <= 0xfe19) || /* Vertical forms */ (ucs >= 0xfe30 && ucs <= 0xfe6f) || /* CJK Compatibility Forms */ (ucs >= 0xff00 && ucs <= 0xff60) || /* Fullwidth Forms */ (ucs >= 0xffe0 && ucs <= 0xffe6) || (ucs >= 0x20000 && ucs <= 0x2fffd) || (ucs >= 0x30000 && ucs <= 0x3fffd))); } #endif /* !HAVE_WC_FUNCS */ #if 0 /* original */ int wcswidth(const wchar_t *pwcs, size_t n) { int w, width = 0; for (;*pwcs && n-- > 0; pwcs++) if ((w = wcwidth(*pwcs)) < 0) return -1; else width += w; return width; } #endif mutt-2.1.4/muttbug0000755000175000017500000000021514116114174011043 00000000000000#!/bin/sh echo "To report a bug, please contact the Mutt maintainers via gitlab:" echo echo " https://gitlab.com/muttmua/mutt/issues" mutt-2.1.4/wcscasecmp.c0000644000175000017500000000231013653360550011726 00000000000000/* * Copyright (C) 2009 Rocco Rutte * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #if HAVE_CONFIG_H # include "config.h" #endif #include "mutt.h" #include "mbyte.h" int wcscasecmp (const wchar_t *a, const wchar_t *b) { const wchar_t *p = a; const wchar_t *q = b; int i; if (!a && !b) return 0; if (!a && b) return -1; if (a && !b) return 1; for ( ; *p || *q; p++, q++) { if ((i = towlower (*p)) - towlower (*q)) return i; } return 0; } mutt-2.1.4/_mutt_regex.h0000644000175000017500000005011514116114174012125 00000000000000/* Definitions for data structures and routines for the regular expression library, version 0.12. Copyright (C) 1985,89,90,91,92,93,95,96,97 Free Software Foundation, Inc. This file is part of the GNU C Library. Its master source is NOT part of the C library, however. The master source lives in /gd/gnu/lib. The GNU C Library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. The GNU C Library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with the GNU C Library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __REGEXP_LIBRARY_H__ #define __REGEXP_LIBRARY_H__ /* Allow the use in C++ code. */ #ifdef __cplusplus extern "C" { #endif /* POSIX says that must be included (by the caller) before . */ #if !defined (_POSIX_C_SOURCE) && !defined (_POSIX_SOURCE) && defined (VMS) /* VMS doesn't have `size_t' in , even though POSIX says it should be there. */ #include #endif /* The following two types have to be signed and unsigned integer type wide enough to hold a value of a pointer. For most ANSI compilers ptrdiff_t and size_t should be likely OK. Still size of these two types is 2 for Microsoft C. Ugh... */ typedef long int s_reg_t; typedef unsigned long int active_reg_t; /* The following bits are used to determine the regexp syntax we recognize. The set/not-set meanings are chosen so that Emacs syntax remains the value 0. The bits are given in alphabetical order, and the definitions shifted by one from the previous bit; thus, when we add or remove a bit, only one other definition need change. */ typedef unsigned long int reg_syntax_t; /* If this bit is not set, then \ inside a bracket expression is literal. If set, then such a \ quotes the following character. */ #define RE_BACKSLASH_ESCAPE_IN_LISTS ((unsigned long int) 1) /* If this bit is not set, then + and ? are operators, and \+ and \? are literals. If set, then \+ and \? are operators and + and ? are literals. */ #define RE_BK_PLUS_QM (RE_BACKSLASH_ESCAPE_IN_LISTS << 1) /* If this bit is set, then character classes are supported. They are: [:alpha:], [:upper:], [:lower:], [:digit:], [:alnum:], [:xdigit:], [:space:], [:print:], [:punct:], [:graph:], and [:cntrl:]. If not set, then character classes are not supported. */ #define RE_CHAR_CLASSES (RE_BK_PLUS_QM << 1) /* If this bit is set, then ^ and $ are always anchors (outside bracket expressions, of course). If this bit is not set, then it depends: ^ is an anchor if it is at the beginning of a regular expression or after an open-group or an alternation operator; $ is an anchor if it is at the end of a regular expression, or before a close-group or an alternation operator. This bit could be (re)combined with RE_CONTEXT_INDEP_OPS, because POSIX draft 11.2 says that * etc. in leading positions is undefined. We already implemented a previous draft which made those constructs invalid, though, so we haven't changed the code back. */ #define RE_CONTEXT_INDEP_ANCHORS (RE_CHAR_CLASSES << 1) /* If this bit is set, then special characters are always special regardless of where they are in the pattern. If this bit is not set, then special characters are special only in some contexts; otherwise they are ordinary. Specifically, * + ? and intervals are only special when not after the beginning, open-group, or alternation operator. */ #define RE_CONTEXT_INDEP_OPS (RE_CONTEXT_INDEP_ANCHORS << 1) /* If this bit is set, then *, +, ?, and { cannot be first in an re or immediately after an alternation or begin-group operator. */ #define RE_CONTEXT_INVALID_OPS (RE_CONTEXT_INDEP_OPS << 1) /* If this bit is set, then . matches newline. If not set, then it doesn't. */ #define RE_DOT_NEWLINE (RE_CONTEXT_INVALID_OPS << 1) /* If this bit is set, then . doesn't match NUL. If not set, then it does. */ #define RE_DOT_NOT_NULL (RE_DOT_NEWLINE << 1) /* If this bit is set, nonmatching lists [^...] do not match newline. If not set, they do. */ #define RE_HAT_LISTS_NOT_NEWLINE (RE_DOT_NOT_NULL << 1) /* If this bit is set, either \{...\} or {...} defines an interval, depending on RE_NO_BK_BRACES. If not set, \{, \}, {, and } are literals. */ #define RE_INTERVALS (RE_HAT_LISTS_NOT_NEWLINE << 1) /* If this bit is set, +, ? and | aren't recognized as operators. If not set, they are. */ #define RE_LIMITED_OPS (RE_INTERVALS << 1) /* If this bit is set, newline is an alternation operator. If not set, newline is literal. */ #define RE_NEWLINE_ALT (RE_LIMITED_OPS << 1) /* If this bit is set, then `{...}' defines an interval, and \{ and \} are literals. If not set, then `\{...\}' defines an interval. */ #define RE_NO_BK_BRACES (RE_NEWLINE_ALT << 1) /* If this bit is set, (...) defines a group, and \( and \) are literals. If not set, \(...\) defines a group, and ( and ) are literals. */ #define RE_NO_BK_PARENS (RE_NO_BK_BRACES << 1) /* If this bit is set, then \ matches . If not set, then \ is a back-reference. */ #define RE_NO_BK_REFS (RE_NO_BK_PARENS << 1) /* If this bit is set, then | is an alternation operator, and \| is literal. If not set, then \| is an alternation operator, and | is literal. */ #define RE_NO_BK_VBAR (RE_NO_BK_REFS << 1) /* If this bit is set, then an ending range point collating higher than the starting range point, as in [z-a], is invalid. If not set, then when ending range point collates higher than the starting range point, the range is ignored. */ #define RE_NO_EMPTY_RANGES (RE_NO_BK_VBAR << 1) /* If this bit is set, then an unmatched ) is ordinary. If not set, then an unmatched ) is invalid. */ #define RE_UNMATCHED_RIGHT_PAREN_ORD (RE_NO_EMPTY_RANGES << 1) /* If this bit is set, succeed as soon as we match the whole pattern, without further backtracking. */ #define RE_NO_POSIX_BACKTRACKING (RE_UNMATCHED_RIGHT_PAREN_ORD << 1) /* If this bit is set, do not process the GNU regex operators. If not set, then the GNU regex operators are recognized. */ #define RE_NO_GNU_OPS (RE_NO_POSIX_BACKTRACKING << 1) /* If this bit is set, turn on internal regex debugging. If not set, and debugging was on, turn it off. This only works if regex.c is compiled -DDEBUG. We define this bit always, so that all that's needed to turn on debugging is to recompile regex.c; the calling code can always have this bit set, and it won't affect anything in the normal case. */ #define RE_DEBUG (RE_NO_GNU_OPS << 1) /* This global variable defines the particular regexp syntax to use (for some interfaces). When a regexp is compiled, the syntax used is stored in the pattern buffer, so changing this does not affect already-compiled regexps. */ extern reg_syntax_t re_syntax_options; /* Define combinations of the above bits for the standard possibilities. (The [[[ comments delimit what gets put into the Texinfo file, so don't delete them!) */ /* [[[begin syntaxes]]] */ #define RE_SYNTAX_EMACS 0 #define RE_SYNTAX_AWK \ (RE_BACKSLASH_ESCAPE_IN_LISTS | RE_DOT_NOT_NULL \ | RE_NO_BK_PARENS | RE_NO_BK_REFS \ | RE_NO_BK_VBAR | RE_NO_EMPTY_RANGES \ | RE_DOT_NEWLINE | RE_CONTEXT_INDEP_ANCHORS \ | RE_UNMATCHED_RIGHT_PAREN_ORD | RE_NO_GNU_OPS) #define RE_SYNTAX_GNU_AWK \ ((RE_SYNTAX_POSIX_EXTENDED | RE_BACKSLASH_ESCAPE_IN_LISTS | RE_DEBUG) \ & ~(RE_DOT_NOT_NULL | RE_INTERVALS | RE_CONTEXT_INDEP_OPS)) #define RE_SYNTAX_POSIX_AWK \ (RE_SYNTAX_POSIX_EXTENDED | RE_BACKSLASH_ESCAPE_IN_LISTS \ | RE_INTERVALS | RE_NO_GNU_OPS) #define RE_SYNTAX_GREP \ (RE_BK_PLUS_QM | RE_CHAR_CLASSES \ | RE_HAT_LISTS_NOT_NEWLINE | RE_INTERVALS \ | RE_NEWLINE_ALT) #define RE_SYNTAX_EGREP \ (RE_CHAR_CLASSES | RE_CONTEXT_INDEP_ANCHORS \ | RE_CONTEXT_INDEP_OPS | RE_HAT_LISTS_NOT_NEWLINE \ | RE_NEWLINE_ALT | RE_NO_BK_PARENS \ | RE_NO_BK_VBAR) #define RE_SYNTAX_POSIX_EGREP \ (RE_SYNTAX_EGREP | RE_INTERVALS | RE_NO_BK_BRACES) /* P1003.2/D11.2, section 4.20.7.1, lines 5078ff. */ #define RE_SYNTAX_ED RE_SYNTAX_POSIX_BASIC #define RE_SYNTAX_SED RE_SYNTAX_POSIX_BASIC /* Syntax bits common to both basic and extended POSIX regex syntax. */ #define _RE_SYNTAX_POSIX_COMMON \ (RE_CHAR_CLASSES | RE_DOT_NEWLINE | RE_DOT_NOT_NULL \ | RE_INTERVALS | RE_NO_EMPTY_RANGES) #define RE_SYNTAX_POSIX_BASIC \ (_RE_SYNTAX_POSIX_COMMON | RE_BK_PLUS_QM) /* Differs from ..._POSIX_BASIC only in that RE_BK_PLUS_QM becomes RE_LIMITED_OPS, i.e., \? \+ \| are not recognized. Actually, this isn't minimal, since other operators, such as \`, aren't disabled. */ #define RE_SYNTAX_POSIX_MINIMAL_BASIC \ (_RE_SYNTAX_POSIX_COMMON | RE_LIMITED_OPS) #define RE_SYNTAX_POSIX_EXTENDED \ (_RE_SYNTAX_POSIX_COMMON | RE_CONTEXT_INDEP_ANCHORS \ | RE_CONTEXT_INDEP_OPS | RE_NO_BK_BRACES \ | RE_NO_BK_PARENS | RE_NO_BK_VBAR \ | RE_UNMATCHED_RIGHT_PAREN_ORD) /* Differs from ..._POSIX_EXTENDED in that RE_CONTEXT_INVALID_OPS replaces RE_CONTEXT_INDEP_OPS and RE_NO_BK_REFS is added. */ #define RE_SYNTAX_POSIX_MINIMAL_EXTENDED \ (_RE_SYNTAX_POSIX_COMMON | RE_CONTEXT_INDEP_ANCHORS \ | RE_CONTEXT_INVALID_OPS | RE_NO_BK_BRACES \ | RE_NO_BK_PARENS | RE_NO_BK_REFS \ | RE_NO_BK_VBAR | RE_UNMATCHED_RIGHT_PAREN_ORD) /* [[[end syntaxes]]] */ /* Maximum number of duplicates an interval can allow. Some systems (erroneously) define this in other header files, but we want our value, so remove any previous define. */ #ifdef RE_DUP_MAX #undef RE_DUP_MAX #endif /* If sizeof(int) == 2, then ((1 << 15) - 1) overflows. */ #define RE_DUP_MAX (0x7fff) /* POSIX `cflags' bits (i.e., information for `regcomp'). */ /* If this bit is set, then use extended regular expression syntax. If not set, then use basic regular expression syntax. */ #define REG_EXTENDED 1 /* If this bit is set, then ignore case when matching. If not set, then case is significant. */ #define REG_ICASE (REG_EXTENDED << 1) /* If this bit is set, then anchors do not match at newline characters in the string. If not set, then anchors do match at newlines. */ #define REG_NEWLINE (REG_ICASE << 1) /* If this bit is set, then report only success or fail in regexec. If not set, then returns differ between not matching and errors. */ #define REG_NOSUB (REG_NEWLINE << 1) /* POSIX `eflags' bits (i.e., information for regexec). */ /* If this bit is set, then the beginning-of-line operator doesn't match the beginning of the string (presumably because it's not the beginning of a line). If not set, then the beginning-of-line operator does match the beginning of the string. */ #define REG_NOTBOL 1 /* Like REG_NOTBOL, except for the end-of-line. */ #define REG_NOTEOL (1 << 1) /* If any error codes are removed, changed, or added, update the `re_error_msg' table in regex.c. */ typedef enum { REG_NOERROR = 0, /* Success. */ REG_NOMATCH, /* Didn't find a match (for regexec). */ /* POSIX regcomp return error codes. (In the order listed in the standard.) */ REG_BADPAT, /* Invalid pattern. */ REG_ECOLLATE, /* Not implemented. */ REG_ECTYPE, /* Invalid character class name. */ REG_EESCAPE, /* Trailing backslash. */ REG_ESUBREG, /* Invalid back reference. */ REG_EBRACK, /* Unmatched left bracket. */ REG_EPAREN, /* Parenthesis imbalance. */ REG_EBRACE, /* Unmatched \{. */ REG_BADBR, /* Invalid contents of \{\}. */ REG_ERANGE, /* Invalid range end. */ REG_ESPACE, /* Ran out of memory. */ REG_BADRPT, /* No preceding re for repetition op. */ /* Error codes we've added. */ REG_EEND, /* Premature end. */ REG_ESIZE, /* Compiled pattern bigger than 2^16 bytes. */ REG_ERPAREN /* Unmatched ) or \); not returned from regcomp. */ } reg_errcode_t; /* This data structure represents a compiled pattern. Before calling the pattern compiler, the fields `buffer', `allocated', `fastmap', `translate', and `no_sub' can be set. After the pattern has been compiled, the `re_nsub' field is available. All other fields are private to the regex routines. */ #ifndef RE_TRANSLATE_TYPE #define RE_TRANSLATE_TYPE char * #endif struct re_pattern_buffer { /* [[[begin pattern_buffer]]] */ /* Space that holds the compiled pattern. It is declared as `unsigned char *' because its elements are sometimes used as array indexes. */ unsigned char *buffer; /* Number of bytes to which `buffer' points. */ unsigned long int allocated; /* Number of bytes actually used in `buffer'. */ unsigned long int used; /* Syntax setting with which the pattern was compiled. */ reg_syntax_t syntax; /* Pointer to a fastmap, if any, otherwise zero. re_search uses the fastmap, if there is one, to skip over impossible starting points for matches. */ char *fastmap; /* Either a translate table to apply to all characters before comparing them, or zero for no translation. The translation is applied to a pattern when it is compiled and to a string when it is matched. */ RE_TRANSLATE_TYPE translate; /* Number of subexpressions found by the compiler. */ size_t re_nsub; /* Zero if this pattern cannot match the empty string, one else. Well, in truth it's used only in `re_search_2', to see whether or not we should use the fastmap, so we don't set this absolutely perfectly; see `re_compile_fastmap' (the `duplicate' case). */ unsigned can_be_null : 1; /* If REGS_UNALLOCATED, allocate space in the `regs' structure for `max (RE_NREGS, re_nsub + 1)' groups. If REGS_REALLOCATE, reallocate space if necessary. If REGS_FIXED, use what's there. */ #define REGS_UNALLOCATED 0 #define REGS_REALLOCATE 1 #define REGS_FIXED 2 unsigned regs_allocated : 2; /* Set to zero when `regex_compile' compiles a pattern; set to one by `re_compile_fastmap' if it updates the fastmap. */ unsigned fastmap_accurate : 1; /* If set, `re_match_2' does not return information about subexpressions. */ unsigned no_sub : 1; /* If set, a beginning-of-line anchor doesn't match at the beginning of the string. */ unsigned not_bol : 1; /* Similarly for an end-of-line anchor. */ unsigned not_eol : 1; /* If true, an anchor at a newline matches. */ unsigned newline_anchor : 1; /* [[[end pattern_buffer]]] */ }; typedef struct re_pattern_buffer regex_t; /* Type for byte offsets within the string. POSIX mandates this. */ typedef int regoff_t; /* This is the structure we store register match data in. See regex.texinfo for a full description of what registers match. */ struct re_registers { unsigned num_regs; regoff_t *start; regoff_t *end; }; /* If `regs_allocated' is REGS_UNALLOCATED in the pattern buffer, `re_match_2' returns information about at least this many registers the first time a `regs' structure is passed. */ #ifndef RE_NREGS #define RE_NREGS 30 #endif /* POSIX specification for registers. Aside from the different names than `re_registers', POSIX uses an array of structures, instead of a structure of arrays. */ typedef struct { regoff_t rm_so; /* Byte offset from string's start to substring's start. */ regoff_t rm_eo; /* Byte offset from string's start to substring's end. */ } regmatch_t; /* Declarations for routines. */ /* To avoid duplicating every routine declaration -- once with a prototype (if we are ANSI), and once without (if we aren't) -- we use the following macro to declare argument types. This unfortunately clutters up the declarations a bit, but I think it's worth it. */ #if __STDC__ #define _RE_ARGS(args) args #else /* not __STDC__ */ #define _RE_ARGS(args) () #endif /* not __STDC__ */ /* Sets the current default syntax to SYNTAX, and return the old syntax. You can also simply assign to the `re_syntax_options' variable. */ extern reg_syntax_t re_set_syntax _RE_ARGS ((reg_syntax_t syntax)); /* Compile the regular expression PATTERN, with length LENGTH and syntax given by the global `re_syntax_options', into the buffer BUFFER. Return NULL if successful, and an error string if not. */ extern const char *re_compile_pattern _RE_ARGS ((const char *pattern, size_t length, struct re_pattern_buffer *buffer)); /* Compile a fastmap for the compiled pattern in BUFFER; used to accelerate searches. Return 0 if successful and -2 if was an internal error. */ extern int re_compile_fastmap _RE_ARGS ((struct re_pattern_buffer *buffer)); /* Search in the string STRING (with length LENGTH) for the pattern compiled into BUFFER. Start searching at position START, for RANGE characters. Return the starting position of the match, -1 for no match, or -2 for an internal error. Also return register information in REGS (if REGS and BUFFER->no_sub are nonzero). */ extern int re_search _RE_ARGS ((struct re_pattern_buffer *buffer, const char *string, int length, int start, int range, struct re_registers *regs)); /* Like `re_search', but search in the concatenation of STRING1 and STRING2. Also, stop searching at index START + STOP. */ extern int re_search_2 _RE_ARGS ((struct re_pattern_buffer *buffer, const char *string1, int length1, const char *string2, int length2, int start, int range, struct re_registers *regs, int stop)); /* Like `re_search', but return how many characters in STRING the regexp in BUFFER matched, starting at position START. */ extern int re_match _RE_ARGS ((struct re_pattern_buffer *buffer, const char *string, int length, int start, struct re_registers *regs)); /* Relates to `re_match' as `re_search_2' relates to `re_search'. */ extern int re_match_2 _RE_ARGS ((struct re_pattern_buffer *buffer, const char *string1, int length1, const char *string2, int length2, int start, struct re_registers *regs, int stop)); /* Set REGS to hold NUM_REGS registers, storing them in STARTS and ENDS. Subsequent matches using BUFFER and REGS will use this memory for recording register information. STARTS and ENDS must be allocated with malloc, and must each be at least `NUM_REGS * sizeof (regoff_t)' bytes long. If NUM_REGS == 0, then subsequent matches should allocate their own register data. Unless this function is called, the first search or match using PATTERN_BUFFER will allocate its own register data, without freeing the old data. */ extern void re_set_registers _RE_ARGS ((struct re_pattern_buffer *buffer, struct re_registers *regs, unsigned num_regs, regoff_t *starts, regoff_t *ends)); #ifdef _REGEX_RE_COMP #ifndef _CRAY /* 4.2 bsd compatibility. */ extern char *re_comp _RE_ARGS ((const char *)); extern int re_exec _RE_ARGS ((const char *)); #endif #endif /* POSIX compatibility. */ extern int regcomp _RE_ARGS ((regex_t *preg, const char *pattern, int cflags)); extern int regexec _RE_ARGS ((const regex_t *preg, const char *string, size_t nmatch, regmatch_t pmatch[], int eflags)); extern size_t regerror _RE_ARGS ((int errcode, const regex_t *preg, char *errbuf, size_t errbuf_size)); extern void regfree _RE_ARGS ((regex_t *preg)); #ifdef __cplusplus } #endif /* C++ */ #endif /* not __REGEXP_LIBRARY_H__ */ /* Local variables: make-backup-files: t version-control: t trim-versions-without-asking: nil End: */ mutt-2.1.4/mkdtemp.c0000644000175000017500000000176714116114174011250 00000000000000/* taken from XFCE's Xarchiver, made to work without glib for mutt */ #include #include #include #include #include /* mkdtemp function for systems which don't have one */ char *mkdtemp (char *tmpl) { static const char LETTERS[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; static unsigned long value = 0; unsigned long v; int len; int i, j; len = strlen (tmpl); if (len < 6 || strcmp (&tmpl[len - 6], "XXXXXX") != 0) { errno = EINVAL; return NULL; } value += ((unsigned long) time (NULL)) ^ getpid (); for (i = 0; i < 7 ; ++i, value += 7777) { /* fill in the random bits */ for (j = 0, v = value; j < 6; ++j) { tmpl[(len - 6) + j] = LETTERS[v % 62]; v /= 62; } /* try to create the directory */ if (mkdir (tmpl, 0700) == 0) return tmpl; else if (errno != EEXIST) return NULL; } errno = EEXIST; return NULL; } mutt-2.1.4/crypt-gpgme.h0000644000175000017500000000416414116114174012044 00000000000000/* * Copyright (C) 2004 g10 Code GmbH * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef CRYPT_GPGME_H #define CRYPT_GPGME_H #include "mutt_crypt.h" void pgp_gpgme_init (void); void smime_gpgme_init (void); char *pgp_gpgme_findkeys (ADDRESS *adrlist, int oppenc_mode); char *smime_gpgme_findkeys (ADDRESS *adrlist, int oppenc_mode); BODY *pgp_gpgme_encrypt_message (BODY *a, char *keylist, int sign); BODY *smime_gpgme_build_smime_entity (BODY *a, char *keylist); int pgp_gpgme_decrypt_mime (FILE *fpin, FILE **fpout, BODY *b, BODY **cur); int smime_gpgme_decrypt_mime (FILE *fpin, FILE **fpout, BODY *b, BODY **cur); int pgp_gpgme_check_traditional (FILE *fp, BODY *b, int just_one); void pgp_gpgme_invoke_import (const char* fname); int pgp_gpgme_application_handler (BODY *m, STATE *s); int smime_gpgme_application_handler (BODY *a, STATE *s); int pgp_gpgme_encrypted_handler (BODY *a, STATE *s); BODY *pgp_gpgme_make_key_attachment (void); BODY *pgp_gpgme_sign_message (BODY *a); BODY *smime_gpgme_sign_message (BODY *a); int pgp_gpgme_verify_one (BODY *sigbdy, STATE *s, const char *tempfile); int smime_gpgme_verify_one (BODY *sigbdy, STATE *s, const char *tempfile); void pgp_gpgme_send_menu (SEND_CONTEXT *sctx); void smime_gpgme_send_menu (SEND_CONTEXT *sctx); int smime_gpgme_verify_sender (HEADER *h); void mutt_gpgme_set_sender (const char *sender); int mutt_gpgme_select_secret_key (BUFFER *keyid); #endif mutt-2.1.4/buffy.c0000644000175000017500000005505514155211517010723 00000000000000/* * Copyright (C) 1996-2000,2010,2013 Michael R. Elkins * Copyright (C) 2016-2017 Kevin J. McCarthy * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #if HAVE_CONFIG_H # include "config.h" #endif #include "mutt.h" #include "buffy.h" #include "mailbox.h" #include "mx.h" #include "mutt_curses.h" #include "mutt_menu.h" #ifdef USE_SIDEBAR #include "sidebar.h" #endif #ifdef USE_IMAP #include "imap.h" #endif #ifdef USE_INOTIFY #include "monitor.h" #endif #include #include #include #include #include #include #include #include static time_t BuffyTime = 0; /* last time we started checking for mail */ static time_t BuffyStatsTime = 0; /* last time we check performed mail_check_stats */ time_t BuffyDoneTime = 0; /* last time we knew for sure how much mail there was. */ static short BuffyCount = 0; /* how many boxes with new mail */ static short BuffyNotify = 0; /* # of unnotified new boxes */ static BUFFY* buffy_get (const char *path); /* Find the last message in the file. * upon success return 0. If no message found - return -1 */ static int fseek_last_message (FILE * f) { LOFF_T pos; char buffer[BUFSIZ + 9]; /* 7 for "\n\nFrom " */ int bytes_read; int i; /* Index into `buffer' for scanning. */ memset (buffer, 0, sizeof(buffer)); fseek (f, 0, SEEK_END); pos = ftello (f); /* Set `bytes_read' to the size of the last, probably partial, buffer; 0 < * `bytes_read' <= `BUFSIZ'. */ bytes_read = pos % BUFSIZ; if (bytes_read == 0) bytes_read = BUFSIZ; /* Make `pos' a multiple of `BUFSIZ' (0 if the file is short), so that all * reads will be on block boundaries, which might increase efficiency. */ while ((pos -= bytes_read) >= 0) { /* we save in the buffer at the end the first 7 chars from the last read */ strncpy (buffer + BUFSIZ, buffer, 5+2); /* 2 == 2 * mutt_strlen(CRLF) */ fseeko (f, pos, SEEK_SET); bytes_read = fread (buffer, sizeof (char), bytes_read, f); if (bytes_read == -1) return -1; for (i = bytes_read; --i >= 0;) if (!mutt_strncmp (buffer + i, "\n\nFrom ", mutt_strlen ("\n\nFrom "))) { /* found it - go to the beginning of the From */ fseeko (f, pos + i + 2, SEEK_SET); return 0; } bytes_read = BUFSIZ; } /* here we are at the beginning of the file */ if (!mutt_strncmp ("From ", buffer, 5)) { fseek (f, 0, 0); return (0); } return (-1); } /* Return 1 if the last message is new */ static int test_last_status_new (FILE * f) { HEADER *hdr; ENVELOPE* tmp_envelope; int result = 0; if (fseek_last_message (f) == -1) return (0); hdr = mutt_new_header (); tmp_envelope = mutt_read_rfc822_header (f, hdr, 0, 0); if (!(hdr->read || hdr->old)) result = 1; mutt_free_envelope(&tmp_envelope); mutt_free_header (&hdr); return result; } static int test_new_folder (const char *path) { FILE *f; int rc = 0; int typ; typ = mx_get_magic (path); if (typ != MUTT_MBOX && typ != MUTT_MMDF) return 0; if ((f = fopen (path, "rb"))) { rc = test_last_status_new (f); safe_fclose (&f); } return rc; } void mutt_buffy_cleanup (const char *buf, struct stat *st) { #ifdef HAVE_UTIMENSAT struct timespec ts[2]; #else struct utimbuf ut; #endif BUFFY *tmp; if (option(OPTCHECKMBOXSIZE)) { tmp = mutt_find_mailbox (buf); if (tmp && !tmp->new) mutt_update_mailbox (tmp); } else { /* fix up the times so buffy won't get confused */ if (st->st_mtime > st->st_atime) { #ifdef HAVE_UTIMENSAT ts[0].tv_sec = 0; ts[0].tv_nsec = UTIME_OMIT; ts[1].tv_sec = 0; ts[1].tv_nsec = UTIME_NOW; utimensat (AT_FDCWD, buf, ts, 0); #else ut.actime = st->st_atime; ut.modtime = time (NULL); utime (buf, &ut); #endif } else { #ifdef HAVE_UTIMENSAT ts[0].tv_sec = 0; ts[0].tv_nsec = UTIME_NOW; ts[1].tv_sec = 0; ts[1].tv_nsec = UTIME_NOW; utimensat (AT_FDCWD, buf, ts, 0); #else utime (buf, NULL); #endif } } } BUFFY *mutt_find_mailbox (const char *path) { BUFFY *tmp = NULL; struct stat sb; struct stat tmp_sb; if (stat (path,&sb) != 0) return NULL; for (tmp = Incoming; tmp; tmp = tmp->next) { if (stat (mutt_b2s (tmp->pathbuf), &tmp_sb) ==0 && sb.st_dev == tmp_sb.st_dev && sb.st_ino == tmp_sb.st_ino) break; } return tmp; } void mutt_update_mailbox (BUFFY * b) { struct stat sb; if (!b) return; if (stat (mutt_b2s (b->pathbuf), &sb) == 0) b->size = (off_t) sb.st_size; else b->size = 0; return; } static BUFFY *buffy_new (const char *path) { BUFFY* buffy; char rp[PATH_MAX] = ""; char *r = NULL; buffy = (BUFFY *) safe_calloc (1, sizeof (BUFFY)); buffy->pathbuf = mutt_buffer_new (); mutt_buffer_strcpy (buffy->pathbuf, path); r = realpath (path, rp); buffy->realpath = safe_strdup (r ? rp : path); buffy->next = NULL; buffy->magic = 0; return buffy; } static void buffy_free (BUFFY **mailbox) { if (!(mailbox && *mailbox)) return; mutt_buffer_free (&((*mailbox)->pathbuf)); FREE (&((*mailbox)->realpath)); FREE (&((*mailbox)->label)); FREE (mailbox); /* __FREE_CHECKED__ */ } static BUFFY **find_buffy_slot (const char *path) { const char *p; char rp[PATH_MAX]; BUFFY **slot; p = realpath (path, rp); for (slot = &Incoming; *slot; slot = &((*slot)->next)) if (mutt_strcmp (p ? p : path, (*slot)->realpath) == 0) break; return slot; } /* To avoid overwriting existing values: * - label should be NULL if unspecified * - nopoll should be -1 if unspecified * - nonotify should be -1 if unspecified */ void mutt_buffy_add (const char *path, const char *label, int nopoll, int nonotify) { BUFFY **tmp; struct stat sb; int new = 0; if (!path || !*path) return; dprint (3, (debugfile, "mutt_buffy_add: %s\n", path)); tmp = find_buffy_slot (path); if (!*tmp) { new = 1; *tmp = buffy_new (path); #ifdef USE_SIDEBAR mutt_sb_notify_mailbox (*tmp, 1); #endif } if (label) mutt_str_replace (&(*tmp)->label, label); if (nopoll == -1) nopoll = (*tmp)->nopoll; if (new || (nopoll != (*tmp)->nopoll)) { (*tmp)->nopoll = nopoll; #ifdef USE_INOTIFY if (!nopoll) { (*tmp)->magic = mx_get_magic (mutt_b2s ((*tmp)->pathbuf)); mutt_monitor_add (*tmp); } else mutt_monitor_remove (*tmp); #endif } if (nonotify != -1) (*tmp)->nonotify = nonotify; (*tmp)->new = 0; (*tmp)->notified = 1; (*tmp)->newly_created = 0; /* for check_mbox_size, it is important that if the folder is new (tested by * reading it), the size is set to 0 so that later when we check we see * that it increased . without check_mbox_size we probably don't care. */ if (!nopoll && option(OPTCHECKMBOXSIZE) && stat (mutt_b2s ((*tmp)->pathbuf), &sb) == 0 && !test_new_folder (mutt_b2s ((*tmp)->pathbuf))) { /* some systems out there don't have an off_t type */ (*tmp)->size = (off_t) sb.st_size; } else (*tmp)->size = 0; } static void buffy_remove (BUFFY **pbuffy) { BUFFY *next; next = (*pbuffy)->next; #ifdef USE_SIDEBAR mutt_sb_notify_mailbox (*pbuffy, 0); #endif #ifdef USE_INOTIFY if (!(*pbuffy)->nopoll) mutt_monitor_remove (*pbuffy); #endif buffy_free (pbuffy); *pbuffy = next; } void mutt_buffy_remove (const char *path) { BUFFY **pbuffy; if (!path || !*path) return; dprint (3, (debugfile, "mutt_buffy_remove: %s\n", path)); pbuffy = find_buffy_slot (path); if (*pbuffy) buffy_remove (pbuffy); } int mutt_parse_mailboxes (BUFFER *path, BUFFER *s, union pointer_long_t udata, BUFFER *err) { BUFFER *label = NULL; BUFFER *mailbox = NULL; int nonotify = -1, nopoll = -1, rc = -1; int label_set = 0, mailbox_set = 0; mailbox = mutt_buffer_pool_get (); label = mutt_buffer_pool_get (); while (MoreArgs (s)) { do { mutt_extract_token (path, s, 0); if (mutt_strcmp (mutt_b2s (path), "-poll") == 0) nopoll = 0; else if (mutt_strcmp (mutt_b2s (path), "-nopoll") == 0) nopoll = 1; else if (mutt_strcmp (mutt_b2s (path), "-notify") == 0) nonotify = 0; else if (mutt_strcmp (mutt_b2s (path), "-nonotify") == 0) nonotify = 1; else if (mutt_strcmp (mutt_b2s (path), "-label") == 0) { if (!MoreArgs (s)) { mutt_buffer_strcpy (err, _("too few arguments")); goto cleanup; } label_set = 1; mutt_extract_token (label, s, 0); } else if (mutt_strcmp (mutt_b2s (path), "-nolabel") == 0) { label_set = 1; mutt_buffer_clear (label); } else { mailbox_set = 1; mutt_buffer_strcpy (mailbox, mutt_b2s (path)); mutt_buffer_expand_path (mailbox); break; } } while (MoreArgs (s)); if (!mutt_buffer_len (mailbox)) { if (!mailbox_set) { mutt_buffer_strcpy (err, _("too few arguments")); goto cleanup; } } else mutt_buffy_add (mutt_b2s (mailbox), label_set ? mutt_b2s (label) : NULL, nopoll, nonotify); mutt_buffer_clear (mailbox); mutt_buffer_clear (label); nopoll = -1; label_set = 0; mailbox_set = 0; } rc = 0; cleanup: mutt_buffer_pool_release (&mailbox); mutt_buffer_pool_release (&label); return rc; } int mutt_parse_unmailboxes (BUFFER *path, BUFFER *s, union pointer_long_t udata, BUFFER *err) { BUFFY **pbuffy; while (MoreArgs (s)) { mutt_extract_token (path, s, 0); if (mutt_strcmp(mutt_b2s (path),"*") == 0) { pbuffy = &Incoming; while (*pbuffy) buffy_remove (pbuffy); return 0; } mutt_buffer_expand_path (path); if (!mutt_buffer_len (path)) continue; pbuffy = find_buffy_slot (mutt_b2s (path)); if (*pbuffy) buffy_remove (pbuffy); } return 0; } /* Checks the specified maildir subdir (cur or new) for new mail or mail counts. * check_new: if true, check for new mail. * check_stats: if true, count total, new, and flagged messages. * Returns 1 if the dir has new mail. */ static int buffy_maildir_check_dir (BUFFY* mailbox, const char *dir_name, int check_new, int check_stats) { BUFFER *path = NULL; BUFFER *msgpath = NULL; DIR *dirp; struct dirent *de; char *p; int rc = 0; struct stat sb; path = mutt_buffer_pool_get (); msgpath = mutt_buffer_pool_get (); mutt_buffer_printf (path, "%s/%s", mutt_b2s (mailbox->pathbuf), dir_name); /* when $mail_check_recent is set, if the new/ directory hasn't been modified since * the user last exited the mailbox, then we know there is no recent mail. */ if (check_new && option(OPTMAILCHECKRECENT)) { if (stat(mutt_b2s (path), &sb) == 0 && mutt_stat_timespec_compare (&sb, MUTT_STAT_MTIME, &mailbox->last_visited) < 0) { rc = 0; check_new = 0; } } if (! (check_new || check_stats)) goto cleanup; if ((dirp = opendir (mutt_b2s (path))) == NULL) { mailbox->magic = 0; rc = 0; goto cleanup; } while ((de = readdir (dirp)) != NULL) { if (*de->d_name == '.') continue; p = strstr (de->d_name, ":2,"); if (p && strchr (p + 3, 'T')) continue; if (check_stats) { mailbox->msg_count++; if (p && strchr (p + 3, 'F')) mailbox->msg_flagged++; } if (!p || !strchr (p + 3, 'S')) { if (check_stats) mailbox->msg_unread++; if (check_new) { if (option(OPTMAILCHECKRECENT)) { mutt_buffer_printf (msgpath, "%s/%s", mutt_b2s (path), de->d_name); /* ensure this message was received since leaving this mailbox */ if (stat(mutt_b2s (msgpath), &sb) == 0 && (mutt_stat_timespec_compare (&sb, MUTT_STAT_CTIME, &mailbox->last_visited) <= 0)) continue; } mailbox->new = 1; rc = 1; check_new = 0; if (!check_stats) break; } } } closedir (dirp); cleanup: mutt_buffer_pool_release (&path); mutt_buffer_pool_release (&msgpath); return rc; } /* Checks new mail for a maildir mailbox. * check_stats: if true, also count total, new, and flagged messages. * Returns 1 if the mailbox has new mail. */ static int buffy_maildir_check (BUFFY* mailbox, int check_stats) { int rc, check_new = 1; if (check_stats) { mailbox->msg_count = 0; mailbox->msg_unread = 0; mailbox->msg_flagged = 0; } rc = buffy_maildir_check_dir (mailbox, "new", check_new, check_stats); check_new = !rc && option (OPTMAILDIRCHECKCUR); if (check_new || check_stats) if (buffy_maildir_check_dir (mailbox, "cur", check_new, check_stats)) rc = 1; return rc; } /* Checks new mail for an mbox mailbox * check_stats: if true, also count total, new, and flagged messages. * Returns 1 if the mailbox has new mail. */ static int buffy_mbox_check (BUFFY* mailbox, struct stat *sb, int check_stats) { int rc = 0; int new_or_changed; CONTEXT ctx; if (option (OPTCHECKMBOXSIZE)) new_or_changed = sb->st_size > mailbox->size; else new_or_changed = (mutt_stat_compare (sb, MUTT_STAT_MTIME, sb, MUTT_STAT_ATIME) > 0) || (mailbox->newly_created && (mutt_stat_compare (sb, MUTT_STAT_CTIME, sb, MUTT_STAT_MTIME) == 0) && (mutt_stat_compare (sb, MUTT_STAT_CTIME, sb, MUTT_STAT_ATIME) == 0)); if (new_or_changed) { if (!option(OPTMAILCHECKRECENT) || (mutt_stat_timespec_compare (sb, MUTT_STAT_MTIME, &mailbox->last_visited) > 0)) { rc = 1; mailbox->new = 1; } } else if (option(OPTCHECKMBOXSIZE)) { /* some other program has deleted mail from the folder */ mailbox->size = (off_t) sb->st_size; } if (mailbox->newly_created && (sb->st_ctime != sb->st_mtime || sb->st_ctime != sb->st_atime)) mailbox->newly_created = 0; if (check_stats && (mutt_stat_timespec_compare (sb, MUTT_STAT_MTIME, &mailbox->stats_last_checked) > 0)) { if (mx_open_mailbox (mutt_b2s (mailbox->pathbuf), MUTT_READONLY | MUTT_QUIET | MUTT_NOSORT | MUTT_PEEK, &ctx) != NULL) { mailbox->msg_count = ctx.msgcount; mailbox->msg_unread = ctx.unread; mailbox->msg_flagged = ctx.flagged; mailbox->stats_last_checked = ctx.mtime; mx_close_mailbox (&ctx, 0); } } return rc; } /* Check all Incoming for new mail and total/new/flagged messages * The force argument may be any combination of the following values: * MUTT_BUFFY_CHECK_FORCE ignore BuffyTimeout and check for new mail * MUTT_BUFFY_CHECK_FORCE_STATS ignore BuffyTimeout and calculate statistics */ int mutt_buffy_check (int force) { BUFFY *tmp; struct stat sb; struct stat contex_sb; time_t t; int check_stats = 0; #ifdef USE_SIDEBAR short orig_new; int orig_count, orig_unread, orig_flagged; #endif sb.st_size=0; contex_sb.st_dev=0; contex_sb.st_ino=0; #ifdef USE_IMAP /* update postponed count as well, on force */ if (force & MUTT_BUFFY_CHECK_FORCE) mutt_update_num_postponed (); #endif /* fastest return if there are no mailboxes */ if (!Incoming) return 0; t = time (NULL); if (!force && (t - BuffyTime < BuffyTimeout)) return BuffyCount; if ((force & MUTT_BUFFY_CHECK_FORCE_STATS) || (option (OPTMAILCHECKSTATS) && (t - BuffyStatsTime >= BuffyCheckStatsInterval))) { check_stats = 1; BuffyStatsTime = t; } BuffyTime = t; BuffyCount = 0; BuffyNotify = 0; #ifdef USE_IMAP BuffyCount += imap_buffy_check (force, check_stats); #endif /* check device ID and serial number instead of comparing paths */ if (!Context || Context->magic == MUTT_IMAP || Context->magic == MUTT_POP || stat (Context->path, &contex_sb) != 0) { contex_sb.st_dev=0; contex_sb.st_ino=0; } for (tmp = Incoming; tmp; tmp = tmp->next) { if (tmp->nopoll) continue; #ifdef USE_SIDEBAR orig_new = tmp->new; orig_count = tmp->msg_count; orig_unread = tmp->msg_unread; orig_flagged = tmp->msg_flagged; #endif if (tmp->magic != MUTT_IMAP) { tmp->new = 0; #ifdef USE_POP if (mx_is_pop (mutt_b2s (tmp->pathbuf))) tmp->magic = MUTT_POP; else #endif if (stat (mutt_b2s (tmp->pathbuf), &sb) != 0 || (S_ISREG(sb.st_mode) && sb.st_size == 0) || (!tmp->magic && (tmp->magic = mx_get_magic (mutt_b2s (tmp->pathbuf))) <= 0)) { /* if the mailbox still doesn't exist, set the newly created flag to * be ready for when it does. */ tmp->newly_created = 1; tmp->magic = 0; tmp->size = 0; continue; } } /* check to see if the folder is the currently selected folder * before polling */ if (!Context || !Context->path || (( tmp->magic == MUTT_IMAP || tmp->magic == MUTT_POP ) ? mutt_strcmp (mutt_b2s (tmp->pathbuf), Context->path) : (sb.st_dev != contex_sb.st_dev || sb.st_ino != contex_sb.st_ino))) { switch (tmp->magic) { case MUTT_MBOX: case MUTT_MMDF: if (buffy_mbox_check (tmp, &sb, check_stats) > 0) BuffyCount++; break; case MUTT_MAILDIR: if (buffy_maildir_check (tmp, check_stats) > 0) BuffyCount++; break; case MUTT_MH: if (mh_buffy (tmp, check_stats) > 0) BuffyCount++; break; } } else if (option(OPTCHECKMBOXSIZE) && Context && Context->path) tmp->size = (off_t) sb.st_size; /* update the size of current folder */ #ifdef USE_SIDEBAR if ((orig_new != tmp->new) || (orig_count != tmp->msg_count) || (orig_unread != tmp->msg_unread) || (orig_flagged != tmp->msg_flagged)) mutt_set_current_menu_redraw (REDRAW_SIDEBAR); #endif if (!tmp->new) tmp->notified = 0; else { /* pretend we've already notified for the mailbox */ if (tmp->nonotify) tmp->notified = 1; else if (!tmp->notified) BuffyNotify++; } } BuffyDoneTime = BuffyTime; return (BuffyCount); } int mutt_buffy_list (void) { BUFFY *tmp; BUFFER *path = NULL; char buffylist[2*STRING]; size_t pos = 0; int first = 1; int have_unnotified = BuffyNotify; path = mutt_buffer_pool_get (); buffylist[0] = 0; pos += strlen (strncat (buffylist, _("New mail in "), sizeof (buffylist) - 1 - pos)); /* __STRNCAT_CHECKED__ */ for (tmp = Incoming; tmp; tmp = tmp->next) { /* Is there new mail in this mailbox? */ if (!tmp->new || (have_unnotified && tmp->notified)) continue; mutt_buffer_strcpy (path, mutt_b2s (tmp->pathbuf)); mutt_buffer_pretty_mailbox (path); if (!first && (MuttMessageWindow->cols >= 7) && (pos + mutt_buffer_len (path) >= (size_t)MuttMessageWindow->cols - 7)) break; if (!first) pos += strlen (strncat(buffylist + pos, ", ", sizeof(buffylist)-1-pos)); /* __STRNCAT_CHECKED__ */ /* Prepend an asterisk to mailboxes not already notified */ if (!tmp->notified) { /* pos += strlen (strncat(buffylist + pos, "*", sizeof(buffylist)-1-pos)); __STRNCAT_CHECKED__ */ tmp->notified = 1; BuffyNotify--; } pos += strlen (strncat(buffylist + pos, mutt_b2s (path), sizeof(buffylist)-1-pos)); /* __STRNCAT_CHECKED__ */ first = 0; } if (!first && tmp) { strncat (buffylist + pos, ", ...", sizeof (buffylist) - 1 - pos); /* __STRNCAT_CHECKED__ */ } mutt_buffer_pool_release (&path); if (!first) { mutt_message ("%s", buffylist); return (1); } else { /* there were no mailboxes needing to be notified, so clean up since * BuffyNotify has somehow gotten out of sync */ BuffyNotify = 0; return (0); } } void mutt_buffy_setnotified (const char *path) { BUFFY *buffy; buffy = buffy_get(path); if (!buffy) return; buffy->notified = 1; #if HAVE_CLOCK_GETTIME clock_gettime (CLOCK_REALTIME, &buffy->last_visited); #else buffy->last_visited.tv_nsec = 0; time(&buffy->last_visited.tv_sec); #endif } int mutt_buffy_notify (void) { if (mutt_buffy_check (0) && BuffyNotify) { return (mutt_buffy_list ()); } return (0); } void mutt_buffy (char *s, size_t slen) { BUFFER *s_buf; s_buf = mutt_buffer_pool_get (); mutt_buffer_addstr (s_buf, NONULL (s)); mutt_buffer_buffy (s_buf); strfcpy (s, mutt_b2s (s_buf), slen); mutt_buffer_pool_release (&s_buf); } /* * mutt_buffy() -- incoming folders completion routine * * given a folder name, this routine gives the next incoming folder with new * mail. */ void mutt_buffer_buffy (BUFFER *s) { BUFFY *tmp = Incoming; int pass, found = 0; mutt_buffer_expand_path (s); if (mutt_buffy_check (0)) { for (pass = 0; pass < 2; pass++) for (tmp = Incoming; tmp; tmp = tmp->next) { mutt_buffer_expand_path (tmp->pathbuf); if ((found || pass) && tmp->new) { mutt_buffer_strcpy (s, mutt_b2s (tmp->pathbuf)); mutt_buffer_pretty_mailbox (s); return; } if (mutt_strcmp (mutt_b2s (s), mutt_b2s (tmp->pathbuf)) == 0) found = 1; } mutt_buffy_check (MUTT_BUFFY_CHECK_FORCE); /* buffy was wrong - resync things */ } /* no folders with new mail */ mutt_buffer_clear (s); } /* fetch buffy object for given path, if present */ static BUFFY* buffy_get (const char *path) { BUFFY *cur; BUFFER *epath; if (!path) return NULL; epath = mutt_buffer_pool_get (); mutt_buffer_strcpy (epath, NONULL (path)); mutt_buffer_expand_path (epath); for (cur = Incoming; cur; cur = cur->next) { /* must be done late because e.g. IMAP delimiter may change */ mutt_buffer_expand_path (cur->pathbuf); if (!mutt_strcmp (mutt_b2s (cur->pathbuf), mutt_b2s (epath))) { mutt_buffer_pool_release (&epath); return cur; } } mutt_buffer_pool_release (&epath); return NULL; } mutt-2.1.4/status.c0000644000175000017500000002211414155211517011121 00000000000000/* * Copyright (C) 1996-2000,2007 Michael R. Elkins * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #if HAVE_CONFIG_H # include "config.h" #endif #include "version.h" #include "mutt.h" #include "mutt_menu.h" #include "mutt_curses.h" #include "sort.h" #include "mapping.h" #include "mx.h" #include "buffy.h" #include "background.h" #include #include #include static char *get_sort_str (char *buf, size_t buflen, int method) { snprintf (buf, buflen, "%s%s%s", (method & SORT_REVERSE) ? "reverse-" : "", (method & SORT_LAST) ? "last-" : "", mutt_getnamebyvalue (method & SORT_MASK, SortMethods)); return buf; } static void _menu_status_line (char *buf, size_t buflen, size_t col, int cols, MUTTMENU *menu, const char *p); /* %b = number of incoming folders with unread messages [option] * %d = number of deleted messages [option] * %f = full mailbox path * %F = number of flagged messages [option] * %h = hostname * %l = length of mailbox (in bytes) [option] * %m = total number of messages [option] * %M = number of messages shown (virtual message count when limiting) [option] * %n = number of new messages [option] * %o = number of old unread messages [option] * %p = number of postponed messages [option] * %P = percent of way through index * %r = readonly/wontwrite/changed flag * %R = number of read messages [option] * %s = current sorting method ($sort) * %S = current aux sorting method ($sort_aux) * %t = # of tagged messages [option] * %u = number of unread messages [option] * %v = Mutt version * %V = currently active limit pattern [option] */ static const char * status_format_str (char *buf, size_t buflen, size_t col, int cols, char op, const char *src, const char *prefix, const char *ifstring, const char *elsestring, void *data, format_flag flags) { char fmt[SHORT_STRING], tmp[SHORT_STRING], *cp; int count, optional = (flags & MUTT_FORMAT_OPTIONAL); MUTTMENU *menu = (MUTTMENU *) data; *buf = 0; switch (op) { case 'b': if (!optional) { snprintf (fmt, sizeof (fmt), "%%%sd", prefix); snprintf (buf, buflen, fmt, mutt_buffy_check (0)); } else if (!mutt_buffy_check (0)) optional = 0; break; case 'B': if (!optional) { snprintf (fmt, sizeof (fmt), "%%%sd", prefix); snprintf (buf, buflen, fmt, BackgroundProcessCount); } else if (!BackgroundProcessCount) optional = 0; break; case 'd': if (!optional) { snprintf (fmt, sizeof (fmt), "%%%sd", prefix); snprintf (buf, buflen, fmt, Context ? Context->deleted : 0); } else if (!Context || !Context->deleted) optional = 0; break; case 'f': snprintf (fmt, sizeof(fmt), "%%%ss", prefix); #ifdef USE_COMPRESSED if (Context && Context->compress_info && Context->realpath) { strfcpy (tmp, Context->realpath, sizeof (tmp)); mutt_pretty_mailbox (tmp, sizeof (tmp)); } else #endif if (Context && Context->path) { strfcpy (tmp, Context->path, sizeof (tmp)); mutt_pretty_mailbox (tmp, sizeof (tmp)); } else strfcpy (tmp, _("(no mailbox)"), sizeof (tmp)); snprintf (buf, buflen, fmt, tmp); break; case 'F': if (!optional) { snprintf (fmt, sizeof (fmt), "%%%sd", prefix); snprintf (buf, buflen, fmt, Context ? Context->flagged : 0); } else if (!Context || !Context->flagged) optional = 0; break; case 'h': snprintf (fmt, sizeof (fmt), "%%%ss", prefix); snprintf (buf, buflen, fmt, NONULL(Hostname)); break; case 'l': if (!optional) { snprintf (fmt, sizeof (fmt), "%%%ss", prefix); mutt_pretty_size (tmp, sizeof (tmp), Context ? Context->size : 0); snprintf (buf, buflen, fmt, tmp); } else if (!Context || !Context->size) optional = 0; break; case 'L': if (!optional) { snprintf (fmt, sizeof (fmt), "%%%ss", prefix); mutt_pretty_size (tmp, sizeof (tmp), Context ? Context->vsize: 0); snprintf (buf, buflen, fmt, tmp); } else if (!Context || !Context->pattern) optional = 0; break; case 'm': if (!optional) { snprintf (fmt, sizeof (fmt), "%%%sd", prefix); snprintf (buf, buflen, fmt, Context ? Context->msgcount : 0); } else if (!Context || !Context->msgcount) optional = 0; break; case 'M': if (!optional) { snprintf (fmt, sizeof(fmt), "%%%sd", prefix); snprintf (buf, buflen, fmt, Context ? Context->vcount : 0); } else if (!Context || !Context->pattern) optional = 0; break; case 'n': if (!optional) { snprintf (fmt, sizeof (fmt), "%%%sd", prefix); snprintf (buf, buflen, fmt, Context ? Context->new : 0); } else if (!Context || !Context->new) optional = 0; break; case 'o': if (!optional) { snprintf (fmt, sizeof (fmt), "%%%sd", prefix); snprintf (buf, buflen, fmt, Context ? Context->unread - Context->new : 0); } else if (!Context || !(Context->unread - Context->new)) optional = 0; break; case 'p': count = mutt_num_postponed (0); if (!optional) { snprintf (fmt, sizeof (fmt), "%%%sd", prefix); snprintf (buf, buflen, fmt, count); } else if (!count) optional = 0; break; case 'P': if (!menu) break; if (menu->top + menu->pagelen >= menu->max) cp = menu->top ? "end" : "all"; else { count = (100 * (menu->top + menu->pagelen)) / menu->max; snprintf (tmp, sizeof (tmp), "%d%%", count); cp = tmp; } snprintf (fmt, sizeof (fmt), "%%%ss", prefix); snprintf (buf, buflen, fmt, cp); break; case 'r': { size_t i = 0; if (Context) { i = option(OPTATTACHMSG) ? 3 : ((Context->readonly || Context->dontwrite) ? 2 : (Context->changed || /* deleted doesn't necessarily mean changed in IMAP */ (Context->magic != MUTT_IMAP && Context->deleted)) ? 1 : 0); } if (!StChars || !StChars->len) buf[0] = 0; else if (i >= StChars->len) snprintf (buf, buflen, "%s", StChars->chars[0]); else snprintf (buf, buflen, "%s", StChars->chars[i]); break; } case 'R': { int read = Context ? Context->msgcount - Context->unread : 0; if (!optional) { snprintf (fmt, sizeof (fmt), "%%%sd", prefix); snprintf (buf, buflen, fmt, read); } else if (!read) optional = 0; break; } case 's': snprintf (fmt, sizeof (fmt), "%%%ss", prefix); snprintf (buf, buflen, fmt, get_sort_str (tmp, sizeof (tmp), Sort)); break; case 'S': snprintf (fmt, sizeof (fmt), "%%%ss", prefix); snprintf (buf, buflen, fmt, get_sort_str (tmp, sizeof (tmp), SortAux)); break; case 't': if (!optional) { snprintf (fmt, sizeof (fmt), "%%%sd", prefix); snprintf (buf, buflen, fmt, Context ? Context->tagged : 0); } else if (!Context || !Context->tagged) optional = 0; break; case 'u': if (!optional) { snprintf (fmt, sizeof (fmt), "%%%sd", prefix); snprintf (buf, buflen, fmt, Context ? Context->unread : 0); } else if (!Context || !Context->unread) optional = 0; break; case 'v': snprintf (fmt, sizeof (fmt), "Mutt %%s"); snprintf (buf, buflen, fmt, MUTT_VERSION); break; case 'V': if (!optional) { snprintf (fmt, sizeof(fmt), "%%%ss", prefix); snprintf (buf, buflen, fmt, (Context && Context->pattern) ? Context->pattern : ""); } else if (!Context || !Context->pattern) optional = 0; break; case 0: *buf = 0; return (src); default: snprintf (buf, buflen, "%%%s%c", prefix, op); break; } if (optional) _menu_status_line (buf, buflen, col, cols, menu, ifstring); else if (flags & MUTT_FORMAT_OPTIONAL) _menu_status_line (buf, buflen, col, cols, menu, elsestring); return (src); } static void _menu_status_line (char *buf, size_t buflen, size_t col, int cols, MUTTMENU *menu, const char *p) { mutt_FormatString (buf, buflen, col, cols, p, status_format_str, menu, 0); } void menu_status_line (char *buf, size_t buflen, MUTTMENU *menu, const char *p) { mutt_FormatString (buf, buflen, 0, menu ? menu->statuswin->cols : MuttStatusWindow->cols, p, status_format_str, menu, 0); } mutt-2.1.4/GPL0000644000175000017500000004313513653360550010011 00000000000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Library General Public License instead of this License. mutt-2.1.4/from.c0000644000175000017500000001075614155211517010552 00000000000000/* * Copyright (C) 1996-2000,2013 Michael R. Elkins * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #if HAVE_CONFIG_H # include "config.h" #endif #include "mutt.h" #include #include static const char *next_word (const char *s) { while (*s && !ISSPACE (*s)) s++; SKIPWS (s); return s; } int mutt_check_month (const char *s) { int i; for (i = 0; i < 12; i++) if (mutt_strncasecmp (s, Months[i], 3) == 0) return (i); return (-1); /* error */ } static int is_day_name (const char *s) { int i; if ((strlen (s) < 3) || !*(s + 3) || !ISSPACE (*(s+3))) return 0; for (i=0; i<7; i++) if (mutt_strncasecmp (s, Weekdays[i], 3) == 0) return 1; return 0; } /* * A valid message separator looks like: * * From [ ]