muw (12191B)
1 #!/bin/sh -e 2 3 out() { printf '%s\n' "$@" >&2 ;} 4 err() { printf 'err: %s\n' "$@" >&2 ;} 5 die() { err "$@"; exit 1 ;} 6 prompt() { 7 # The first argument is used as the prompt, and will print as 'prompt: '. 8 # Any other arguments will be printed before prompt if they exist. 9 prompt=$1 ans= 10 [ "$2" ] && { shift 1; out "$@" ;} 11 printf '%s: ' "$prompt" >&2 12 read -r ans || return 13 printf %s "$ans" 14 } 15 prompt_required() { 16 promptout=''; promptout=$(prompt "$@") 17 until [ "$promptout" ]; do 18 out "This is required." 19 promptout=$(prompt "$@") 20 done 21 printf %s "$promptout" 22 } 23 prompt_noecho() { 24 # This is the same with the prompt() function except that it doesn't echo 25 # the user input to the terminal. It can be used to ask for passwords and 26 # other secret information. 27 stty=$(stty -g); stty -echo 28 prompt "$@"; printf '\n' >&2 29 stty "$stty" 30 } 31 32 getbut() { 33 # This function can be used to get a single button input. No arguments 34 # necessary. 35 stty=$(stty -g); stty -icanon -echo 36 dd bs=1 count=1 2>/dev/null 37 stty "$stty" 38 } 39 40 yesno() { 41 # Function to ask the user yes/no questions. Returns 0 if the button 42 # received is Y/y, Returns 1 if the button received is N/n, and returns 43 # 2 if any other button is received. 44 printf '%s\n' "$@"; printf 'y/n: ' 45 ans=$(getbut) 46 printf '%s\n' "$ans" >&2 47 case "$ans" in Y|y) return 0;; N|n) return 1; esac; return 2 48 } 49 50 delete() { 51 rm -f "$accountdir/$title.el" 52 sed_i "/^IMAPStore $title-remote\$/,/^# End profile\$/d" "$HOME/.mbsyncrc" 53 sed_i "/^account $title\$/,/^# End profile\$/d" "$config_home/msmtp/config" 54 rm -f /tmp/mbsync-boxes 55 } 56 57 contains() { 58 # Check whether a list contains the given value 59 # Example: 'contains "a" "a b c d e f"' returns 0 60 case " $2 " in *" $1 "*) return 0; esac; return 1 61 } 62 63 sed_i() { 64 # 'sed -i' like function without actually running it. This can be only used 65 # for a single file, and the filename should always come last. 66 eval "[ -f \$$# ] || return 1" 67 sed "$@" > _; for file; do :; done; mv _ "$file" 68 } 69 70 get_profiles() { 71 # Function to get all available profiles 72 unset profiles 73 for profile in "$accountdir"/*.el; do 74 [ -f "$profile" ] || continue 75 profile=${profile##*/} 76 profiles="${profile%.el} $profiles" 77 done 78 } 79 80 msmtp_header() { 81 mkdir -p "$config_home/msmtp" \ 82 "${XDG_CACHE_HOME:=$HOME/.cache}/msmtp" 83 cat <<EOF > "$config_home/msmtp/config" 84 defaults 85 auth on 86 tls on 87 tls_trust_file $sslcert 88 logfile $XDG_CACHE_HOME/msmtp/msmtp.log 89 EOF 90 } 91 92 pm_ask() { 93 case ${pass_prog##*/} in 94 pass) pass insert "$pmt" 95 password_command="pass show $pmt" ;; 96 pash) trap 'delete; rm -f _' EXIT INT 97 sed 's/yn "Gen/false "Gen/g' "$(command -v pash)" >_ 98 sh _ add "$pmt" 99 rm -f _; trap delete EXIT INT 100 password_command="pash show $pmt" ;; 101 pm) pass=$(prompt_noecho "Enter your password") 102 pass2=$(prompt_noecho "Enter your password again") 103 if [ "$pass" = "$pass2" ]; then 104 pm add "$pmt" <<EOF 105 $pass 106 EOF 107 password_command="pm show $pmt" 108 else 109 err "Passwords don't match" 110 return 1 111 fi 112 esac 113 } 114 115 pm_del() { 116 case ${pass_prog##*/} in 117 pass) pass rm -f "$pmt" ;; 118 pash) yes | pash del "$pmt" ;; 119 pm) pm del "$pmt" 120 esac >/dev/null 2>&1 ||: 121 } 122 123 test_connection() { 124 mkdir -p "$maildir/$title" 125 126 # Since we are "Flattening" the inbox structure by replacing '/' with '.', 127 # we need to also replace it in the 'mbsync -l' output by hand. See the 128 # Flatten section on mbsync(1). 129 { mbsync -l "$title" || die "Log-on not successful." \ 130 "It seems that either you have inputted the wrong password or server settings," \ 131 "or there are requirements for your account out of the control of mu-wizard." 132 } | sed 's|/|.|g' > /tmp/mbsync-boxes 133 134 while read -r dir; do mkdir -p "$maildir/$title/${dir#/}"; done < /tmp/mbsync-boxes 135 136 drafts=$(grep -i drafts /tmp/mbsync-boxes | sed 1q) 137 trash=$(grep -i trash /tmp/mbsync-boxes | sed 1q) 138 inbox=$(grep -i inbox /tmp/mbsync-boxes | sed 1q) 139 sent=$(grep -i sent /tmp/mbsync-boxes | sed 1q) 140 141 rm -f /tmp/mbsync-boxes 142 } 143 144 get_domains() { 145 if [ -f "$confdir/domains.csv" ]; then 146 { [ -f "$sharedir/domains.csv" ] && cat "$sharedir/domains.csv" 147 cat "$confdir/domains.csv" ;} | sort -uo "$confdir/domains.csv" 148 else 149 if [ -f "$sharedir/domains.csv" ]; then 150 cat "$sharedir/domains.csv"; fi > "$confdir/domains.csv" 151 fi 152 } 153 154 set_mbsync() { 155 cat <<EOF >> "$HOME/.mbsyncrc" 156 IMAPStore $title-remote 157 Host $imap 158 Port $iport 159 User $login 160 PassCmd "$password_command" 161 SSLType $ssltype 162 CertificateFile $sslcert 163 164 MaildirStore $title-local 165 Subfolders Verbatim 166 Path ~/.local/share/mail/$title/ 167 Inbox ~/.local/share/mail/$title/INBOX 168 Flatten . 169 170 Channel $title 171 Expunge Both 172 Master :$title-remote: 173 Slave :$title-local: 174 Patterns * !"[Gmail]/All Mail" 175 Create Both 176 SyncState * 177 MaxMessages ${maxmes:-0} 178 ExpireUnread no 179 # End profile 180 181 EOF 182 } 183 184 set_msmtp() { 185 [ -f "$config_home/msmtp/config" ] || msmtp_header 186 187 cat <<EOF >> "$config_home/msmtp/config" 188 account $title 189 host $smtp 190 port $sport 191 from $fulladdr 192 user $login 193 passwordeval "$password_command" 194 $starttlsoff 195 # End profile 196 197 EOF 198 } 199 200 set_accountlisp() { 201 cat <<EOF > "$accountdir/$title.el" 202 (add-to-list 'mu4e-contexts 203 (make-mu4e-context 204 :name "$title" 205 :match-func (lambda (msg) 206 (when msg 207 (mu4e-message-contact-field-matches msg 208 :to "$fulladdr"))) 209 :vars '((user-mail-address . "$fulladdr") 210 (message-sendmail-extra-arguments . ("-a" "$title")) 211 (mu4e-sent-messages-behavior . $msg_behavior) 212 (mu4e-drafts-folder . "/$title/${drafts:=Drafts}") 213 (mu4e-trash-folder . "/$title/${trash:=Trash}") 214 (mu4e-sent-folder . "/$title/${sent:=Sent}") 215 (mu4e-maildir-shortcuts . (("/$title/${inbox:-INBOX}" . ?i) 216 ("/$title/$drafts" . ?d) 217 ("/$title/${sent:-Sent}" . ?s))) 218 (user-full-name . "$realname")))) 219 EOF 220 221 } 222 223 get_information() { 224 for file in /etc/ssl/certs/ca-certificates.crt /etc/pki/tls/certs/ca-bundle.crt \ 225 /etc/certificates/cert.pem /etc/ssl/ca-bundle.pem /etc/ssl/cert.pem \ 226 /etc/pki/tls/cacert.pem /usr/share/ca-certificates; do 227 [ -r "$file" ] || continue 228 sslcert=$file; break 229 done 230 231 fulladdr=$(prompt_required Email \ 232 "Insert the email address that you want to configure for mu4e") 233 234 # Check the override directory for possible alterations of the 235 # configuration. If the override is found on the configuration dirctory, it 236 # will be used instead. 237 for file in "$confdir/overrides/"* "$sharedir/overrides/"*; do 238 [ -f "$sharedir/overrides/${fulladdr##*@}" ] && { 239 domain=${fulladdr##*@} 240 # shellcheck disable=1090 241 . "$sharedir/overrides/$domain" 242 break 243 } 244 done 245 246 [ "$domain" ] || while IFS=, read -r domain imap iport smtp sport; do 247 case "$domain" in "${fulladdr##*@}") break; esac 248 done < "$confdir/domains.csv" 249 250 if [ "$domain" = "${fulladdr##*@}" ]; then 251 out "Your domain information was found!'" \ 252 "The following information will be used:" ""\ 253 "Domain: $domain" \ 254 "IMAP: $imap:$iport" \ 255 "SMTP: $smtp:$sport" 256 else 257 imap=$(prompt Server "Insert the IMAP server for your email provider" \ 258 "(excluding the port number)") 259 260 iport=$(prompt Port \ 261 "What is your server's IMAP port number? (Usually 993)") 262 263 smtp=$(prompt Server "Insert the SMTP server for your email provider" \ 264 "(excluding the port number)") 265 266 sport=$(prompt Port \ 267 "What is your server's SMTP port number? (Usually 587)") 268 fi 269 270 271 realname=$(prompt "Real name" \ 272 "Enter your full name you want to be identified on this account.") 273 274 title=$(prompt_required "Account name" \ 275 "Enter a short, one-word identifier for this email account that will" \ 276 "distinguish them from any other accounts you add") 277 278 while contains "$title" "$profiles"; do 279 out "The title '$title' is already used." 280 title=$(prompt_required "Account name" \ 281 "Enter a short, one-word identifier for this email account that will" \ 282 "distinguish them from any other accounts you add") 283 done 284 285 login=$(prompt "Login" \ 286 "If your account has a special username different from your address," \ 287 "insert it now. Otherwise leave this blank.") 288 289 # If login is empty use fulladdr 290 [ "$login" ] || login="$fulladdr" 291 292 maxmes=$(prompt "Maximum messages" \ 293 "If you want to limit the number of messages kept offline to a number," \ 294 "enter it below. Otherwise leave this blank.") 295 296 case "$sport" in 465) starttlsoff="tls_starttls off"; esac 297 } 298 299 main() { 300 pass_prog=${MUW_PWM:-$(command -v pass || command -v pash || command -v pm)} || 301 die "No applicable password manager found." 302 command -v mbsync >/dev/null || die "mbsync must be installed for mu-wizard to work." 303 304 sharedir=/usr/share/mu-wizard 305 ssltype=IMAPS 306 msg_behavior=sent 307 308 mkdir -p "${config_home:=${XDG_CONFIG_HOME:-$HOME/.config}}" \ 309 "${confdir:=$config_home/mu4e}" \ 310 "${accountdir:=$confdir/accounts}" \ 311 "${maildir:=$HOME/.local/share/mail}" 312 313 get_profiles 314 315 case "$1" in 316 l|list) for profile in $profiles; do out " $profile"; done ;; 317 318 a|add) 319 get_domains 320 get_information 321 trap delete INT EXIT 322 pmt="mu-wizard-$title" 323 while :; do pm_del; pm_ask && break; done 324 set_mbsync 325 test_connection 326 set_msmtp 327 set_accountlisp 328 [ -f "$confdir/mu4e-config.el" ] || cp "$sharedir/mu4e-config.el" "$confdir/mu4e-config.el" 329 330 # We add user's personal domain information to the config directory, 331 # so even if the user doesn't want to share the information in a git 332 # repository, they can have it themselves when they may need it. 333 334 printf '%s,%s,%s,%s,%s' \ 335 "${fulladdr##*@}" "$imap" "$iport" "$smtp" "$sport" >> "$confdir/domains.csv" 336 trap - INT EXIT 337 out "All done. You can now run 'mbsync $title' in order to sync this account." 338 ;; 339 340 d|delete) 341 out "Pick a profile to be deleted" 342 for profile in $profiles; do out " $profile"; done 343 read -r title 344 contains "$title" "$profiles" || die "Profile '$title' doesn't exist." 345 yesno "Are you sure you want to delete '$title'?" && delete 346 ;; 347 p|purge) 348 yesno "Are you sure you want to delete all account data?" || exit 1 349 rm -rf "$HOME/.mbsyncrc" "$confdir" "$config_home/msmtp" 350 ;; 351 s|share) 352 # Output the share directory and exit. 353 printf '%s\n' "$sharedir" ;; 354 ''|--help|-h) 355 out "usage: ${0##*/} [action]" \ 356 "mu-wizard, auto-configure email accounts for mu4e" \ 357 " Options:" \ 358 " [a]dd: Add and autoconfigure an email address" \ 359 " [d]elete: Pick an account to delete" \ 360 " [l]ist: List configured accounts" \ 361 " [p]urge: Purge all configuration" 362 " [s]hare: See your share directory" "" \ 363 "NOTE: Once at least one account is added, you can run" \ 364 "'mbsync -a' to begin downloading mail." 365 ;; 366 *) die "Unknown action '$1'" 367 esac 368 369 } 370 371 main "$@"