#!/bin/sh if [ -z "$prefix" ]; then case "$(uname)" in Linux) prefix="/usr" ;; *) prefix="/usr/local" ;; esac fi VERSION="2.0" _MW_="mutt-wizard" _MW_generated="mutt-wizard-generated" 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/$_MW_" mwmbsyncrc="$mwconfigdir/isync/mbsyncrc" mwgetmaildir=$mwconfigdir/getmail # POP3 mwmsmtprc="$mwconfigdir/msmtp/config" mwsharerc="$mwshare/$_MW_.muttrc" mwcachedir="${XDG_CACHE_HOME:-$HOME/.cache}/$_MW_" 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 [ -z "$mwpass" ] || mwpass=$_MW_-$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() { 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" return 1 } _mwcheckcert && _mwcheckinternet && _mwasktype && _mwaskinfo && _mwprofiles && _mwtryconnect && _mwfinalize || _mwdelete } _mwgetpass() { while : ; do pass rm -f "$mwpass" >/dev/null 2>&1 pass insert "$mwpass" && break; done ; } _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 $_MW_.\\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 mwurl 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 mwsurl 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" "$mwurl" "$mwport" "$mwsurl" "$mwsport" else # found server info IFS=, read -r mwservice mwurl mwport mwsurl mwsport < "$mwmsmtprc" </dev/null fi cat >> "$mwmsmtprc" </dev/null } _mwmbsync() { mkdir -p "${mwmbsyncrc%/*}" [ -f "$mwmbsyncrc" ] && sed -i "/IMAPStore $mwaddr-remote$/,/# End $_MW_ profile/d" "$mwmbsyncrc" cat >> "$mwmbsyncrc" < "$mwgetmaildir/$mwaddr" </dev/null && printf "\033[31mFor POP3 \`getmail\` must be installed to use \`mw\`. You can do that afterwards.\\n\033[0m" _mwpop else ! type "$prefix/bin/mbsync" >/dev/null && printf "\033[31mFor IMAP \`mbsync\` must be installed to use \`mw\`. You can do that afterwards.\\n\033[0m" _mwmbsync fi _mwsmtp fi return 0 } _mwtryconnect() { if [ ! -d "$mwaccmaildir" ]; then [ ! "${mwkind::3}" = "pop" ] && mwaccmaildirWasThere="NO" # mutt uses $mwacccachedir for "online" IMAP, so clean up later mkdir -p "$mwaccmaildir" fi if [ -z "$mwmailboxes" ]; then #just check server, no mail retrieval if [ "${mwkind::3}" = "pop" ]; then [ "$mwport" = "110" ] && mwp="POP3" || mwp="POP3_SSL" python -c "from poplib import $mwp;p=$mwp('$mwurl');print(p.getwelcome());p.quit()" | \ grep "+OK" && mwmailboxes="INBOX" else # for online do without mbsync, i.e. don't require it for mail setup, either # mwmailboxes="$($mwmbsyncbin -l $mwaddr | sed 's/\//./')" >/dev/null 2>&1 [ "$mwport" = "143" ] && mwi="IMAP4" || mwi="IMAP4_SSL" mwpw="$(pass $mwpass)" mwmailboxes="$(python -c "from imaplib import $mwi;m=$mwi('$mwurl');m.login('$mwlogin','$mwpw');b=m.list()[1];print('\n'.join(e.split(b' \"/\" ')[-1].strip(b'\"').decode() for e in b if all(x not in e for x in [b'[Gmail]/All Mail',b'Noselect',b'HasChildren'])));m.logout()")" fi fi if [ -n "$mwmailboxes" ]; then mwspoolfile=$(echo "$mwmailboxes" | grep -i -m 1 inbox | sed -ne 's/.*/+\0/p') [ -z "$mwspoolfile" ] && printf "\033[31mNo spoolfile.\033[0m\\n" && return 1 printf "\033[32mMailboxes detected.\033[0m\\n" if [ -z "$mwserverinfo" ]; then printf "\033[32mPlease add the line below at https://github.com/rpuntaie/mutt-wizard/issues/1\033[0m\\n\\n" printf "\033[31m$mwservice,$mwurl,$mwport,$mwsurl,$mwsport\0330m\\n\\n" fi echo "$mwmailboxes" | xargs -I {} mkdir -p "$mwaccmaildir/{}/"{cur,new,tmp} return 0 else printf "\033[31mLog-on not successful.\033[0m\\n" printf "It seems that either you entered the wrong password or server settings, or there are other requirements for your account out of the control of $_MW_.\\n" return 1 fi } _mwfinalize() { # new mwaccmuttrc if [ "$mwtype" = "online" ]; then if [ ! "${mwkind::3}" = "pop" ]; then mkdir -p "$mwacccachedir" fi fi _mwgenmwaccmuttrc || return 1 if [ "$mwtype" = "offline" ]; then # not online _mwnotmuchauto printf "All done.\\n\033[33mYou can now run \`\033[32mmw [%s]\033[33m\` to sync your mail.\033[0m\\n" "$mwaddr" elif [ "${mwkind::3}" = "pop" ]; then # and online _mwnotmuchauto #still usable for online (in mutt) pop fi if [ "$mwtype" = "online" ]; then [ "$mwaccmaildirWasThere" = "NO" ] && rm -rf "$mwaccmaildir" &>/dev/null fi return 0 } _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() { [ "$mwaccmaildirWasThere" = "NO" ] && rm -rf "$mwaccmaildir" &>/dev/null rm -f "$mwaccmuttrc" &>/dev/null if [ "$mwtype" = "offline" ]; then # not online sed -i "/${mwaccmuttrc//\//\\\/}/d" "$mwgenmuttaccs" &>/dev/null sed -i "/account $mwaddr/,/^\(\s*$\|account\)/d" "$mwmsmtprc" &>/dev/null if [ -f "$mwgetmaildir/$mwaddr" ]; then rm -f "$mwgetmaildir/$mwaddr" &>/dev/null else sed -i "/IMAPStore $mwaddr-remote$/,/# End $_MW_ profile/d" "$mwmbsyncrc" &>/dev/null fi else sed -i "/${mwaccmuttrc//\//\\\/}/d" "$mwmuttrc" &>/dev/null rm -rf "$mwacccachedir" &>/dev/null 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 "Do you want to sync your mails using the command 'mw' outside mutt? [yes/no]\\n" printf "'no' makes Neomutt slow for IMAP accounts, so probably you want 'yes'.\\n\t" read -r mwoffline case "$mwoffline" in [Yy][Ee][Ss]) mwtype="offline" && break ;; [Nn][Oo]) mwtype="online" && break ;; *) echo "Write out either yes or no completely. Try again or press ctrl-c to quit." ;; esac done fi if [ "$mwtype" == "offline" ]; then ! type "$prefix/bin/msmtp" >/dev/null && printf "\033[31m\`msmtp\` must be installed. You can do that afterwards.\\n\033[0m" else ! ( type mutt >/dev/null || type neomutt >/dev/null ) && printf "\033[31m\`mutt\` must be installed. You can do that afterwards.\\n\033[0m" fi return 0 } _mwpurge() { _mwconfirm "Remove all $_MW_ data" || exit mwaddrs="$(find "$mwaccmuttdir" -type f | grep -o "[1-9]-.*.muttrc" | sed "s/\.muttrc//")" for mwaddrsentry in $mwaddrs; do mwtype="$(echo "$mwaddrsentry" | sed "s/\.mwonofftype\./ /" | awk '{print $2}')" mwaddr="$(echo "$mwaddrsentry" | sed "s/\.mwonofftype\./ /" | awk '{print $1}')" mwidnum="$(echo "$mwaddr" | sed "s/-/ /" | awk '{print $1}')" mwaddr="$(echo "$mwaddr" | sed "s/-/ /" | awk '{print $2}')" _mwtakeaddr _mwdelete done rm -rf "$mwaccmuttdir"/[1-9]-*.muttrc "$mwcachedir" "$mwgenmuttaccs" sed -i "/\# $_MW_generated/d" "$mwmuttrc" 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 # link mwaccmuttrc ... if [ "$mwtype" = "offline" ]; then ! grep "source $mwaccmuttrc" "$mwgenmuttaccs" &>/dev/null && echo "source $mwaccmuttrc" >> $mwgenmuttaccs else #online ! grep "source $mwaccmuttrc" "$mwmuttrc" &>/dev/null && echo "source $mwaccmuttrc # $_MW_generated" >> $mwmuttrc fi # (re-)generate mwaccmuttrc ... mwmuttsync=$(printf 'source %s!;' $mwaccmuttrc) cat > "$mwaccmuttrc" <> "$mwaccmuttrc" <mw $mwaddr" "sync mail $mwaddr" set folder = "$mwaccmaildir" folder-hook \$folder '$mwmuttsync' set sendmail = "/usr/bin/msmtp -a \$from" EOF else # online if [ "${mwkind::3}" = "pop" ]; then cat >> "$mwaccmuttrc" <> "$mwaccmuttrc" <> "$mwaccmuttrc" <> "$mwaccmuttrc" <> "$mwaccmuttrc" < EOF IFS=$'\n' for eqbox in $mwmailboxes; do qbox="${eqbox/\[/}" b1="${qbox::1}" box="${qbox##*/}" [ "$box" = "$qbox" ] && b2="${qbox:1:1}" || b2="${box:1:1}" boxi="$b1$b2" boxi="${boxi,,}" cat >> $mwaccmuttrc <=$eqbox" "go to $eqbox" macro index,pager M$boxi "=$eqbox" "move mail to $eqbox" macro index,pager C$boxi "=$eqbox" "copy mail to $eqbox" EOF done unset IFS return 0 } 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 "mw" "📬 $2 new mail(s) in \`$1\` account." ;} else _mwnotify() { echo "mw: 📬 $2 new mail(s) in \`$1\` account." ;} fi fi _mwaddr() { mwaddr=$1 mwaddr=${mwaddr/#\~/$HOME} mwaddr=${mwaddr#$mwmaildir/} mwaddr=${mwaddr%%/*} mkdir -p $mwmaildir/$mwaddr } _mwgenmuttaccs(){ mkdir -p "$mwmuttdir" "$mwaccmuttdir" [ ! -f "$mwmuttrc" ] && echo "# vim: filetype=muttrc" > "$mwmuttrc" && echo "muttrc created." ! grep "source $mwsharerc" "$mwmuttrc" >/dev/null && echo "source $mwsharerc # $_MW_generated" >> "$mwmuttrc" ! grep "source $mwgenmuttaccs" "$mwmuttrc" >/dev/null && echo "source $mwgenmuttaccs # $_MW_generated" >> "$mwmuttrc" cat > "$mwgenmuttaccs" <mw' "sync all mail" EOF } _mwgatheremails() { unset mwemails _mwgenmuttaccs 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 new ... notmuch new &>/dev/null } _mwmuttemails() { for mwaddr in $mwemails; do _mwgenmwaccmuttrc done } _mwcheckcert() { 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 ;} } _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 if [ "$1" = "" ]; then _mwsync else case "$1" in list|ls) _mwlist ;; add) _mwadd ;; pass) _mwpick "change the password of" && _mwgetpass ;; remove|rm) _mwpick remove && _mwconfirm "remove the \`$mwaddr\` profile" && _mwdelete ;; purge) _mwpurge ;; cron) _mwcron ;; sync) _mwsync ;; *@*) _mwsync $1 ;; --version|-v) echo "$_MW_ $VERSION" ;; --help|-h) cat <