diff --git a/fleacollar.sh b/fleacollar.sh index d7882d2..f67149d 100755 --- a/fleacollar.sh +++ b/fleacollar.sh @@ -4,14 +4,11 @@ # Written by Storm Dragon: https://social.stormdragon.tk/storm # Written by Michael Taboada: https://2mb.social/mwtab # 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 # 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() { @@ -105,9 +77,9 @@ menulist() { } msgbox() { -# Returns: None -# Shows the provided message on the screen with an ok button. -dialog --msgbox "$*" 0 0 + # Returns: None + # Shows the provided message on the screen with an ok button. + dialog --msgbox "$*" 0 0 } passwordbox() { @@ -131,52 +103,100 @@ yesno() { echo "No" fi } + +# 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() -{ +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 - fi +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 + +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 "$MASTER_FILE" ]]; then + echo "Error: Master password file not found" + exit 1 +fi + +# Function to decrypt +decrypt() { + local stored_hash="$(cat "$MASTER_FILE")" + local passphrase + local attempt=0 - # Make sure the configuration directory exists - if ! [ -d ~/.gnupg/ ]; then - gpg2 --list-secret-keys &> /dev/null - fi + 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 - 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 - fi - - # Copy GPG configuration files - cp "$fileDir/gpg.conf" ~/.gnupg/gpg.conf - - 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 - fi - - 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 \"goobook add\" \"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