Initial Windows agent repository

This commit is contained in:
Frank Harris 2026-06-08 10:45:20 -05:00
commit a0db0c2e5b
10589 changed files with 3844063 additions and 0 deletions

View file

@ -0,0 +1,20 @@
#
# Key bindings similar to those of MUSH
#
# $Id$
bind index . display-message
bind index t display-message
macro index n "<next-entry><display-message>"
bind index + next-entry
bind index j next-entry
bind index J next-entry
bind index - previous-entry
bind index k previous-entry
bind index K previous-entry
bind index { top-page
bind index } bottom-page
bind index f change-folder
bind index \cu sync-mailbox
bind index * flag-message

View file

@ -0,0 +1,44 @@
#
# This file contains commands to change the keybindings in Mutt to be
# similar to those of PINE 3.95.
#
#
# $Id$
#
bind index v display-message
bind index p previous-undeleted
bind index n next-undeleted
bind index ' ' next-page
bind index c mail
bind index g change-folder
bind index w search
bind index y print-message
bind index x sync-mailbox
bind index $ sort-mailbox
bind index a tag-prefix
bind index \; tag-entry
# Not possible to simulate zoom-out...
macro index z "<limit>~T<Enter>"
bind pager p previous-undeleted
bind pager n next-undeleted
bind pager ' ' next-page
bind pager g change-folder
bind pager c mail
bind pager w search
bind pager y print-message
bind pager \n noop # PINE prints "No default action for this menu."
bind pager <up> previous-line
bind pager <down> next-line
bind compose \cx send-message
# PINE has different defaults for this variables
set folder=~/mail
set record=+sent-mail
set nosave_name
set postponed=~/postponed-msgs
set hdr_format="%Z %3C %{%b %d} %-19.19L (%5c) %s"

View file

@ -0,0 +1,22 @@
# From: Tom Gilbert <gilbertt@tomgilbert.freeserve.co.uk>
# To: mutt-users@mutt.org
# Subject: Re: Lynx-like movements
# Date: Sat, 4 Sep 1999 21:09:00 +0000
#
# These key bindings may be nice for notorious lynx or tin users.
#
bind pager <up> previous-line
bind pager <down> next-line
bind pager <left> exit
bind pager <right> view-attachments
bind attach <left> exit
bind attach <right> view-attach
bind index <right> display-message
macro index <left> "<change-folder>?"
bind browser <right> select-entry
macro browser <left> "<exit><change-folder>!<Enter>"

View file

@ -0,0 +1,34 @@
#!/bin/sh
#
# Copyright (C) 2020 Eike Rathke <erack@erack.de>
#
# 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.
#
# To conveniently switch between graphics and terminal editor I have the
# following in my muttrc:
# source '~/.mutt/bgedit-detectgui.sh|'
#
# So exporting MUTT_USE_GVIM=yes (or anything) in the shell invoking mutt
# switches to the background editing feature.
#
if [ -n "$MUTT_USE_GVIM" ] && [ -n "$DISPLAY" ]; then
# Foreground gvim, window 80 cols by 40 rows at X 400 and Y 0.
echo 'set editor="gvim -f -geometry 80x40+400+0"'
echo 'set background_edit=yes'
else
echo 'set editor=vim'
fi

View file

@ -0,0 +1,66 @@
#!/bin/sh
#
# Copyright (C) 2020 Kevin J. McCarthy <kevin@8t8.us>
#
# 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.
#
# Invoke a background edit session in a new GNU Screen or tmux window.
#
# This script is derived from Aaron Schrab's tmuxwait script, posted to
# mutt-dev at
# <http://lists.mutt.org/pipermail/mutt-dev/Week-of-Mon-20200406/000591.html>.
#
# If you run mutt inside screen or tmux, add to your muttrc:
# set background_edit
# set editor = '/path/to/bgedit-screen-tmux.sh [youreditor]'
#
# It may also be useful to modify something like contrib/bgedit-detectgui.sh
# to look for the $STY or $TMUX environment variables and set those
# configuration variables appropriately.
#
set -e
if [ "$#" -lt 2 ]; then
echo "Usage: $0 editor tempfile" >&2
exit 1
fi
editor=$1
shift
tmpdir=$(mktemp -d)
trap 'rm -rf "$tmpdir"' EXIT INT QUIT
mkfifo "$tmpdir/status"
cat >"$tmpdir/run" <<END_SCRIPT
exitval=1
trap 'echo \$exitval > "$tmpdir/status"' EXIT INT QUIT
$editor "\$@"
exitval=\$?
END_SCRIPT
if test x$STY != x; then
screen -X screen /bin/sh "$tmpdir/run" "$@"
elif test x$TMUX != x; then
tmux neww /bin/sh "$tmpdir/run" "$@"
else
echo "Not running inside a terminal emulator" >&2
exit 1
fi
read exitval <"$tmpdir/status"
exit "$exitval"

View file

@ -0,0 +1,24 @@
# -*-muttrc-*-
# Colors for use with xterm and the like, white background.
color hdrdefault blue white
color quoted blue white
color signature red white
color attachment red white
color prompt brightmagenta white
color message brightred white
color error brightred white
color indicator brightyellow red
color status brightgreen blue
color tree black white
color normal black white
color markers red white
color search white black
color tilde brightmagenta white
color index blue white ~F
color index red white "~N|~O"
# color body brightblack white '\*+[^*]+\*+'
# color body brightblack white '_+[^_]+_+'

View file

@ -0,0 +1,23 @@
# -*-muttrc-*-
# Palette for use with the Linux console. Black background.
color hdrdefault blue black
color quoted blue black
color signature blue black
color attachment red black
color prompt brightmagenta black
color message brightred black
color error brightred black
color indicator black red
color status brightgreen blue
color tree white black
color normal white black
color markers red black
color search white black
color tilde brightmagenta black
color index blue black ~F
color index red black "~N|~O"
# color body brightwhite black '\*+[^*]+\*+'
# color body brightwhite black '_+[^_]+_+'

View file

@ -0,0 +1,114 @@
# -*-muttrc-*-
#
# Command formats for gpg.
#
# Some of the older commented-out versions of the commands use gpg-2comp from:
# http://70t.de/download/gpg-2comp.tar.gz
#
# %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, if set, otherwise
# $pgp_default_key. 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.
# Section A: Key Management
# The default key for encryption (used by $pgp_self_encrypt and
# $postpone_encrypt).
#
# It will also be used for signing unless $pgp_sign_as is set to a
# key.
#
# Unless your key does not have encryption capability, uncomment this
# line and replace the keyid with your own.
#
# set pgp_default_key="0x12345678"
# If you have a separate signing key, or your key _only_ has signing
# capability, uncomment this line and replace the keyid with your
# signing keyid.
#
# set pgp_sign_as="0x87654321"
# Section B: Commands
# 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="gpg --status-fd=2 %?p?--passphrase-fd 0? --no-verbose --quiet --batch --output - %f"
# verify a pgp/mime signature
set pgp_verify_command="gpg --status-fd=2 --no-verbose --quiet --batch --output - --verify %s %f"
# decrypt a pgp/mime attachment
set pgp_decrypt_command="gpg --status-fd=2 %?p?--passphrase-fd 0? --no-verbose --quiet --batch --output - %f"
# create a pgp/mime signed attachment
# set pgp_sign_command="gpg-2comp --comment '' --no-verbose --batch --output - %?p?--passphrase-fd 0? --armor --detach-sign --textmode %?a?-u %a? %f"
set pgp_sign_command="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="gpg-2comp --comment '' --no-verbose --batch --output - %?p?--passphrase-fd 0? --armor --textmode --clearsign %?a?-u %a? %f"
set pgp_clearsign_command="gpg --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="pgpewrap gpg-2comp -v --batch --output - --encrypt --textmode --armor --always-trust -- -r %r -- %f"
set pgp_encrypt_only_command="pgpewrap gpg --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="pgpewrap gpg-2comp %?p?--passphrase-fd 0? -v --batch --output - --encrypt --sign %?a?-u %a? --armor --always-trust -- -r %r -- %f"
set pgp_encrypt_sign_command="pgpewrap gpg %?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="gpg --no-verbose --import %f"
# export a key from the public key ring
set pgp_export_command="gpg --no-verbose --export --armor %r"
# verify a key
set pgp_verify_key_command="gpg --verbose --batch --fingerprint --check-sigs %r"
# read in the public key ring
# note: the second --with-fingerprint adds fingerprints to subkeys
set pgp_list_pubring_command="gpg --no-verbose --batch --quiet --with-colons --with-fingerprint --with-fingerprint --list-keys %r"
# read in the secret key ring
# note: the second --with-fingerprint adds fingerprints to subkeys
set pgp_list_secring_command="gpg --no-verbose --batch --quiet --with-colons --with-fingerprint --with-fingerprint --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="^gpgv?: 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 '"'`"
# This version uses --status-fd messages
set pgp_good_sign="^\\[GNUPG:\\] GOODSIG"
# pattern to verify a decryption occurred
# This is now deprecated by pgp_check_gpg_decrypt_status_fd:
# set pgp_decryption_okay="^\\[GNUPG:\\] DECRYPTION_OKAY"
set pgp_check_gpg_decrypt_status_fd

View file

@ -0,0 +1,2 @@
iconv-hook CP850 IBM-850
iconv-hook ISO-8859-1 ISO8859-1

View file

@ -0,0 +1,13 @@
iconv-hook CP1046 IBM-1046
iconv-hook CP850 IBM-850
iconv-hook CP856 IBM-856
iconv-hook CP932 IBM-932
iconv-hook EUC-CN IBM-eucCN
iconv-hook EUC-JP IBM-eucJP
iconv-hook EUC-KR IBM-eucKR
iconv-hook EUC-TW IBM-eucTW
iconv-hook ISO-8859-1 ISO8859-1
iconv-hook ISO-8859-2 ISO8859-2
iconv-hook ISO-8859-5 ISO8859-5
iconv-hook ISO-8859-6 ISO8859-6
iconv-hook ISO-8859-8 ISO8859-8

View file

@ -0,0 +1,18 @@
iconv-hook BIG5 big5
iconv-hook CP1046 IBM-1046
iconv-hook CP850 IBM-850
iconv-hook CP856 IBM-856
iconv-hook CP922 IBM-922
iconv-hook CP932 IBM-932
iconv-hook EUC-CN IBM-eucCN
iconv-hook EUC-JP IBM-eucJP
iconv-hook EUC-KR IBM-eucKR
iconv-hook EUC-TW IBM-eucTW
iconv-hook ISO-8859-13 IBM-921
iconv-hook ISO-8859-1 ISO8859-1
iconv-hook ISO-8859-2 ISO8859-2
iconv-hook ISO-8859-5 ISO8859-5
iconv-hook ISO-8859-6 ISO8859-6
iconv-hook ISO-8859-7 ISO8859-7
iconv-hook ISO-8859-8 ISO8859-8
iconv-hook ISO-8859-9 ISO8859-9

View file

@ -0,0 +1,23 @@
iconv-hook BIG5 big5
iconv-hook CP1046 IBM-1046
iconv-hook CP1124 IBM-1124
iconv-hook CP1129 IBM-1129
iconv-hook CP1252 IBM-1252
iconv-hook CP850 IBM-850
iconv-hook CP856 IBM-856
iconv-hook CP922 IBM-922
iconv-hook CP932 IBM-932
iconv-hook CP943 IBM-943
iconv-hook EUC-CN IBM-eucCN
iconv-hook EUC-JP IBM-eucJP
iconv-hook EUC-KR IBM-eucKR
iconv-hook EUC-TW IBM-eucTW
iconv-hook ISO-8859-13 IBM-921
iconv-hook ISO-8859-15 ISO8859-15
iconv-hook ISO-8859-1 ISO8859-1
iconv-hook ISO-8859-2 ISO8859-2
iconv-hook ISO-8859-5 ISO8859-5
iconv-hook ISO-8859-6 ISO8859-6
iconv-hook ISO-8859-7 ISO8859-7
iconv-hook ISO-8859-8 ISO8859-8
iconv-hook ISO-8859-9 ISO8859-9

View file

@ -0,0 +1,6 @@
iconv-hook ASCII <error>
iconv-hook CP866 <error>
iconv-hook ISO-8859-15 <error>
iconv-hook ISO-8859-1 <error>
iconv-hook ISO-8859-2 <error>
iconv-hook KOI8-R <error>

View file

@ -0,0 +1 @@
iconv-hook ISO-8859-1 ANSI_X3.4-1968

View file

@ -0,0 +1 @@
iconv-hook ASCII ANSI_X3.4-1968

View file

@ -0,0 +1,15 @@
iconv-hook EUC-CN hp15CN
iconv-hook EUC-TW eucTW
iconv-hook HP-ARABIC8 arabic8
iconv-hook HP-GREEK8 greek8
iconv-hook HP-HEBREW8 hebrew8
iconv-hook HP-ROMAN8 roman8
iconv-hook HP-TURKISH8 turkish8
iconv-hook ISO-8859-1 iso88591
iconv-hook ISO-8859-2 iso88592
iconv-hook ISO-8859-5 iso88595
iconv-hook ISO-8859-6 iso88596
iconv-hook ISO-8859-7 iso88597
iconv-hook ISO-8859-8 iso88598
iconv-hook ISO-8859-9 iso88599
iconv-hook TIS-620 tis620

View file

@ -0,0 +1,15 @@
iconv-hook HP-ARABIC8 arabic8
iconv-hook HP-GREEK8 greek8
iconv-hook HP-HEBREW8 hebrew8
iconv-hook HP-ROMAN8 roman8
iconv-hook HP-TURKISH8 turkish8
iconv-hook ISO-8859-15 iso885915
iconv-hook ISO-8859-1 iso88591
iconv-hook ISO-8859-2 iso88592
iconv-hook ISO-8859-5 iso88595
iconv-hook ISO-8859-6 iso88596
iconv-hook ISO-8859-7 iso88597
iconv-hook ISO-8859-8 iso88598
iconv-hook ISO-8859-9 iso88599
iconv-hook TIS-620 tis620
iconv-hook UTF-8 utf8

View file

@ -0,0 +1,21 @@
iconv-hook BIG5 big5
iconv-hook EUC-CN hp15CN
iconv-hook EUC-JP eucJP
iconv-hook EUC-KR eucKR
iconv-hook EUC-TW eucTW
iconv-hook HP-ARABIC8 arabic8
iconv-hook HP-GREEK8 greek8
iconv-hook HP-HEBREW8 hebrew8
iconv-hook HP-KANA8 kana8
iconv-hook HP-ROMAN8 roman8
iconv-hook HP-TURKISH8 turkish8
iconv-hook ISO-8859-15 iso885915
iconv-hook ISO-8859-1 iso88591
iconv-hook ISO-8859-2 iso88592
iconv-hook ISO-8859-5 iso88595
iconv-hook ISO-8859-6 iso88596
iconv-hook ISO-8859-7 iso88597
iconv-hook ISO-8859-8 iso88598
iconv-hook ISO-8859-9 iso88599
iconv-hook TIS-620 tis620
iconv-hook UTF-8 utf8

View file

@ -0,0 +1,9 @@
iconv-hook EUC-CN eucCN
iconv-hook EUC-JP eucJP
iconv-hook EUC-KR eucKR
iconv-hook EUC-TW eucTW
iconv-hook ISO-8859-1 ISO8859-1
iconv-hook ISO-8859-2 ISO8859-2
iconv-hook ISO-8859-5 ISO8859-5
iconv-hook ISO-8859-7 ISO8859-7
iconv-hook ISO-8859-9 ISO8859-9

View file

@ -0,0 +1,3 @@
iconv-hook ISO-8859-1 ISO8859-1
iconv-hook ISO-8859-7 ISO8859-7
iconv-hook ISO-8859-9 ISO8859-9

View file

@ -0,0 +1,4 @@
iconv-hook CP850 cp850
iconv-hook ISO-8859-1 ISO8859-1
iconv-hook ISO-8859-7 ISO8859-7
iconv-hook ISO-8859-9 ISO8859-9

View file

@ -0,0 +1 @@
iconv-hook bug

View file

@ -0,0 +1 @@
iconv-hook ISO-8859-1 ISO8859-1

View file

@ -0,0 +1,11 @@
iconv-hook EUC-CN gb2312
iconv-hook EUC-JP eucJP
iconv-hook EUC-KR 5601
iconv-hook EUC-TW cns11643
iconv-hook ISO-8859-1 ISO8859-1
iconv-hook ISO-8859-2 ISO8859-2
iconv-hook ISO-8859-4 ISO8859-4
iconv-hook ISO-8859-5 ISO8859-5
iconv-hook ISO-8859-7 ISO8859-7
iconv-hook ISO-8859-9 ISO8859-9
iconv-hook Shift_JIS PCK

View file

@ -0,0 +1,6 @@
iconv-hook ISO-8859-1 ISO8859-1
iconv-hook ISO-8859-2 ISO8859-2
iconv-hook ISO-8859-4 ISO8859-4
iconv-hook ISO-8859-5 ISO8859-5
iconv-hook ISO-8859-7 ISO8859-7
iconv-hook ISO-8859-9 ISO8859-9

View file

@ -0,0 +1,12 @@
iconv-hook ASCII 646
iconv-hook ISO-8859-15 ISO8859-15
iconv-hook ISO-8859-1 ISO8859-1
iconv-hook ISO-8859-2 ISO8859-2
iconv-hook ISO-8859-4 ISO8859-4
iconv-hook ISO-8859-5 ISO8859-5
iconv-hook ISO-8859-6 ISO8859-6
iconv-hook ISO-8859-7 ISO8859-7
iconv-hook ISO-8859-8 ISO8859-8
iconv-hook ISO-8859-9 ISO8859-9
iconv-hook KOI8-R koi8-r
iconv-hook TIS-620 TIS620.2533

View file

@ -0,0 +1,309 @@
#!/usr/bin/python3
#
# markdown2html.py — simple Markdown-to-HTML converter for use with Mutt
#
# Mutt recently learnt [how to compose `multipart/alternative`
# emails][1]. This script assumes a message has been composed using Markdown
# (with a lot of pandoc extensions enabled), and translates it to `text/html`
# for Mutt to tie into such a `multipart/alternative` message.
#
# [1]: https://gitlab.com/muttmua/mutt/commit/0e566a03725b4ad789aa6ac1d17cdf7bf4e7e354)
#
# Configuration:
# muttrc:
# set send_multipart_alternative=yes
# set send_multipart_alternative_filter=/path/to/markdown2html.py
#
# Optionally, Custom CSS styles will be read from `~/.mutt/markdown2html.css`,
# if present.
#
# Requirements:
# - python3
# - PyPandoc (and pandoc installed, or downloaded)
# - Pynliner
#
# Optional:
# - Pygments, if installed, then syntax highlighting is enabled
#
# Latest version:
# https://git.madduck.net/etc/mutt.git/blob_plain/HEAD:/.mutt/markdown2html
#
# Copyright © 2019 martin f. krafft <madduck@madduck.net>
# Released under the GPL-2+ licence, just like Mutt itself.
#
import pypandoc
import pynliner
import re
import os
import sys
try:
from pygments.formatters import get_formatter_by_name
formatter = get_formatter_by_name('html', style='default')
DEFAULT_CSS = formatter.get_style_defs('.sourceCode')
except ImportError:
DEFAULT_CSS = ""
DEFAULT_CSS += '''
.quote {
padding: 0 0.5em;
margin: 0;
font-style: italic;
border-left: 2px solid #ccc;
color: #999;
font-size: 80%;
}
.quotelead {
font-style: italic;
margin-bottom: -1em;
color: #999;
font-size: 80%;
}
.quotechar { display: none; }
.footnote-ref, .footnote-back { text-decoration: none;}
.signature {
color: #999;
font-family: monospace;
white-space: pre;
margin: 1em 0 0 0;
font-size: 80%;
}
table, th, td {
border-collapse: collapse;
border: 1px solid #999;
}
th, td { padding: 0.5em; }
.header {
background: #eee;
}
.even { background: #eee; }
'''
STYLESHEET = os.path.join(os.path.expanduser('~/.mutt'),
'markdown2html.css')
if os.path.exists(STYLESHEET):
DEFAULT_CSS += open(STYLESHEET).read()
HTML_DOCUMENT = '''<!DOCTYPE html>
<html><head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes"/>
<title>HTML E-Mail</title>
</head><body class="email">
{htmlbody}
</body></html>'''
SIGNATURE_HTML = \
'<div class="signature"><span class="leader">-- </span>{sig}</div>'
def _preprocess_markdown(mdwn):
'''
Preprocess Markdown for handling by the converter.
'''
# convert hard line breaks within paragraphs to 2 trailing spaces, which
# is the markdown way of representing hard line breaks. Note how the
# regexp will not match between paragraphs.
ret = re.sub(r'(\S)\n(\s*\S)', r'\g<1> \n\g<2>', mdwn, flags=re.MULTILINE)
# Clients like Thunderbird need the leading '>' to be able to properly
# create nested quotes, so we duplicate the symbol, the first instance
# will tell pandoc to create a blockquote, while the second instance will
# be a <span> containing the character, along with a class that causes CSS
# to actually hide it from display. However, this does not work with the
# text-mode HTML2text converters, and so it's left commented for now.
#ret = re.sub(r'\n>', r' \n>[>]{.quotechar}', ret, flags=re.MULTILINE)
return ret
def _identify_quotes_for_later(mdwn):
'''
Email quoting such as:
```
On 1970-01-01, you said:
> The Flat Earth Society has members all around the globe.
```
isn't really properly handled by Markdown, so let's do our best to
identify the individual elements, and mark them, using a syntax similar to
what pandoc uses already in some cases. As pandoc won't actually use these
data (yet?), we call `self._reformat_quotes` later to use these markers
to slap the appropriate classes on the HTML tags.
'''
def generate_lines_with_context(mdwn):
'''
Iterates the input string line-wise, returning a triplet of
previous, current, and next line, the first and last of which
will be None on the first and last line of the input data
respectively.
'''
prev = cur = nxt = None
lines = iter(mdwn.splitlines())
cur = next(lines)
for nxt in lines:
yield prev, cur, nxt
prev = cur
cur = nxt
yield prev, cur, None
ret = []
for prev, cur, nxt in generate_lines_with_context(mdwn):
# The lead-in to a quote is a single line immediately preceding the
# quote, and ending with ':'. Note that there could be multiple of
# these:
if nxt is not None and re.match(r'^\s*[^>].+:\s*$', cur) and nxt.startswith('>'):
ret.append(f'{{.quotelead}}{cur.strip()}')
# pandoc needs an empty line before the blockquote, so
# we enter one for the purpose of HTML rendition:
ret.append('')
continue
# The first blockquote after such a lead-in gets marked as the
# "initial" quote:
elif prev is not None and re.match(r'^\s*[^>].+:\s*$', prev) and cur.startswith('>'):
ret.append(re.sub(r'^(\s*>\s*)+(.+)',
r'\g<1>{.quoteinitial}\g<2>',
cur, flags=re.MULTILINE))
# All other occurrences of blockquotes get the "subsequent" marker:
elif cur.startswith('>') and prev is not None and not prev.startswith('>'):
ret.append(re.sub(r'^((?:\s*>\s*)+)(.+)',
r'\g<1>{.quotesubsequent}\g<2>',
cur, flags=re.MULTILINE))
else: # pass through everything else.
ret.append(cur)
return '\n'.join(ret)
def _reformat_quotes(html):
'''
Earlier in the pipeline, we marked email quoting, using markers, which we
now need to turn into HTML classes, so that we can use CSS to style them.
'''
ret = html.replace('{.quotelead}', '<p class="quotelead">')
ret = re.sub(r'<blockquote>\n((?:<blockquote>\n)*)<p>(?:\{\.quote(\w+)\})',
r'<blockquote class="quote \g<2>">\n\g<1><p>', ret, flags=re.MULTILINE)
return ret
def _convert_with_pandoc(mdwn, inputfmt='markdown', outputfmt='html5',
ext_enabled=None, ext_disabled=None,
standalone=True, title="HTML E-Mail"):
'''
Invoke pandoc to do the actual conversion of Markdown to HTML5.
'''
if not ext_enabled:
ext_enabled = [ 'backtick_code_blocks',
'line_blocks',
'fancy_lists',
'startnum',
'definition_lists',
'example_lists',
'table_captions',
'simple_tables',
'multiline_tables',
'grid_tables',
'pipe_tables',
'all_symbols_escapable',
'intraword_underscores',
'strikeout',
'superscript',
'subscript',
'fenced_divs',
'bracketed_spans',
'footnotes',
'inline_notes',
'emoji',
'tex_math_double_backslash',
'autolink_bare_uris'
]
if not ext_disabled:
ext_disabled = [ 'tex_math_single_backslash',
'tex_math_dollars',
'smart',
'raw_html'
]
enabled = '+'.join(ext_enabled)
disabled = '-'.join(ext_disabled)
inputfmt = f'{inputfmt}+{enabled}-{disabled}'
args = []
if standalone:
args.append('--standalone')
if title:
args.append(f'--metadata=pagetitle:"{title}"')
return pypandoc.convert_text(mdwn, format=inputfmt, to=outputfmt,
extra_args=args)
def _apply_styling(html):
'''
Inline all styles defined and used into the individual HTML tags.
'''
return pynliner.Pynliner().from_string(html).with_cssString(DEFAULT_CSS).run()
def _postprocess_html(html):
'''
Postprocess the generated and styled HTML.
'''
return html
def convert_markdown_to_html(mdwn):
'''
Converts the input Markdown to HTML, handling separately the body, as well
as an optional signature.
'''
parts = re.split(r'^-- $', mdwn, 1, flags=re.MULTILINE)
body = parts[0]
if len(parts) == 2:
sig = parts[1]
else:
sig = None
html=''
if body:
body = _preprocess_markdown(body)
body = _identify_quotes_for_later(body)
html = _convert_with_pandoc(body, standalone=False)
html = _reformat_quotes(html)
if sig:
sig = _preprocess_markdown(sig)
html += SIGNATURE_HTML.format(sig='<br/>'.join(sig.splitlines()))
html = HTML_DOCUMENT.format(htmlbody=html)
html = _apply_styling(html)
html = _postprocess_html(html)
return html
def main():
'''
Convert text on stdin to HTML, and print it to stdout, like mutt would
expect.
'''
html = convert_markdown_to_html(sys.stdin.read())
if html:
# mutt expects the content type in the first line, so:
print(f'text/html\n\n{html}')
if __name__ == '__main__':
main()

View file

@ -0,0 +1,421 @@
#!/usr/bin/env 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
from datetime import timedelta, datetime
from pathlib import Path
import socket
import http.server
import subprocess
import readline
# 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:
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.')
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})
print('Exchanging the authorization code for an access 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'])
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)

View file

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

View file

@ -0,0 +1,9 @@
#!/bin/sh
# Demonstration of format string pipes. Sets the xterm title and returns the
# string unchanged.
#
# Example usage:
# set status_format="mutt_xtitle '%r %f (%L) [Msgs:%?M?%M/?%m%?n? New:%n?%?d? Del:%d?%?F? Flag:%F?%?t? Tag:%t?%?p? Post:%p?%?b? Inc:%b?]'|"
printf "\033]0;$1\007" > /dev/tty
echo "$1"

View file

@ -0,0 +1,51 @@
# -*-muttrc-*-
#
# PGP command formats for PGP 2.
#
# $Id$
#
#
# Note: In order to be able to read your own messages, you'll have
# the +encrypttoself command line parameter to the pgp_encrypt_only_command
# and pgp_encrypt_sign_command variables.
#
# decode application/pgp
set pgp_decode_command="%?p?PGPPASSFD=0; export PGPPASSFD;? cat %?p?-? %f | pgp +language=mutt +verbose=0 +batchmode -f"
# verify a pgp/mime signature
set pgp_verify_command="pgp +language=mutt +verbose=0 +batchmode -t %s %f"
# decrypt a pgp/mime attachment
set pgp_decrypt_command="PGPPASSFD=0; export PGPPASSFD; cat - %f | pgp +language=mutt +verbose=0 +batchmode -f"
# don't check for GnuPG decryption status codes
unset pgp_check_gpg_decrypt_status_fd
# create a pgp/mime signed attachment
set pgp_sign_command="PGPPASSFD=0; export PGPPASSFD; cat - %f | pgp +language=mutt +verbose=0 +batchmode -abfst %?a? -u %a?"
# create a pgp/mime encrypted attachment
set pgp_encrypt_only_command="pgp +language=mutt +verbose=0 +batchmode -aeft %r < %f"
# create a pgp/mime encrypted and signed attachment
set pgp_encrypt_sign_command="PGPPASSFD=0; export PGPPASSFD; cat - %f | pgp +language=mutt +verbose=0 +batchmode -aefts %?a?-u %a? %r"
# import a key into the public key ring
set pgp_import_command="pgp -ka %f +language=mutt"
# export a key from the public key ring
set pgp_export_command="pgp -kxaf +language=mutt %r"
# verify a key
set pgp_verify_key_command="pgp -kcc +language=mutt %r"
# read in the public key ring
set pgp_list_pubring_command="mutt_pgpring -2 %r"
# read in the secret key ring
set pgp_list_secring_command="mutt_pgpring -s -2 %r"
# pattern for good signature
set pgp_good_sign="Good signature"

View file

@ -0,0 +1,47 @@
# -*-muttrc-*-
#
# PGP command formats for PGP 5.
#
# $Id$
#
# decode application/pgp
set pgp_decode_command="%?p?PGPPASSFD=0; export PGPPASSFD;? cat %?p?-? %f | pgpv +language=mutt +verbose=0 +batchmode -f --OutputInformationFD=0"
# verify a pgp/mime signature
set pgp_verify_command="pgpv +language=mutt +verbose=0 +batchmode --OutputInformationFD=1 %f %s"
# string that the verify command outputs if the signature is good
set pgp_good_sign = "Good signature"
# decrypt a pgp/mime attachment
set pgp_decrypt_command="PGPPASSFD=0; export PGPPASSFD; cat - %f | pgpv +language=mutt +verbose=0 +batchmode --OutputInformationFD=2 -f"
# don't check for GnuPG decryption status codes
unset pgp_check_gpg_decrypt_status_fd
# create a pgp/mime signed attachment
set pgp_sign_command="PGPPASSFD=0; export PGPPASSFD; cat - %f | pgps +language=mutt +verbose=0 +batchmode -abft %?a? -u %a?"
# create a pgp/mime encrypted attachment
set pgp_encrypt_only_command="pgpewrap pgpe +language=mutt +verbose=0 +batchmode +nobatchinvalidkeys=off -aft -- -r %r < %f"
# create a pgp/mime encrypted and signed attachment
set pgp_encrypt_sign_command="PGPPASSFD=0; export PGPPASSFD; cat - %f | pgpewrap pgpe +language=mutt +verbose=0 +batchmode +nobatchinvalidkeys=off -afts %?a? -u %a? -- -r %r"
# import a key into the public key ring
set pgp_import_command="pgpk -a +language=mutt --OutputInformationFD=1 %f"
# export a key from the public key ring
set pgp_export_command="pgpk -xa +language=mutt --OutputInformationFD=1 %r"
# verify a key
set pgp_verify_key_command="pgpk -c +batchmode +language=mutt --OutputInformationFD=1 %r"
# read in the public key ring
set pgp_list_pubring_command="mutt_pgpring -5 %r"
# read in the secret key ring
set pgp_list_secring_command="mutt_pgpring -5 -s %r"

View file

@ -0,0 +1,48 @@
# -*-muttrc-*-
#
# PGP command formats for PGP 6.
#
# $Id$
#
# decode application/pgp
set pgp_decode_command="%?p?PGPPASSFD=0; export PGPPASSFD;? cat %?p?-? %f | pgp6 +compatible +verbose=0 +batchmode -f"
# verify a pgp/mime signature
set pgp_verify_command="pgp6 +compatible +verbose=0 +batchmode -t %s %f"
# decrypt a pgp/mime attachment
set pgp_decrypt_command="PGPPASSFD=0; export PGPPASSFD; cat - %f | pgp6 +compatible +verbose=0 +batchmode -f"
# don't check for GnuPG decryption status codes
unset pgp_check_gpg_decrypt_status_fd
# create a pgp/mime signed attachment
set pgp_sign_command="PGPPASSFD=0; export PGPPASSFD; cat - %f | pgp6 +compatible +verbose=0 +batchmode -abfst %?a? -u %a?"
# create a pgp/mime encrypted attachment
set pgp_encrypt_only_command="pgp6 +compatible +verbose=0 +encrypttoself +batchmode -aeft %r < %f"
# create a pgp/mime encrypted and signed attachment
set pgp_encrypt_sign_command="PGPPASSFD=0; export PGPPASSFD; cat - %f | pgp6 +compatible +verbose=0 +encrypttoself +batchmode +clearsig=off -aefts %?a? -u %a? %r"
# import a key into the public key ring
set pgp_import_command="pgp6 +compatible -ka %f "
# export a key from the public key ring
set pgp_export_command="pgp6 +compatible -kxaf %r"
# verify a key
set pgp_verify_key_command="pgp6 +compatible -kcc %r"
# read in the public key ring
set pgp_list_pubring_command="mutt_pgpring -5 %r"
# read in the secret key ring
set pgp_list_secring_command="mutt_pgpring -s -5 %r"
# create a clearsigned message
set pgp_clearsign_command="PGPPASSFD=0; export PGPPASSFD; cat - %f | pgp6 +compatible +verbose=0 +batchmode +clearsig -afst %?a? -u %a?"
# fetch keys
set pgp_getkeys_command="pkspxycwrap %r"

View file

@ -0,0 +1,6 @@
# $Id$
text/html; netscape -remote openURL\(%s\)
image/gif; xv %s
image/jpg; xv %s
application/pgp-keys; pgp -f < %s ; copiousoutput

View file

@ -0,0 +1,340 @@
# $Id$
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
#
# ME's personal .muttrc (Mutt 0.92.5)
#
# The format of this file is one command per line. Everything after a pound
# sign (#) is a comment, unless a backward slash (\) precedes it. Note: In
# folder-hook and send-hook you need to account for two levels of dequoting
# (see manual).
#
# Note: $folder should be set _before_ any other path vars where `+' or `='
# is used because paths are expanded when parsed
#
#set folder=~/Mail # where i keep my mailboxes
#set abort_unmodified=yes # automatically abort replies if I don't
# change the message
set alias_file=~/.mail_aliases # where I keep my aliases
#set allow_8bit # never do Q-P encoding on legal 8-bit chars
set arrow_cursor # use -> instead of hiliting the whole line
#set ascii_chars # use ASCII instead of ACS chars for threads
#set askbcc
#set askcc
#set attribution="On %d, %n wrote:" # how to attribute replies
set autoedit # go to the editor right away when composing
#set auto_tag # always operate on tagged messages
#set charset="iso-8859-1" # character set for your terminal
set noconfirmappend # don't ask me if i want to append to mailboxes
#set confirmcreate # prompt when creating new files
set copy=yes # always save a copy of outgoing messages
set delete=yes # purge deleted messages without asking
set edit_headers # let me edit the message header when composing
#set editor="emacs -nw" # editor to use when composing messages
#set bounce=yes # don't ask about bouncing messages, just do it
#set fast_reply # skip initial prompts when replying
#set fcc_attach # keep attachments in copies of sent messages?
#set force_name # fcc by recipient, create if mailbox doesn't exist
#set forward_decode # weed and MIME decode forwarded messages
#set forward_format="[%a: %s]" # subject to use when forwarding messages
#set forward_quote # quote the header and body of forward msgs
#set index_format="%4C %Z %{%m/%d} [%2N] %-15.15F (%4c) %s"
set index_format="%4C %Z %{%m/%d} %-15.15F (%4c) %s" # format of the index
#set hdrs # include `my_hdr' lines in outgoing messages
#set header # include message header when replying
set help # show the help lines
#set history=20 # number of lines of history to remember
#set hostname="mutt.org" # my DNS domain
set include # always include messages when replying
#set indent_string="> " # how to quote replied text
#set locale="C" # locale to use for printing time
#set mailcap_path="~/.mailcap:/usr/local/share/mailcap"
set nomark_old # i don't care about whether a message is old
set mail_check=10 # how often to poll for new mail
set mbox=+mbox # where to store read messages
#set menu_scroll # no implicit next-page/prev-page
#set metoo # remove my address when replying
set mime_forward # use message/rfc822 type to forward messages
set move=yes # don't ask about moving messages, just do it
#set pager=less # some people prefer an external pager
#set pager_context=3 # no. of lines of context to give when scrolling
#set pager_format="-%S- %-20.20f %s" # format of the pager status bar
set pager_index_lines=6 # how many index lines to show in the pager
#set pager_stop # don't move to the next message on next-page
#set pgp_strict_enc # use Q-P encoding when needed for PGP
set postponed=+postponed # mailbox to store postponed messages in
#set post_indent_string='---end quoted text---'
#set print=ask-yes # ask me if I really want to print messages
set print_command=/bin/false # how to print things (I like to save trees)
set noprompt_after # ask me for a command after the external pager exits
#set quote_regexp="^ *[a-zA-Z]*[>:#}]" # how to catch quoted text
set read_inc=25 # show progress when reading a mailbox
#set recall # prompt to recall postponed messages
set record=+outbox # default location to save outgoing mail
set reply_to # always use reply-to if present
#set reply_regexp="^(re:[ \t]*)+"# how to identify replies in the subject:
#set resolve # move to the next message when an action is performed
#set reverse_alias # attempt to look up my names for people
set reverse_name # use my address as it appears in the message
# i am replying to
set nosave_empty # remove files when no messages are left
#set save_name # save outgoing messages by recipient, if the
#set sendmail="/usr/lib/sendmail -oi -oem" # how to deliver mail
#set shell="/bin/zsh" # program to use for shell escapes
#set signature="~/.signature" # file which contains my signature
# I subscribe to a lot of mailing lists, so this is _very_ useful. This
# groups messages on the same subject to make it easier to follow a
# discussion. Mutt will draw a nice tree showing how the discussion flows.
set sort=threads # primary sorting method
#set sort_aux=reverse-date-received # how to sort subthreads
#set sort_aux=last-date # date of the last message in thread
set sort_browser=reverse-date # how to sort files in the dir browser
set spoolfile='~/mailbox' # where my new mail is located
#set status_format="-%r-Mutt: %f [Msgs:%?M?%M/?%m%?n? New:%n?%?d? Del:%d?%?F? Flag:%F?%?t? Tag:%t?%?p? Post:%p?%?b? Inc:%b? %l]---(%s)-%>-(%P)---"
#set status_on_top # some people prefer the status bar on top
#set strict_threads # don't thread by subject
set tilde # virtual lines to pad blank lines in the pager
#set timeout=0 # timeout for prompt in the index menu
#set tmpdir=~/tmp # where to store temp files
#set to_chars=" +TCF"
#set use_8bitmime # enable the -B8BITMIME sendmail flag
set nouse_domain # don't qualify local addresses with $domain
#set use_from # always generate the `From:' header field
set implicit_autoview=yes # pager shows parts having a mailcap viewer
set pgp_verify_sig=no # don't automatically verify message signatures
#set visual=vim # editor invoked by ~v in the builtin editor
#set nowait_key # prompt when a pipe returns normal status
set write_inc=25 # show progress while writing mailboxes
# only enable the following IFF you have sendmail 8.8.x or you will not
# be able to send mail!!!
#set dsn_notify='failure,delay' # when to return an error message
#set dsn_return=hdrs # what to return in the error message
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
#
# Header fields I don't normally want to see
#
ignore * # this means "ignore all lines by default"
# I do want to see these fields, though!
unignore from: subject to cc mail-followup-to \
date x-mailer x-url # this shows how nicely wrap long lines
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
#
# Color definitions
#
#color normal white default
color hdrdefault red default
color quoted brightblue default
color signature red default
color indicator brightyellow red
color error brightred default
color status yellow blue
color tree magenta default # the thread tree in the index menu
color tilde magenta default
color message brightcyan default
color markers brightcyan default
color attachment brightmagenta default
color search default green # how to hilite search patterns in the pager
color header brightred default ^(From|Subject):
color body magenta default "(ftp|http|https)://[^ ]+" # point out URLs
color body magenta default [-a-z_0-9.]+@[-a-z_0-9.]+ # e-mail addresses
color underline brightgreen default
# attributes when using a mono terminal
#mono header underline ^(From|Subject):
mono quoted bold
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
#
# Key bindings
#
# maps:
# alias alias menu
# attach attachment menu
# browser directory browser
# compose compose menu
# index message index
# pgp pgp menu
# postpone postponed message recall menu
# generic generic keymap for all of the above
# editor line editor
# pager text viewer
#
bind generic "\e<" first-entry # emacs-like bindings for moving to top/bottom
bind generic \e> last-entry
bind generic { top-page
bind generic } bottom-page
bind generic \177 last-entry
macro index \cb "<pipe-message> urlview<Enter>" # simulate the old browse-url function
macro index S "<save-message>+spam<Enter>"
macro pager S "<save-message>+spam<Enter>"
#macro index \# "<search>bug<Enter>" # search for bugs
#macro index "\"" "<enter-command> set realname=\"real hairy macro\" ?realname<Enter>" # and a comment to boot!
#macro index f1 "<enter-command>woohoo!"
bind pager G bottom # just like vi and less
#macro pager \Ck "<pipe-message> pgp -kaf<Enter>" # a comment is valid here
#macro pager X "<pipe-message> morepgp<Enter>" # pipe PGP message to a script
#bind editor \cy eol # make ^Y jump to the end of the line
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
#
# User Defined Headers
#
#my_hdr X-Useless-Header: Look ma, it's a \# sign! # real comment
#my_hdr X-Operating-System: `uname -a`
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
#
# Specify default filename when saving messages
#
# save-hook [!]<pattern> <mailbox>
#
# <mailbox> is provided as default when saving messages from <pattern>
#save-hook mutt- =mutt-mail
#save-hook aol\\.com$ +spam
save-hook ^judge +diplomacy
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
#
# Multiple spool mailboxes
#
# mbox-hook [!]<pattern> <mbox-mailbox>
#
# Read mail in <pattern> is moved to <mbox-mailbox> when <pattern> is
# closed.
#mbox-hook =mutt-users.in =mutt-users
#mbox-hook +TEST +inbox
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
#
# Change settings based upon message recipient
#
# send-hook [!]<pattern> <command>
#
# <command> is executed when sending mail to an address matching <pattern>
#send-hook mutt- 'set signature=~/.sigmutt; my_hdr From: Mutt User <user@example.com>'
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
#
# Specify where to save composed messages
#
# fcc-hook [!]<pattern> <mailbox>
#
# <pattern> is recipient(s), <mailbox> is where to save a copy
#fcc-hook joe +joe
#fcc-hook bob +bob
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
#
# Change settings based on mailbox
#
# folder-hook [!]<pattern> <command>
#
# <command> is executed when opening a mailbox matching <pattern>
#folder-hook . 'set sort=date-sent'
#folder-hook mutt 'set index_format="%4C %Z %02m/%02N %-20.20F (%4l) %s"'
#folder-hook =mutt my_hdr Revolution: \#9 # real comment
#folder-hook . 'set reply_regexp="^re:[ \t]*"'
# this mailing list prepends "[WM]" to all non reply subjects, so set
# $reply_regexp to ignore it
# Warning: May break threads for other people.
#folder-hook +wmaker 'set reply_regexp="^(re:[ \t]*)?\[WM\][ \t]*"'
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
#
# Aliases
#
# alias <name> <address> [ , <address> ... ]
#alias exam "\# to annoy michael" <user@host>
#alias me Michael Elkins <me@mutt.org> # me!
alias mutt-dev Mutt Development List <mutt-dev@mutt.org> # power users
alias mutt-users Mutt User List <mutt-users@mutt.org>
alias mutt-announce Mutt Announcement List <mutt-announce@mutt.org>
alias wmaker WindowMaker Mailing List <wmaker@eosys.com>
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
#
# Mailboxes to watch for new mail
#
# mailboxes <path1> [ <path2> ... ]
#
mailboxes ! +mutt-dev +mutt-users +open-pgp +wmaker +hurricane +vim +ietf \
+drums
#mailboxes `echo $HOME/Mail/*`
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
#
# Specify the order of the headers to appear when displaying a message
#
# hdr_order <hdr1> [ <hdr2> ... ]
#
unhdr_order * # forget the previous settings
hdr_order date from subject to cc
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
#
# Identify mailing lists I subscribe to
#
# lists <list-name> [ <list-name> ... ]
lists ^mutt-dev@mutt\\.org$ ^mutt-users@mutt\\.org$
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
#
# Automatically use entries from ~/.mailcap to view these MIME types
#
# auto_view <type> [ <type> ... ]
auto_view application/x-gunzip
auto_view application/x-gzip
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
#
# Scoring
#
# score <pattern> <value>
#
# 9999 and -9999 are special values which cause processing of hooks to stop
# at that entry. If you prefix the score with an equal sign (=), the score
# is assigned to the message and processing stops.
#score '~f ^me@cs\.hmc\.edu$' 1000
#score '~t mutt | ~c mutt' =500
#score '~f aol\.com$' -9999
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
#
# I use Mutt on several different machines, so I put local config commands
# in a separate file so I can have the rest of the settings the same on all
# machines.
#
source ~/.muttrc-local # config commands local to this site
# EOF

View file

@ -0,0 +1,38 @@
# Example Mutt config file for the compress feature.
# This feature adds three hooks to Mutt which allow it to
# work with compressed, or encrypted, mailboxes.
# The hooks are of the form:
# open-hook regexp "shell-command"
# close-hook regexp "shell-command"
# append-hook regexp "shell-command"
# The 'append-hook' is optional.
# Handler for gzip compressed mailboxes
open-hook '\.gz$' "gzip -cd '%f' > '%t'"
close-hook '\.gz$' "gzip -c '%t' > '%f'"
append-hook '\.gz$' "gzip -c '%t' >> '%f'"
# Handler for bzip2 compressed mailboxes
open-hook '\.bz2$' "bzip2 -cd '%f' > '%t'"
close-hook '\.bz2$' "bzip2 -c '%t' > '%f'"
append-hook '\.bz2$' "bzip2 -c '%t' >> '%f'"
# Handler for xz compressed mailboxes
open-hook '\.xz$' "xz -cd '%f' > '%t'"
close-hook '\.xz$' "xz -c '%t' > '%f'"
append-hook '\.xz$' "xz -c '%t' >> '%f'"
# Handler for pgp encrypted mailboxes
# PGP does not support appending to an encrypted file
open-hook '\.pgp$' "pgp -f < '%f' > '%t'"
close-hook '\.pgp$' "pgp -fe YourPgpUserIdOrKeyId < '%t' > '%f'"
# Handler for gpg encrypted mailboxes
# gpg does not support appending to an encrypted file
open-hook '\.gpg$' "gpg --decrypt < '%f' > '%t'"
close-hook '\.gpg$' "gpg --encrypt --recipient YourGpgUserIdOrKeyId < '%t' > '%f'"
# vim: syntax=muttrc

View file

@ -0,0 +1,116 @@
# This is a complete list of sidebar-related configuration.
# --------------------------------------------------------------------------
# VARIABLES - shown with their default values
# --------------------------------------------------------------------------
# Should the Sidebar be shown?
set sidebar_visible = no
# How wide should the Sidebar be in screen columns?
# Note: Some characters, e.g. Chinese, take up two columns each.
set sidebar_width = 20
# Should the mailbox paths be abbreviated?
set sidebar_short_path = no
# When abbreviating mailbox path names, use any of these characters as path
# separators. Only the part after the last separators will be shown.
# For file folders '/' is good. For IMAP folders, often '.' is useful.
set sidebar_delim_chars = '/.'
# If the mailbox path is abbreviated, should it be indented?
set sidebar_folder_indent = no
# Indent mailbox paths with this string.
set sidebar_indent_string = ' '
# Make the Sidebar only display mailboxes that contain new, or flagged,
# mail.
set sidebar_new_mail_only = no
# Any mailboxes that are whitelisted will always be visible, even if the
# sidebar_new_mail_only option is enabled.
sidebar_whitelist '/home/user/mailbox1'
sidebar_whitelist '/home/user/mailbox2'
# When searching for mailboxes containing new mail, should the search wrap
# around when it reaches the end of the list?
set sidebar_next_new_wrap = no
# The character to use as the divider between the Sidebar and the other Mutt
# panels.
# Note: Only the first character of this string is used.
set sidebar_divider_char = '|'
# Enable extended buffy mode to calculate total, new, and flagged
# message counts for each mailbox.
set mail_check_stats
# Display the Sidebar mailboxes using this format string.
set sidebar_format = '%B%?F? [%F]?%* %?N?%N/?%S'
# Sort the mailboxes in the Sidebar using this method:
# count - total number of messages
# flagged - number of flagged messages
# new - number of new messages
# path - mailbox path
# unsorted - do not sort the mailboxes
set sidebar_sort_method = 'unsorted'
# --------------------------------------------------------------------------
# FUNCTIONS - shown with an example mapping
# --------------------------------------------------------------------------
# Move the highlight to the previous mailbox
bind index,pager \Cp sidebar-prev
# Move the highlight to the next mailbox
bind index,pager \Cn sidebar-next
# Open the highlighted mailbox
bind index,pager \Co sidebar-open
# Move the highlight to the previous page
# This is useful if you have a LOT of mailboxes.
bind index,pager <F3> sidebar-page-up
# Move the highlight to the next page
# This is useful if you have a LOT of mailboxes.
bind index,pager <F4> sidebar-page-down
# Move the highlight to the previous mailbox containing new, or flagged,
# mail.
bind index,pager <F5> sidebar-prev-new
# Move the highlight to the next mailbox containing new, or flagged, mail.
bind index,pager <F6> sidebar-next-new
# Toggle the visibility of the Sidebar.
bind index,pager B sidebar-toggle-visible
# --------------------------------------------------------------------------
# COLORS - some unpleasant examples are given
# --------------------------------------------------------------------------
# Note: All color operations are of the form:
# color OBJECT FOREGROUND BACKGROUND
# Color of the current, open, mailbox
# Note: This is a general Mutt option which colors all selected items.
color indicator cyan black
# Color of the highlighted, but not open, mailbox.
color sidebar_highlight black color8
# Color of the divider separating the Sidebar from Mutt panels
color sidebar_divider color8 black
# Color to give mailboxes containing flagged mail
color sidebar_flagged red black
# Color to give mailboxes containing new mail
color sidebar_new green black
# --------------------------------------------------------------------------
# vim: syntax=muttrc

View file

@ -0,0 +1,106 @@
#
# Starter muttrc file, with just a few suggestions and settings.
#
# This file purposely doesn't include hooks, keybinding, macros, colors, etc.
# Read the manual, explore, and have fun!
#
###############
# Identity
#
set realname = "Example User"
set from = "user@example.com"
# If you have another address:
alternates "^mutt@example\.com$"
# Or, if you use the entire domain:
alternates "@example\.com$"
set reverse_name
###############
# Example: local mailboxes
#
# Some people use mbsync or getmail to retrieve their mail locally.
#
set folder = ~/Mail # This has the shortcut '+' or '='
set spoolfile = "+inbox" # This has the shortcut '!'
set record = "+sent"
set trash = "+trash"
set postponed = "+drafts"
mailboxes ! +mutt +family +work
###############
# Example: Gmail over IMAP
#
set imap_user = ".....@gmail.com"
# To avoid storing your password in the .muttrc:
# echo -n 'mypassword' | gpg --encrypt -r 0x1234567890ABCDEF > ~/.mutt/account.gpg
set imap_pass = "`gpg --batch -q --decrypt ~/.mutt/account.gpg`"
set folder = imaps://imap.gmail.com/
set spoolfile = "+INBOX"
unset record # Gmail auto-stores in "+[Gmail].Sent Mail"
unset trash # Unset, deletion will remove labels
set postponed = "+[Gmail].Drafts"
set mail_check = 60
###############
# Pager settings
#
ignore *
unignore From Message-ID Date To Cc Bcc Subject
set pager_stop
unset markers
# Prefer plain text to html.
# However, for brain dead clients that bundle attachments inside a
# multipart/alternative, prefer that alternative.
alternative_order multipart/mixed multipart/related text/plain
# Consult mime.types for determining types of these attachments
mime_lookup application/octet-stream
# This requires a ~/.mailcap entry with the copiousoutput flag, such as:
# text/html; lynx -dump -width ${COLUMNS:-80} %s; nametemplate=%s.html; copiousoutput
auto_view text/html
###############
# Index settings
#
set quit = ask-yes
set sort = threads
# Remember to `mkdir -p ~/.mutt/hcache` first:
set header_cache= "~/.mutt/hcache"
###############
# Message composition settings
#
set edit_headers
# set editor = "emacsclient -a emacs -t"
# set editor = "vim"
set mime_type_query_command = "xdg-mime query filetype"
# msmtp is a solid SMTP client.
# mutt also has built-in SMTP, or you can use an MTA like exim4 or postfix.
set sendmail = "/usr/bin/msmtp"
# lbdb is a versatile contact query tool.
# Invoke via ctrl-t in an address prompt
set query_command = "/usr/bin/lbdbq"
###############
# GnuPG
#
unset crypt_use_gpgme
source /usr/local/share/doc/mutt/samples/gpg.rc
set pgp_default_key = "0x1234567890ABCDEF"
set crypt_opportunistic_encrypt
set postpone_encrypt

View file

@ -0,0 +1,304 @@
# -*-muttrc-*-
#
# Mutt configuration file of Thomas Roessler <roessler@does-not-exist.org>
#
# Use and distribute freely.
#
# Note: This file doesn't contain any personal customization, i.e.,
# using it won't make you send messages with my name in the header.
#
# Things to change: You probably want to change the "priv.rc" source
# command in the end of this file. Also, it's likely you want to have
# a look at the the $editor and $tmpdir variables.
#
#
# MIME settings
#
# auto_view application/ms-tnef text/x-vcard
# auto_view application/x-chess application/x-lotus-notes
# auto_view text/html application/x-gzip application/x-gunzip
# auto_view application/rtf application/x-rath
# auto_view application/msword
auto_view text/html
mime_lookup application/octet-stream
# alternative_order application/pgp text/html text/enriched text/plain
alternative_order text/plain text/html
#
# Key bindings
#
#
# A few of these may resemble Pine. ups.
#
bind alias " " tag-entry
bind alias \n select-entry
bind alias \r select-entry
bind attach i exit
bind attach n next-entry
bind attach p previous-entry
bind attach " " select-entry
bind attach y print-entry
bind browser <end> last-entry
bind browser <home> first-entry
bind editor "\e<backspace>" kill-word
bind editor "\e<delete>" kill-word
bind editor "<backtab>" complete-query
bind editor "\eq" complete-query
bind editor "\Ct" transpose-chars
bind generic "\CV" next-page
bind generic "\Ca" first-entry
bind generic "\Ce" last-entry
bind generic "\eV" previous-page
bind generic "\ev" previous-page
bind generic + tag-entry
bind generic ^ first-entry
bind generic a tag-prefix
bind generic $ last-entry
bind generic q exit
bind index ";" limit
bind index "\Ce" last-entry # override edit-type
bind index "\eV" previous-page # override collapse-something
bind index "\e<" collapse-thread
bind index "\eq" query
bind index $ last-entry
bind index * flag-message
bind index <delete> delete-message
bind index <end> last-entry
bind index <home> first-entry
bind index J next-entry
bind index K previous-entry
bind index Q quit
bind index R group-reply
bind index \em recall-message
bind index a tag-prefix
bind index m mail
bind index p previous-entry
bind index t create-alias
bind index x sync-mailbox
bind index y print-message
bind index n next-entry
bind index "\ev" previous-page
bind pager "\Cn" next-line
bind pager "\Cp" previous-line
bind pager + tag-message
bind pager * flag-message
bind pager <delete> delete-message
bind pager <down> next-line
bind pager <end> bottom
bind pager <home> top
bind pager <up> previous-line
bind pager G group-reply
bind pager R group-reply
bind pager \em recall-message
bind pager t display-toggle-weed # like slrn
bind pager y print-message
bind query i exit
# make it feel like emacs
macro generic "\ex" ":exec "
macro pager "\ex" ":exec "
macro generic "\eX" "\ex"
macro pager "\eX" "\ex"
macro index "~" ";~"
# macro index "%" ";%"
# Thread tagging
bind index "\et" tag-subthread
bind index "\eT" tag-thread
# for majordomo list owner and moderator jobs
macro index "\ea" ":set nopipe_decode wait_key\n|approve\n:set nowait_key\n"
macro pager "\ea" ":set nopipe_decode wait_key\n|approve\n:set nowait_key\n"
# emulate the old URL-browser key bindings.
macro pager "\Cb" "| urlview -\n"
macro index "\Cb" "| urlview -\n"
# permit limiting from the pager.
macro pager "~" "<exit><limit>~"
macro pager ";" "<exit><limit>"
# emulate the old POP-feature bindings
macro index G "!fetchmail\n"
macro pager G "!fetchmail\n"
# razor-report: Report spam.
# macro index S ":set nopipe_decode nowait_key\n|razor-report > /dev/null 2> /dev/null\ns+junk\n"
# macro pager S ":set nopipe_decode nowait_key\n|razor-report > /dev/null 2> /dev/null\ns+junk\n"
macro index S "s+junk\n"
macro pager S "s+junk\n"
#
# Colors
#
# This is a tiny hack, so I can get different
# color schemes on the console and under X11.
source ~/.mutt/colors.`if [ "$TERM" = "linux" ] ; then echo linux ; else echo default ; fi`
mono index bold ~F
# mono body bold '\*[^*]+\*'
# mono body underline '_[^_]+_'
#
# The header weed list
#
ignore delivered-to
ignore content- errors-to in-reply-to mime-version
ignore lines precedence status
ignore nntp-posting-host path old-return-path received references
ignore priority >received >>received
ignore resent- return-path xref path
ignore x400 importance sensitivity autoforward original-encoded-information
ignore x- thread-
ignore DomainKey-Signature mail-followup-to
ignore list- comments posted-to approved-by
unignore x-spam-level x-url x-mailer list-id x-no-spam x-archived-at
unignore x-diagnostic
hdr_order from to cc date subject reply-to mail-followup-to list-id
#
# Various settings
#
set abort_nosubject=no # Let me send messages with an empty subject
set abort_unmodified=no # Let me send empty messages
set alias_file=~/.mutt/aliases # Where to store aliases
unset allow_8bit # Produce correct MIME
unset arrow_cursor # Use the bar cursor
set askcc # Ask me about CCs
unset bounce_delivered # Don't include Delivered-to with bounces
# set charset=iso-8859-1 # The local character set
set send_charset="us-ascii:iso-8859-1:iso-8859-15:iso-8859-2:utf-8"
set confirmcreate # Ask me about creating new files
unset confirmappend # Don't ask me about appending to files
set delete=yes # Don't ask me whether or not I meant to delete messages
# set display_filter="tr '\240\204\223\226' ' \"\"-'" # fix some funny characters
set edit_hdrs # I want to edit the headers.
set editor="/usr/bin/jed %s -f 'mail_mode();'"
# Invoke jed with mail_mode. This may
# or may not work for you.
set noenvelope_from # set messages' envelope-from header.
set fcc_clear # Store local copies of messages in the clear.
set folder=~/Mail # Where my mail folders go
set followup_to # Create Mail-Followup-To headers.
unset force_name # Don't create save folders which don't exist.
set forward_decode # Decode messages when forwarding.
set forward_decrypt # Decrypt messages when forwarding.
set nohelp # No help line.
set include=yes # Always include a copy when replying.
set mark_old # Distinguish between seen (but unread) and new messages
set mbox=+mbox # The (unused) mbox file.
unset metoo # Remove me from CC headers.
set mime_fwd=ask-no # Ask me whether or not to create a MIME-encapsulated forward
set move=no # Don't use mbox
set pager_stop # Don't fall through to the next message in the pager
set pager_index_lines=0 # The pager index is ugly.
set pgp_replyencrypt # Encrypt when replying to encrypted messages.
set pgp_replysignencrypted # Sign when replying to encrypted messages.
set pgp_show_unusable="no" # Don't display unusable keys.
set pgp_sort_keys="keyid" # Sort keys by key ID
set pgp_replysign # Sign when replying to signed messages.
set pgp_timeout=3600 # Forget the PGP passphrase after an hour.
set pipe_decode # Decode messages I pipe to commands, typically to patch(1).
set postponed=~/.mutt/postponed # Where to put postponed messages
set print=ask-no # Don't waste paper
set print_cmd="enscript -2Gr -Email" # Two columns, landscape, fancy header.
set print_split=yes # Invoke enscript once per message
set quit=yes # Don't ask me whether or not I want to quit.
set quote_regexp="^ *[a-zA-Z]*[>|][>:|]*" # Recognize quotes in the pager.
set read_inc=50 # Progress indicator when reading folders.
set recall=ask-no # When I say "compose", ask me whether I want to continue
# composing a postponed message.
set record="+archive/now" # Put copies of most outgoing messages to ~/Mail/archive/now
set reply_to=ask-yes # Ask me whether I want to honor users' reply-to headers.
set reverse_alias # Use aliases to display real names on the index.
set save_name # Save copies by name. Together with $record and $save_name,
# this means that when a folder exists, copies of outgoing
# messages are written to ~/Mail/<name>, otherwise they go to
# ~/Mail/archive/now
set signature=~/.signature # Silly signature
set sig_dashes # Add dashes above my signature
set smart_wrap # Try to be smart when wrapping around lines in the pager
set sort=threads # sort by threads,
set sort_aux=date # then by date
unset strict_threads # don't be strict about threads
# set suspend=no # Don't suspend - I usually run mutt like this: "xterm -e mutt"
set tilde # Indicate empty lines in the pager.
set tmpdir=~/.tmp # Temporary files aren't stored in public places.
set to_chars=" +TCF " # Don't tag list mail in the index
unset use_domain # Don't append a domain to addresses.
set write_inc=50 # Progress indicator when writing folders.
set query_command="lbdb2q.pl %s" # Use the Little Brother's Database with the external
# query feature.
set sendmail_wait=-1 # Don't put sendmail into the background.
set encode_from # "From " in the beginning of a line triggers quoted-printable
set nowait_key # Return immediately from external programs
set forw_format="[fwd] %s (from: %a)" # A different subject for forwarded messages
set nobeep # Shut up. ;-)
set reply_regexp="^((re([\\[0-9\\]+])*|aw):[ \t]*)+[ \t]*" # A regular expression to detect replies
set header # Include the message header when replying.
set ignore_list_reply_to # Ignore Reply-To headers pointing to mailing lists.
set norfc2047_parameters # Sometimes, I get mails which use a bogus encoding for
# MIME parameters. Setting this shouldn't harm.
# (OK, she doesn't use Notes any more, so I can unset this. ;-)
# set text_flowed # Generate text/plain; format=flowed
# unset use_ipv6 # Don't try to use IPv6 - it doesn't work here.
set keep_flagged # don't move flagged messages to mbox
set hide_missing=yes # Don't show how many messages are missing in a thread structure
set status_format="-%r-+(%v) %f [Msgs:%?M?%M/?%m%?n? New:%n?%?o? Old:%o?%?d? Del:%d?%?F? Flag:%F?%?t? Tag:%t?%?p? Post:%p?%?b? Inc:%b?%?l? %l?]----%>-(%P)---"
set compose_format="--+(%v) Compose [Approx. msg size: %l Atts: %a]%>-"
set pager_format="-%Z- %C/%m: %.20n %> %s"
set smileys="^$"
set ispell=iaspell
set markers=no # Don't mark wrapped lines
set wrapmargin=4 # Leave a margin in the pager
# PGP command configuration
# source ~/.mutt/pgp2.rc
source ~/.mutt/gpg.rc
set pgp_getkeys_command=""
# source ~/.mutt/smime.rc
# source non-public stuff, (hooks, alternates, ...)
source ~/.mutt/priv.rc
# source aliases
# source ~/.mutt/aliases-coruscant
source ~/.mutt/aliases

View file

@ -0,0 +1,35 @@
" Vim syntax file for the mutt sidebar patch
syntax keyword muttrcVarBool skipwhite contained sidebar_folder_indent nextgroup=muttrcSetBoolAssignment,muttrcVPrefix,muttrcVarBool,muttrcVarQuad,muttrcVarNum,muttrcVarStr
syntax keyword muttrcVarBool skipwhite contained sidebar_new_mail_only nextgroup=muttrcSetBoolAssignment,muttrcVPrefix,muttrcVarBool,muttrcVarQuad,muttrcVarNum,muttrcVarStr
syntax keyword muttrcVarBool skipwhite contained sidebar_next_new_wrap nextgroup=muttrcSetBoolAssignment,muttrcVPrefix,muttrcVarBool,muttrcVarQuad,muttrcVarNum,muttrcVarStr
syntax keyword muttrcVarBool skipwhite contained sidebar_short_path nextgroup=muttrcSetBoolAssignment,muttrcVPrefix,muttrcVarBool,muttrcVarQuad,muttrcVarNum,muttrcVarStr
syntax keyword muttrcVarBool skipwhite contained sidebar_visible nextgroup=muttrcSetBoolAssignment,muttrcVPrefix,muttrcVarBool,muttrcVarQuad,muttrcVarNum,muttrcVarStr
syntax keyword muttrcVarNum skipwhite contained sidebar_refresh_time nextgroup=muttrcSetNumAssignment,muttrcVPrefix,muttrcVarBool,muttrcVarQuad,muttrcVarNum,muttrcVarStr
syntax keyword muttrcVarNum skipwhite contained sidebar_width nextgroup=muttrcSetNumAssignment,muttrcVPrefix,muttrcVarBool,muttrcVarQuad,muttrcVarNum,muttrcVarStr
syntax keyword muttrcVarStr contained skipwhite sidebar_divider_char nextgroup=muttrcVarEqualsIdxFmt
syntax keyword muttrcVarStr contained skipwhite sidebar_delim_chars nextgroup=muttrcVarEqualsIdxFmt
syntax keyword muttrcVarStr contained skipwhite sidebar_format nextgroup=muttrcVarEqualsIdxFmt
syntax keyword muttrcVarStr contained skipwhite sidebar_indent_string nextgroup=muttrcVarEqualsIdxFmt
syntax keyword muttrcVarStr contained skipwhite sidebar_sort_method nextgroup=muttrcVarEqualsIdxFmt
syntax keyword muttrcCommand sidebar_whitelist
syntax match muttrcFunction contained "\<sidebar-next\>"
syntax match muttrcFunction contained "\<sidebar-next-new\>"
syntax match muttrcFunction contained "\<sidebar-open\>"
syntax match muttrcFunction contained "\<sidebar-page-down\>"
syntax match muttrcFunction contained "\<sidebar-page-up\>"
syntax match muttrcFunction contained "\<sidebar-prev\>"
syntax match muttrcFunction contained "\<sidebar-prev-new\>"
syntax match muttrcFunction contained "\<sidebar-toggle-visible\>"
syntax keyword muttrcColorField contained sidebar_divider
syntax keyword muttrcColorField contained sidebar_flagged
syntax keyword muttrcColorField contained sidebar_highlight
syntax keyword muttrcColorField contained sidebar_indicator
syntax keyword muttrcColorField contained sidebar_new
" vim: syntax=vim

View file

@ -0,0 +1,127 @@
# -*-muttrc-*-
## The following options are only available if you have
## compiled in S/MIME support
# If you compiled mutt with support for both PGP and S/MIME, PGP
# will be the default method unless the following option is set
# set smime_is_default
# Uncomment this if you don't want to set labels for certificates you add.
# unset smime_ask_cert_label
# Passphrase expiration
# set smime_timeout=300
# Global crypto options -- these affect PGP operations as well.
# set crypt_autosign = yes
# set crypt_replyencrypt = yes
# set crypt_replysign = yes
# set crypt_replysignencrypted = yes
# set crypt_verify_sig = yes
# Section A: Key Management
# The default keyfile for encryption (used by $smime_self_encrypt and
# $postpone_encrypt).
#
# It will also be used for decryption unless
# $smime_decrypt_use_default_key is unset.
#
# It will additionally be used for signing unless $smime_sign_as is
# set to a key.
#
# Unless your key does not have encryption capability, uncomment this
# line and replace the keyid with your own.
#
# set smime_default_key="12345678.0"
# If you have a separate signing key, or your key _only_ has signing
# capability, uncomment this line and replace the keyid with your
# signing keyid.
#
# set smime_sign_as="87654321.0"
# Uncomment to make mutt ask what key to use when trying to decrypt a message.
# It will use the default key above (if that was set) else.
# unset smime_decrypt_use_default_key
# Path to a file or directory with trusted certificates
set smime_ca_location="~/.smime/ca-bundle.crt"
# Path to where all known certificates go. (must exist!)
set smime_certificates="~/.smime/certificates"
# Path to where all private keys go. (must exist!)
set smime_keys="~/.smime/keys"
# These are used to extract a certificate from a message.
# First generate a PKCS#7 structure from the message.
set smime_pk7out_command="openssl smime -verify -in %f -noverify -pk7out"
# Extract the included certificate(s) from a PKCS#7 structure.
set smime_get_cert_command="openssl pkcs7 -print_certs -in %f"
# Extract the signer's certificate only from a S/MIME signature (sender verification)
set smime_get_signer_cert_command="openssl smime -verify -in %f -noverify -signer %c -out /dev/null"
# This is used to get the email address the certificate was issued to.
set smime_get_cert_email_command="openssl x509 -in %f -noout -email"
# Add a certificate to the database using smime_keys.
set smime_import_cert_command="smime_keys add_cert %f"
# Section B: Outgoing messages
# Algorithm to use for encryption.
# valid choices are aes128, aes192, aes256, rc2-40, rc2-64, rc2-128, des, des3
set smime_encrypt_with="aes256"
# Encrypt a message. Input file is a MIME entity.
set smime_encrypt_command="openssl cms -encrypt -%a -outform DER -in %f %c"
# Algorithm for the signature message digest.
# Valid choices are md5, sha1, sha224, sha256, sha384, sha512.
set smime_sign_digest_alg="sha256"
# Sign.
set smime_sign_command="openssl smime -sign -md %d -signer %c -inkey %k -passin stdin -in %f -certfile %i -outform DER"
# Section C: Incoming messages
# Decrypt a message. Output is a MIME entity.
set smime_decrypt_command="openssl cms -decrypt -passin stdin -inform DER -in %f -inkey %k -recip %c"
# Verify a signature of type multipart/signed
set smime_verify_command="openssl smime -verify -inform DER -in %s %C -content %f"
# Verify a signature of type application/x-pkcs7-mime
set smime_verify_opaque_command="\
openssl smime -verify -inform DER -in %s %C || \
openssl smime -verify -inform DER -in %s -noverify 2>/dev/null"
# application/pkcs7-mime ".p7m" messages should have a smime-type
# parameter to tell Mutt whether it's signed or encrypted data.
#
# If the parameter is missing, Mutt by default assumes it's SignedData.
# This can be used to change Mutt's assumption to EnvelopedData (encrypted).
#
# set smime_pkcs7_default_smime_type="enveloped"
# Section D: Alternatives
# Sign. If you wish to NOT include the certificate your CA used in signing
# your public key, use this command instead.
# set smime_sign_command="openssl smime -sign -md %d -signer %c -inkey %k -passin stdin -in %f -outform DER"
#
# In order to verify the signature only and skip checking the certificate chain:
#
# set smime_verify_command="openssl smime -verify -inform DER -in %s -content %f -noverify"
# set smime_verify_opaque_command="openssl smime -verify -inform DER -in %s -noverify"
#

View file

@ -0,0 +1,134 @@
#! /usr/bin/perl -W
# by Mike Schiraldi <raldi@research.netsol.com>
use strict;
use Expect;
sub run ($;$ );
umask 077; # probably not necc. but can't hurt
my $tmpdir = "/tmp/smime_keys_test-$$-" . time;
mkdir $tmpdir or die;
chdir $tmpdir or die;
open TMP, '>muttrc' or die;
print TMP <<EOF;
set smime_ca_location="$tmpdir/ca-bundle.crt"
set smime_certificates="$tmpdir/certificates"
set smime_keys="$tmpdir/keys"
EOF
close TMP;
$ENV{MUTT_CMDLINE} = "mutt -F $tmpdir/muttrc";
# make a user key
run 'smime_keys init';
run 'openssl genrsa -out user.key 1024';
# make a request for this key to be signed
run 'openssl req -new -key user.key -out newreq.pem', "\n\nx\n\nx\nx\nuser\@smime.mutt\n\nx\n";
mkdir 'demoCA' or die;
mkdir 'demoCA/certs' or die;
mkdir 'demoCA/crl' or die;
mkdir 'demoCA/newcerts' or die;
mkdir 'demoCA/private' or die;
open OUT, '>demoCA/serial' or die;
print OUT "01\n";
close OUT;
open OUT, '>demoCA/index.txt' or die;
close OUT;
# make the CA
run 'openssl req -new -x509 -keyout demoCA/private/cakey.pem -out demoCA/cacert.pem -days 7300 -nodes',
"\n\nx\n\nx\nx\n\n";
# trust it
run 'smime_keys add_root demoCA/cacert.pem', "root_CA\n";
# have the CA process the request
run 'openssl ca -batch -startdate 000101000000Z -enddate 200101000000Z -days 7300 ' .
'-policy policy_anything -out newcert.pem -infiles newreq.pem';
unlink 'newreq.pem' or die;
# put it all in a .p12 bundle
run 'openssl pkcs12 -export -inkey user.key -in newcert.pem -out cert.p12 -CAfile demoCA/cacert.pem -chain', "pass1\n" x 2;
unlink 'newcert.pem' or die;
unlink 'demoCA/cacert.pem' or die;
unlink 'demoCA/index.txt' or die;
unlink 'demoCA/index.txt.old' or die;
unlink 'demoCA/serial' or die;
unlink 'demoCA/serial.old' or die;
unlink 'demoCA/newcerts/01.pem' or die;
unlink 'demoCA/private/cakey.pem' or die;
rmdir 'demoCA/certs' or die;
rmdir 'demoCA/crl' or die;
rmdir 'demoCA/private' or die;
rmdir 'demoCA/newcerts' or die;
rmdir 'demoCA' or die;
# have smime_keys process it
run 'smime_keys add_p12 cert.p12', "pass1\n" . "pass2\n" x 2 . "old_label\n";
unlink 'cert.p12' or die;
# make sure it showed up
run 'smime_keys list > list';
open IN, 'list' or die;
<IN> eq "\n" or die;
<IN> =~ /^(.*)\: Issued for\: user\@smime\.mutt \"old_label\" \(Unverified\)\n/ or die;
close IN;
my $keyid = $1;
# see if we can rename it
run "smime_keys label $keyid", "new_label\n";
# make sure it worked
run 'smime_keys list > list';
open IN, 'list' or die;
<IN> eq "\n" or die;
<IN> =~ /^$keyid\: Issued for\: user\@smime\.mutt \"new_label\" \(Unverified\)\n/ or die;
close IN;
unlink 'list' or die;
# try signing something
run "openssl smime -sign -signer certificates/$keyid -inkey user.key -in /etc/passwd -certfile certificates/37adefc3.0 > signed";
unlink 'user.key' or die;
# verify it
run 'openssl smime -verify -out /dev/null -in signed -CAfile ca-bundle.crt';
unlink 'signed' or die;
# clean up
unlink 'ca-bundle.crt' or die;
unlink 'muttrc' or die;
unlink 'keys/.index' or die;
unlink 'certificates/.index' or die;
unlink <keys/*> or die;
unlink <certificates/*> or die;
rmdir 'keys' or die;
rmdir 'certificates' or die;
chdir '/' or die;
rmdir $tmpdir or die;
sub run ($;$) {
my $cmd = shift or die;
my $input = shift;
print "\n\nRunning [$cmd]\n";
my $exp = Expect->spawn ($cmd);
if (defined $input) {
print $exp $input;
}
$exp->soft_close;
$? and die "$cmd returned $?";
}