Moving mtoward using openssl to encrypt email address files.

This commit is contained in:
Storm Dragon 2025-04-10 07:45:09 -04:00
parent 7e7853f050
commit a9de7f4481

View File

@ -9,9 +9,6 @@
# Settings to improve accessibility of dialog.
export DIALOGOPTS='--insecure --no-lines --visit-items'
# Use the cli entry system for gpg keys.
export PINENTRY_USER_DATA="USE_CURSES=1"
# Array of command line arguments
declare -A command=(
[h]="Show this help information."
@ -20,7 +17,6 @@ declare -A command=(
# Variables
muttHome=~/.mutt
fileDir="$(dirname "$(realpath "$0")")/files"
testMode=false
# Process command line arguments
@ -37,6 +33,7 @@ while getopts "${args}" i ; do
esac
done
# Functions
help() {
echo "fleacollar.sh"
echo "Released under the terms of the WTFPL: http://wtfpl.net"
@ -49,39 +46,14 @@ help() {
exit 0
}
initialize_pass() {
# Check if password store is initialized
if [ ! -f ~/.password-store/.gpg-id ]; then
# Get the GPG key ID that we already have in the script
keyName="$(gpg2 --list-secret-keys --keyid-format short | grep -B1 ^uid | head -n1 | rev | cut -c -8 | rev)"
# If we have a key, initialize the password store
if [ -n "$keyName" ]; then
echo "Initializing password store with GPG key: $keyName"
pass init "$keyName"
else
msgbox "No GPG key found. Please run 'pass init your-gpg-id' manually after creating a GPG key."
exit 1
fi
fi
}
check_dependancies()
{
check_dependancies() {
local dep
for dep in dialog gpg2 mutt pass w3m ; do
for dep in dialog mutt w3m openssl ; do
if ! command -v $dep &> /dev/null ; then
echo "$dep is not installed. Please install $dep and run this script again."
exit 1
fi
done
if ! [ -d ~/.gnupg ]; then
read -p "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
# Initialize the password store if needed
initialize_pass
}
inputbox() {
@ -132,51 +104,99 @@ yesno() {
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:")"
passphrase="$(passwordbox "Please create a master password for encrypting your email credentials:")"
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}/scripts"
if ! [ -f "$muttHome/aliases" ]; then
touch "$muttHome/aliases"
fi
# Copy the add_address.sh script
if ! [ -f "$muttHome/scripts/add_address.sh" ]; then
cp "$fileDir/add_address.sh" "$muttHome/scripts/add_address.sh"
cp "files/add_address.sh" "$muttHome/scripts/add_address.sh"
chmod 700 "$muttHome/scripts/add_address.sh"
fi
# Create mailcap file (now always using w3m)
if ! [ -f "$muttHome/mailcap" ]; then
cp "$fileDir/mailcap" "$muttHome/mailcap"
cp "scripts/mailcap" "$muttHome/mailcap"
fi
# Create gpg.rc file
if ! [ -f "$muttHome/gpg.rc" ]; then
find /usr -name gpg.rc -exec cp "{}" "$muttHome/" \; 2> /dev/null
echo "set pgp_autosign=yes" >> "$muttHome/gpg.rc"
echo "set crypt_autosign=yes" >> "$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 "No gpg key was found. Type your name and press enter to generate a PGP key. Control+c if you would like to create it manually. " continue
# Try to use quick key generation, and fall back to the more verbose version for legacy systems.
if ! gpg2 --quick-gen-key "${continue:-${USER}}"; then
gpg2 --gen-key
fi
fi
PS3="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="$(gpg2 --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"
# Copy the gpg.rc file if it exists in files directory
if [[ -f "files/gpg.rc" ]] && ! [[ -f "$muttHome/gpg.rc" ]]; then
cp "files/gpg.rc" "$muttHome/gpg.rc"
echo "# Note: GPG signing/encryption is available but not configured." >> "$muttHome/gpg.rc"
echo "# You can manually set up GPG for email if needed." >> "$muttHome/gpg.rc"
fi
# Create or update macro file
cp "$fileDir/macros" "$muttHome/macros"
cp "files/macros" "$muttHome/macros"
# Create basic muttrc
if ! [ -f "$muttHome/muttrc" ]; then
@ -185,19 +205,19 @@ initialize_directory()
for i in nano vim ; do
unset editorPath
editorPath="$(command -v $i)"
if [ -n "$editorPath" ]; then
if [[ -n "$editorPath" ]]; then
editors[$x]="$editorPath"
((x++))
fi
done
echo "Select editor for email composition:"
i="$(menulist ${EDITOR} ${EDITOR} $(for i in "${editors[@]##*/}" ; do echo "$i";echo "$i";done))"
if [ -z "$i" ]; then
if [[ -z "$i" ]]; then
exit 0
fi
# Copy base muttrc
cp "$fileDir/muttrc" "$muttHome/muttrc"
cp "files/muttrc" "$muttHome/muttrc"
# Set editor in muttrc
case "$i" in
@ -208,61 +228,117 @@ initialize_directory()
# Replace muttHome in muttrc
sed -i "s|\$muttHome|${muttHome/#$HOME/\~}|g" "$muttHome/muttrc"
fi
# Create the decrypt helper script
create_decrypt_helper
}
configure_gpg()
{
# GPG stuff in .bashrc:
if ! grep 'GPG_TTY=$(tty)' ~/.bashrc ; then
echo -e 'GPG_TTY=$(tty)\nexport GPG_TTY' >> ~/.bashrc
create_decrypt_helper() {
# Create a helper script to decrypt files
cat > "$muttHome/scripts/decrypt.sh" << 'EOF'
#!/bin/bash
# Script to decrypt an encrypted email configuration file
# Usage: decrypt.sh encrypted_file temp_output_file
if [[ $# -ne 2 ]]; then
echo "Usage: $0 encrypted_file temp_output_file"
exit 1
fi
# Make sure the configuration directory exists
if ! [ -d ~/.gnupg/ ]; then
gpg2 --list-secret-keys &> /dev/null
ENCRYPTED_FILE="$1"
OUTPUT_FILE="$2"
MASTER_FILE="$(dirname "$ENCRYPTED_FILE")/.master_password"
if [[ ! -f "$ENCRYPTED_FILE" ]]; then
echo "Error: Encrypted file not found"
exit 1
fi
if [ -f ~/.gnupg/gpg.conf ]; then
read -p "This will overwrite your existing ~/.gnupg/gpg.conf file. Press enter to continue or control+c to abort. " continue
if [[ ! -f "$MASTER_FILE" ]]; then
echo "Error: Master password file not found"
exit 1
fi
# Copy GPG configuration files
cp "$fileDir/gpg.conf" ~/.gnupg/gpg.conf
# Function to decrypt
decrypt() {
local stored_hash="$(cat "$MASTER_FILE")"
local passphrase
local attempt=0
if [ -f ~/.gnupg/gpg-agent.conf ]; then
read -p "This will overwrite your existing ~/.gnupg/gpg-agent.conf file. Press enter to continue or control+c to abort. " continue
while [ $attempt -lt 3 ]; do
passphrase="$(dialog --passwordbox "Enter master password to decrypt:" 0 0 --stdout)"
# 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
cp "$fileDir/gpg-agent.conf" ~/.gnupg/gpg-agent.conf
echo "Too many failed attempts."
return 1
}
add_email_address()
{
# Create temp directory if it doesn't exist
mkdir -p "$(dirname "$OUTPUT_FILE")"
# 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
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
fi
fi
if [ -f "$muttHome/$emailAddress" ]; then
if [[ -f "$muttHome/$emailAddress" ]] || [[ -f "$muttHome/$emailAddress.ssl" ]]; then
read -p "This email address already exists. Overwrite the existing settings? (y/n) " continue
if [ "${continue^}" != "Y" ]; then
if [[ "${continue^}" != "Y" ]]; then
exit 0
else
sed -i "/$emailAddress/d" "$muttHome/muttrc"
rm -f "$muttHome/$emailAddress" "$muttHome/$emailAddress.ssl"
fi
fi
read -p "Enter your name as you want it to appear in emails. From: " realName
echo "set realname=\"$realName\"" > "$muttHome/$emailAddress"
echo "set from=\"$emailAddress\"" >> "$muttHome/$emailAddress"
echo "set use_from = \"yes\"" >> "$muttHome/$emailAddress"
echo "set hostname=${emailAddress##*@}" >> "$muttHome/$emailAddress"
# Create a temporary file for configuration
configFile="$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
*gmail.com)
configure_gmail "$emailAddress"
;;
*hotmail.com)
*hotmail.com | *outlook.com | *live.com)
configure_hotmail "$emailAddress"
;;
*)
@ -274,36 +350,41 @@ add_email_address()
fi
esac
# Initialize pass before using it
initialize_pass
# Get password for email
emailPassword="$(passwordbox "Please enter the password for $emailAddress:")"
# Password storage with pass
passOne=a
passTwo=b
until [ "$passOne" = "$passTwo" ]; do
passOne="$(passwordbox "Please enter the password for $emailAddress:")"
passTwo="$(passwordbox "Please enter the password again: ")"
if [ "$passOne" != "$passTwo" ]; then
echo "The passwords do not match."
fi
done
# Add password to the config file
echo "set imap_pass=\"$emailPassword\"" >> "$configFile"
echo "set smtp_pass=\"$emailPassword\"" >> "$configFile"
# Store password in pass
pass insert -f "email/$emailAddress" <<< "$passOne"
# Add source for aliases
echo "source ~/${muttHome#/home/*/}/aliases" >> "$configFile"
# Add configuration to retrieve password from pass
echo "set imap_pass=\"`pass show email/$emailAddress`\"" >> "$muttHome/$emailAddress"
echo "set smtp_pass=\"`pass show email/$emailAddress`\"" >> "$muttHome/$emailAddress"
# Add folder-hook
echo "folder-hook .*$emailAddress/ 'source ${muttHome/#$HOME/\~}/$emailAddress.ssl'" >> "$configFile"
# Encrypt the configuration file
encrypt_file "$configFile" "$configFile.ssl"
# Create a wrapper configuration file that sources the encrypted file
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
echo "folder-hook .*$emailAddress/ 'source ${muttHome/#$HOME/\~}/$emailAddress'" >> "$muttHome/$emailAddress"
# 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
cp "$fileDir/gmail.template" "$muttHome/$1.tmp"
cp "files/gmail.template" "$muttHome/$1.tmp"
# Replace email placeholder in the template
sed -i "s|EMAIL_ADDRESS|$1|g" "$muttHome/$1.tmp"
@ -317,21 +398,16 @@ configure_gmail()
unset continue
if command -v goobook &> /dev/null ; then
read -p "Goobook is installed, would you like to use it as your addressbook for the account $1? " continue
if [ "${continue^}" = "Y" ]; then
if [[ "${continue^}" = "Y" ]]; then
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"
else
echo "source ~/${muttHome#/home/*/}/aliases" >> "$muttHome/$1"
fi
else
echo "source ~/${muttHome#/home/*/}/aliases" >> "$muttHome/$1"
fi
}
configure_hotmail()
{
configure_hotmail() {
# Copy the Hotmail template and replace placeholders
cp "$fileDir/hotmail.template" "$muttHome/$1.tmp"
cp "files/hotmail.template" "$muttHome/$1.tmp"
# Replace email placeholder in the template
sed -i "s|EMAIL_ADDRESS|$1|g" "$muttHome/$1.tmp"
@ -339,13 +415,9 @@ configure_hotmail()
# Append the template to the existing config
cat "$muttHome/$1.tmp" >> "$muttHome/$1"
rm "$muttHome/$1.tmp"
# Add source for aliases
echo "source ~/${muttHome#/home/*/}/aliases" >> "$muttHome/$1"
}
configure_generic()
{
configure_generic() {
# Break the email address into its components:
local userName="${1%%@*}"
local hostName="${1##*@}"
@ -365,7 +437,7 @@ configure_generic()
read -p "Enter smtp port: " -e -i 587 smtpPort
# Copy the generic template
cp "$fileDir/generic.template" "$muttHome/$1.tmp"
cp "files/generic.template" "$muttHome/$1.tmp"
# Replace placeholders
sed -i "s|IMAP_HOST|$imapHost|g" "$muttHome/$1.tmp"
@ -385,13 +457,9 @@ configure_generic()
echo "$extraSettings" >> "$muttHome/$1"
read extraSettings
done
# Add source for aliases
echo "source ~/${muttHome#/home/*/}/aliases" >> "$muttHome/$1"
}
add_keybinding()
{
add_keybinding() {
# Here we search for previous keybinding
local fNumber=1
while : ; do
@ -403,20 +471,19 @@ add_keybinding()
echo "mail account $emailAddress bound to F$fNumber."
if ! grep "^source.*@.*\..*" "$muttHome/muttrc" &> /dev/null ; then
continue="$(yesno "Make $emailAddress the default account?")"
if [ "$continue" = "Yes" ]; then
if [[ "$continue" = "Yes" ]]; then
echo "source ${muttHome/#$HOME/\~}/$emailAddress" >> "$muttHome/muttrc"
fi
fi
}
new_contact()
{
new_contact() {
contactName="$(inputbox "Enter the contact's first and last name..")"
if [ -z "$contactName" ]; then
if [[ -z "$contactName" ]]; then
exit 0
fi
contactEmail="$(inputbox "Enter the email address for $contactName")"
if [ -z "$contactEmail" ]; then
if [[ -z "$contactEmail" ]]; then
exit 0
fi
contactAlias="${contactName,,}"
@ -435,12 +502,12 @@ check_dependancies
initialize_directory
# If in test mode, display a notification
if [ "$testMode" = true ]; then
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.
mainmenu=("Add Email Address" "Configure GPG" "New Contact" "Exit")
mainmenu=("Add Email Address" "New Contact" "Exit")
while : ; do
i="$(IFS=$'\n';menulist $(for i in "${mainmenu[@]}" ; do echo "$i";echo "$i";done))"
[[ -z "$i" ]] && continue