#!/bin/sh if [ -z "$prefix" ]; then case "$(uname)" in Linux) prefix="/usr" ;; *) prefix="/usr/local" ;; esac fi mwconfigdir=${XDG_CONFIG_HOME:-$HOME/.config} # Main mutt config location mwmuttdir="$mwconfigdir/mutt" mwmuttrc="$mwmuttdir/muttrc" # Directory for account settings mwaccmuttdir="$mwmuttdir/accounts" # Generated at every full sync mwgenmuttaccs=$mwmuttdir/mw_generated.muttrc # Location of mail storage mwmaildir="${MAILDIR:-$HOME/mail}" # Regex to confirm valid email address mwemailre=".\+@.\+\\..\+" mwshare="$prefix/share/mutt-wizard" mwmbsyncrc="$mwconfigdir/isync/mbsyncrc" mwgetmaildir=$mwconfigdir/getmail #POP3 mwmsmtprc="$mwconfigdir/msmtp/config" mwsharerc="$mwshare/mutt-wizard.muttrc" mwcachedir="${XDG_CACHE_HOME:-$HOME/.cache}/mutt-wizard" mwssltype="IMAPS" mwmbsyncbin="$prefix/bin/mbsync -c $mwmbsyncrc" msmtpbin="$prefix/bin/msmtp" mwtype=offline _mwaddrmwtype(){ mwacc="$mwaddr" # let the user always just deal with his email mwaccmaildir="$mwmaildir/$mwaddr" # mail dir is $MAILDIR/email mwacccachedir=$mwcachedir/${mwaddr//[.@]/_} # @ cannot stay because of mutt, . could mwpass=mutt-wizard-$mwaddr [ -n "$mwidnum" ] && mwaccmuttrc="$mwaccmuttdir/$mwidnum-$mwacc.mwonofftype.$mwtype.muttrc" } _mwaccs() { mwaccs="$(find "$mwaccmuttdir" -type f | grep -o "[1-9]-.*.muttrc" | sed "s/-/: /;s/\.muttrc//" | sort -n)" } _mwlist() { _mwaccs && [ -n "$mwaccs" ] && echo "${mwaccs//.mwonofftype./ }" } _mwadd() { _mwcheckinternet && _mwasktype && _mwaskinfo && _mwtryconnect && _mwfinalize || _mwdelete } _mwgetprofiles() { unset mwmsmtpheader mwmsmtpprofile mwmbsyncprofile for mwcrt in "/etc/ssl/certs/ca-certificates.crt" "/etc/pki/tls/certs/ca-bundle.crt" "/etc/ssl/ca-bundle.pem" "/etc/pki/tls/cacert.pem" "/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem" "/etc/ssl/cert.pem" "/usr/local/share/ca-certificates/" do [ -f "$mwcrt" ] && mwsslcert="$mwcrt" && break done || { echo "CA Certificate not found. Please install one or link it to /etc/ssl/certs/ca-certificates.crt" && return 1 ;} printf "Creating profiles for \`%s\`..." "$mwaddr" mwmsmtpheader="defaults auth on tls on tls_trust_file $mwsslcert logfile ${XDG_LOG_HOME:-$HOME}/msmtp.log " mwmsmtpprofile="account $mwacc host $mwsmtp port $mwsport from $mwaddr user $mwlogin passwordeval \"pass $mwpass\" $mwstarttlsoff " mwmbsyncprofile="IMAPStore $mwacc-remote Host $mwserver Port $mwport User $mwlogin PassCmd \"pass $mwpass\" SSLType $mwssltype CertificateFile $mwsslcert MaildirStore $mwacc-local Subfolders Verbatim Path $mwaccmaildir/ Inbox $mwaccmaildir/INBOX Flatten . Channel $mwacc Expunge Both Master :$mwacc-remote: Slave :$mwacc-local: Patterns * !\"[Gmail]/All Mail\" Create Both SyncState * MaxMessages 0 # End profile " printf "DONE.\\n" } _mwaskinfo() { if [ -z "$mwaddr" ]; then printf "Type the \033[31memail address\033[0m\\n\t\033[36m" read -r mwaddr printf "\033[0m" while ! echo "$mwaddr" | grep "$mwemailre" >/dev/null; do printf "That is not a valid \033[31memail address\033[0m, please retype\\n\t\033[36m" read -r mwaddr printf "\033[0m" done fi mwdomain="$(echo "$mwaddr" | sed "s/.*@//")" printf "\\nSearching for \033[32m%s\033[0m in \033[34m\`domains.csv\`\033[0m..." "$mwdomain" mwserverinfo="$(grep "^$mwdomain" "$mwshare/domains.csv" 2>/dev/null)" if [ -z "$mwserverinfo" ]; then printf "Your email domain is not known to mutt-wizard.\\nType in your settings.\\nUsually you find them by an internet search.\\n" printf "Type the IMAP/POP3 server (excluding the port number)\\n\033[36m\t" read -r mwserver printf "\033[0mIMAP port number (usually 993)\\n\033[36m\t" read -r mwport printf "\033[0mSMTP server (excluding the port number)\\n\033[36m\t" read -r mwsmtp printf "\033[0mSMTP port number (usually 587 or 465)\\n\033[36m\t" read -r mwsport printf "\033[0m\\nIf you want, you can copy the line below and add it to the \`domains.csv\` file on Github, for others.\\n\\n%s,%s,%s,%s,%s\\n\\nBut be sure the setting works, first! ;-)\\n" "$mwdomain" "$mwserver" "$mwport" "$mwsmtp" "$mwsport" else IFS=, read -r mwservice mwserver mwport mwsmtp mwsport < "$mwgetmaildir/$mwaddr" << EOF [retriever] type = SimplePOP3SSLRetriever server = $mwserver username = $mwaddr port = $mwport password_command = ("pass", "$mwpass") [destination] type = Maildir path = $mwaccmaildir/INBOX/ [options] read_all = true delete = true message_log = ${XDG_LOG_HOME:-$HOME}/getmail.log # vim: ft=conf EOF #from now on pop is lile offline mwtype=offline return 0 fi _mwgetprofiles || return 1 mkdir -p "$mwmuttdir" "$mwaccmuttdir" "${mwmsmtprc%/*}" "${mwmbsyncrc%/*}" if [ ! -f "$mwmsmtprc" ]; then echo "$mwmsmtpheader" > "$mwmsmtprc" else sed -i "/account $mwacc/,/^\(\s*$\|account\)/d" "$mwmsmtprc" fi echo "$mwmsmtpprofile" >> "$mwmsmtprc" case "$mwservice" in protonmail.ch|protonmail.com|pm.me) protonfinger || return 1 ;; esac [ -f "$mwmbsyncrc" ] && sed -i "/IMAPStore $mwacc-remote$/,/# End profile/d" "$mwmbsyncrc" echo "$mwmbsyncprofile" >> "$mwmbsyncrc" [ ! -f "$mwmuttrc" ] && echo "# vim: filetype=muttrc" > "$mwmuttrc" && echo "muttrc created." ! grep "source $mwsharerc" "$mwmuttrc" >/dev/null && echo "source $mwsharerc # mw-autogenerated" >> "$mwmuttrc" return 0 } _mwprotonfinger() { printf "Getting Protonmail bridge fingerprint...\\n" mwfingerprint="$($msmtpbin --serverinfo --host=127.0.0.1 --port=1025 --tls --tls-certcheck=off)" || return 1 sed -i "s/account $mwacc/&\ntls_trust_file\ntls_fingerprint $mwfingerprint/" "$mwmsmtprc" } _mwgetpass() { while : ; do pass rm -f "$mwpass" >/dev/null 2>&1 pass insert "$mwpass" && break; done ;} _mwtryconnect() { if [ ! -d "$mwaccmaildir" ]; then mwaccmaildirWasThere="NO" # we need to remove again for "online" mkdir -p "$mwaccmaildir" fi if [ -z "$mwmailboxes" ]; then if [ "$mwtype" = "pop" ]; then mwgetmailbin --rcfile=$mwaddr --getmaildir=$mwgetmaildir && mwmailboxes="INBOX" else mwmailboxes="$($mwmbsyncbin -l $mwacc | sed 's/\//./')" >/dev/null 2>&1 fi fi if [ -n "$mwmailboxes" ]; then mwspoolfile=$(echo "$mwmailboxes" | grep -i -m 1 inbox | sed -ne 's/.*/+\0/p') [ -z "$mwspoolfile" ] && printf "\033[31m\033[31mNo spoolfile.\033[0m\\n" && return 1 printf "\033[32mMailboxes detected.\033[0m\\n" echo "$mwmailboxes" | xargs -I {} mkdir -p "$mwaccmaildir/{}/"{cur,new,tmp} else printf "\033[31m\033[31mLog-on not successful.\033[0m\\nIt seems that either you inputted the wrong password or server settings, or there are other requirements for your account out of the control of mutt-wizard.\\n" return 1 fi } _mwfinalize() { # new mwaccmuttrc _genmwaccmuttrc if [ "$mwtype" = "online" ]; then cat >> "$mwaccmuttrc" </dev/null && printf "That doesn't seem like a yes to me.\\n\\n" && return 1 return 0 ; } _mwpick() { printf "Select an accounts to %s:\\n" "$1" _mwlist [ -z "$mwpick" ] && read -r mwpick mwidnum="$mwpick" [ -z "$mwidnum" ] && return 1 mwaddr="$(echo "$mwaccs" | grep "$mwidnum:" | sed "s/\.mwonofftype\./ /" | awk '{print $2}')" mwtype="$(echo "$mwaccs" | grep "$mwidnum:" | sed "s/\.mwonofftype\./ /" | awk '{print $3}')" _mwaddrmwtype [ -z "$mwacc" ] && printf "Invalid response." && return 1 return 0 ; } _mwdelete() { rm -f "$mwaccmuttrc" rm -f "$mwgetmaildir/$mwaddr" sed -i "/$mwaccmuttrc/d" "$mwmuttrc" if [ "$mwtype" = "offline" ]; then sed -i "/IMAPStore $mwacc-remote$/,/# End profile/d" "$mwmbsyncrc" sed -i "/account $mwacc/,/^\(\s*$\|account\)/d" "$mwmsmtprc" else rm -rf "$mwacccachedir" fi } _mwcron() { ! pgrep cron >/dev/null && echo "No cron manager running. Install/enable one and then select this option again." && return 1 if crontab -l | grep "mw sync" >/dev/null; then echo "Active mail sync cronjob detected. Do you want to remove it?" printf "\033[36m\t" [ -z "$mwcronremove" ] && read -r mwcronremove printf "\033[0m" echo "$mwcronremove" | grep -i "^y\(es\)*$" >/dev/null && crontab -l | sed '/mw sync/d' | crontab - >/dev/null && echo "Mail sync turned off." else echo "How many minutes between each mail sync?" printf "\033[36m\t" [ -z $mwcronminutes ] && read -r mwcronminutes printf "\033[0m" while ! echo "$mwcronminutes" | grep "^[1-9]\+$" >/dev/null; do printf "That doesn't look like a number. How many minutes between each mail sync?\\n\033[36m\t" read -r mwcronminutes printf "\033[0m" done (crontab -l; echo "*/$mwcronminutes * * * * export DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$(id -u)/bus; export DISPLAY=:0; $(type mw | cut -d' ' -f3) sync") | crontab - && echo "Cronjob added. Mail will sync every $mwcronminutes minutes. Be sure you have your cron manager running." fi } _mwasktype() { if [ -z "$mwtype" ]; then while : ; do printf "[yes/no]: Local mail via mbsync? No: Mutt remotes (slower).\\n" printf "[pop]: Local mail from POP3 using getmail.\\n\t" read -r mwoffline case "$mwoffline" in [Yy][Ee][Ss]) mwtype="offline" && break ;; [Nn][Oo]) mwtype="online" && break ;; [Pp][Oo][Pp]) mwtype="pop" && break ;; *) echo "Write out either yes or no completely. Try again or press ctrl-c to quit." ;; esac done fi } _mwpurge() { _mwconfirm "delete all account data" || exit rm -rf "$mwaccmuttdir/[1-9]-.*.muttrc" "${mwmsmtprc%/*}" "${mwmbsyncrc%/*}" "$mwcachedir" "$mwgenmuttaccs" sed -i "/\# mw-autogenerated/d" "$mwmuttrc" pgrep cron >/dev/null && crontab -l | sed '/mw sync/d' | crontab - >/dev/null echo "All configs and account settings have been purged." } _mwnotmuchauto() { [ -z "$NOTMUCH_CONFIG" ] && NOTMUCH_CONFIG="$HOME/.notmuch-config" [ -f "$NOTMUCH_CONFIG" ] && return 0 mkdir -p "${NOTMUCH_CONFIG%/*}" cat > "$NOTMUCH_CONFIG" </dev/null 2>&1 || { export mwidnum="$mwx"; break ;}; done mwaccmuttrc="$mwaccmuttdir/$mwidnum-$mwacc.mwonofftype.$mwtype.muttrc" else mwidnum=${mwaccmuttrc%%-*} mwidnum=${mwidnum##*/} fi ! grep "source $mwaccmuttrc" "$mwgenmuttaccs" >/dev/null && echo "source $mwaccmuttrc" >> $mwgenmuttaccs mwmailboxes="$(find "$mwaccmaildir/" -name cur | sed "s,$mwaccmaildir/,,g;s,/cur,,")" [[ "$mwmailboxes" =~ ^[[:space:]]*$ ]] && echo "no mailboxes" && return mwspoolfile=$(echo "$mwmailboxes" | grep -i -m 1 inbox | sed -ne 's/.*/+\0/p') [[ "$mwspoolfile" =~ ^[[:space:]]*$ ]] && return mwrecord=$(echo "$mwmailboxes" | grep -i -m 1 sent | sed -ne 's/.*/+\0/p') [ -z "$mwrecord" ] && mkdir -p "$mwaccmaildir/Sent/"{cur,new,tmp} && mwrecord="Sent" mwpostponed=$(echo "$mwmailboxes" | grep -i -m 1 draft | sed -ne 's/.*/+\0/p') [ -z "$mwpostponed" ] && mkdir -p "$mwaccmaildir/Drafts/"{cur,new,tmp} && mwpostponed="Drafts" mwtrash=$(echo "$mwmailboxes" | grep -i -m 1 trash | sed -ne 's/.*/+\0/p') [ -z "$mwtrash" ] && mkdir -p "$mwaccmaildir/Trash/"{cur,new,tmp} && mwtrash="Trash" mwaccmutt="${mwaddr//[.@]/_}" mwmuttsync=$(printf 'source %s!;' $mwaccmuttrc) cat > "$mwaccmuttrc" <> "$mwaccmuttrc" < EOF for eqbox in $mwmailboxes; do box=${eqbox##*/} box=${box##*.} boxi=${box::2} boxi=${boxi,,} cat >> $mwaccmuttrc <=$eqbox" "go to $box" macro index,pager M$boxi "=$eqbox" "move mail to $box" macro index,pager C$boxi "=$eqbox" "copy mail to $box" EOF done if [ "$mwtype" = "offline" ]; then #can be online when called from mw cat >> "$mwaccmuttrc" <mw $mwaddr" "sync mail $mwaddr" set folder = "$mwaccmaildir" folder-hook \$folder '$mwmuttsync' EOF fi } if [ "$(uname)" = "Darwin" ]; then _mwnotify() { osascript -e "display notification \"$2 in $1\" with title \"You've got Mail\" subtitle \"Account: $1\"" & ;} else if command -v notify-send >/dev/null; then _mwnotify() { notify-send "mutt-wizard" "📬 $2 new mail(s) in \`$1\` account." &;} else _mwnotify() { echo "mutt-wizard: 📬 $2 new mail(s) in \`$1\` account." ;} fi function _mwaddr() { mwaddr=$1 mwaddr=${mwaddr/#\~/$HOME} mwaddr=${mwaddr#$mwmaildir/} mwaddr=${mwaddr%%/*} mkdir -p $mwmaildir/$mwaddr } function _mwgatheremails() { unset mwemails ! grep "source $mwgenmuttaccs" "$mwmuttrc" >/dev/null && echo "source $mwgenmuttaccs # mw-autogenerated" >> "$mwmuttrc" cat > "$mwgenmuttaccs" <mw' "sync all mail" EOF if [[ -f $mwmbsyncrc ]]; then for store in $(sed -ne 's/^Path\s*//p' $mwmbsyncrc); do _mwaddr "$store" mwemails+=" $mwaddr" done fi if [[ -d $mwgetmaildir ]]; then for gmrc in $mwgetmaildir/*; do store=$(sed -ne "s/^path\s*=\s*//p" $gmrc) store=${store/#\~/$HOME} _mwaddr "$store" mkdir -p "$store"/{cur,new,tmp} mwemails+=" $mwaddr" done fi } _mwsyncandnotify() { mwaccmaildir="$mwmaildir/$mwaddr" if [[ -f "$mwgetmaildir/$mwaddr" ]]; then $mwgetmailbin --rcfile=$mwaddr --getmaildir=$mwgetmaildir else $mwmbsyncbin $mwaddr fi mwlastrun="$mwaccmaildir/.mw.lastrun" mwnew=$(find "$mwaccmaildir/INBOX/new/" "$mwaccmaildir/Inbox/new/" "$mwaccmaildir/inbox/new/" -type f -newer "$mwlastrun" 2> /dev/null) newcount=$(echo "$mwnew" | sed '/^\s*$/d' | wc -l) if [ "$newcount" -gt "0" ]; then _mwnotify "$mwaddr" "$newcount" for file in $mwnew; do # Extract subject and sender from mail. from=$(awk '/^From: / && ++n ==1,/^\<.*\>:/' "$file" | perl -CS -MEncode -ne 'print decode("MIME-Header", $_)' | awk '{ $1=""; if (NF>=3)$NF=""; print $0 }' | sed 's/^[[:blank:]]*[\"'\''\<]*//;s/[\"'\''\>]*[[:blank:]]*$//') subject=$(awk '/^Subject: / && ++n == 1,/^\<.*\>: / && ++i == 2' "$file" | head -n-1 | perl -CS -MEncode -ne 'print decode("MIME-Header", $_)' | sed 's/^Subject: //' | sed 's/^{[[:blank:]]*[\"'\''\<]*//;s/[\"'\''\>]*[[:blank:]]*$//' | tr -d '\n') _mwnotify "📧$from:" "$subject" done fi touch "$mwlastrun" } function _mwsyncemails() { for mwaddr in $mwemails; do _mwsyncandnotify done rm $XDG_CONFIG_HOME/getmail/oldmail-* &>/dev/null echo notmuch+afew ... notmuch new &>/dev/null #notmuch also created afew MailMover target mailboxes } function _mwmuttemails() { for mwaddr in $mwemails; do _genmwaccmuttrc done } _mwcheckinternet() { # Checks for internet connection if command -v systemctl >/dev/null; then if ! systemctl --type service | grep net | grep active > /dev/null; then echo "No internet connection." return 1 fi else if ! ping -q -c 1 1.1.1.1 > /dev/null; echo "No internet connection (ping failed)." return 1 fi fi } function _mwsync() { unset mwemails # Run only if user logged in (prevent cron errors) if ! pgrep -u "$USER" >/dev/null; then echo "$USER not logged in; sync will not run." return 1 ; fi # Run only if not already running in other instance if pgrep -x mbsync >/dev/null; then echo "mbsync is running." return 1 fi if pgrep -x notmuch >/dev/null; then echo "notmuch is running." return 1 fi _mwcheckinternet || return 1 _mwaddr "$1" mwemails="$mwaddr" [[ $mwemails == "" ]] && _mwgatheremails && _mwsyncemails && _mwmuttemails && return 0 _mwsyncemails return 0 } trap 'echo -e "\033[0m\n"; exit' STOP INT ABRT KILL if [ "${BASH_SOURCE[0]}" = "${0}" ]; then if [ "$#" -gt 1 ]; then echo "Command or email or no parameter (=sync all). See --help." return 1 fi command -V gpg >/dev/null 2>&1 && GPG="gpg" || GPG="gpg2" [ -z "$PASSWORD_STORE_DIR" ] && PASSWORD_STORE_DIR="$HOME/.password-store" [ -r "$PASSWORD_STORE_DIR/.gpg-id" ] && "$GPG" --list-secret-keys $(cat "$PASSWORD_STORE_DIR/.gpg-id") >/dev/null 2>&1 || { printf "\`pass\` must be installed and initialized to encrypt passwords.\\nBe sure it is installed and run \`pass init \`.\\nIf you don't have a GPG public private key pair, run \`$GPG --full-gen-key\` first.\\n" exit } ! command -v "$prefix/bin/mbsync" >/dev/null && printf "\`mbsync\` must be installed to run mutt-wizard.\\n" && exit ! command -v "$prefix/bin/msmtp" >/dev/null && printf "\`msmtp\` must be installed.\\n" && exit ! ( command -v mutt >/dev/null || command -v neomutt >/dev/null ) && printf "\`mutt\` must be installed.\\n" && exit case "$1" in ls) _mwlist ;; add) _mwadd ;; pass) _mwpick "change the password of" && _mwgetpass ;; delete) _mwpick delete && _mwconfirm "delete the \`$mwacc\` profile" && _mwdelete ;; purge) _mwpurge ;; cron) _mwcron ;; sync) _mwsync ;; *@*) _mwsync $1 ;; --help|-h) cat << EOF mw: mutt-wizard, auto-configure and sync email accounts for mutt, including mutt-independent sync of mail with \`isync\` or download of pop3 mail with \`getmail\`. Commands: add Add and autoconfigure an email address (9 max.) ls List configured accounts delete Pick an account to delete purge Delete all accounts and settings cron Enable or disable an autosync via cronjob sync sync/download mail based on config in $mwmbsyncrc and $mwgetmaildir --help|-h Print this message With no parameter all configured emails are synced and all account muttrc's are created. With a parameter containing a @, an mail is assumed and that account is synced. Config requirements: - mbsyncrc: "Path" ends in email - getmail: config file name = email, "path" ends in email EOF esac exit $? fi