Sync from SUSE:SLFO:Main mutt revision 116e33a45b7565da78ac0a3c2fd26076

This commit is contained in:
Adrian Schröter 2025-02-26 11:47:28 +01:00
commit 1bb94dad72
27 changed files with 5199 additions and 0 deletions

23
.gitattributes vendored Normal file
View File

@ -0,0 +1,23 @@
## Default LFS
*.7z filter=lfs diff=lfs merge=lfs -text
*.bsp filter=lfs diff=lfs merge=lfs -text
*.bz2 filter=lfs diff=lfs merge=lfs -text
*.gem filter=lfs diff=lfs merge=lfs -text
*.gz filter=lfs diff=lfs merge=lfs -text
*.jar filter=lfs diff=lfs merge=lfs -text
*.lz filter=lfs diff=lfs merge=lfs -text
*.lzma filter=lfs diff=lfs merge=lfs -text
*.obscpio filter=lfs diff=lfs merge=lfs -text
*.oxt filter=lfs diff=lfs merge=lfs -text
*.pdf filter=lfs diff=lfs merge=lfs -text
*.png filter=lfs diff=lfs merge=lfs -text
*.rpm filter=lfs diff=lfs merge=lfs -text
*.tbz filter=lfs diff=lfs merge=lfs -text
*.tbz2 filter=lfs diff=lfs merge=lfs -text
*.tgz filter=lfs diff=lfs merge=lfs -text
*.ttf filter=lfs diff=lfs merge=lfs -text
*.txz filter=lfs diff=lfs merge=lfs -text
*.whl filter=lfs diff=lfs merge=lfs -text
*.xz filter=lfs diff=lfs merge=lfs -text
*.zip filter=lfs diff=lfs merge=lfs -text
*.zst filter=lfs diff=lfs merge=lfs -text

8
README.alternates Normal file
View File

@ -0,0 +1,8 @@
Since Mutt version 1.5.6 there's changes with regards to the $alternates
variable. It's no longer a variable, but a command.
When it before was: set alternates=foo@bar.org
It's now: alternates foo@bar.org
It's elaborated in the alternates section of the manpage muttrc.5

94
Signature_conversion Normal file
View File

@ -0,0 +1,94 @@
#!/bin/sh
#
# Wed May 7 14:36:35 2003 Mike FABIAN <mfabian@suse.de>
SIGFILE=$1
if [ ! -e $SIGFILE ] ; then
# there is no such signature file, do nothing
exit 0
fi
guess_legacy_encoding () {
# Guess the legacy encoding used by the language/country
# found in the current LC_CTYPE value.
# First determine the LC_CTYPE locale category setting
ctype="en_US"
ctype=${LC_ALL-${LC_CTYPE-${LANG-${ctype}}}}
case $ctype in
zh_TW*)
LEGACY_ENCODING=Big5
;;
zh_HK*)
LEGACY_ENCODING=Big5HKSCS
;;
zh*)
LEGACY_ENCODING=GB2312
;;
ja*)
LEGACY_ENCODING=EUC-JP
;;
ko*)
LEGACY_ENCODING=EUC-KR
;;
ru*)
LEGACY_ENCODING=KOI8-R
;;
uk*)
LEGACY_ENCODING=KOI8-U
;;
pl*|hr*|hu*|cs*|sk*|sl*)
LEGACY_ENCODING=ISO-8859-2
;;
eo*|mt*)
LEGACY_ENCODING=ISO-8859-3
;;
el*)
LEGACY_ENCODING=ISO-8859-7
;;
he*)
LEGACY_ENCODING=ISO-8859-8
;;
tr*)
LEGACY_ENCODING=ISO-8859-9
;;
th*)
LEGACY_ENCODING=TIS-620 # or ISO-8859-11
;;
lt*)
LEGACY_ENCODING=ISO-8859-13
;;
cy*)
LEGACY_ENCODING=ISO-8859-14
;;
ro*)
LEGACY_ENCODING=ISO-8859-14 # or ISO-8859-16
;;
am*|vi*)
LEGACY_ENCODING=UTF-8
;;
*)
LEGACY_ENCODING=ISO-8859-1
;;
esac
}
guess_legacy_encoding;
LOCALE_CHARMAP=$(locale charmap)
for encoding in UTF-8 $LOCALE_CHARMAP $LEGACY_ENCODING ISO-8859-1
do
if iconv --from $encoding --to $LOCALE_CHARMAP < $SIGFILE > /dev/null 2>&1 ; then
iconv --from $encoding --to $LOCALE_CHARMAP < $SIGFILE
exit 0
fi
done
# unknown encoding in signature file, print it to stdout unchanged:
# (should never happen because the conversion from ISO-8859-1 should
# never return a failure).
cat $SIGFILE

BIN
backports-datetime-fromisoformat-1.0.0.tar.gz (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -0,0 +1,30 @@
This patch solves the issue raised by CVE-2014-9116 in bug 771125.
We correctly redefine what are the whitespace characters as per RFC5322; by
doing so we prevent mutt_substrdup from being used in a way that could lead to
a segfault.
The lib.c part was written by Antonio Radici <antonio@debian.org> to prevent
crashes due to this kind of bugs from happening again.
The wheezy version of this patch is slightly different, therefore this patch
has -jessie prefixed in its name.
Index: mutt/lib.c
===================================================================
---
lib.c | 3 +++
1 file changed, 3 insertions(+)
--- lib.c
+++ lib.c 2019-01-02 13:25:44.767193676 +0000
@@ -675,6 +675,9 @@ char *mutt_substrdup (const char *begin,
size_t len;
char *p;
+ if (end != NULL && end < begin)
+ return NULL;
+
if (end)
len = end - begin;
else

23
mutt-1.10.1-imap.patch Normal file
View File

@ -0,0 +1,23 @@
---
browser.c | 10 ++++++++++
1 file changed, 10 insertions(+)
--- browser.c
+++ browser.c 2020-01-14 14:51:44.927127369 +0000
@@ -1096,6 +1096,16 @@ void _mutt_buffer_select_file (BUFFER *f
mutt_buffer_strcpy (f, state.entry[menu->current].full_path);
+#ifdef USE_IMAP
+ if (buffy)
+ {
+ if (mx_is_imap (mutt_b2s (f)))
+ {
+ state.imap_browse = 1;
+ mutt_buffer_strcpy (LastDir, mutt_b2s (f));
+ }
+ }
+#endif
/* fall through */
case OP_EXIT:

110
mutt-1.13.3.dif Normal file
View File

@ -0,0 +1,110 @@
---
configure.ac | 18 +++++++++++++-----
doc/Muttrc.head | 2 +-
imap/auth.c | 23 ++++++++++++++++++++++-
mx.c | 3 +++
4 files changed, 39 insertions(+), 7 deletions(-)
--- configure.ac
+++ configure.ac 2020-01-14 13:04:28.102878757 +0000
@@ -299,7 +299,7 @@ main ()
mutt_cv_slang=$withval
if test -d $withval/include/slang; then
CPPFLAGS="$CPPFLAGS -I${withval}/include/slang"
- elif test -d $withval/include; then
+ elif test -d $withval/include && $withval != /usr ; then
CPPFLAGS="$CPPFLAGS -I${withval}/include"
fi
LDFLAGS="$LDFLAGS -L${withval}/lib"
@@ -748,8 +748,12 @@ AC_ARG_WITH(ssl, AS_HELP_STRING([--with-
else
if test "$with_ssl" != "yes"
then
- LDFLAGS="$LDFLAGS -L$withval/lib"
- CPPFLAGS="$CPPFLAGS -I$withval/include"
+ case $withval in /usr|/usr/local) ;;
+ *)
+ LDFLAGS="$LDFLAGS -L$withval/lib"
+ CPPFLAGS="$CPPFLAGS -I$withval/include"
+ ;;
+ esac
fi
saved_LIBS="$LIBS"
@@ -836,8 +840,12 @@ AC_ARG_WITH(sasl, AS_HELP_STRING([--with
if test "$with_sasl" != "yes"
then
- CPPFLAGS="$CPPFLAGS -I$with_sasl/include"
- LDFLAGS="$LDFLAGS -L$with_sasl/lib"
+ case $with_sasl in /usr|/usr/local) ;;
+ *)
+ CPPFLAGS="$CPPFLAGS -I$with_sasl/include"
+ LDFLAGS="$LDFLAGS -L$with_sasl/lib"
+ ;;
+ esac
fi
saved_LIBS="$LIBS"
--- doc/Muttrc.head
+++ doc/Muttrc.head 2020-01-14 13:05:31.265704120 +0000
@@ -23,7 +23,7 @@ macro index,pager,attach,compose \cb "\
"call urlview to extract URLs out of a message"
# Show documentation when pressing F1
-macro generic,pager <F1> "<shell-escape> less @docdir@/manual.txt<Enter>" "show Mutt documentation"
+macro generic,index,pager <F1> "<shell-escape> less @docdir@/manual.txt.gz<Enter>" "show Mutt documentation"
# show the incoming mailboxes list (just like "mutt -y") and back when pressing "y"
# note: these macros have been subsumed by the <browse-mailboxes> function.
--- imap/auth.c
+++ imap/auth.c 2020-01-14 13:04:28.102878757 +0000
@@ -79,6 +79,25 @@ int imap_authenticate (IMAP_DATA* idata)
dprint (2, (debugfile, "imap_authenticate: Trying method %s\n", method));
authenticator = imap_authenticators;
+#ifdef USE_SASL
+ /* "login" not supported by SASL */
+ if (!ascii_strcasecmp ("login", method))
+ {
+ while (authenticator->authenticate)
+ {
+ const char *identify = authenticator->method;
+ if (identify && !ascii_strcasecmp(identify, method))
+ if ((r = authenticator->authenticate(idata, method)) !=
+ IMAP_AUTH_UNAVAIL)
+ {
+ FREE(&methods);
+ return r;
+ }
+
+ authenticator++;
+ }
+ } else {
+#endif
while (authenticator->authenticate)
{
if (!authenticator->method ||
@@ -93,7 +112,9 @@ int imap_authenticate (IMAP_DATA* idata)
authenticator++;
}
}
-
+#ifdef USE_SASL
+ }
+#endif
FREE (&methods);
}
else
--- mx.c
+++ mx.c 2020-01-14 13:04:28.102878757 +0000
@@ -1501,6 +1501,9 @@ void mx_update_context (CONTEXT *ctx, in
{
h = ctx->hdrs[msgno];
+ if (!h)
+ continue;
+
if (WithCrypto)
{
/* NOTE: this _must_ be done before the check for mailcap! */

View File

@ -0,0 +1,16 @@
Index: init.h
===================================================================
---
init.h | 1 +
1 file changed, 1 insertion(+)
--- init.h
+++ init.h 2020-01-14 13:28:27.408149074 +0000
@@ -4843,6 +4843,7 @@ struct option_t MuttVars[] = {
** Also see $$copy_decode_weed, $$pipe_decode_weed, $$print_decode_weed.
*/
{ "wrap", DT_NUM, R_PAGER_FLOW, {.p=&Wrap}, {.l=0} },
+ { "wrapcolumn", DT_SYN, R_NONE, {.p="wrap"}, {.p=0} },
/*
** .pp
** When set to a positive value, mutt will wrap text at $$wrap characters.

View File

@ -0,0 +1,50 @@
---
init.h | 7 +++++++
mutt.h | 1 +
send.c | 9 ++++++---
3 files changed, 14 insertions(+), 3 deletions(-)
--- init.h
+++ init.h 2020-01-14 13:12:26.725982952 +0000
@@ -3479,6 +3479,13 @@ struct option_t MuttVars[] = {
** .pp
** Also see the $$force_name variable.
*/
+ { "send_group_reply_to", DT_BOOL, R_NONE, {.l=OPTSENDGROUPREPLYTO}, {.l=0} },
+ /*
+ ** .pp
+ ** This variable controls how group replies are done.
+ ** When set, all recepients listet in "To:" are set in the
+ ** "To:" header again, else in the "CC", which is the default.
+ */
{ "score", DT_BOOL, R_NONE, {.l=OPTSCORE}, {.l=1} },
/*
** .pp
--- mutt.h
+++ mutt.h 2020-01-14 13:07:33.727426389 +0000
@@ -539,6 +539,7 @@ enum
OPTSAVEADDRESS,
OPTSAVEEMPTY,
OPTSAVENAME,
+ OPTSENDGROUPREPLYTO,
OPTSCORE,
#ifdef USE_SIDEBAR
OPTSIDEBAR,
--- send.c
+++ send.c 2020-01-14 13:07:33.727426389 +0000
@@ -700,9 +700,12 @@ int mutt_fetch_recips (ENVELOPE *out, EN
(!in->mail_followup_to || hmfupto != MUTT_YES))
{
/* if (!mutt_addr_is_user(in->to)) */
- if (flags & SENDGROUPREPLY)
- rfc822_append (&out->cc, in->to, 1);
- else
+ if (flags & SENDGROUPREPLY) {
+ if (option (OPTSENDGROUPREPLYTO))
+ rfc822_append (&out->to, in->to, 1);
+ else
+ rfc822_append (&out->cc, in->to, 1);
+ } else
rfc822_append (&out->to, in->to, 1);
rfc822_append (&out->cc, in->cc, 1);
}

20
mutt-1.5.21-mailcap.diff Normal file
View File

@ -0,0 +1,20 @@
read /usr/share/mutt/mailcap as fallback by default. This allows to
set some useful defaults specifically for mutt. For example
text/html
Index: mutt-1.5.21/init.c
===================================================================
---
init.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
--- init.c
+++ init.c 2019-01-02 13:25:20.795634552 +0000
@@ -3854,7 +3854,7 @@ void mutt_init (int skip_sys_rc, LIST *c
else
{
/* Default search path from RFC1524 */
- MailcapPath = safe_strdup ("~/.mailcap:" PKGDATADIR "/mailcap:" SYSCONFDIR "/mailcap:/etc/mailcap:/usr/etc/mailcap:/usr/local/etc/mailcap");
+ MailcapPath = safe_strdup ("~/.mailcap:" PKGDATADIR "/mailcap:" SYSCONFDIR "/mailcap:/usr/etc/mailcap:/usr/local/etc/mailcap:" SYSCONFDIR "/mutt/mailcap");
}
Tempdir = safe_strdup ((p = getenv ("TMPDIR")) ? p : "/tmp");

View File

@ -0,0 +1,127 @@
---
crypt-gpgme.c | 2 +-
lib.c | 11 +++++++++++
lib.h | 1 +
pager.c | 2 +-
pgp.c | 24 ++++++++++++------------
5 files changed, 26 insertions(+), 14 deletions(-)
--- crypt-gpgme.c
+++ crypt-gpgme.c 2019-11-13 13:46:16.512388398 +0000
@@ -2733,7 +2733,7 @@ static void copy_clearsigned (gpgme_data
if (armor_header)
{
- if (buf[0] == '\n')
+ if (buf[0] == '\n' || (buf[0] == '\r' && buf[1] == '\n'))
armor_header = 0;
continue;
}
--- lib.c
+++ lib.c 2019-11-13 13:42:00.245189313 +0000
@@ -693,6 +693,17 @@ int mutt_strcmp(const char *a, const cha
return strcmp(NONULL(a), NONULL(b));
}
+int mutt_strxcmp(const char *a, const char *b)
+{
+ const size_t xa = strcspn(NONULL(a), "\r\n");
+ const size_t xb = strcspn(NONULL(b), "\r\n");
+ if (xb != xa)
+ return -1;
+ if (!xa)
+ return 0;
+ return strncmp(NONULL(a), NONULL(b), (size_t)xa);
+}
+
int mutt_strcasecmp(const char *a, const char *b)
{
return strcasecmp(NONULL(a), NONULL(b));
--- lib.h
+++ lib.h 2019-11-13 13:47:17.399247662 +0000
@@ -225,6 +225,7 @@ int mutt_copy_stream (FILE *, FILE *);
int mutt_copy_bytes (FILE *, FILE *, size_t);
int mutt_strcasecmp (const char *, const char *);
int mutt_strcmp (const char *, const char *);
+int mutt_strxcmp (const char *, const char *);
int mutt_strncasecmp (const char *, const char *, size_t);
int mutt_strncmp (const char *, const char *, size_t);
int mutt_strcoll (const char *, const char *);
--- pager.c
+++ pager.c 2019-11-13 13:48:32.257845120 +0000
@@ -975,7 +975,7 @@ resolve_types (char *buf, char *raw, str
lineInfo[n].type = MT_COLOR_NORMAL;
else if (check_attachment_marker ((char *) raw) == 0)
lineInfo[n].type = MT_COLOR_ATTACHMENT;
- else if (mutt_strcmp ("-- \n", buf) == 0 || mutt_strcmp ("-- \r\n", buf) == 0)
+ else if (mutt_strxcmp ("-- \n", buf) == 0)
{
i = n + 1;
--- pgp.c
+++ pgp.c 2019-11-13 13:51:06.746954963 +0000
@@ -371,7 +371,7 @@ static void pgp_copy_clearsigned (FILE *
continue;
}
- if (mutt_strcmp (buf, "-----BEGIN PGP SIGNATURE-----\n") == 0)
+ if (mutt_strxcmp (buf, "-----BEGIN PGP SIGNATURE-----\n") == 0)
break;
if (armor_header)
@@ -446,11 +446,11 @@ int pgp_application_pgp_handler (BODY *m
could_not_decrypt = 0;
decrypt_okay_rc = 0;
- if (mutt_strcmp ("MESSAGE-----\n", buf + 15) == 0)
+ if (mutt_strxcmp ("MESSAGE-----\n", buf + 15) == 0)
needpass = 1;
- else if (mutt_strcmp ("SIGNED MESSAGE-----\n", buf + 15) == 0)
+ else if (mutt_strxcmp ("SIGNED MESSAGE-----\n", buf + 15) == 0)
clearsign = 1;
- else if (!mutt_strcmp ("PUBLIC KEY BLOCK-----\n", buf + 15))
+ else if (!mutt_strxcmp ("PUBLIC KEY BLOCK-----\n", buf + 15))
pgp_keyblock = 1;
else
{
@@ -480,10 +480,10 @@ int pgp_application_pgp_handler (BODY *m
fputs (buf, tmpfp);
- if ((needpass && mutt_strcmp ("-----END PGP MESSAGE-----\n", buf) == 0) ||
+ if ((needpass && mutt_strxcmp ("-----END PGP MESSAGE-----\n", buf) == 0) ||
(!needpass
- && (mutt_strcmp ("-----END PGP SIGNATURE-----\n", buf) == 0
- || mutt_strcmp ("-----END PGP PUBLIC KEY BLOCK-----\n",buf) == 0)))
+ && (mutt_strxcmp ("-----END PGP SIGNATURE-----\n", buf) == 0
+ || mutt_strxcmp ("-----END PGP PUBLIC KEY BLOCK-----\n",buf) == 0)))
break;
/* remember optional Charset: armor header as defined by RfC4880 */
if (mutt_strncmp ("Charset: ", buf, 9) == 0)
@@ -746,11 +746,11 @@ static int pgp_check_traditional_one_bod
{
if (mutt_strncmp ("-----BEGIN PGP ", buf, 15) == 0)
{
- if (mutt_strcmp ("MESSAGE-----\n", buf + 15) == 0)
+ if (mutt_strxcmp ("MESSAGE-----\n", buf + 15) == 0)
enc = 1;
- else if (mutt_strcmp ("SIGNED MESSAGE-----\n", buf + 15) == 0)
+ else if (mutt_strxcmp ("SIGNED MESSAGE-----\n", buf + 15) == 0)
sgn = 1;
- else if (mutt_strcmp ("PUBLIC KEY BLOCK-----\n", buf + 15) == 0)
+ else if (mutt_strxcmp ("PUBLIC KEY BLOCK-----\n", buf + 15) == 0)
key = 1;
}
}
@@ -1296,9 +1296,9 @@ BODY *pgp_sign_message (BODY *a)
*/
while (fgets (buffer, sizeof (buffer) - 1, pgpout) != NULL)
{
- if (mutt_strcmp ("-----BEGIN PGP MESSAGE-----\n", buffer) == 0)
+ if (mutt_strxcmp ("-----BEGIN PGP MESSAGE-----\n", buffer) == 0)
fputs ("-----BEGIN PGP SIGNATURE-----\n", fp);
- else if (mutt_strcmp("-----END PGP MESSAGE-----\n", buffer) == 0)
+ else if (mutt_strxcmp("-----END PGP MESSAGE-----\n", buffer) == 0)
fputs ("-----END PGP SIGNATURE-----\n", fp);
else
fputs (buffer, fp);

18
mutt-1.5.9i-pgpewrap.diff Normal file
View File

@ -0,0 +1,18 @@
---
pgpewrap.c | 5 +++++
1 file changed, 5 insertions(+)
--- pgpewrap.c
+++ pgpewrap.c 2019-11-13 13:00:01.256398592 +0000
@@ -57,6 +57,11 @@ int main(int argc, char **argv)
}
*opt = NULL;
+ if (opts[0] == NULL)
+ {
+ fprintf(stderr, "Command line usage: %s [flags] -- prefix [recipients]\n", argv[0]);
+ return 1;
+ }
execvp(opts[0], opts);
perror(argv[0]);
return 2;

244
mutt-1.6.1-opennfs.dif Normal file
View File

@ -0,0 +1,244 @@
---
Makefile.am | 4 -
mbox.c | 5 ++
mh.c | 9 +++-
mutt.h | 3 +
muttlib.c | 6 ++
opennfs.c | 122 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
sendlib.c | 4 +
7 files changed, 149 insertions(+), 4 deletions(-)
--- Makefile.am
+++ Makefile.am 2020-01-14 13:28:45.787807571 +0000
@@ -34,7 +34,7 @@ mutt_SOURCES = \
edit.c enter.c flags.c init.c filter.c from.c \
getdomain.c group.c \
handler.c hash.c hdrline.c headers.c help.c hook.c keymap.c \
- main.c mbox.c menu.c mh.c mx.c pager.c parse.c pattern.c \
+ main.c mbox.c menu.c mh.c mx.c opennfs.c pager.c parse.c pattern.c \
postpone.c query.c recvattach.c recvcmd.c \
rfc822.c rfc1524.c rfc2047.c rfc2231.c rfc3676.c \
score.c send.c sendlib.c signal.c sort.c \
@@ -99,7 +99,7 @@ mutt_dotlock_SOURCES = mutt_dotlock.c
mutt_dotlock_LDADD = $(LIBOBJS)
mutt_dotlock_DEPENDENCIES = $(LIBOBJS)
-mutt_pgpring_SOURCES = pgppubring.c pgplib.c lib.c extlib.c sha1.c md5.c pgppacket.c ascii.c
+mutt_pgpring_SOURCES = opennfs.c pgppubring.c pgplib.c lib.c extlib.c sha1.c md5.c pgppacket.c ascii.c
mutt_pgpring_LDADD = $(LIBOBJS) $(LIBINTL)
mutt_pgpring_DEPENDENCIES = $(LIBOBJS) $(INTLDEPS)
--- mbox.c
+++ mbox.c 2020-01-14 13:30:35.969760118 +0000
@@ -893,8 +893,13 @@ static int mbox_sync_mailbox (CONTEXT *c
/* Create a temporary file to write the new version of the mailbox in. */
tempfile = mutt_buffer_pool_get ();
mutt_buffer_mktemp (tempfile);
+#if defined(__linux__)
+ if ((i = opennfs (mutt_b2s (tempfile), O_WRONLY | O_EXCL | O_CREAT, 0600)) == -1 ||
+ (fp = fdopen (i, "w")) == NULL)
+#else
if ((i = open (mutt_b2s (tempfile), O_WRONLY | O_EXCL | O_CREAT, 0600)) == -1 ||
(fp = fdopen (i, "w")) == NULL)
+#endif
{
if (-1 != i)
{
--- mh.c
+++ mh.c 2020-01-14 13:28:45.787807571 +0000
@@ -369,7 +369,11 @@ static int mh_mkstemp (CONTEXT * dest, F
{
mutt_buffer_printf (path, "%s/.mutt-%s-%d-%d",
dest->path, NONULL (Hostname), (int) getpid (), Counter++);
+#if defined(__linux__)
+ if ((fd = opennfs (mutt_b2s (path), O_WRONLY | O_EXCL | O_CREAT, 0666)) == -1)
+#else
if ((fd = open (mutt_b2s (path), O_WRONLY | O_EXCL | O_CREAT, 0666)) == -1)
+#endif
{
if (errno != EEXIST)
{
@@ -1559,8 +1563,11 @@ static int maildir_open_new_message (MES
dprint (2, (debugfile, "maildir_open_new_message (): Trying %s.\n",
mutt_b2s (path)));
-
+#if defined(__linux__)
+ if ((fd = opennfs(mutt_b2s (path), O_WRONLY | O_EXCL | O_CREAT, 0666)) == -1)
+#else
if ((fd = open (mutt_b2s (path), O_WRONLY | O_EXCL | O_CREAT, 0666)) == -1)
+#endif
{
if (errno != EEXIST)
{
--- mutt.h
+++ mutt.h 2020-01-14 13:28:45.787807571 +0000
@@ -1221,4 +1221,7 @@ typedef struct
#include "lib.h"
#include "globals.h"
+#if defined(__linux__)
+extern int opennfs(const char *, int, int);
+#endif
#endif /*MUTT_H*/
--- muttlib.c
+++ muttlib.c 2020-01-14 13:28:45.787807571 +0000
@@ -2562,6 +2562,10 @@ int safe_open (const char *path, int fla
BUFFER *safe_file = NULL;
BUFFER *safe_dir = NULL;
+#if defined(__linux__)
+ if ((fd = opennfs (path, flags, 0600)) < 0)
+ return fd;
+#else
if (flags & O_EXCL)
{
safe_file = mutt_buffer_pool_get ();
@@ -2590,7 +2594,7 @@ int safe_open (const char *path, int fla
if ((fd = open (path, flags & ~O_EXCL, 0600)) < 0)
goto cleanup;
-
+#endif
/* make sure the file is not symlink */
if (lstat (path, &osb) < 0 || fstat (fd, &nsb) < 0 ||
compare_stat(&osb, &nsb) == -1)
--- opennfs.c
+++ opennfs.c 2020-01-14 13:28:45.787807571 +0000
@@ -0,0 +1,122 @@
+#include <errno.h>
+#include <fcntl.h>
+#include <libgen.h>
+#include <limits.h>
+#include <nfs/nfs.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/vfs.h>
+#include <unistd.h>
+
+#ifndef NFS_SUPER_MAGIC
+# define NFS_SUPER_MAGIC 0x6969
+#endif
+
+int opennfs(const char *path, int flags, int mode)
+{
+ char tmplock[NFS_MAXPATHLEN+1], sysname[256];
+ char *slash, *ptr, *dir, *base, *clear = (char*)0;
+ struct stat ps, ts;
+ struct statfs fs;
+ ssize_t len;
+ int ret;
+
+ if ((flags & (O_WRONLY|O_RDWR)) == 0)
+ goto safe;
+
+ if ((flags & (O_EXCL|O_CREAT)) != (O_EXCL|O_CREAT))
+ goto safe;
+
+#if defined(O_NOFOLLOW)
+ flags |= O_NOFOLLOW;
+#endif
+
+ ret = -1;
+ if ((clear = strdup(path)) == (char*)0)
+ goto err;
+ dir = dirname(clear);
+
+ if ((ret = (statfs(dir, &fs))) < 0)
+ goto err;
+
+ if (fs.f_type != NFS_SUPER_MAGIC)
+ goto safe;
+
+ if ((ret = gethostname(sysname, sizeof(sysname))) < 0)
+ goto err;
+
+ ret = -1;
+ ptr = &tmplock[0];
+ if (((len = snprintf(ptr, NFS_MAXPATHLEN, "%s/.%s-XXXXXX", dir, sysname)) < 0) || (len >= NFS_MAXPATHLEN))
+ goto err;
+ ptr += len;
+ slash = ptr;
+
+ free(clear);
+ clear = (char*)0;
+
+ if (mkdtemp(tmplock) == (char*)0)
+ goto err;
+
+ ret = -1;
+ if ((clear = strdup(path)) == (char*)0)
+ goto rmd;
+ base = basename(clear);
+
+ ret = -1;
+ if (((len = snprintf(ptr, NFS_MAXPATHLEN - len, "/%s", base)) < 0) || (len >= (NFS_MAXPATHLEN - len)))
+ goto rmd;
+
+ free(clear);
+ clear = (char*)0;
+
+ if ((ret = open(tmplock, flags, mode)) < 0)
+ goto rmd;
+
+ errno = 0;
+ do {
+ len = write(ret, "0", 2);
+ } while ((len < 0) && (errno == EINTR));
+ close(ret);
+
+ ret = -1;
+ errno = EBADF;
+ if (len != 2)
+ goto unl;
+
+ errno = 0;
+ if ((ret = lstat(tmplock, &ts)) < 0)
+ goto unl;
+
+ if (((ret = link(tmplock, path)) < 0) && (errno == EEXIST))
+ goto unl;
+
+ if ((ret = lstat(path, &ps)) < 0)
+ goto unl;
+
+ ret = -1;
+ errno = EEXIST;
+ if (ps.st_nlink != 2)
+ goto unl;
+ if ((ps.st_rdev != ts.st_rdev) || (ps.st_ino != ts.st_ino))
+ goto unl;
+
+ errno = 0;
+ flags |= O_TRUNC;
+ flags &= ~(O_EXCL|O_CREAT);
+ ret = open(path, flags, mode);
+unl:
+ unlink(tmplock);
+rmd:
+ *slash = '\0';
+ rmdir(tmplock);
+err:
+ if (clear) free(clear);
+ return ret;
+safe:
+ if (clear) free(clear);
+ return open(path, flags, mode);
+}
--- sendlib.c
+++ sendlib.c 2020-01-14 13:28:45.787807571 +0000
@@ -2589,7 +2589,11 @@ send_msg (const char *path, char **args,
if (SendmailWait >= 0 && tempfile && *tempfile)
{
/* *tempfile will be opened as stdout */
+#if defined(__linux__)
+ if (opennfs(*tempfile, O_WRONLY | O_APPEND | O_CREAT | O_EXCL, 0600) < 0)
+#else
if (open (*tempfile, O_WRONLY | O_APPEND | O_CREAT | O_EXCL, 0600) < 0)
+#endif
_exit (S_ERR);
/* redirect stderr to *tempfile too */
if (dup (1) < 0)

BIN
mutt-2.2.13.tar.gz (Stored with Git LFS) Normal file

Binary file not shown.

16
mutt-2.2.13.tar.gz.asc Normal file
View File

@ -0,0 +1,16 @@
-----BEGIN PGP SIGNATURE-----
iQIzBAABCgAdFiEEiXWpszqjeRA4XFMIre92hIAxa9oFAmXsOmQACgkQre92hIAx
a9qIgRAAx7qmqdcvjqHRnvkzvA2QFZ7secrn9f9bxBTjbmzLiQ3w/LFDLJjulZ46
9Qxes13ZO8K446zqqx+Wkr8n0kCDOOSwhbQzZFoFjQAu/NRbmoO6KyJ6jIPdGD+h
x9OXnRKsQqFYkzJi6wRqomGzHda5fo5EF5J+wf+NNoxnAg/FO/9AfPnY9OucsVDL
FtGGtt+Zk9ViqOiVflbRS3WSdA7sXcnCFikwMg3w20KSD+V3yD9TSkExuvm+mAYU
b6aRgzJSP3tOldEnSoTBIOr7seuIZk3G9E9/ZG/8fa1o0I/qFg6n0anlBCnP00sO
w+0NFaAHgoXrnXS67sKF0E7Ry19nxM9CEiD6LxrCj1MybLkOnE3/jtuE98Sur9Rn
qtR5frQ3y1KCFHL/GT8cohhn7tcNQH8D6moTMB3pHvoKdFj777u/WeOgU6mtly11
WVmws+GCeXOf/DuvwzbDmLGl/gz/njpoGgY6oD0NPjL6IJMIsxUO2wOmZ0/RgOuP
iDYZTeBBnKRitx4j3Z33Ak8aNJgq//qzXX1ZSd/Y3jDcv7auGc8BMpd7QuYfQbIg
BDA/Im0Nji9lFeQHw7F8DPZjK4Q1faYDPaIexfxDLpdpTxEqLs+bf2iGkKlIOioa
kDoAHywyuOiOf7dSqC3D8icib7WgJlyYe4a/8P5QQJJKtIUal+k=
=e91b
-----END PGP SIGNATURE-----

View File

@ -0,0 +1,57 @@
From 6fa7a74c23760283282fd8b369d2b7b0fae69fec Mon Sep 17 00:00:00 2001
From: Michal Suchanek <msuchanek@suse.de>
Date: Tue, 12 Nov 2019 14:21:21 +0100
Subject: [PATCH] Fix SIGQUIT handling.
SIGQUIT is not masked in mutt_block_signals. This is not consistent with
SIGTERM/SIGINT handling.
When quit = ask-yes is set the SIGQUIT handler does not ask. Use the
SIGINT handler for handling SIGQUIT as well to fix this.
Signed-off-by: Michal Suchanek <msuchanek@suse.de>
---
signal.c | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
--- signal.c
+++ signal.c 2019-11-14 08:40:31.823464569 +0000
@@ -170,6 +170,7 @@ static void sighandler (int sig)
break;
#endif
+ case SIGQUIT:
case SIGINT:
SigInt = 1;
break;
@@ -199,7 +200,6 @@ void mutt_signal_init (void)
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);
@@ -219,6 +219,7 @@ void mutt_signal_init (void)
sigaction (SIGCONT, &act, NULL);
sigaction (SIGTSTP, &act, NULL);
+ sigaction (SIGQUIT, &act, NULL);
sigaction (SIGINT, &act, NULL);
#if defined (USE_SLANG_CURSES) || defined (HAVE_RESIZETERM)
sigaction (SIGWINCH, &act, NULL);
@@ -258,6 +259,7 @@ void mutt_block_signals (void)
sigaddset (&Sigset, SIGTERM);
sigaddset (&Sigset, SIGHUP);
sigaddset (&Sigset, SIGTSTP);
+ sigaddset (&Sigset, SIGQUIT);
sigaddset (&Sigset, SIGINT);
#if defined (USE_SLANG_CURSES) || defined (HAVE_RESIZETERM)
sigaddset (&Sigset, SIGWINCH);
@@ -358,5 +360,6 @@ void mutt_allow_interrupt (int dispositi
if (disposition == 0)
sa.sa_flags |= SA_RESTART;
#endif
+ sigaction (SIGQUIT, &sa, NULL);
sigaction (SIGINT, &sa, NULL);
}

2909
mutt.changes Normal file

File diff suppressed because it is too large Load Diff

10
mutt.desktop Normal file
View File

@ -0,0 +1,10 @@
[Desktop Entry]
Categories=ConsoleOnly;Network;Email
Encoding=UTF-8
Exec=mutt %u
Icon=mutt
Type=Application
Name=Mutt
GenericName=Mail Client
Terminal=true
MimeType=x-scheme-handler/mailto;

52
mutt.keyring Normal file
View File

@ -0,0 +1,52 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
mQINBFVrOF8BEADyloq5SM1I7bN6CabfJMbd4kdd2hoeAlvcBOmI5TKoB5IDXhzQ
1WsNza4Afl0dRyP9y2ERn8wK7YYqD9UU50p9dw9WN/Ghyr0ZzBBF0d/hygEzfXR8
byTPSWsBa+i7dEyYPEyTj8RBx7WkPr7XcV1pTTcK/qhHYLxynIa5yAJ5Hp/SmgRj
tu0LoNQ5P6XoQ1OVg/ItboqTnp1uYaVIy1j8mtCffUiyBiSB5fcM7wXiqBQMEe6i
egJM0Hnb38GTDZjdevlk2DMMXx6yWdvHa8y1U5pkY8vQsVPnJb/sErqoQK+q+UDX
lHoNGqfvGiq1VdQxW2NcY3RLkQmxlYo3Y0246XS+jift470Ip9bvDDUFoXF637B+
LNrX8dwGAzb3nP6bW5N81SFv8lrGEEK6hj2rrBUsCFGDvkgl7EhLAKnySGJq/ZXo
Vk74whAC0DDRrSHQ402GfJEiEIeJQSbAQuExRwUmWC4K0VPbCKmrDHgO147TfJOb
cNVBrxXhgEuZV3/UEm0gL9fhK4MEZ+sf/qfLjdc400Xli4WtDFaOWtmcefuRAHbA
Huns2upG6Jh3ONByb9wPcJ9hzDFfBcXWjpvHpho9jH6ZreDlUg6AN6j3Fj6D3Sgo
1AAF0aGdc5KoeoD90P2vNI30AhSiYiJBg90rFyWCcPUYAZXwUMS8u4yOswARAQAB
tCBLZXZpbiBKLiBNY0NhcnRoeSA8a2V2aW5AOHQ4LnVzPokCVAQTAQoAPgIbAwUL
CQgHAwUVCgkICwUWAgMBAAIeAQIXgBYhBIl1qbM6o3kQOFxTCK3vdoSAMWvaBQJf
b644BQkPqBBZAAoJEK3vdoSAMWvaUYQQAOon9lb9iOZlvCU8jz3uM1EqOT8zcaSx
HVkAi8lpudurpp3B1j6QnAfZltnbSpzczUx7ar9fQ2C6FIxuCR9z3pnY8ETeHLfF
oSiNveBBUbWIRxGUjLjaid556VSYMfrk9jrE9OnHseEuHlnN/HKgmAgC/bJnUNPQ
NJ4jEsDIgKvWUoXV4SUffW+Z97AAMNhIXT+2IP1kuvPF4fU2ogsQOGTdVuuRUwv2
3jR/YgOIH9+W1CIGXljZhXRus3sYFoxx3JaM5AYVgG3cK678O0uPgUsI0ihXc8Ab
tVCY04sA4/PtkeduDNlIfgwmLhA+a2c1NPbBYG8jYVaQsg3FHQ/OXaoYhbPus4is
uhcmExjmP2NMckYzBHrE32Ar9mRcEHvaoEwm/sJUkEUbqKZOvYb3kFsJnW8sl20B
SS8TIqnXdCgyxHANJihzQXoTSi++WzjPvKn7lMVptsN4lMQvbQo/9AZTxPhZrQAs
573UFmtgWbExHQkDv+bIIpWXmszNffdMpRvUyQMH87rF9vxZeepmwyDWB58ph+r0
Pj5nBKiicA8hDM65It/S9xgPU/b+slrcw3htr2BvUnN+ygmTsWc87TqiiLpvpvw3
W9AKHZjAakqYkkFmFXY4I7RmZEPn0ktkM8zrw2TJXPPgpWqFmP6LGtuyrd/OMqmZ
Fhr+hXSQOJtMuQINBFVrOF8BEAC8mH9Y69KnIrPPBShaw6DTXENRDZzbs0xWbYsX
K8Osqvq5bS+0YhqbNDlChQp92mkMx/Nj+duci2Vh4cfHZCaklEchTAkvCDp+plcM
ctOeohT0lPjC4X/Z1/tSfsbw7d9sKMVCTAEBXFTRPsw5wzRI1RUbdm0birwmqjpI
as8NmyDH/tUINOthKFlnzPPK7+L2s4gpuZvV8PyXTfhoose3UMcuT2SJ2JvKaYgz
XtpRn2bwfD7mCzfp3v7bfyAZtimUyXH3LXEmepxHtFQjINDwaPShIOxykDXfZghQ
0n8uTNmkcydLi6TcOkrN5JMFMCaT4JA2xsjhRBSxXhCQS1Z5Gow6CL8OuLhDT4/1
3wSt4EWMdaDjPQGWO86GMjmb/BSQBGFb3NfIRMeqKslfSswLcbUDv5z8OuasCLE7
e9aenuRyimU82PFdLnx6EObhG9F6zbYpMWxwwOnH1k7eXdh5rMhHrDT60G2ytQYb
kT0gux3ytP0N9eeVbkCKbXVIqNdil2fYAL9ydDm7+AtEi/aOWsybVX0Dy//yEUAt
Auqo8ifPDOpXi3mS2nLfk25sWumV/3qHn0YODk9ht9xMpU8LNZcJ99qhizEp+fuw
ZZJDWtqwIWguJ8au0zoHRXdkWoXBh6akoO3sEXzxMeCofKnYo6+GVayGGCUPbMtd
/xR9CQARAQABiQI8BBgBCgAmAhsMFiEEiXWpszqjeRA4XFMIre92hIAxa9oFAl9v
rkEFCQ+oEGIACgkQre92hIAxa9qh4w/+KpiZmFxNRewCpZTAclO8RFGhZznMsfgY
bEWuD+0zPE62JRKP73e6VjJaKLjv91Se394Wt1WCiM/xUioNIgTntj00N7mxNi91
huQdm4HcY5MDtMVzUFimIYTDkO8lEV26klLr6csXQ1JLC7H4BPi+QMrWfYfn0dcv
ozmLK+OtR+Qdl6Se6KrGQmSq2Ic/hf0Pz1TkPH3wovolHDhvOPfrm2WG/nmnW1+O
xdAitfXVdLXaC53j0Aypbqlu7gBE3t1/vEplewCxpUrwe2Vig8SNjngV70/jZlHZ
+IaSk1cqpeUjIvT3l1xg6cGtkZeD6xKzHG3/pHiWGcwgQbHpwnpLo2Fp5NVVm/Sm
5ny3uK/JrmeqZjqSzmek635p9cVHQvscp2IwChI1HBMbvdAMNCOiwZdcTtDj2Lcf
r3DA3uV6cfeu6k3LPzX30igpVkM4Bmx+5z3C0tPDN52UpG0sz2khszmHVENpwD3N
599+w/Bn3dH97M7HywnH8RdWeyql7rhJMe4QjfO4VVLNFJbX40bRrdsFyCeza7Rl
gW5Dw0F2fFg/JHfk+KtWbz8GL5yK2O3ohUw6jk5qgett2JAH3ro7My6pfsvQrLNf
zfu3rcNcRPWDE+Fg3GhOyQWAYxN2lGncWuOBfNhDOyjvBsLUfrKQtIV5kuYf7/oi
Vpd8jtu9noc=
=uho+
-----END PGP PUBLIC KEY BLOCK-----

2
mutt.mailcap Normal file
View File

@ -0,0 +1,2 @@
# render html mail with w3m
text/html; w3m -I %{charset} -T text/html -dump %s; copiousoutput

BIN
mutt.png (Stored with Git LFS) Normal file

Binary file not shown.

408
mutt.spec Normal file
View File

@ -0,0 +1,408 @@
#
# spec file for package mutt
#
# Copyright (c) 2024 SUSE LLC
#
# All modifications and additions to the file contributed by third parties
# remain the property of their copyright owners, unless otherwise agreed
# upon. The license for this file, and modifications and additions to the
# file, is the same license as for the pristine package itself (unless the
# license for the pristine package is not an Open Source License, in which
# case the license is the MIT License). An "Open Source License" is a
# license that conforms to the Open Source Definition (Version 1.9)
# published by the Open Source Initiative.
# Please submit bugfixes or comments via https://bugs.opensuse.org/
#
%global _sysconfdir %{_sysconfdir}
%bcond_with mutt_openssl
%bcond_without mutt_gnutls
Name: mutt
Version: 2.2.13
Release: 0
Summary: Mail Program
# ftp://ftp.mutt.org/mutt/devel/
# https:///bitbucket.org/mutt/mutt/downloads/%%name-%%version.tar.gz
License: GPL-2.0-or-later
Group: Productivity/Networking/Email/Clients
URL: http://www.mutt.org
Source0: https://bitbucket.org/mutt/mutt/downloads/mutt-%{version}.tar.gz
Source1: Signature_conversion
Source2: README.alternates
Source3: mutt.png
Source4: mutt.desktop
Source5: skel.muttrc
Source6: mutt_oauth2.py-3.6
Source7: mutt_oauth2.py.README
Source8: backports-datetime-fromisoformat-1.0.0.tar.gz
Source9: mutt.mailcap
Source10: https://bitbucket.org/mutt/mutt/downloads/mutt-%{version}.tar.gz.asc
Source11: mutt.keyring
Patch0: %{name}-1.13.3.dif
# http://www.spinnaker.de/mutt/compressed/
Patch2: %{name}-1.5.9i-pgpewrap.diff
Patch3: %{name}-1.5.20-sendgroupreplyto.diff
Patch4: %{name}-1.5.15-wrapcolumn.diff
Patch7: mutt-1.6.1-opennfs.dif
Patch12: patch-1.5.24.vk.pgp_verbose_mime
# PATCH-FIX-OPENSUSE: bnc#813498 - mutt crashes in fgetwc in text_enriched_handler
Patch15: widechar.sidebar.dif
# PATCH-FIX-OPENSUSE: Be able to read signed/encrypted messsages even with CRLF
Patch16: mutt-1.5.23-carriage-return.path
# PATCH-FIX-OPENSUSE bnc#899712 - fallback mailcap for e.g text/html
Patch18: mutt-1.5.21-mailcap.diff
# PATCH-FIX-SUSE: bsc#907453 - CVE-2014-9116: mutt: heap-based buffer overflow in mutt_substrdup()
Patch19: bsc907453-CVE-2014-9116-jessie.patch
# PATCH-ENHANCE-SUSE: allow to list current imap folders
Patch20: mutt-1.10.1-imap.patch
# PATCH-ENHANCE-SUSE: boo#1156477 - Mutt has an option to ask before quitting on ^C but quits immediately on ^4
Patch21: mutt-Fix-SIGQUIT-handling.patch
BuildRequires: autoconf
BuildRequires: automake
BuildRequires: cyrus-sasl
BuildRequires: cyrus-sasl-gssapi
BuildRequires: cyrus-sasl-plain
BuildRequires: docbook-dsssl-stylesheets
BuildRequires: docbook-xsl-stylesheets
BuildRequires: docbook2x
BuildRequires: hunspell
BuildRequires: iso_ent
BuildRequires: libgpgme-devel
BuildRequires: libxslt-tools
BuildRequires: makeinfo
BuildRequires: openjade
BuildRequires: opensp
BuildRequires: pkgconfig
BuildRequires: texlive-jadetex
BuildRequires: w3m
BuildRequires: pkgconfig(gssrpc)
BuildRequires: pkgconfig(krb5)
BuildRequires: pkgconfig(libsasl2)
BuildRequires: pkgconfig(sqlite3)
BuildRequires: tex(8r.enc)
%if 0%{suse_version} >= 1500
BuildRequires: python3-base
BuildRequires: python3-devel
BuildRequires: python3-setuptools
BuildRequires: smtp_daemon
%endif
Requires: cyrus-sasl-gssapi
Requires: cyrus-sasl-plain
Requires: glibc-locale
Suggests: smtp_daemon
Requires(post): %{_bindir}/cat
Requires(post): %{_bindir}/mkdir
Requires(postun):%{_bindir}/rm
Requires(pre): %{_bindir}/grep
Requires(pre): %{_bindir}/zcat
Recommends: hunspell
Recommends: mutt-doc
Recommends: mutt-lang
Recommends: urlscan
Recommends: urlview
Recommends: w3m
%if %{with mutt_openssl}
BuildRequires: pkgconfig(openssl)
%endif
%if %{with mutt_gnutls}
BuildRequires: pkgconfig(gnutls)
%endif
%if 0%{?suse_version} > 1315
BuildRequires: pkgconfig(kyotocabinet)
%else
BuildRequires: libkyotocabinet-devel
%endif
%if 0%{?suse_version} > 1315
BuildRequires: pkgconfig(libidn2)
%else
BuildRequires: pkgconfig(libidn)
%endif
%if 0%{?suse_version} > 1315
BuildRequires: pkgconfig(ncurses)
%else
BuildRequires: ncurses-devel
%endif
%if 0%{?suse_version} > 1130
BuildRequires: pkgconfig(shared-mime-info)
%endif
%if 0%{?suse_version}
BuildRequires: update-desktop-files
%endif
%if 0%{?suse_version} > 1130
Requires(post): shared-mime-info
Requires(postun):shared-mime-info
%endif
%description
A very powerful mail user agent. It supports (among other nice things)
highlighting, threading, and PGP. It takes some time to get used to,
however. This version is based on NeoMutt, that is it includes many
enhancements.
%package doc
Summary: Additional Documentation about Mutt
Group: Documentation/Other
Requires: %{name} = %{version}
Requires(post): %{install_info_prereq}
Requires(preun):%{install_info_prereq}
Recommends: perl(Expect)
Provides: %{name}:%{_docdir}/%{name}/COPYRIGHT
BuildArch: noarch
%description doc
Some extend documentation about mutt together with muttrc examples
for different environments and requirements.
%package lang
# FIXME: consider using %%lang_package macro
Summary: Languages for Mutt
Group: System/Localization
Requires: %{name} = %{version}
Provides: mutt:%{_datadir}/locale/en_GB/LC_MESSAGES/mutt.mo
BuildArch: noarch
%description lang
Provides translations to the package mutt.
%prep
%setup -q -n mutt-%{version}
%patch -P 0 -b .p0
%patch -P 2 -b .pgpewrap
%patch -P 3 -b .sendgroupreplyto
%patch -P 4 -b .wrapcolumn
%patch -P 7 -b .opennfs
%patch -P 12 -b .pgp_verbose_mtime
%patch -P 15 -b .widechar.sidebar
%patch -P 16 -b .crlf
%patch -P 18 -b .mailcap
%patch -P 19 -b .cvw2014.9116
%patch -P 20 -b .imap
%patch -P 21 -b .quit
cp %{SOURCE2} .
%build
mkdir bin
ln -sf /usr/bin/docbook-to-texi bin/docbook2texi
%if %{with mutt_gnutls}
echo 'set ssl_ca_certificates_file="%{_sysconfdir}/ssl/ca-bundle.pem"' >> doc/Muttrc.head
%endif
autoreconf -fi
cflags ()
{
local flag=$1; shift
local var=$1; shift
test -n "${flag}" -a -n "${var}" || return
case "${!var}" in
*${flag}*) return
esac
set -o noclobber
case "$flag" in
-Wl,*)
if echo 'int main () { return 0; }' | \
${CC:-gcc} -Werror $flag -o /dev/null -xc - > /dev/null 2>&1 ; then
eval $var=\${$var:+\$$var\ }$flag
fi
;;
*)
if ${CC:-gcc} -Werror $flag -S -o /dev/null -xc /dev/null > /dev/null 2>&1 ; then
eval $var=\${$var:+\$$var\ }$flag
fi
esac
set +o noclobber
}
CC=gcc
CFLAGS="-Wall %{optflags} -I. -D_GNU_SOURCE"
cflags -fno-strict-aliasing CFLAGS
cflags -fstack-protector CFLAGS
cflags -fPIE CFLAGS
cflags -g3 CFLAGS
cflags -pipe CFLAGS
cflags -Wl,--as-needed LDFLAGS
cflags -Wl,-O2 LDFLAGS
cflags -pie LDFLAGS
export CC CFLAGS LDFLAGS
export SENDMAIL=%{_sbindir}/sendmail
export ISPELL=%{_bindir}/hunspell
export PATH="%{_prefix}/lib/mit/bin:${PWD}/bin:$PATH"
export KRB5CFGPATH="$(type -p krb5-config)"
$KRB5CFGPATH --cflags gssapi
$KRB5CFGPATH --libs gssapi
$KRB5CFGPATH --version
%configure \
--with-docdir=%{_docdir}/%{name} \
%if %{with mutt_openssl}
--without-gnutls \
--with-ssl=%{_prefix} \
%endif
%if %{with mutt_gnutls}
--without-ssl \
--with-gnutls=%{_prefix} \
%endif
--enable-autocrypt \
--enable-imap \
--enable-pop \
--enable-pgp \
--enable-gpgme \
--enable-nfs-fix \
--enable-mailtool \
--enable-compressed \
--enable-sidebar \
--disable-external-dotlock \
--with-kyotocabinet \
--with-sasl=%{_prefix} \
--with-gss=%{_prefix} \
--with-curses=%{_prefix} \
--with-sqlite3=%{_prefix} \
--enable-smtp \
--enable-hcache \
--enable-debug \
%if 0%{?suse_version} > 1315
--with-idn2
%else
--with-idn
%endif
%make_build -C doc clean
%make_build
%make_build -C doc
%make_build manual.pdf -C doc
%install
%make_install
install -m 755 %{SOURCE1} %{buildroot}%{_bindir}
gzip -n -9 doc/manu*.txt
rm -f contrib/Makefile*
# datadir not automatically created:
mkdir -p %{buildroot}%{_datadir}/mutt
# INSTALL file should be removed:
rm -rf %{buildroot}%{_docdir}/%{name}/INSTALL
# mbox/mmdf manual page conflicts with the one from tin, so rename it
mv %{buildroot}%{_mandir}/man5/mbox.5 \
%{buildroot}%{_mandir}/man5/mbox_mutt.5
mv %{buildroot}%{_mandir}/man5/mmdf.5 \
%{buildroot}%{_mandir}/man5/mmdf_mutt.5
%find_lang %{name}
# We get mime.types from aaa_base
rm -f %{buildroot}%{_sysconfdir}/mime.types
# Mutt BTS is gone
rm -f %{buildroot}%{_mandir}/man1/{flea*,muttbug*}
rm -f %{buildroot}%{_bindir}/{flea,muttbug}
rm -f %{buildroot}%{_sysconfdir}/Muttrc.dist
rm -f %{buildroot}%{_sysconfdir}/mime.types.dist
mkdir -p %{buildroot}%{_datadir}/pixmaps
install -m 644 %{SOURCE3} %{buildroot}%{_datadir}/pixmaps/
mkdir -p %{buildroot}%{_datadir}/applications
install -m 644 %{SOURCE4} %{buildroot}%{_datadir}/applications/
install -D -m 644 %{SOURCE5} %{buildroot}%{_sysconfdir}/skel/.muttrc
install -D -m 644 %{SOURCE9} %{buildroot}%{_datadir}/%{name}/mailcap
rm -vf %{buildroot}%{_docdir}/%{name}/manual.txt
install -D -m 644 doc/manual.txt.gz %{buildroot}%{_docdir}/%{name}/
install -D -m 644 doc/manual.pdf %{buildroot}%{_docdir}/%{name}/
mv %{buildroot}%{_sysconfdir}/Muttrc %{buildroot}%{_datadir}/%{name}/Muttrc
sed -rn '/Command formats for gpg/,$p' %{SOURCE5} >> %{buildroot}%{_datadir}/%{name}/Muttrc
%if 0%{?suse_version}
%suse_update_desktop_file mutt
%endif
%if 0%{suse_version} >= 1500
mkdir -p %{buildroot}%{_docdir}/%{name}
install -m 755 %{SOURCE6} %{buildroot}%{_docdir}/%{name}/mutt_oauth2.py
install -m 644 %{SOURCE7} %{buildroot}%{_docdir}/%{name}/mutt_oauth2.py.README
%if %{?pkg_vcmp:%{pkg_vcmp python3-base < 3.7.0}}%{!?pkg_vcmp:0}
tar xf %{SOURCE8}
pushd backports-datetime-fromisoformat-1.0.0
python3 setup.py install --root %{buildroot}
popd
%endif
%endif
%pre
if test $1 -gt 1 -a -e %{_docdir}/%{name}/manual.txt.gz
then
zcat %{_docdir}/%{name}/manual.txt.gz | grep -F 'version %{version}' > /run/mutt-version || :
fi
%post
%mime_database_post
if test -f /run/mutt-version -a ! -s /run/mutt-version
then
mkdir -p %{_localstatedir}/adm/update-messages
cat > %{_localstatedir}/adm/update-messages/%{name}-%{version}-%{release}-notify <<-'EOF'
With %{name}-%{version} some variables and the behaviour changes:
$send_multipart_alternative changes to run in batch mode on ask-yes.
$write_bcc changes to default off
EOF
fi
%postun
%mime_database_postun
rm -f %{_localstatedir}/adm/update-messages/%{name}-%{version}-%{release}-notify
%post doc
%install_info --info-dir=%{_infodir} "%{_infodir}/mutt.info.gz"
%preun doc
%install_info_delete --info-dir=%{_infodir} "%{_infodir}/mutt.info.gz"
%files
%config(noreplace) %{_sysconfdir}/skel/.muttrc
%{_bindir}/mutt
%{_bindir}/pgpewrap
%{_bindir}/mutt_pgpring
%{_bindir}/smime_keys
%{_bindir}/Signature_conversion
%{_datadir}/applications/*.desktop
%{_datadir}/pixmaps/mutt.png
%{_mandir}/man1/mutt.1%{?ext_man}
%{_mandir}/man1/*pgp*.1%{?ext_man}
%{_mandir}/man1/smime_keys.1%{?ext_man}
%{_mandir}/man5/mmdf_mutt.5%{?ext_man}
%{_mandir}/man5/muttrc.5%{?ext_man}
%{_mandir}/man5/mbox_mutt.5%{?ext_man}
%dir %{_datadir}/mutt/
%{_datadir}/mutt/mailcap
%dir %doc %{_docdir}/%{name}/
%doc %{_docdir}/%{name}/manual.txt.gz
%{_datadir}/%{name}/Muttrc
%if 0%{suse_version} >= 1500
%{_docdir}/%{name}/mutt_oauth2.py
%{_docdir}/%{name}/mutt_oauth2.py.README
%if %{?pkg_vcmp:%{pkg_vcmp python3-base < 3.7.0}}%{!?pkg_vcmp:0}
%{python3_sitearch}/backports
%{python3_sitearch}/backports_datetime_fromisoformat-1.0.0-py3.6.egg-info
%endif
%endif
%files doc
%doc %{_docdir}/%{name}/manual.pdf
%doc %{_docdir}/%{name}/COPYRIGHT
%doc %{_docdir}/%{name}/ChangeLog
%doc %{_docdir}/%{name}/GPL
%doc %{_docdir}/%{name}/NEWS
%doc %{_docdir}/%{name}/README*
%doc %{_docdir}/%{name}/TODO
%doc %{_docdir}/%{name}/*.html
%doc %{_docdir}/%{name}/*.txt
%dir %doc %{_docdir}/%{name}/samples/
%doc %{_docdir}/%{name}/samples/*.rc
%doc %{_docdir}/%{name}/samples/ca-bundle.crt
%doc %{_docdir}/%{name}/samples/colors.*
%doc %{_docdir}/%{name}/samples/markdown2html
%doc %{_docdir}/%{name}/samples/mutt_oauth2.py
%doc %{_docdir}/%{name}/samples/mutt_oauth2.py.README
%doc %{_docdir}/%{name}/samples/mutt_xtitle
%doc %{_docdir}/%{name}/samples/sample.*
%doc %{_docdir}/%{name}/samples/smime_keys_test.pl
%doc %{_docdir}/%{name}/samples/bgedit-detectgui.sh
%doc %{_docdir}/%{name}/samples/bgedit-screen-tmux.sh
%dir %doc %{_docdir}/%{name}/samples/iconv/
%doc %{_docdir}/%{name}/samples/iconv/*.rc
%{_infodir}/*.gz
%files lang -f %{name}.lang
%changelog

434
mutt_oauth2.py-3.6 Normal file
View File

@ -0,0 +1,434 @@
#!/usr/bin/python3
#
# Mutt OAuth2 token management script, version 2020-08-07
# Written against python 3.7.3, not tried with earlier python versions.
#
# Copyright (C) 2020 Alexander Perlis
#
# 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.
'''Mutt OAuth2 token management'''
import sys
import json
import argparse
import urllib.parse
import urllib.request
import imaplib
import poplib
import smtplib
import base64
import secrets
import hashlib
import time
if sys.version_info < (3,7):
from datetime import date, timedelta, datetime, time
from backports.datetime_fromisoformat import MonkeyPatch
else:
from datetime import timedelta, datetime
from pathlib import Path
import socket
import http.server
import subprocess
if sys.version_info < (3,7):
from subprocess import PIPE
MonkeyPatch.patch_fromisoformat()
# The token file must be encrypted because it contains multi-use bearer tokens
# whose usage does not require additional verification. Specify whichever
# encryption and decryption pipes you prefer. They should read from standard
# input and write to standard output. The example values here invoke GPG,
# although won't work until an appropriate identity appears in the first line.
ENCRYPTION_PIPE = ['gpg', '--encrypt', '--recipient', 'YOUR_GPG_IDENTITY']
DECRYPTION_PIPE = ['gpg', '--decrypt']
registrations = {
'google': {
'authorize_endpoint': 'https://accounts.google.com/o/oauth2/auth',
'devicecode_endpoint': 'https://oauth2.googleapis.com/device/code',
'token_endpoint': 'https://accounts.google.com/o/oauth2/token',
'redirect_uri': 'urn:ietf:wg:oauth:2.0:oob',
'imap_endpoint': 'imap.gmail.com',
'pop_endpoint': 'pop.gmail.com',
'smtp_endpoint': 'smtp.gmail.com',
'sasl_method': 'OAUTHBEARER',
'scope': 'https://mail.google.com/',
'client_id': '',
'client_secret': '',
},
'microsoft': {
'authorize_endpoint': 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize',
'devicecode_endpoint': 'https://login.microsoftonline.com/common/oauth2/v2.0/devicecode',
'token_endpoint': 'https://login.microsoftonline.com/common/oauth2/v2.0/token',
'redirect_uri': 'https://login.microsoftonline.com/common/oauth2/nativeclient',
'tenant': 'common',
'imap_endpoint': 'outlook.office365.com',
'pop_endpoint': 'outlook.office365.com',
'smtp_endpoint': 'smtp.office365.com',
'sasl_method': 'XOAUTH2',
'scope': ('offline_access https://outlook.office.com/IMAP.AccessAsUser.All '
'https://outlook.office.com/POP.AccessAsUser.All '
'https://outlook.office.com/SMTP.Send'),
'client_id': '',
'client_secret': '',
},
}
ap = argparse.ArgumentParser(epilog='''
This script obtains and prints a valid OAuth2 access token. State is maintained in an
encrypted TOKENFILE. Run with "--verbose --authorize" to get started or whenever all
tokens have expired, optionally with "--authflow" to override the default authorization
flow. To truly start over from scratch, first delete TOKENFILE. Use "--verbose --test"
to test the IMAP/POP/SMTP endpoints.
''')
ap.add_argument('-v', '--verbose', action='store_true', help='increase verbosity')
ap.add_argument('-d', '--debug', action='store_true', help='enable debug output')
ap.add_argument('tokenfile', help='persistent token storage')
ap.add_argument('-a', '--authorize', action='store_true', help='manually authorize new tokens')
ap.add_argument('--authflow', help='authcode | localhostauthcode | devicecode')
ap.add_argument('-t', '--test', action='store_true', help='test IMAP/POP/SMTP endpoints')
args = ap.parse_args()
token = {}
path = Path(args.tokenfile)
if path.exists():
if 0o777 & path.stat().st_mode != 0o600:
sys.exit('Token file has unsafe mode. Suggest deleting and starting over.')
try:
if sys.version_info < (3,7):
sub = subprocess.run(DECRYPTION_PIPE, check=True, input=path.read_bytes(),
stdout=PIPE, stderr=PIPE)
else:
sub = subprocess.run(DECRYPTION_PIPE, check=True, input=path.read_bytes(),
capture_output=True)
token = json.loads(sub.stdout)
except subprocess.CalledProcessError:
sys.exit('Difficulty decrypting token file. Is your decryption agent primed for '
'non-interactive usage, or an appropriate environment variable such as '
'GPG_TTY set to allow interactive agent usage from inside a pipe?')
def writetokenfile():
'''Writes global token dictionary into token file.'''
if not path.exists():
path.touch(mode=0o600)
if 0o777 & path.stat().st_mode != 0o600:
sys.exit('Token file has unsafe mode. Suggest deleting and starting over.')
if sys.version_info < (3,7):
sub2 = subprocess.run(ENCRYPTION_PIPE, check=True, input=json.dumps(token).encode(),
stdout=PIPE, stderr=PIPE)
else:
sub2 = subprocess.run(ENCRYPTION_PIPE, check=True, input=json.dumps(token).encode(),
capture_output=True)
path.write_bytes(sub2.stdout)
if args.debug:
print('Obtained from token file:', json.dumps(token))
if not token:
if not args.authorize:
sys.exit('You must run script with "--authorize" at least once.')
print('Available app and endpoint registrations:', *registrations)
token['registration'] = input('OAuth2 registration: ')
token['authflow'] = input('Preferred OAuth2 flow ("authcode" or "localhostauthcode" '
'or "devicecode"): ')
token['email'] = input('Account e-mail address: ')
token['access_token'] = ''
token['access_token_expiration'] = ''
token['refresh_token'] = ''
writetokenfile()
if token['registration'] not in registrations:
sys.exit(f'ERROR: Unknown registration "{token["registration"]}". Delete token file '
f'and start over.')
registration = registrations[token['registration']]
authflow = token['authflow']
if args.authflow:
authflow = args.authflow
baseparams = {'client_id': registration['client_id']}
# Microsoft uses 'tenant' but Google does not
if 'tenant' in registration:
baseparams['tenant'] = registration['tenant']
def access_token_valid():
'''Returns True when stored access token exists and is still valid at this time.'''
token_exp = token['access_token_expiration']
return token_exp and datetime.now() < datetime.fromisoformat(token_exp)
def update_tokens(r):
'''Takes a response dictionary, extracts tokens out of it, and updates token file.'''
token['access_token'] = r['access_token']
token['access_token_expiration'] = (datetime.now() +
timedelta(seconds=int(r['expires_in']))).isoformat()
if 'refresh_token' in r:
token['refresh_token'] = r['refresh_token']
writetokenfile()
if args.verbose:
print(f'NOTICE: Obtained new access token, expires {token["access_token_expiration"]}.')
if args.authorize:
p = baseparams.copy()
p['scope'] = registration['scope']
if authflow in ('authcode', 'localhostauthcode'):
verifier = secrets.token_urlsafe(90)
challenge = base64.urlsafe_b64encode(hashlib.sha256(verifier.encode()).digest())[:-1]
redirect_uri = registration['redirect_uri']
listen_port = 0
if authflow == 'localhostauthcode':
# Find an available port to listen on
s = socket.socket()
s.bind(('127.0.0.1', 0))
listen_port = s.getsockname()[1]
s.close()
redirect_uri = 'http://localhost:'+str(listen_port)+'/'
# Probably should edit the port number into the actual redirect URL.
p.update({'login_hint': token['email'],
'response_type': 'code',
'redirect_uri': redirect_uri,
'code_challenge': challenge,
'code_challenge_method': 'S256'})
print(registration["authorize_endpoint"] + '?' +
urllib.parse.urlencode(p, quote_via=urllib.parse.quote))
authcode = ''
if authflow == 'authcode':
authcode = input('Visit displayed URL to retrieve authorization code. Enter '
'code from server (might be in browser address bar): ')
else:
print('Visit displayed URL to authorize this application. Waiting...',
end='', flush=True)
class MyHandler(http.server.BaseHTTPRequestHandler):
'''Handles the browser query resulting from redirect to redirect_uri.'''
# pylint: disable=C0103
def do_HEAD(self):
'''Response to a HEAD requests.'''
self.send_response(200)
self.send_header('Content-type', 'text/html')
self.end_headers()
def do_GET(self):
'''For GET request, extract code parameter from URL.'''
# pylint: disable=W0603
global authcode
querystring = urllib.parse.urlparse(self.path).query
querydict = urllib.parse.parse_qs(querystring)
if 'code' in querydict:
authcode = querydict['code'][0]
self.do_HEAD()
self.wfile.write(b'<html><head><title>Authorizaton result</title></head>')
self.wfile.write(b'<body><p>Authorization redirect completed. You may '
b'close this window.</p></body></html>')
with http.server.HTTPServer(('127.0.0.1', listen_port), MyHandler) as httpd:
try:
httpd.handle_request()
except KeyboardInterrupt:
pass
if not authcode:
sys.exit('Did not obtain an authcode.')
for k in 'response_type', 'login_hint', 'code_challenge', 'code_challenge_method':
del p[k]
p.update({'grant_type': 'authorization_code',
'code': authcode,
'client_secret': registration['client_secret'],
'code_verifier': verifier})
try:
response = urllib.request.urlopen(registration['token_endpoint'],
urllib.parse.urlencode(p).encode())
except urllib.error.HTTPError as err:
print(err.code, err.reason)
response = err
response = response.read()
if args.debug:
print(response)
response = json.loads(response)
if 'error' in response:
print(response['error'])
if 'error_description' in response:
print(response['error_description'])
sys.exit(1)
elif authflow == 'devicecode':
try:
response = urllib.request.urlopen(registration['devicecode_endpoint'],
urllib.parse.urlencode(p).encode())
except urllib.error.HTTPError as err:
print(err.code, err.reason)
response = err
response = response.read()
if args.debug:
print(response)
response = json.loads(response)
if 'error' in response:
print(response['error'])
if 'error_description' in response:
print(response['error_description'])
sys.exit(1)
print(response['message'])
del p['scope']
p.update({'grant_type': 'urn:ietf:params:oauth:grant-type:device_code',
'client_secret': registration['client_secret'],
'device_code': response['device_code']})
interval = int(response['interval'])
print('Polling...', end='', flush=True)
while True:
time.sleep(interval)
print('.', end='', flush=True)
try:
response = urllib.request.urlopen(registration['token_endpoint'],
urllib.parse.urlencode(p).encode())
except urllib.error.HTTPError as err:
# Not actually always an error, might just mean "keep trying..."
response = err
response = response.read()
if args.debug:
print(response)
response = json.loads(response)
if 'error' not in response:
break
if response['error'] == 'authorization_declined':
print(' user declined authorization.')
sys.exit(1)
if response['error'] == 'expired_token':
print(' too much time has elapsed.')
sys.exit(1)
if response['error'] != 'authorization_pending':
print(response['error'])
if 'error_description' in response:
print(response['error_description'])
sys.exit(1)
print()
else:
sys.exit(f'ERROR: Unknown OAuth2 flow "{token["authflow"]}. Delete token file and '
f'start over.')
update_tokens(response)
if not access_token_valid():
if args.verbose:
print('NOTICE: Invalid or expired access token; using refresh token '
'to obtain new access token.')
if not token['refresh_token']:
sys.exit('ERROR: No refresh token. Run script with "--authorize".')
p = baseparams.copy()
p.update({'client_secret': registration['client_secret'],
'refresh_token': token['refresh_token'],
'grant_type': 'refresh_token'})
try:
response = urllib.request.urlopen(registration['token_endpoint'],
urllib.parse.urlencode(p).encode())
except urllib.error.HTTPError as err:
print(err.code, err.reason)
response = err
response = response.read()
if args.debug:
print(response)
response = json.loads(response)
if 'error' in response:
print(response['error'])
if 'error_description' in response:
print(response['error_description'])
print('Perhaps refresh token invalid. Try running once with "--authorize"')
sys.exit(1)
update_tokens(response)
if not access_token_valid():
sys.exit('ERROR: No valid access token. This should not be able to happen.')
if args.verbose:
print('Access Token: ', end='')
print(token['access_token'])
def build_sasl_string(user, host, port, bearer_token):
'''Build appropriate SASL string, which depends on cloud server's supported SASL method.'''
if registration['sasl_method'] == 'OAUTHBEARER':
return f'n,a={user},\1host={host}\1port={port}\1auth=Bearer {bearer_token}\1\1'
if registration['sasl_method'] == 'XOAUTH2':
return f'user={user}\1auth=Bearer {bearer_token}\1\1'
sys.exit(f'Unknown SASL method {registration["sasl_method"]}.')
if args.test:
errors = False
imap_conn = imaplib.IMAP4_SSL(registration['imap_endpoint'])
sasl_string = build_sasl_string(token['email'], registration['imap_endpoint'], 993,
token['access_token'])
if args.debug:
imap_conn.debug = 4
try:
imap_conn.authenticate(registration['sasl_method'], lambda _: sasl_string.encode())
# Microsoft has a bug wherein a mismatch between username and token can still report a
# successful login... (Try a consumer login with the token from a work/school account.)
# Fortunately subsequent commands fail with an error. Thus we follow AUTH with another
# IMAP command before reporting success.
imap_conn.list()
if args.verbose:
print('IMAP authentication succeeded')
except imaplib.IMAP4.error as e:
print('IMAP authentication FAILED (does your account allow IMAP?):', e)
errors = True
pop_conn = poplib.POP3_SSL(registration['pop_endpoint'])
sasl_string = build_sasl_string(token['email'], registration['pop_endpoint'], 995,
token['access_token'])
if args.debug:
pop_conn.set_debuglevel(2)
try:
# poplib doesn't have an auth command taking an authenticator object
# Microsoft requires a two-line SASL for POP
# pylint: disable=W0212
pop_conn._shortcmd('AUTH ' + registration['sasl_method'])
pop_conn._shortcmd(base64.standard_b64encode(sasl_string.encode()).decode())
if args.verbose:
print('POP authentication succeeded')
except poplib.error_proto as e:
print('POP authentication FAILED (does your account allow POP?):', e.args[0].decode())
errors = True
# SMTP_SSL would be simpler but Microsoft does not answer on port 465.
smtp_conn = smtplib.SMTP(registration['smtp_endpoint'], 587)
sasl_string = build_sasl_string(token['email'], registration['smtp_endpoint'], 587,
token['access_token'])
smtp_conn.ehlo('test')
smtp_conn.starttls()
smtp_conn.ehlo('test')
if args.debug:
smtp_conn.set_debuglevel(2)
try:
smtp_conn.auth(registration['sasl_method'], lambda _=None: sasl_string)
if args.verbose:
print('SMTP authentication succeeded')
except smtplib.SMTPAuthenticationError as e:
print('SMTP authentication FAILED:', e)
errors = True
if errors:
sys.exit(1)

290
mutt_oauth2.py.README Normal file
View File

@ -0,0 +1,290 @@
mutt_oauth.py README by Alexander Perlis, 2020-07-15
====================================================
Background on plain passwords, app passwords, OAuth2 bearer tokens
------------------------------------------------------------------
An auth stage occurs near the start of the IMAP/POP/SMTP protocol
conversation. Various SASL methods can be used (depends on what the
server offers, and what the client supports). The PLAIN method, also
known as "basic auth", involves simply sending the username and
password (this occurs over an encrypted connection), and used to be
common; but, for large cloud mail providers, basic auth is a security
hole. User passwords often have low entropy (humans generally choose
passwords that can be produced from human memory), thus are targets
for various types of exhaustive attacks. Older attacks try different
passwords against one user, whereas newer spray attacks try one
password against different users. General mitigation efforts such as
rate-limiting, or detection and outright blocking efforts, lead to
degraded or outright denied services for legitimate users. The
security weakness is two-fold: the low entropy of the user password,
together with the alarming consequence that the password often unlocks
many disparate systems in a typical enterprise single-sign-on
environment. Also, humans type passwords or copy/paste them from
elsewhere on the screen, so they can also be grabbed via keyloggers or
screen capture (or a human bystander). Two ways to solve these
conundrums:
- app passwords
- bearer tokens
App passwords are simply high-entropy protocol-specific passwords, in
other words a long computer-generated random string, you use one for
your mail system, a different one for your payroll system, and so
on. With app passwords in use, brute-force attacks become useless. App
passwords require no modifications to client software, and only minor
changes on the server side. One way to think about app passwords is
that they essentially impose on you the use of a password manager. Any
user can go to the trouble of using a password manager but most users
don't bother. App passwords put the password manager inside the server
and force you to use it.
Bearer tokens take the idea of app passwords to the next level. Much
like app passwords, they too are just long computer-generated random
strings, knowledge of which simply "lets you in". But unlike an app
password which the user must manually copy from a server password
screen and then paste into their client account config screen (a
process the user doesn't want to follow too often), bearer tokens get
swapped out approximately once an hour without user interaction. For
this to work, both clients and servers must be modified to speak a
separate out-of-band protocol (the "OAuth2" protocol) to swap out
tokens. More precisely, from start to finish, the process goes like
this: the client and server must once-and-for-all be informed about
each other (this is called "app registration" and might be done by the
client developer or left to each end user), then the client informs
the server that it wants to connect, then the user is informed to
independently use a web browser to visit a server destination to
approve this request (at this stage the server will require the user
to authenticate using say their password and perhaps additional
factors such as an SMS verification or crypto device), then the client
will have a long-term "refresh token" as well as an "access token"
good for about an hour. The access token can now be used with
IMAP/POP/SMTP to access the account. When it expires, the refresh
token is used to get a new access token and perhaps a new refresh
token. After several months of such usage, even the refresh token may
expire and the human user will have to go back and re-authenticate
(password, SMS, crypto device, etc) for things to start anew.
Since app passwords and tokens are high-entropy and their compromise
should compromise only a particular system (rather than all systems in
a single-sign-on environment), they have similar security strength
when compared to stark weakness of traditional human passwords. But if
compared only to each other, tokens provide more security. App
passwords must be short enough for humans to easily copy/paste them,
might get written down or snooped during that process, and anyhow are
long-lived and thus could get compromised by other means. The main
drawback to tokens is that their support requires significant changes
to clients and servers, but once such support exists, they are
superior and easier to use.
Many cloud providers are eliminating support for human passwords. Some are
allowing app passwords in addition to tokens. Some allow only tokens.
OAuth2 token support in mutt
----------------------------
Mutt supports the two SASL methods OAUTHBEARER and XOAUTH2 for presenting an
OAuth2 access token near the start of the IMAP/POP/SMTP connection.
(Two different SASL methods exist for historical reasons. While OAuth2
was under development, the experimental offering by servers was called
XOAUTH2, later fleshed out into a standard named OAUTHBEARER, but not
all servers have been updated to offer OAUTHBEARER. Once the major
cloud providers all support OAUTHBEARER, clients like mutt might be
modified to no longer know about XOAUTH2.)
Mutt can present a token inside IMAP/POP/SMTP, but by design mutt itself
does not know how to have a separate conversation (outside of IMAP/POP/SMTP)
with the server to authorize the user and obtain refresh and access tokens.
Mutt just needs an access token, and has a hook for an external script to
somehow obtain one.
mutt_oauth2.py is an example of such an external script. It likely can be
adapted to work with OAuth2 on many different cloud mail providers, and has
been tested against:
- Google consumer account (@gmail.com)
- Google work/school account (G Suite tenant)
- Microsoft consumer account (e.g., @live.com, @outlook.com, ...)
- Microsoft work/school account (Azure tenant)
(Note that Microsoft uses the marketing term "Modern Auth" in lieu of
"OAuth2". In that terminology, mutt indeed supports "Modern Auth".)
Configure script's token file encryption
----------------------------------------
The script remembers tokens between invocations by keeping them in a
token file. This file is encrypted. Inside the script are two lines
ENCRYPTION_PIPE
DECRYPTION_PIPE
that must be edited to specify your choice of encryption system. A
popular choice is gpg. To use this:
- Install gpg. For example, "sudo apt install gpg".
- "gpg --gen-key". Answer the questions. Instead of your email
address you could choose say "My mutt_oauth2 token store", then
choose a passphrase. You will need to produce that same passphrase
whenever mutt_oauth2 needs to unlock the token store.
- Edit mutt_oauth2.py and put your GPG identity (your email address or
whatever you picked above) in the ENCRYPTION_PIPE line.
- For the gpg-agent to be able to ask you the unlock passphrase,
the environment variable GPG_TTY must be set to the current tty.
Typically you would put the following inside your .bashrc or equivalent:
export GPG_TTY=$(tty)
Create an app registration
--------------------------
Before you can connect the script to an account, you need an
"app registration" for that service. Cloud entities (like Google and
Microsoft) and/or the tenant admins (the central technology admins at
your school or place of work) might be restrictive in who can create
app registrations, as well as who can subsequently use them. For
personal/consumer accounts, you can generally create your own
registration and then use it with a limited number of different personal
accounts. But for work/school accounts, the tenant admins might approve an
app registration that you created with a personal/consumer account, or
might want an official app registration from a developer (the creation of
which and blessing by the cloud provider might require payment and/or arduous
review), or might perhaps be willing to roll their own "in-house" registration.
What you ultimately need is the "client_id" (and "client_secret" if
one was set) for this registration. Those values must be edited into
the mutt_oauth2.py script. If your work or school environment has a
knowledge base that provides the client_id, then someone already took
care of the app registration, and you can skip the step of creating
your own registration.
-- How to create a Google registration --
Go to console.developers.google.com, and create a new project. The name doesn't
matter and could be "mutt registration project".
- Go to Library, choose Gmail API, and enable it
- Hit left arrow icon to get back to console.developers.google.com
- Choose OAuth Consent Screen
- Choose Internal for an organizational G Suite
- Choose External if that's your only choice
- For Application Name, put for example "Mutt"
- Under scopes, choose Add scope, scroll all the way down, enable the "https://mail.google.com/" scope
- Fill out additional fields (application logo, etc) if you feel like it (will make the consent screen look nicer)
- Back at console.developers.google.com, choose Credentials
- At top, choose Create Credentials / OAuth2 client iD
- Application type is "Desktop app"
Edit the client_id (and client_secret if there is one) into the
mutt_oauth2.py script.
-- How to create a Microsoft registration --
Go to portal.azure.com, log in with a Microsoft account (get a free
one at outlook.com), then search for "app registration", and add a
new registration. On the initial form that appears, put a name like
"Mutt", allow any type of account, and put "http://localhost/" as
the redirect URI, then more carefully go through each
screen:
Branding
- Leave fields blank or put in reasonable values
- For official registration, verify your choice of publisher domain
Authentication:
- Platform "Mobile and desktop"
- Redirect URI "http://localhost/"
- Any kind of account
- Enable public client (allow device code flow)
API permissions:
- Microsoft Graph, Delegated, "offline_access"
- Microsoft Graph, Delegated, "IMAP.AccessAsUser.All"
- Microsoft Graph, Delegated, "POP.AccessAsUser.All"
- Microsoft Graph, Delegated, "SMTP.Send"
- Microsoft Graph, Delegated, "User.Read"
Overview:
- Take note of the Application ID (a.k.a. Client ID), you'll need it shortly
End users who aren't able to get to the app registration screen within
portal.azure.com for their work/school account can temporarily use an
incognito browser window to create a free outlook.com account and use that
to create the app registration.
Edit the client_id (and client_secret if there is one) into the
mutt_oauth2.py script.
Running the script manually to authorize tokens
-----------------------------------------------
Run "mutt_oauth2.py --help" to learn script usage. To obtain the
initial set of tokens, run the script specifying a name for a
disposable token storage file, as well as "--authorize", for example
using this naming scheme:
mutt_oauth2.py userid@myschool.edu.tokens --verbose --authorize
The script will ask questions and provide some instructions. For the
flow question:
- "authcode": you paste a complicated URL into a browser, then
manually extract a "code" parameter from a subsequent URL in the
browser address bar and paste that back to the script.
- "localhostauthcode": you again paste the complicated URL into a browser
but that's it --- the code is automatically extracted from the response
relying on a localhost redirect and temporarily listening on a localhost
port. This flow can only be used if the web browser opening the redirect
URL sits on the same machine as where mutt is running, in other words can not
be used if you ssh to a remote machine and run mutt on that remote machine
while your web browser remains on your local machine.
- "devicecode": you go to a simple URL and just enter a short code.
Your answer here determines the default flow, but on any invocation of
the script you can override the default with the optional "--authflow"
parameter. To change the default, delete your token file and start over.
To figure out which flow to use, I suggest trying all three.
Depending on the OAuth2 provider and how the app registration was
configured, some flows might not work, so simply trying them is the
best way to figure out what works and which one you prefer. Personally
I prefer the "localhostauthcode" flow when I can use it.
Once you attempt an actual authorization, you might get stuck because
the web browser step might indicate your institution admins must grant
approval. Indeed engage them in a conversation about approving the
use of mutt to access mail. If that fails, an alternative is to
identify some other well-known IMAP/POP/SMTP client that they might
have already approved, or might be willing to approve, and first go
configure it for OAuth2 and see whether it will work to reach your
mail, and then you could dig into the source code for that client and
extract its client_id, client_secret, and redirect_uri and put those
into the mutt_oauth2.py script. This would be a temporary punt for
end-user experimentation, but not an approach for configuring systems
to be used by other people. Engaging your institution admins to create
a mutt registration is the better way to go.
Once you've succeeded authorizing mutt_oauth2.py to obtain tokens, try
one of the following to see whether IMAP/POP/SMTP are working:
mutt_oauth2.py userid@myschool.edu.tokens --verbose --test
mutt_oauth2.py userid@myschool.edu.tokens --verbose --debug --test
Without optional parameters, the script simply returns an access token
(possibly first conducting a behind-the-scenes URL retrieval using a
stored refresh token to obtain an updated access token). Calling the
script without optional parameters is how it will be used by
mutt. Your .muttrc would look something like:
set imap_user="userid@myschool.edu"
set folder="imap://outlook.office365.com/"
set smtp_url="smtp://${imap_user}@smtp.office365.com:587/"
set imap_authenticators="oauthbearer:xoauth2"
set imap_oauth_refresh_command="/path/to/script/mutt_oauth2.py ${imap_user}.tokens"
set smtp_authenticators=${imap_authenticators}
set smtp_oauth_refresh_command=${imap_oauth_refresh_command}

View File

@ -0,0 +1,53 @@
---
globals.h | 2 ++
init.h | 15 ++++++++++++---
pgp.c | 3 ++-
3 files changed, 16 insertions(+), 4 deletions(-)
--- globals.h
+++ globals.h 2020-01-14 13:37:00.770606718 +0000
@@ -280,6 +280,8 @@ WHERE char *PgpDefaultKey;
WHERE char *PgpSignAs;
WHERE long PgpTimeout;
WHERE char *PgpEntryFormat;
+WHERE char *PgpMimeSignatureFilename;
+WHERE char *PgpMimeSignatureDescription;
WHERE char *PgpClearSignCommand;
WHERE char *PgpDecodeCommand;
WHERE char *PgpVerifyCommand;
--- init.h
+++ init.h 2020-01-14 13:41:15.145875625 +0000
@@ -3869,9 +3869,18 @@ struct option_t MuttVars[] = {
** a line quoted text if it also matches $$smileys. This mostly
** happens at the beginning of a line.
*/
-
-
-
+ { "pgp_mime_signature_filename", DT_STR, R_NONE, {.p=&PgpMimeSignatureFilename}, {.p="signature.asc"} },
+ /*
+ ** .pp
+ ** This option sets the filename used for signature parts in PGP/MIME
+ ** signed messages.
+ */
+ { "pgp_mime_signature_description", DT_STR, R_NONE, {.p=&PgpMimeSignatureDescription}, {.p="Digital signature"} },
+ /*
+ ** .pp
+ ** This option sets the Content-Description used for signature parts in
+ ** PGP/MIME signed messages.
+ */
{ "smime_ask_cert_label", DT_BOOL, R_NONE, {.l=OPTASKCERTLABEL}, {.l=1} },
/*
** .pp
--- pgp.c
+++ pgp.c 2020-01-14 13:42:29.880485319 +0000
@@ -1359,7 +1359,8 @@ BODY *pgp_sign_message (BODY *a)
t->disposition = DISPNONE;
t->encoding = ENC7BIT;
t->unlink = 1; /* ok to remove this file after sending. */
- mutt_set_parameter ("name", "signature.asc", &t->parameter);
+ mutt_set_parameter ("name", PgpMimeSignatureFilename, &t->parameter);
+ t->description = safe_strdup (PgpMimeSignatureDescription);
cleanup:
mutt_buffer_pool_release (&sigfile);

143
skel.muttrc Normal file
View File

@ -0,0 +1,143 @@
# Sample ~/.muttrc for SUSE Linux
# Setting
set pager_context=4
set pager_index_lines=10
set pager_stop
# Binding
bind pager <backspace> previous-page
bind pager - previous-line
bind pager \eOm previous-line
bind pager + next-line
bind pager \eOk next-line
bind pager \eOM next-line
bind pager \e[1~ top
bind pager \e[4~ bottom
bind index <backspace> previous-entry
bind index - previous-entry
bind index \eOm previous-entry
bind index + next-entry
bind index \eOk next-entry
bind index \eOM display-message
bind index \e[H first-entry
bind index \e[F last-entry
bind index \e[1~ first-entry
bind index \e[4~ last-entry
bind alias <space> select-entry
bind alias x exit
bind attach x exit
bind browser x exit
# What headers to show
ignore *
unignore from: date subject to cc reply-to:
unignore resent- x-resent
# What order to show them
unhdr_order *
hdr_order Date: From: To: Reply-To: Cc: Subject:
# On to the colors
color attachment blue default
color header green default "^message-id:"
color header green default "^x-mailer:"
color header green default "^user-agent:"
color header blue default "^date: "
color header brightblue default "^from: "
color header green default "^subject: "
color header brightblue default "^to: "
color header brightblue default "^cc: "
color header brightblue default "^reply-to: "
color index green default ~F
color index red default ~D
color index blue default ~T
color index brightblue default ~N
color indicator brightwhite blue
color markers red default
color quoted magenta default
color signature brightblue default
color status brightwhite green
color tilde cyan default
color tree blue default
color body black default "(^| )_[-a-z0-9_]+_[,.?]?[ \n]"
# Command formats for gpg.
#
# This version uses gpg-2comp from
# http://muppet.faveve.uni-stuttgart.de/~gero/gpg-2comp.tar.gz
#
# $Id: gpg.rc,v 3.1 2002/03/26 22:23:58 roessler Exp $
#
# %p The empty string when no passphrase is needed,
# the string "PGPPASSFD=0" if one is needed.
#
# This is mostly used in conditional % sequences.
#
# %f Most PGP commands operate on a single file or a file
# containing a message. %f expands to this file's name.
#
# %s When verifying signatures, there is another temporary file
# containing the detached signature. %s expands to this
# file's name.
#
# %a In "signing" contexts, this expands to the value of the
# configuration variable $pgp_sign_as. You probably need to
# use this within a conditional % sequence.
#
# %r In many contexts, mutt passes key IDs to pgp. %r expands to
# a list of key IDs.
# Note that we explicitly set the comment armor header since GnuPG, when used
# in some localiaztion environments, generates 8bit data in that header, thereby
# breaking PGP/MIME.
# decode application/pgp
set pgp_decode_command="/usr/bin/gpg --charset utf-8 --status-fd=2 %?p?--passphrase-fd 0? --no-verbose --quiet --batch --output - %f"
# verify a pgp/mime signature
set pgp_verify_command="/usr/bin/gpg --status-fd=2 --no-verbose --quiet --batch --output - --verify %s %f"
# decrypt a pgp/mime attachment
set pgp_decrypt_command="/usr/bin/gpg --status-fd=2 %?p?--passphrase-fd 0? --no-verbose --quiet --batch --output - %f"
# create a pgp/mime signed attachment
set pgp_sign_command="/usr/bin/gpg --no-verbose --batch --quiet --output - %?p?--passphrase-fd 0? --armor --detach-sign --textmode %?a?-u %a? %f"
# create a application/pgp signed (old-style) message
set pgp_clearsign_command="/usr/bin/gpg --charset utf-8 --no-verbose --batch --quiet --output - %?p?--passphrase-fd 0? --armor --textmode --clearsign %?a?-u %a? %f"
# create a pgp/mime encrypted attachment
set pgp_encrypt_only_command="/usr/bin/pgpewrap /usr/bin/gpg --charset utf-8 --batch --quiet --no-verbose --output - --encrypt --textmode --armor --always-trust -- -r %r -- %f"
# create a pgp/mime encrypted and signed attachment
set pgp_encrypt_sign_command="/usr/bin/pgpewrap /usr/bin/gpg --charset utf-8 %?p?--passphrase-fd 0? --batch --quiet --no-verbose --textmode --output - --encrypt --sign %?a?-u %a? --armor --always-trust -- -r %r -- %f"
# import a key into the public key ring
set pgp_import_command="/usr/bin/gpg --no-verbose --import --verbose %f"
# export a key from the public key ring
set pgp_export_command="/usr/bin/gpg --no-verbose --export --armor %r"
# verify a key
set pgp_verify_key_command="/usr/bin/gpg --verbose --batch --fingerprint --check-sigs %r"
# read in the public key ring
set pgp_list_pubring_command="/usr/bin/gpg --no-verbose --batch --quiet --with-colons --list-keys %r"
# read in the secret key ring
set pgp_list_secring_command="/usr/bin/gpg --no-verbose --batch --quiet --with-colons --list-secret-keys %r"
# fetch keys
# set pgp_getkeys_command="pkspxycwrap %r"
# pattern for good signature - may need to be adapted to locale!
set pgp_good_sign="gpg: Good signature from"
# OK, here's a version which uses gnupg's message catalog:
# set pgp_good_sign="`gettext -d gnupg -s 'Good signature from "' | tr -d '"'`"
#set pgp_auto_decode=yes

53
widechar.sidebar.dif Normal file
View File

@ -0,0 +1,53 @@
---
handler.c | 11 ++++++-----
1 file changed, 6 insertions(+), 5 deletions(-)
--- handler.c
+++ handler.c 2022-01-11 10:41:03.491446992 +0000
@@ -782,7 +782,7 @@ static void enriched_set_flags (const wc
static int text_enriched_handler (BODY *a, STATE *s)
{
enum {
- TEXT, LANGLE, TAG, BOGUS_TAG, NEWLINE, ST_EOF, DONE
+ TEXT, TEXT_GOTWC, LANGLE, TAG, BOGUS_TAG, NEWLINE, ST_EOF, DONE
} state = TEXT;
LOFF_T bytes = a->length;
@@ -812,7 +812,7 @@ static int text_enriched_handler (BODY *
{
if (state != ST_EOF)
{
- if (!bytes || (wc = fgetwc (s->fpin)) == WEOF)
+ if (state != TEXT_GOTWC && (!bytes || (wc = fgetwc (s->fpin)) == WEOF))
state = ST_EOF;
else
bytes--;
@@ -820,6 +820,8 @@ static int text_enriched_handler (BODY *
switch (state)
{
+ case TEXT_GOTWC:
+ state = TEXT;
case TEXT :
switch (wc)
{
@@ -881,9 +883,8 @@ static int text_enriched_handler (BODY *
enriched_flush (&stte, 1);
else
{
- ungetwc (wc, s->fpin);
bytes++;
- state = TEXT;
+ state = TEXT_GOTWC;
}
break;
@@ -1588,7 +1589,7 @@ void mutt_decode_attachment (BODY *b, ST
* strip all trailing spaces to improve interoperability;
* if $text_flowed is unset, simply verbatim copy input
*/
-static int text_plain_handler (BODY *b, STATE *s)
+static int text_plain_handler (BODY *b __attribute__((unused)), STATE *s)
{
char *buf = NULL;
size_t l = 0, sz = 0;