Compare commits

...

16 Commits

Author SHA1 Message Date
648eb720fc Even more fixes for replacements. 2025-04-10 08:13:44 -04:00
8d508416c3 Fixed mistakes in templates and sed statements. 2025-04-10 08:08:41 -04:00
7ce163b271 Fixed more errors with passpharse comparison. 2025-04-10 07:57:18 -04:00
1b7341656e Fixed the master passphrase comparison. 2025-04-10 07:54:05 -04:00
a9de7f4481 Moving mtoward using openssl to encrypt email address files. 2025-04-10 07:45:09 -04:00
7e7853f050 Fix problems with pass. 2025-04-09 21:28:47 -04:00
4183969b68 Started some much needed updating of the fleacollar code. Likely not usable yet. 2025-04-09 20:46:38 -04:00
1d609e46d3 Improved w3m handling for html messages. 2023-07-10 17:10:39 -04:00
523e7d4eb4 Call gpg2 specifically instead of just gpg. Some systems have both gpg1 and gpg2, and we need gpg2. 2021-01-18 11:18:11 -05:00
267fbbbc9a Try to make the copy of gpg.rc work on more distros. 2021-01-16 16:35:13 -05:00
c572fbcbc1 Update the automatic address script. 2021-01-16 16:33:00 -05:00
0f8d74cafb fixed a bug when using fleacollar from a GUI based terminal. 2021-01-13 18:47:34 -05:00
dff1079d60 Improvements to the address adding script. 2021-01-13 18:31:17 -05:00
da8565e028 Changed the index_format slightly. 2019-07-25 14:24:46 -04:00
8cbd3ea3fb Updated the index_format. 2019-07-23 14:43:33 -04:00
4500d198d1 use gpg --quick-gen-key by default, then fall back to just --gen-key on older legacy systems. 2019-07-23 12:27:29 -04:00
10 changed files with 501 additions and 332 deletions

49
files/add_address.sh Normal file
View File

@ -0,0 +1,49 @@
#!/usr/bin/env bash
add_email() {
local alias="<${newAlias% *}"
local emailAddress="<${newAlias##*<}"
local ignoreList=(
'amazon.com'
'billing@'
'do-not-reply'
'donotreply'
'github.com'
'gitlab.com'
'mailto'
'no-reply'
'noreply'
'paypal.com'
'walmart.com'
)
# Make the list into a format that works with bash regexp.
ignoreList="${ignoreList[*]}"
ignoreList="${ignoreList// /|}"
# Do not add aliases with no alias-name
if [[ "$alias" == "UNNAMED" ]]; then
return
fi
# Try to filter out addresses that do not receive replies.
if [[ "${emailAddress,,}" =~ ${ignoreList} ]]; then
return
fi
# Do not add the same address twice.
if grep -Fq "$emailAddress" $HOME/.mutt/aliases ; then
return
fi
# Try to not use the same alias-name
if grep -Fq " $alias " $HOME/.mutt/aliases; then
alias="${alias}-${emailAddress#*@}"
alias="${alias%.*}"
fi
if grep -Fq "$alias" $HOME/.mutt/aliases; then
return
fi
echo "$newAlias" >> $HOME/.mutt/aliases
sort -u ~/.mutt/aliases -o ~/.mutt/aliases
}
message=$(cat)
newAlias=$(echo "${message}" | grep ^"From: " | sed -e s/[\,\"\']//g -e s/'From: '//g | awk -F" " '{if (NF == 1) {print "alias UNNAMED UNNAMED " $0;} else if (NF == 2) {print "alias " $1" " toupper(substr($0,1,1)) tolower(substr($0,2));} else if (NF >= 3) {print "alias ", tolower($1)"-"tolower($(NF-1))" " toupper(substr($0,1,1)) tolower(substr($0,2));}}')
add_email
echo "${message}"

10
files/generic.template Normal file
View File

@ -0,0 +1,10 @@
unset imap_passive
unset record
set smtp_url="smtp://SMTP_USER@SMTP_HOST:SMTP_PORT/
set folder=imaps://IMAP_USER@IMAP_HOST:IMAP_PORT/
mailboxes = +INBOX
set spoolfile = +INBOX
set postponed = +Drafts
set imap_keepalive=300
set mail_check=300
bind editor <Tab> complete-query

11
files/gmail.template Normal file
View File

@ -0,0 +1,11 @@
unset imap_passive
unset record
set imap_user=EMAIL_ADDRESS
set smtp_url="smtp://USERNAME@smtp.gmail.com:587/
set folder=imaps://USERNAME@imap.gmail.com/
set spoolfile = +INBOX
mailboxes = +INBOX
set postponed = +[Gmail]/Drafts
set record=+[Gmail]/Sent
set imap_keepalive=300
set mail_check=300

3
files/gpg-agent.conf Normal file
View File

@ -0,0 +1,3 @@
default-cache-ttl 300
max-cache-ttl 999999
allow-loopback-pinentry

12
files/gpg.conf Normal file
View File

@ -0,0 +1,12 @@
charset utf-8
require-cross-certification
no-escape-from-lines
no-mangle-dos-filenames
personal-digest-preferences SHA512
cert-digest-algo SHA512
use-agent
default-preference-list SHA512 SHA384 SHA256 SHA224 AES256 AES192 AES CAST5 ZLIB BZIP2 ZIP Uncompressed
keyserver wwwkeys.pgp.net
keyserver hkp://pool.sks-keyservers.net
keyserver pgp.zdv.uni-mainz.de
keyserver-options auto-key-retrieve

13
files/hotmail.template Normal file
View File

@ -0,0 +1,13 @@
unset imap_passive
unset record
set imap_user=EMAIL_ADDRESS
set smtp_url="smtp://EMAIL_ADDRESS@smtp-mail.outlook.com:587/
set folder=imaps://EMAIL_ADDRESS@imap-mail.outlook.com/
set ssl_force_tls=yes
set spoolfile=+Inbox
mailboxes = +Inbox
set postponed=+Drafts
set record=+Sent
set imap_keepalive=300
set mail_check=300
bind editor <Tab> complete-query

7
files/macros Normal file
View File

@ -0,0 +1,7 @@
macro index 'c' '<change-folder>?<change-dir><home>^K=<enter>'
bind index "^" imap-fetch-mail"
macro pager \cb <pipe-entry>'urlview'<enter> 'Follow links with urlview'
macro attach 'B' "<pipe-entry>cat >~/.cache/mutt/mail.html && $BROWSER ~/.cache/"
macro index,pager b '<enter-command>source $muttHome/aliases<enter><bounce-message>'
macro index,pager f '<enter-command>source $muttHome/aliases<enter><forward-message>'
macro index,pager m '<enter-command>source $muttHome/aliases<enter><mail>'

12
files/mailcap Normal file
View File

@ -0,0 +1,12 @@
text/html; w3m -s -o display_link=yes -o display_link_number=yes -o decode_url=yes -dump -T text/html %s -I %{charset};nametemplate=%s.html;copiousoutput
audio/*; mpv --quiet %s; needsterminal
application/msword; soffice --cat %s 2> /dev/null; copiousoutput
application/vnd.openxmlformats-officedocument.wordprocessingml.document; soffice --cat %s 2> /dev/null; copiousoutput
application/vnd.ms-excel; xls2csv %s; copiousoutput
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet; xlsx2csv --all %s; copiousoutput
application/rtf; soffice --cat %s 2> /dev/null; copiousoutput
application/vnd.oasis.opendocument.text; soffice --cat %s 2> /dev/null; copiousoutput
application/vnd.oasis.opendocument.spreadsheet;soffice --cat --convert-to csv:"Text - txt - csv (StarCalc)":"59,ANSI,1" %s 2> /dev/null; copiousoutput
application/pdf; pdftotext %s -; copiousoutput
video/*; mpv --quiet %s; test=test -n "$DISPLAY"; needsterminal
video/*; mpv --quiet --no-video %s; needsterminal

27
files/muttrc Normal file
View File

@ -0,0 +1,27 @@
set text_flowed = yes
set index_format = '[%Z] %-20.20F %s (Attached %X, Msg %4C)'
set send_charset = us-ascii:utf-8
set pager = 'builtin'
set pager_stop = 'yes'
set sort = threads
set beep_new = yes
set display_filter = '$muttHome/scripts/add_address.sh'
set print = yes
set imap_check_subscribed = yes
set sort_alias = alias
set reverse_alias = yes
set alias_file = MUTTHOME/aliases
source MUTTHOME/aliases
set history_file = MUTTHOME/history
set history = 1024
set mailcap_path = MUTTHOME/mailcap
set header_cache = MUTTHOME/cache/headers
set message_cachedir = MUTTHOME/cache/bodies
set certificate_file = MUTTHOME/certificates
set markers = no
unset mark_old
auto_view text/html
alternative_order text/plain text/enriched text/html
message-hook '!(~g|~G) ~b\"^ 5 dash charactersBEGIN\\ PGP\\ (SIGNED\\ )?MESSAGE\"' "exec check-traditional-pgp"
source MUTTHOME/gpg.rc
source MUTTHOME/macros

View File

@ -1,43 +1,66 @@
#!/bin/bash #!/usr/bin/env bash
# This script has the lofty goal of becoming a full configuration utility for mutt. # This script has the lofty goal of becoming a full configuration utility for mutt.
# Written by Storm Dragon: https://social.stormdragon.tk/storm # Written by Storm Dragon: https://social.stormdragon.tk/storm
# Written by Michael Taboada: https://2mb.social/mwtab # Written by Michael Taboada: https://2mb.social/mwtab
# Contributions by Kyle: https://kyle.tk # Contributions by Kyle: https://kyle.tk
# Released under the terms of the WTFPL: http://wtfpl.net # Released under the terms of the WTFPL: http://wtfpl.net
# Setup for gettext localization
export TEXTDOMAIN=fleacollar.sh
export TEXTDOMAINDIR=/usr/share/locale
. gettext.sh
# Settings to improve accessibility of dialog. # Settings to improve accessibility of dialog.
export DIALOGOPTS='--insecure --no-lines --visit-items' export DIALOGOPTS='--insecure --no-lines --visit-items'
# Array of command line arguments
declare -A command=(
[h]="Show this help information."
[t]="Test mode. Use ~/mutt_test instead of ~/.mutt for configuration."
)
# Variables # Variables
muttHome=~/.mutt muttHome="${HOME}/.mutt"
testMode=false
# Process command line arguments
# Convert the keys of the associative array to a format usable by getopts
args="${!command[*]}"
args="${args//[[:space:]]/}"
while getopts "${args}" i ; do
case "$i" in
h) help ;;
t)
testMode=true
muttHome="${HOME}/mutt_test"
;;
esac
done
# Functions # Functions
check_dependancies() help() {
{ echo "fleacollar.sh"
echo "Released under the terms of the WTFPL: http://wtfpl.net"
echo -e "A configuration utility for mutt.\n"
echo -e "Usage:\n"
echo "With no arguments, run the configuration utility."
for i in "${!command[@]}" ; do
echo "-${i}: ${command[${i}]}"
done | sort
exit 0
}
check_dependancies() {
local dep local dep
for dep in dialog gettext gpg mutt ; do for dep in dialog mutt w3m openssl ; do
if ! command -v $dep &> /dev/null ; then if ! command -v $dep &> /dev/null ; then
echo "$(eval_gettext "\$dep is not installed. Please install $dep and run this script again.")" echo "$dep is not installed. Please install $dep and run this script again."
exit 1 exit 1
fi fi
done done
if ! [ -d ~/.gnupg ]; then
read -p "$(eval_gettext "No configuration for GPG was found. To have Fleacollar configure this for you press enter. If you would like to configure GPG manually, press control+c.") " continue
configure_gpg
fi
} }
inputbox() { inputbox() {
# Returns: text entered by the user # Returns: text entered by the user
# Args 1, Instructions for box. # Args 1, Instructions for box.
# args: 2 initial text (optional) # args: 2 initial text (optional)
dialog --backtitle "$(gettext "Enter text and press enter.")" \ dialog --backtitle "Enter text and press enter." \
--inputbox "$1" 0 0 "$2" --stdout --inputbox "$1" 0 0 "$2" --stdout
} }
@ -47,23 +70,23 @@ menulist() {
local menuList local menuList
ifs="$IFS" ifs="$IFS"
IFS=$'\n' IFS=$'\n'
dialog --backtitle "$(gettext "Use the up and down arrow keys to find the option you want, then press enter to select it.")" \ dialog --backtitle "Use the up and down arrow keys to find the option you want, then press enter to select it." \
--no-tags \ --no-tags \
--menu "$(gettext "Please select one")" 0 0 0 $@ --stdout --menu "Please select one" 0 0 0 $@ --stdout
IFS="$ifs" IFS="$ifs"
} }
msgbox() { msgbox() {
# Returns: None # Returns: None
# Shows the provided message on the screen with an ok button. # Shows the provided message on the screen with an ok button.
dialog --msgbox "$*" 0 0 dialog --msgbox "$*" 0 0
} }
passwordbox() { passwordbox() {
# Returns: text entered by the user # Returns: text entered by the user
# Args 1, Instructions for box. # Args 1, Instructions for box.
# args: 2 initial text (optional) # args: 2 initial text (optional)
dialog --backtitle "$(gettext "Enter text and press enter.")" \ dialog --backtitle "Enter text and press enter." \
--passwordbox "$1" 0 0 "$2" --stdout --passwordbox "$1" 0 0 "$2" --stdout
} }
@ -72,8 +95,8 @@ yesno() {
# Args: Question to user. # Args: Question to user.
# Called in if $(yesno) == "Yes" # Called in if $(yesno) == "Yes"
# Or variable=$(yesno) # Or variable=$(yesno)
dialog --backtitle "$(gettext "Press 'Enter' for \"yes\" or 'Escape' for dialog --backtitle "Press 'Enter' for \"yes\" or 'Escape' for
\"no\".")" --yesno "$*" 0 0 --stdout \"no\"." --yesno "$*" 0 0 --stdout
if [[ $? -eq 0 ]]; then if [[ $? -eq 0 ]]; then
echo "Yes" echo "Yes"
else else
@ -81,329 +104,320 @@ yesno() {
fi fi
} }
initialize_directory() # Function to encrypt a file with OpenSSL
{ encrypt_file() {
# $1 = input file to encrypt
# $2 = output file
local passphrase
# If master password file doesn't exist, create it
if [[ ! -f "$muttHome/.master_password" ]]; then
passphrase=""
passphraseCompare="-"
while [[ "${passphrase}" != "${passphraseCompare}" ]] ; do
passphrase="$(passwordbox "Please create a master password for encrypting your email credentials:")"
passphraseCompare="$(passwordbox "Please re-enter the master password:")"
if [[ "${passphrase}" != "${passphraseCompare}" ]]; then
msgbox "Passphrase does not match, please retry."
fi
done
# Store a hash of the master password for verification later
echo "$(echo "$passphrase" | openssl dgst -sha256 -hex | cut -d ' ' -f 2)" > "$muttHome/.master_password"
else
# Ask for existing master password
passphrase="$(passwordbox "Enter your master password to encrypt this email configuration:")"
# Verify the password
local stored_hash="$(cat "$muttHome/.master_password")"
local input_hash="$(echo "$passphrase" | openssl dgst -sha256 -hex | cut -d ' ' -f 2)"
if [[ "$stored_hash" != "$input_hash" ]]; then
msgbox "Incorrect master password. Please try again."
encrypt_file "$1" "$2"
return
fi
fi
# Encrypt the file with AES-256-CBC
openssl enc -aes-256-cbc -salt -a -pbkdf2 -pass pass:"$passphrase" -in "$1" -out "$2"
}
# Function to decrypt a file
decrypt_file() {
# $1 = encrypted file
# $2 = output file (temporary)
local passphrase
# If the master password file doesn't exist, we can't decrypt
if [[ ! -f "$muttHome/.master_password" ]]; then
echo "ERROR: No master password set"
return 1
fi
# Ask for the master password
passphrase="$(passwordbox "Enter your master password to decrypt email configuration:")"
# Verify the password
local stored_hash="$(cat "$muttHome/.master_password")"
local input_hash="$(echo "$passphrase" | openssl dgst -sha256 -hex | cut -d ' ' -f 2)"
if [[ "$stored_hash" != "$input_hash" ]]; then
msgbox "Incorrect master password. Please try again."
decrypt_file "$1" "$2"
return
fi
# Decrypt the file
openssl enc -aes-256-cbc -d -a -pbkdf2 -pass pass:"$passphrase" -in "$1" -out "$2" 2>/dev/null
}
initialize_directory() {
mkdir -p "${muttHome}/cache/bodies" mkdir -p "${muttHome}/cache/bodies"
mkdir -p "${muttHome}/scripts" mkdir -p "${muttHome}/scripts"
if ! [ -f "$muttHome/aliases" ]; then if ! [ -f "$muttHome/aliases" ]; then
touch "$muttHome/aliases" touch "$muttHome/aliases"
fi fi
# Create scripts
# Copy the add_address.sh script
if ! [ -f "$muttHome/scripts/add_address.sh" ]; then if ! [ -f "$muttHome/scripts/add_address.sh" ]; then
cat << EOF > "$muttHome/scripts/add_address.sh" cp "files/add_address.sh" "$muttHome/scripts/add_address.sh"
#!/bin/bash chmod 700 "$muttHome/scripts/add_address.sh"
message=\$(cat)
newAlias=\$(echo "\${message}" | grep ^"From: " | sed -e s/[\,\"\']//g -e s/'From: '//g | awk -F" " '{if (NF == 1) {print "alias UNNAMED UNNAMED " \$0;} else if (NF == 2) {print "alias " \$1" " toupper(substr(\$0,1,1)) tolower(substr(\$0,2));} else if (NF >= 3) {print "alias ", tolower(\$1)"-"tolower(\$(NF-1))" " toupper(substr(\$0,1,1)) tolower(substr(\$0,2));}}')
emailAddress="<\${newAlias##*<}"
if ! grep -Fq "\$emailAddress" \$HOME/.mutt/aliases; then
echo "\$newAlias" >> \$HOME/.mutt/aliases
sort -u ~/.mutt/aliases -o ~/.mutt/aliases
fi
echo "\${message}"
EOF
chmod 700 "$muttHome/scripts/add_address.sh"
fi fi
# End of add_address script
if ! [ -f "$muttHome/mailcap" ]; then if ! [ -f "$muttHome/mailcap" ]; then
# Find desired browser cp "scripts/mailcap" "$muttHome/mailcap"
local x=0
for i in\
$BROWSER\
chromium\
elinks\
epiphany\
firefox\
google-chrome\
links\
lynx\
midori\
seamonkey\
w3m
do
unset browserPath
browserPath="$(command -v $i)"
if [ -n "$browserPath" ]; then
browsers[$x]="$browserPath"
((x++))
fi
done
echo "$(gettext "Select browser for viewing html email:")"
browserPath="$(menulist $(for i in "${browsers[@]##*/}" ; do echo "$i";echo "$i";done))"
if [ -z "$browserPath" ]; then
exit 0
fi
case "${browserPath##*/}" in
"elinks")
echo "text/html; $browserPath %s; nametemplate=%s.html; needsterminal" > "$muttHome/mailcap"
;;
"lynx|w3m")
echo "text/html; $browserPath -I %{charset} -T text/html %s; nametemplate=%s.html; copiousoutput" > "$muttHome/mailcap"
;;
*)
echo "text/html; $browserPath %s; nametemplate=%s.html; needsterminal" > "$muttHome/mailcap"
esac
echo 'audio/*; mpv --quiet %s; needsterminal' >> "$muttHome/mailcap"
echo 'application/msword; soffice --cat %s 2> /dev/null; copiousoutput' >> "$muttHome/mailcap"
echo 'application/vnd.openxmlformats-officedocument.wordprocessingml.document; soffice --cat %s 2> /dev/null; copiousoutput' >> "$muttHome/mailcap"
echo 'application/vnd.ms-excel; xls2csv %s; copiousoutput' >> "$muttHome/mailcap"
echo 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet; xlsx2csv --all %s; copiousoutput' >> "$muttHome/mailcap"
echo 'application/rtf; soffice --cat %s 2> /dev/null; copiousoutput' >> "$muttHome/mailcap"
echo 'application/vnd.oasis.opendocument.text; soffice --cat %s 2> /dev/null; copiousoutput' >> "$muttHome/mailcap"
echo 'application/vnd.oasis.opendocument.spreadsheet;soffice --cat --convert-to csv:"Text - txt - csv (StarCalc)":"59,ANSI,1" %s 2> /dev/null; copiousoutput' >> "$muttHome/mailcap"
echo 'application/pdf; pdftotext %s -; copiousoutput' >> "$muttHome/mailcap"
echo 'video/*; mpv --quiet %s; test=test -n "$DISPLAY"; needsterminal' >> "$muttHome/mailcap"
echo 'video/*; mpv --quiet --no-video %s; needsterminal' >> "$muttHome/mailcap"
fi fi
if ! [ -f "$muttHome/gpg.rc" ]; then
if ! cp "/usr/share/doc/mutt/samples/gpg.rc" "$muttHome/" ; then # Copy the gpg.rc file if it exists in files directory
find /usr -name gpg.rc -exec cp "{}" "$muttHome/" \; 2> /dev/null if [[ -f "files/gpg.rc" ]] && ! [[ -f "$muttHome/gpg.rc" ]]; then
fi cp "files/gpg.rc" "$muttHome/gpg.rc"
echo "set pgp_autosign=yes" >> "$muttHome/gpg.rc" echo "# Note: GPG signing/encryption is available but not configured." >> "$muttHome/gpg.rc"
echo "set crypt_autosign=yes" >> "$muttHome/gpg.rc" echo "# You can manually set up GPG for email if needed." >> "$muttHome/gpg.rc"
echo "set pgp_replyencrypt=yes" >> "$muttHome/gpg.rc"
echo "set pgp_timeout=1800" >> "$muttHome/gpg.rc"
if [[ $(gpg --list-secret-keys | wc -l) -eq 0 ]]; then
read -p "$(gettext "No gpg key was found. Type your name and press entr to generate a PGP key.control+c if you would like to create it manually.") " continue
gpg --gen-key
fi
PS3="$(gettext "Select the key you want to use for encryption/signing:")"
select key in $(gpg --list-secret-keys | grep '.*@.*' | cut -d '<' -f2 | cut -d '>' -f1) ; do
if [[ -n "$key" ]]; then
break
fi
done
keyName="$(gpg --list-secret-keys --keyid-format short | grep -B1 ^uid | head -n1 | rev | cut -c -8 | rev)"
echo "set pgp_sign_as=$keyName" >> "$muttHome/gpg.rc"
fi fi
# Create or update macro file # Create or update macro file
echo "macro index 'c' '<change-folder>?<change-dir><home>^K=<enter>'" >> "$muttHome/macros" cp "files/macros" "$muttHome/macros"
echo "bind index \"^\" imap-fetch-mail\"" >> "$muttHome/macros"
echo "macro pager \cb <pipe-entry>'urlview'<enter> 'Follow links with urlview'" >> "$muttHome/macros"
echo "macro attach 'B' \"<pipe-entry>cat >~/.cache/mutt/mail.html && $BROWSER ~/.cache/\"" >> "$muttHome/macros"
echo "macro index,pager b '<enter-command>source $muttHome/aliases<enter><bounce-message>'" >> "$muttHome/macros"
echo "macro index,pager f '<enter-command>source $muttHome/aliases<enter><forward-message>'" >> "$muttHome/macros"
echo "macro index,pager m '<enter-command>source $muttHome/aliases<enter><mail>'" >> "$muttHome/macros"
# Create basic muttrc # Create basic muttrc
if ! [ -f "$muttHome/muttrc" ]; then if ! [ -f "$muttHome/muttrc" ]; then
# Find desired editor # Find desired editor
x=0 x=0
for i in\ for i in nano vim ; do
emacs\
geany\
gedit\
leafpad\
mousepad\
nano\
ne\
pluma\
vi\
vim
do
unset editorPath unset editorPath
editorPath="$(command -v $i)" editorPath="$(command -v $i)"
if [ -n "$editorPath" ]; then if [[ -n "$editorPath" ]]; then
editors[$x]="$editorPath" editors[$x]="$editorPath"
((x++)) ((x++))
fi fi
done done
echo "$gettext "Select editor for email composition:")" echo "Select editor for email composition:"
i="$(menulist ${EDITOR} ${EDITOR} $(for i in "${editors[@]##*/}" ; do echo "$i";echo "$i";done))" i="$(menulist ${EDITOR} ${EDITOR} $(for i in "${editors[@]##*/}" ; do echo "$i";echo "$i";done))"
if [ -z "$i" ]; then if [[ -z "$i" ]]; then
exit 0 exit 0
fi fi
# Try to handle special cases like with ne and nano skipping headers and line wrapping.
# Copy base muttrc
cp "files/muttrc" "$muttHome/muttrc"
# Set editor in muttrc
case "$i" in case "$i" in
"nano") echo "set editor = '$i +7 -r 72'" > "$muttHome/muttrc";; "nano") sed -i "1s|^|set editor = '$i +7 -r 72'\n|" "$muttHome/muttrc" ;;
"ne") echo "set editor = '$i +7'" > "$muttHome/muttrc";; "vim") sed -i "1s|^|set editor = \"vim -c 'set spell spelllang=${LANG::2}'\"\n|" "$muttHome/muttrc" ;;
"vim") echo "set editor = \"vim -c 'set spell spelllang=${LANG::2}'\"" > "$muttHome/muttrc";;
*) echo "set editor = '$i'" > "$muttHome/muttrc";;
esac esac
echo "set text_flowed = yes" >> "$muttHome/muttrc"
# Move the messge index number to the right edge. # Replace muttHome in muttrc
echo "set index_format = '%Z %{%b %d} %-15.15L (%?l?%4l&%4c?) %s %4C'" >> "$muttHome/muttrc" sed -i "s|MUTTHOME|${muttHome}|g" "$muttHome/muttrc"
# I need to figure out a way to detect and set the language for the next setting.
echo "set send_charset = us-ascii:utf-8" >> "$muttHome/muttrc"
echo "set pager = 'builtin'" >> "$muttHome/muttrc"
echo "set pager_stop = 'yes'" >> "$muttHome/muttrc"
echo "set sort = threads" >> "$muttHome/muttrc"
echo "set beep_new = yes" >> "$muttHome/muttrc"
echo "set display_filter = '$muttHome/scripts/add_address.sh'" >> "$muttHome/muttrc"
echo "set print = yes" >> "$muttHome/muttrc"
echo "set imap_check_subscribed = yes" >> "$muttHome/muttrc"
echo "set sort_alias = alias" >> "$muttHome/muttrc"
echo "set reverse_alias = yes" >> "$muttHome/muttrc"
echo "set alias_file = ${muttHome/#$HOME/\~}/aliases" >> "$muttHome/muttrc"
echo "source ${muttHome/#$HOME/\~}/aliases" >> "$muttHome/muttrc"
echo "set history_file = ${muttHome/#$HOME/\~}/history" >> "$muttHome/muttrc"
echo "set history = 1024" >> "$muttHome/muttrc"
echo "set mailcap_path = ${muttHome/#$HOME/\~}/mailcap" >> "$muttHome/muttrc"
echo "set header_cache = ${muttHome/#$HOME/\~}/cache/headers" >> "$muttHome/muttrc"
echo "set message_cachedir = ${muttHome/#$HOME/\~}/cache/bodies" >> "$muttHome/muttrc"
echo "set certificate_file = ${muttHome/#$HOME/\~}/certificates" >> "$muttHome/muttrc"
echo "set markers = no" >> "$muttHome/muttrc"
echo "unset mark_old" >> "$muttHome/muttrc"
echo "auto_view text/html" >> "$muttHome/muttrc"
echo "alternative_order text/plain text/enriched text/html" >> "$muttHome/muttrc"
echo "message-hook '!(~g|~G) ~b\"^ 5 dash charactersBEGIN\\ PGP\\ (SIGNED\\ )?MESSAGE\"' \"exec check-traditional-pgp\"" >> "$muttHome/muttrc"
echo "source ${muttHome/#$HOME/\~}/gpg.rc" >> "$muttHome/muttrc"
echo "source ${muttHome/#$HOME/\~}/macros" >> "$muttHome/muttrc"
fi fi
# Create the decrypt helper script
create_decrypt_helper
} }
configure_gpg() create_decrypt_helper() {
{ # Create a helper script to decrypt files
# GPG stuff in .bashrc: cat > "$muttHome/scripts/decrypt.sh" << 'EOF'
if ! grep 'GPG_TTY=$(tty)' ~/.bashrc ; then #!/bin/bash
echo -e 'GPG_TTY=$(tty)\nexport GPG_TTY' >> ~/.bashrc # Script to decrypt an encrypted email configuration file
fi # Usage: decrypt.sh encrypted_file temp_output_file
# Make sure the configuration directory exists
if ! [ -d ~/.gnupg/ ]; then if [[ $# -ne 2 ]]; then
gpg --list-secret-keys &> /dev/null echo "Usage: $0 encrypted_file temp_output_file"
fi exit 1
if [ -f ~/.gnupg/gpg.conf ]; then fi
read -p "$(gettext "This will overwrite your existing ~/.gnupg/gpg.conf file. Press enter to continue or control+c to abort. ")" continue
fi ENCRYPTED_FILE="$1"
cat << EOF > ~/.gnupg/gpg.conf OUTPUT_FILE="$2"
charset utf-8 MASTER_FILE="$(dirname "$ENCRYPTED_FILE")/.master_password"
require-cross-certification
no-escape-from-lines if [[ ! -f "$ENCRYPTED_FILE" ]]; then
no-mangle-dos-filenames echo "Error: Encrypted file not found"
personal-digest-preferences SHA512 exit 1
cert-digest-algo SHA512 fi
use-agent
default-preference-list SHA512 SHA384 SHA256 SHA224 AES256 AES192 AES CAST5 ZLIB BZIP2 ZIP Uncompressed if [[ ! -f "$MASTER_FILE" ]]; then
keyserver wwwkeys.pgp.net echo "Error: Master password file not found"
keyserver hkp://pool.sks-keyservers.net exit 1
keyserver pgp.zdv.uni-mainz.de fi
keyserver-options auto-key-retrieve
EOF # Function to decrypt
if [ -f ~/.gnupg/gpg-agent.conf ]; then decrypt() {
read -p "$(gettext "This will overwrite your existing ~/.gnupg/gpg-agent.conf file. Press enter to continue or control+c to abort. ")" continue local stored_hash="$(cat "$MASTER_FILE")"
fi local passphrase
cat << EOF > ~/.gnupg/gpg-agent.conf local attempt=0
default-cache-ttl 300
max-cache-ttl 999999 while [ $attempt -lt 3 ]; do
allow-loopback-pinentry passphrase="$(dialog --passwordbox "Enter master password to decrypt:" 0 0 --stdout)"
EOF
# Check password hash
local input_hash="$(echo "$passphrase" | openssl dgst -sha256 -hex | cut -d ' ' -f 2)"
if [[ "$stored_hash" = "$input_hash" ]]; then
# Decrypt the file
openssl enc -aes-256-cbc -d -a -pbkdf2 -pass pass:"$passphrase" -in "$ENCRYPTED_FILE" -out "$OUTPUT_FILE" 2>/dev/null
return 0
else
dialog --msgbox "Incorrect password. Please try again." 0 0
((attempt++))
fi
done
echo "Too many failed attempts."
return 1
} }
add_email_address() # Create temp directory if it doesn't exist
{ mkdir -p "$(dirname "$OUTPUT_FILE")"
read -p "$(gettext "Please enter your email address: ")" emailAddress
# Decrypt the file
decrypt
# Make sure the output file is readable
if [[ -f "$OUTPUT_FILE" ]]; then
chmod 600 "$OUTPUT_FILE"
else
echo "Error: Failed to decrypt file"
exit 1
fi
exit 0
EOF
# Make script executable
chmod 700 "$muttHome/scripts/decrypt.sh"
}
add_email_address() {
read -p "Please enter your email address: " emailAddress
if ! [[ "$emailAddress" =~ .*@.*\..* ]]; then if ! [[ "$emailAddress" =~ .*@.*\..* ]]; then
read -p "$(gettext "this appears to be an invalid email address. Continue anyway? (y/n) ")" continue read -p "This appears to be an invalid email address. Continue anyway? (y/n) " continue
if [ "${continue^}" != "Y" ]; then if [[ "${continue^}" != "Y" ]]; then
exit 0 exit 0
fi fi
fi fi
if [ -f "$muttHome/$emailAddress" ]; then if [[ -f "$muttHome/$emailAddress" ]] || [[ -f "$muttHome/$emailAddress.ssl" ]]; then
read -p "$(gettext "This email address already exists. Overwrite the existing settings? (y/n) ")" continue read -p "This email address already exists. Overwrite the existing settings? (y/n) " continue
if [ "${continue^}" != "Y" ]; then if [[ "${continue^}" != "Y" ]]; then
exit 0 exit 0
else else
sed -i "/$emailAddress/d" "$muttHome/muttrc" sed -i "/$emailAddress/d" "$muttHome/muttrc"
rm -f "$muttHome/$emailAddress" "$muttHome/$emailAddress.ssl"
fi fi
fi fi
read -p "$(gettext "Enter your name as you want it to appear in emails. From: ")" realName
echo "set realname=\"$realName\"" > "$muttHome/$emailAddress" # Create a temporary file for configuration
echo "set from=\"$emailAddress\"" >> "$muttHome/$emailAddress" configFile="$muttHome/$emailAddress"
echo "set use_from = \"yes\"" >> "$muttHome/$emailAddress"
echo "set hostname=${emailAddress##*@}" >> "$muttHome/$emailAddress" # Write basic config
echo "set realname=\"$(inputbox "Enter your name as you want it to appear in emails:")\"" > "$configFile"
echo "set from=\"$emailAddress\"" >> "$configFile"
echo "set use_from = \"yes\"" >> "$configFile"
echo "set hostname=${emailAddress##*@}" >> "$configFile"
# Configure account based on email domain
case "$emailAddress" in case "$emailAddress" in
*gmail.com) *gmail.com)
configure_gmail "$emailAddress" configure_gmail "$emailAddress"
;; ;;
*hotmail.com) *hotmail.com | *outlook.com | *live.com)
configure_hotmail "$emailAddress" configure_hotmail "$emailAddress"
;; ;;
*) *)
continue="$(yesno "$(gettext "Is this a gmail account?")")" continue="$(yesno "Is this a gmail account?")"
if [[ "$continue" == "Yes" ]]; then if [[ "$continue" == "Yes" ]]; then
configure_gmail "$emailAddress" configure_gmail "$emailAddress"
else else
configure_generic "$emailAddress" configure_generic "$emailAddress"
fi fi
esac esac
# Password encryption with gpg
passOne=a # Get password for email
passTwo=b emailPassword="$(passwordbox "Please enter the password for $emailAddress:")"
until [ "$passOne" = "$passTwo" ]; do
passOne="$(passwordbox "$(gettext "Please enter the password for $emailAddress:")")" # Add password to the config file
passTwo="$(passwordbox "$(gettext "Please enter the password again: ")")" echo "set imap_pass=\"$emailPassword\"" >> "$configFile"
if [ "$passOne" != "$passTwo" ]; then echo "set smtp_pass=\"$emailPassword\"" >> "$configFile"
echo "$(gettext "The passwords do not match.")"
fi # Add source for aliases
done echo "source ~/${muttHome#/home/*/}/aliases" >> "$configFile"
keyName="$(gpg --list-secret-keys --keyid-format short | grep -B1 ^uid | head -n1 | rev | cut -c -8 | rev)"
# I wish it were possible to just echo the password through gpg and not have an unencrypted file at all. # Add folder-hook
# but either it's not, or I just can't figure out how to do it. So we'll use mktemp and shred. echo "folder-hook .*$emailAddress/ 'source ${muttHome/#$HOME/\~}/$emailAddress.ssl'" >> "$configFile"
passwordFile="$(mktemp)"
echo -e "set imap_pass=\"$passOne\"\nset smtp_pass=\"$passOne\"" > "$passwordFile" # Encrypt the configuration file
gpg -r $keyName -e "$passwordFile" encrypt_file "$configFile" "$configFile.ssl"
mv "$passwordFile.gpg" "$muttHome/$emailAddress.gpg"
shred -fuzn 10 "$passwordFile" # Create a wrapper configuration file that sources the encrypted file
echo "source \"gpg -d ${muttHome/#$HOME/\~}/${emailAddress}.gpg|\"" >> "$muttHome/$emailAddress" echo "# Encrypted email configuration for $emailAddress" > "$configFile"
echo "# The real configuration is in $emailAddress.ssl (encrypted)" >> "$configFile"
echo "" >> "$configFile"
echo "# This command will decrypt the configuration when needed" >> "$configFile"
echo "source \"| $muttHome/scripts/decrypt.sh $muttHome/$emailAddress.ssl /tmp/mutt-$emailAddress-$USER\"" >> "$configFile"
# Add keybinding in muttrc
add_keybinding add_keybinding
echo "folder-hook .*$emailAddress/ 'source ${muttHome/#$HOME/\~}/$emailAddress'" >> "$muttHome/$emailAddress"
msgbox "$(gettext "Email address added, press enter to continue.")" # Clean up
rm -f "/tmp/mutt-$emailAddress-$USER" 2>/dev/null
msgbox "Email address added, press enter to continue."
} }
configure_gmail() configure_gmail() {
{ # Copy the Gmail template and replace placeholders
# This is just to create the base file with settings common to all gmail accounts cp "files/gmail.template" "$muttHome/$1.tmp"
# I decided to do these in functions so as to not have a truly giagantic case statement in the add email function
echo "unset imap_passive" >> "$muttHome/$1" # Replace email placeholder in the template
echo "unset record" >> "$muttHome/$1" sed -i "s|EMAIL_ADDRESS|$1|g" "$muttHome/$1.tmp"
echo "set imap_user=$1" >> "$muttHome/$1" sed -i "s|USERNAME|${1%@*}|g" "$muttHome/$1.tmp"
echo "set smtp_url=\"smtp://${1%@*}@smtp.gmail.com:587/" >> "$muttHome/$1"
echo "set folder=imaps://${1%@*}@imap.gmail.com/" >> "$muttHome/$1" # Append the template to the existing config
echo "set spoolfile = +INBOX" >> "$muttHome/$1" cat "$muttHome/$1.tmp" >> "$muttHome/$1"
echo "mailboxes = +INBOX" >> "$muttHome/$1" rm "$muttHome/$1.tmp"
echo "set postponed = +[Gmail]/Drafts" >> "$muttHome/$1"
echo "set record=+[Gmail]/Sent" >> "$muttHome/$1" # Handle goobook integration if available
echo "set imap_keepalive=300" >> "$muttHome/$1"
echo "set mail_check=300" >> "$muttHome/$1"
unset continue unset continue
if command -v goobook &> /dev/null ; then if command -v goobook &> /dev/null ; then
read -p "$(eval_gettext "Goobook is installed, would you like to use it as your addressbook for the account \$1? ")" continue read -p "Goobook is installed, would you like to use it as your addressbook for the account $1? " continue
fi if [[ "${continue^}" = "Y" ]]; then
if [ "${continue^}" = "Y" ]; then echo "set query_command=\"goobook query %s\"" >> "$muttHome/$1"
echo "set query_command=\"goobook query %s\"" >> "$muttHome/$1" echo "macro index,pager a \"<pipe-message>goobook add<return>\" \"add sender to google contacts\"" >> "$muttHome/$1"
# Normally macros go in muttHome/macros, but this may be a gmail specific setting fi
echo "macro index,pager a \"<pipe-message>goobook add<return>\" \"add sender to google contacts\"" >> "$muttHome/$1"
else
echo "source ~/${muttHome#/home/*/}/aliases" >> "$muttHome/$1"
fi fi
} }
configure_hotmail() configure_hotmail() {
{ # Copy the Hotmail template and replace placeholders
# This is just to create the base file with settings common to all hotmail accounts cp "files/hotmail.template" "$muttHome/$1.tmp"
# I decided to do these in functions so as to not have a truly giagantic case statement in the add email function
echo "unset imap_passive" >> "$muttHome/$1" # Replace email placeholder in the template
echo "unset record" >> "$muttHome/$1" sed -i "s|EMAIL_ADDRESS|$1|g" "$muttHome/$1.tmp"
echo "set imap_user=$1" >> "$muttHome/$1"
echo "set smtp_url=\"smtp://$1@smtp-mail.outlook.com:587/" >> "$muttHome/$1" # Append the template to the existing config
echo "set folder=imaps://$1@imap-mail.outlook.com/" >> "$muttHome/$1" cat "$muttHome/$1.tmp" >> "$muttHome/$1"
echo "set ssl_force_tls=yes" >> "$muttHome/$1" rm "$muttHome/$1.tmp"
echo "set spoolfile=+Inbox" >> "$muttHome/$1"
echo "mailboxes = +Inbox" >> "$muttHome/$1"
echo "set postponed=+Drafts" >> "$muttHome/$1"
echo "set record=+Sent" >> "$muttHome/$1"
echo "set imap_keepalive=300" >> "$muttHome/$1"
echo "set mail_check=300" >> "$muttHome/$1"
echo "bind editor <Tab> complete-query" >> "$muttHome/$1"
echo "source ~/${muttHome#/home/*/}/aliases" >> "$muttHome/$1"
} }
configure_generic() configure_generic() {
{
# Break the email address into its components: # Break the email address into its components:
local userName="${1%%@*}" local userName="${1%%@*}"
local hostName="${1##*@}" local hostName="${1##*@}"
@ -413,78 +427,89 @@ configure_generic()
local smtpHost local smtpHost
local smtpUser local smtpUser
local smtpPort local smtpPort
local extraSettings
read -p "$(gettext "Enter imap host: ")" -e -i imap.$hostName imapHost # Gather input
read -p "$(gettext "Enter imap user: ")" -e -i $1 imapUser read -p "Enter imap host: " -e -i imap.$hostName imapHost
read -p "$(gettext "Enter imap port: ")" -e -i 993 imapPort read -p "Enter imap user: " -e -i $1 imapUser
read -p "$(gettext "Enter smtp host: ")" -e -i smtp.$hostName smtpHost read -p "Enter imap port: " -e -i 993 imapPort
read -p "$(gettext "Enter smtp user: ")" -e -i $userName smtpUser read -p "Enter smtp host: " -e -i smtp.$hostName smtpHost
read -p "$(gettext "Enter smtp port: ")" -e -i 587 smtpPort read -p "Enter smtp user: " -e -i $userName smtpUser
read -p "$(gettext "Enter extra settings, one line at a time, just press enter when done: ")" extraSettings read -p "Enter smtp port: " -e -i 587 smtpPort
# Copy the generic template
cp "files/generic.template" "$muttHome/$1.tmp"
# Replace placeholders
sed -i "s|IMAP_HOST|$imapHost|g" "$muttHome/$1.tmp"
sed -i "s|IMAP_USER|$imapUser|g" "$muttHome/$1.tmp"
sed -i "s|IMAP_PORT|$imapPort|g" "$muttHome/$1.tmp"
sed -i "s|SMTP_HOST|$smtpHost|g" "$muttHome/$1.tmp"
sed -i "s|SMTP_USER|$smtpUser|g" "$muttHome/$1.tmp"
sed -i "s|SMTP_PORT|$smtpPort|g" "$muttHome/$1.tmp"
# Append the template to the existing config
cat "$muttHome/$1.tmp" >> "$muttHome/$1"
rm "$muttHome/$1.tmp"
# Add any extra settings
read -p "Enter extra settings, one line at a time, just press enter when done: " extraSettings
while [ "$extraSettings" != "" ]; do while [ "$extraSettings" != "" ]; do
echo "$extraSettings" >> "$muttHome/$1" echo "$extraSettings" >> "$muttHome/$1"
read $extreSettings read extraSettings
done done
echo "unset imap_passive" >> "$muttHome/$1"
echo "unset record" >> "$muttHome/$1"
echo "set smtp_url=\"smtp://$smtpUser@$smtpHost:$smtpPort/" >> "$muttHome/$1"
echo "set folder=imaps://$imapUser@$imapHost:$imapPort/" >> "$muttHome/$1"
echo "mailboxes = +INBOX" >> "$muttHome/$1"
echo "set spoolfile = +INBOX" >> "$muttHome/$1"
echo "set postponed = +Drafts" >> "$muttHome/$1"
echo "set imap_keepalive=300" >> "$muttHome/$1"
echo "set mail_check=300" >> "$muttHome/$1"
echo "bind editor <Tab> complete-query" >> "$muttHome/$1"
echo "source ~/${muttHome#/home/*/}/aliases" >> "$muttHome/$1"
} }
add_keybinding() add_keybinding() {
{ # Here we search for previous keybinding
# Here we search for previous keybinding local fNumber=1
local fNumber=1 while : ; do
while : ; do grep "^[[:space:]m]acro.*index.*<F$fNumber>.*" $muttHome/muttrc &> /dev/null || break # fNumber is now the currently open keybinding.
grep "^[[:space:]m]acro.*index.*<F$fNumber>.*" $muttHome/muttrc &> /dev/null || break # fNumber is now the currently open keybinding. ((fNumber++)) # fNumber was taken, so increment it.
((fNumber++)) # fNumber was taken, so increment it. done
done # Bind key FfNumber to the mail account.
# Bind key FfNumber to the mail account. echo "macro generic,index <F$fNumber> '<sync-mailbox><enter-command>source ${muttHome/#$HOME/\~}/$emailAddress<enter><change-folder>!<enter>'" >> "$muttHome/muttrc"
echo "macro generic,index <F$fNumber> '<sync-mailbox><enter-command>source ${muttHome/#$HOME/\~}/$emailAddress<enter><change-folder>!<enter>'" >> "$muttHome/muttrc" echo "mail account $emailAddress bound to F$fNumber."
echo "mail account $emailAddress bound to F$fNumber." if ! grep "^source.*@.*\..*" "$muttHome/muttrc" &> /dev/null ; then
if ! grep "^source.*@.*\..*" "$muttHome/muttrc" &> /dev/null ; then continue="$(yesno "Make $emailAddress the default account?")"
continue="$(yesno "$(eval_gettext "Make \$emailAddress the default account?")")" if [[ "$continue" = "Yes" ]]; then
if [ "$continue" = "Yes" ]; then echo "source ${muttHome/#$HOME/\~}/$emailAddress" >> "$muttHome/muttrc"
echo "source ${muttHome/#$HOME/\~}/$emailAddress" >> "$muttHome/muttrc" fi
fi fi
fi
} }
new_contact() new_contact() {
{ contactName="$(inputbox "Enter the contact's first and last name..")"
contactName="$(inputbox "$(gettext "Enter the contact's first and last name..")")" if [[ -z "$contactName" ]]; then
if [ -z "$contactName" ]; then
exit 0 exit 0
fi fi
contactEmail="$(inputbox "$(gettext "Enter the email address for") $contactName")" contactEmail="$(inputbox "Enter the email address for $contactName")"
if [ -z "$contactEmail" ]; then if [[ -z "$contactEmail" ]]; then
exit 0 exit 0
fi fi
contactAlias="${contactName,,}" contactAlias="${contactName,,}"
contactAlias="${contactAlias// /-}" contactAlias="${contactAlias// /-}"
if grep "<$contactEmail>\| $contactAlias " "$muttHome/aliases" &> /dev/null ; then if grep "<$contactEmail>\| $contactAlias " "$muttHome/aliases" &> /dev/null ; then
[[ "$(yesno "$(gettext "This email address already exists in your contacts. Continue anyway?")")" != "Yes" ]] && exit 0 [[ "$(yesno "This email address already exists in your contacts. Continue anyway?")" != "Yes" ]] && exit 0
fi fi
echo "alias $contactAlias $contactName <$contactEmail>" >> "$muttHome/aliases" echo "alias $contactAlias $contactName <$contactEmail>" >> "$muttHome/aliases"
sort -u "$muttHome/aliases" -o "$muttHome/aliases" sort -u "$muttHome/aliases" -o "$muttHome/aliases"
msgbox "$contactName $(gettext "added to your address book")." msgbox "$contactName added to your address book."
} }
# This is the main loop of the program # This is the main loop of the program
# Call functions to be ran every time the script is ran. # Call functions to be ran every time the script is ran.
check_dependancies check_dependancies
initialize_directory initialize_directory
# If in test mode, display a notification
if [[ "$testMode" = true ]]; then
echo "Running in test mode. Using $muttHome for configuration."
fi
# Let's make a mainmenu variable to hold all the options for the select loop. # Let's make a mainmenu variable to hold all the options for the select loop.
mainmenu=("$(gettext "Add Email Address")" "$(gettext "Configure GPG")" "$(gettext "New Contact")" "$(gettext "Exit")") mainmenu=("Add Email Address" "New Contact" "Exit")
while : ; do while : ; do
i="$(IFS=$'\n';menulist $(for i in "${mainmenu[@]}" ; do echo "$i";echo "$i";done))" i="$(IFS=$'\n';menulist $(for i in "${mainmenu[@]}" ; do echo "$i";echo "$i";done))"
[[ -z "$i" ]] && continue [[ -z "$i" ]] && continue
functionName="${i,,}" functionName="${i,,}"
functionName="${functionName// /_}" functionName="${functionName// /_}"