Sync from SUSE:SLFO:Main mutt revision 116e33a45b7565da78ac0a3c2fd26076
This commit is contained in:
commit
1bb94dad72
23
.gitattributes
vendored
Normal file
23
.gitattributes
vendored
Normal 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
8
README.alternates
Normal 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
94
Signature_conversion
Normal 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
BIN
backports-datetime-fromisoformat-1.0.0.tar.gz
(Stored with Git LFS)
Normal file
Binary file not shown.
30
bsc907453-CVE-2014-9116-jessie.patch
Normal file
30
bsc907453-CVE-2014-9116-jessie.patch
Normal 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
23
mutt-1.10.1-imap.patch
Normal 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
110
mutt-1.13.3.dif
Normal 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! */
|
16
mutt-1.5.15-wrapcolumn.diff
Normal file
16
mutt-1.5.15-wrapcolumn.diff
Normal 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.
|
50
mutt-1.5.20-sendgroupreplyto.diff
Normal file
50
mutt-1.5.20-sendgroupreplyto.diff
Normal 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
20
mutt-1.5.21-mailcap.diff
Normal 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");
|
127
mutt-1.5.23-carriage-return.path
Normal file
127
mutt-1.5.23-carriage-return.path
Normal 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
18
mutt-1.5.9i-pgpewrap.diff
Normal 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
244
mutt-1.6.1-opennfs.dif
Normal 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
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
16
mutt-2.2.13.tar.gz.asc
Normal 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-----
|
57
mutt-Fix-SIGQUIT-handling.patch
Normal file
57
mutt-Fix-SIGQUIT-handling.patch
Normal 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
2909
mutt.changes
Normal file
File diff suppressed because it is too large
Load Diff
10
mutt.desktop
Normal file
10
mutt.desktop
Normal 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
52
mutt.keyring
Normal 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
2
mutt.mailcap
Normal file
@ -0,0 +1,2 @@
|
||||
# render html mail with w3m
|
||||
text/html; w3m -I %{charset} -T text/html -dump %s; copiousoutput
|
408
mutt.spec
Normal file
408
mutt.spec
Normal 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
434
mutt_oauth2.py-3.6
Normal 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
290
mutt_oauth2.py.README
Normal 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}
|
||||
|
53
patch-1.5.24.vk.pgp_verbose_mime
Normal file
53
patch-1.5.24.vk.pgp_verbose_mime
Normal 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
143
skel.muttrc
Normal 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
53
widechar.sidebar.dif
Normal 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;
|
Loading…
x
Reference in New Issue
Block a user