#!/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_accounts.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" mwgetmailbin="$prefix/bin/getmail" msmtpbin="$prefix/bin/msmtp" _mwtakeaddr(){ 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-$mwaddr.mwonofftype.$mwtype.muttrc" } _mwaddrs() { mwaddrs="$(find "$mwaccmuttdir" -type f | grep -o "[1-9]-.*.muttrc" | sed "s/-/: /;s/\.muttrc//" | sort -n)" } _mwlist() { _mwaddrs && [ -n "$mwaddrs" ] && echo "${mwaddrs//.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 $mwaddr host $mwsmtp port $mwsport from $mwaddr user $mwlogin passwordeval \"pass $mwpass\" $mwstarttlsoff " mwmbsyncprofile="IMAPStore $mwaddr-remote Host $mwserver Port $mwport User $mwlogin PassCmd \"pass $mwpass\" SSLType $mwssltype CertificateFile $mwsslcert MaildirStore $mwaddr-local Subfolders Verbatim Path $mwaccmaildir/ Inbox $mwaccmaildir/INBOX Flatten . Channel $mwaddr Expunge Both Master :$mwaddr-remote: Slave :$mwaddr-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.\\n" printf "Usually 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" printf "%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 $mwaddr/,/^\(\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 $mwaddr-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" ! grep "source $mwgenmuttaccs" "$mwmuttrc" >/dev/null && echo "source $mwgenmuttaccs # 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 $mwaddr/&\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 $mwaddr | 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\\n" printf "It 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 _mwgenmwaccmuttrc if [ "$mwtype" = "online" ]; then mkdir -p "$mwacccachedir" sed -i "/IMAPStore $mwaddr-remote$/,/# End profile/d" "$mwmbsyncrc" [ "$mwaccmaildirWasThere" = "NO" ] && rm -rf "$mwaccmaildir" else _mwnotmuchauto printf "All done.\\n\033[33mYou can now run \`\033[32mmw [%s]\033[33m\` to sync your mail.\033[0m\\n" "$mwaddr" fi } _mwconfirm() { printf "[y/N]: Do you want to %s?\\n\t" "$@" && read -r mwinput && ! echo "$mwinput" | grep -i "^y$\|^yes$" >/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 "$mwaddrs" | grep -i -m 1 "$mwidnum:" | sed "s/\.mwonofftype\./ /" | awk '{print $2}')" mwtype="$(echo "$mwaddrs" | grep -i -m 1 "$mwidnum:" | sed "s/\.mwonofftype\./ /" | awk '{print $3}')" _mwtakeaddr [ -z "$mwaddr" ] && printf "Invalid response." && return 1 return 0 ; } _mwdelete() { rm -f "$mwaccmuttrc" [ -d "$mwgetmaildir" ] && rm -f "$mwgetmaildir/$mwaddr" sed -i "/${mwaccmuttrc//\//\\\/}/d" "$mwgenmuttaccs" if [ "$mwtype" = "offline" ]; then sed -i "/IMAPStore $mwaddr-remote$/,/# End profile/d" "$mwmbsyncrc" sed -i "/account $mwaddr/,/^\(\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-$mwaddr.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/ /}" = "" ] && echo "no mailboxes" && return mwspoolfile=$(echo "$mwmailboxes" | grep -i -m 1 inbox | sed -ne 's/.*/+\0/p') [ "${mwspoolfile/ /}" = "" ] && 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" mwmailboxes="$(find "$mwaccmaildir/" -name cur | sed "s,$mwaccmaildir/,,g;s,/cur,,")" mwaccmutt="${mwaddr//[.@]/_}" mwmuttsync=$(printf 'source %s!;' $mwaccmuttrc) cat > "$mwaccmuttrc" <> "$mwaccmuttrc" <mw $mwaddr" "sync mail $mwaddr" set folder = "$mwaccmaildir" folder-hook \$folder '$mwmuttsync' EOF else cat >> "$mwaccmuttrc" <> "$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 [ "$(uname)" = "Darwin" ]; then _mwnotify() { osascript -e "display notification \"$2 in $1\" with title \"You've got Mail\" subtitle \"Account: $1\"" ;} else if type 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 fi _mwaddr() { mwaddr=$1 mwaddr=${mwaddr/#\~/$HOME} mwaddr=${mwaddr#$mwmaildir/} mwaddr=${mwaddr%%/*} mkdir -p $mwmaildir/$mwaddr } _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 [ "${gmrc#$mwgetmaildir}" = "/*" ] && break store=$(sed -ne "s/^path\s*=\s*//p" $gmrc) store=${store/#\~/$HOME} _mwaddr "$store" mkdir -p "$store"/{cur,new,tmp} mwemails+=" $mwaddr" done fi mwemails="${mwemails/ /}" } _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" } _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 } _mwmuttemails() { for mwaddr in $mwemails; do _mwgenmwaccmuttrc done } _mwcheckinternet() { # Checks for internet connection if type 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; then echo "No internet connection (ping failed)." return 1 fi fi } _mwsync() { unset mwemails mwtype="offline" # 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 type 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 \`.\\n" printf "If you don't have a GPG public private key pair, run \`$GPG --full-gen-key\` first.\\n" exit } ! type "$prefix/bin/mbsync" >/dev/null && printf "\`mbsync\` must be installed to run mutt-wizard.\\n" && exit ! type "$prefix/bin/msmtp" >/dev/null && printf "\`msmtp\` must be installed.\\n" && exit ! ( type mutt >/dev/null || type neomutt >/dev/null ) && printf "\`mutt\` must be installed.\\n" && exit if [ "$1" = "" ]; then _mwsync else case "$1" in ls) _mwlist ;; add) _mwadd ;; pass) _mwpick "change the password of" && _mwgetpass ;; delete) _mwpick delete && _mwconfirm "delete the \`$mwaddr\` 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 fi