edmail 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864
  1. #!/bin/sh
  2. # This file defines the edmail mail user agent.
  3. # Copyright (C) 2017-2018 mlaine@sdfeu.org
  4. # Permission is hereby granted, free of charge, to any person obtaining
  5. # a copy of this software and associated documentation files (the
  6. # "Software"), to deal in the Software without restriction, including
  7. # without limitation the rights to use, copy, modify, merge, publish,
  8. # distribute, sublicense, and/or sell copies of the Software, and to
  9. # permit persons to whom the Software is furnished to do so, subject to
  10. # the following conditions:
  11. # The above copyright notice and this permission notice shall be
  12. # included in all copies or substantial portions of the Software.
  13. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  14. # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  15. # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
  16. # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
  17. # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
  18. # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
  19. # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  20. #-----------------------------------------------------------------------
  21. monnum () {
  22. case "$mon" in
  23. 'Jan') mon=1 ;;
  24. 'Feb') mon=2 ;;
  25. 'Mar') mon=3 ;;
  26. 'Apr') mon=4 ;;
  27. 'May') mon=5 ;;
  28. 'Jun') mon=6 ;;
  29. 'Jul') mon=7 ;;
  30. 'Aug') mon=8 ;;
  31. 'Sep') mon=9 ;;
  32. 'Oct') mon=10 ;;
  33. 'Nov') mon=11 ;;
  34. 'Dec') mon=12 ;;
  35. esac
  36. }
  37. pad () {
  38. case "$1" in
  39. [0-9]) printf '0%s\n' "$1" ;;
  40. *) printf '%s\n' "$1" ;;
  41. esac
  42. }
  43. parsedate () {
  44. date="$(awk '/^Date: /{print;exit}' "$file" | cut -d' ' -f2-)"
  45. set -- $(printf '%s\n' "${date#*, }")
  46. day="${1#0}"; mon="$2"; year="$3"; time="${4%:*}"; tz="${5#*[+-]}"
  47. hrs="${time%:*}"; hrs="${hrs#0}"
  48. min="${time#*:}"; min="${min#0}"
  49. tzhrs="${tz%??*}"; tzhrs="${tzhrs#0}"
  50. tzmin="${tz#*??}"; tzmin="${tzmin#0}"
  51. monnum
  52. case "$5" in
  53. '+0000'|'-0000'|'GMT')
  54. true ;;
  55. '+'*)
  56. hrs="$((hrs-tzhrs))"
  57. min="$((min-tzmin))"
  58. test "$min" -lt 00 && min="$((min+60))" && hrs="$((hrs-1))"
  59. test "$hrs" -lt 00 && hrs="$((hrs+24))" && day="$((day-1))"
  60. case "$mon" in
  61. 3)
  62. test "$day" -lt 1 && day=28 && mon="$((mon-1))" ;;
  63. 5|7|10|12)
  64. test "$day" -lt 1 && day=30 && mon="$((mon-1))" ;;
  65. *)
  66. test "$day" -lt 1 && day=31 && mon="$((mon-1))" ;;
  67. esac ;;
  68. '-'*)
  69. hrs="$((hrs+tzhrs))"
  70. min="$((min+tzmin))"
  71. test "$min" -gt 59 && min="$((min%60))" && hrs="$((hrs+1))"
  72. test "$hrs" -gt 23 && hrs="$((hrs%24))" && day="$((day+1))"
  73. case "$mon" in
  74. 2)
  75. test "$day" -gt 28 && day=1 && mon="$((mon+1))" ;;
  76. 4|6|9|11)
  77. test "$day" -gt 30 && day=1 && mon="$((mon+1))" ;;
  78. *)
  79. test "$day" -gt 31 && day=1 && mon="$((mon+1))" ;;
  80. esac ;;
  81. *)
  82. time='??:??' ;;
  83. esac
  84. test "$mon" -gt 12 && mon=1 && year="$((year+1))"
  85. test "$mon" -lt 1 && mon=12 && year="$((year-1))"
  86. mon="$(pad "$mon")"; day="$(pad "$day")"
  87. hrs="$(pad "$hrs")"; min="$(pad "$min")"
  88. test "$time" = '??:??' || time="$hrs:$min"
  89. }
  90. awkmatch () {
  91. awk -v p="$pat" 'BEGIN{s=1}$0~p{s=0;exit}END{exit s}' "$file"
  92. }
  93. update () {
  94. if [ -n "$filter" ]; then
  95. true > "$templist"
  96. find "$dir" -type f |
  97. while read -r file; do
  98. if awkmatch; then
  99. printf '%s\n' "$file" >> "$templist"
  100. fi
  101. done
  102. else
  103. find "$dir" -type f > "$templist"
  104. fi
  105. if ! cmp -s "$templist" "$filelist"; then
  106. cp "$templist" "$filelist"
  107. true > "$statlist"
  108. while read -r file; do
  109. set -- $(wc < "$file")
  110. lines="$1"; bytes="$3"
  111. parsedate; date="$year $mon $day $hrs $min"
  112. printf '%s %s\n' "$file" "$lines $bytes $date" >> "$statlist"
  113. done < "$filelist"
  114. n="$(awk 'END{print NR}' "$filelist")"
  115. fi
  116. if [ -n "$bysize" ]; then
  117. sort -k 3,3nr -o "$statlist" "$statlist"
  118. elif ! cmp -s "$tempstat" "$statlist"; then
  119. sort -k 4,4n -k 5,5n -k 6,6n -k 7,7n -k 8,8n -o "$statlist" "$statlist"
  120. cp "$statlist" "$tempstat"
  121. fi
  122. }
  123. archive () {
  124. find "$new" -type f > "$templist"
  125. newn="$(awk 'END{print NR}' "$templist")"
  126. test "$newn" -eq 0 && return 1
  127. while read -r file; do
  128. filename="${file##*/}"
  129. mv "$file" "${cur}/${filename%:*}:2,S"
  130. done < "$templist"
  131. }
  132. loop () {
  133. i=0; j=0
  134. while read -r line; do
  135. i="$((i+1))"
  136. if [ "$cmd" = 'z' ]; then
  137. true
  138. elif [ "$i" -le "$upper" ]; then
  139. if [ "$i" -ge "$lower" ]; then
  140. j="$((j+1))"
  141. else
  142. continue
  143. fi
  144. else
  145. break
  146. fi
  147. "$1" "$line"
  148. done < "$statlist"
  149. }
  150. term () {
  151. cols="$(tput cols)"
  152. rows="$(tput lines)"
  153. rows="$((rows-3))"
  154. test "$rows" -lt 10 && rows=10
  155. upper="$((cursor+rows-6))"
  156. test "$upper" -gt "$n" && upper="$n"
  157. lower="$((upper-rows+1))"
  158. if [ "$lower" -lt 1 ]; then
  159. upper="$((upper-lower+1))"
  160. lower=1
  161. fi
  162. }
  163. field () {
  164. printf '%s\n' "$head" | awk -v p="^$1: " '$0~p{print;exit}' | cut -d' ' -f2-
  165. }
  166. brief () {
  167. set -- $1
  168. file="$1"; lines="$2"; bytes="$3"; date="$5/$6/$4 $7:$8"
  169. head="$(awk 'BEGIN{RS=""}NR==1{print;exit}' "$file")"
  170. case "$dir" in
  171. "$tmp"|"$record")
  172. name="$(field 'To')" ;;
  173. *)
  174. name="$(field 'From' | tr -d \")"; name="${name% <*}" ;;
  175. esac
  176. name="$(printf '%s\n' "$name" | cut -c-22)"
  177. subj="$(field 'Subject')"
  178. if [ "$lines" -lt 1000 ] && [ "$bytes" -lt 10000 ]; then
  179. size="$lines/$bytes"
  180. else
  181. size="$(du -h "$file" | cut -f1 )"
  182. fi
  183. pre=' '
  184. if [ "$cmd" = 'z' ]; then
  185. unset pre
  186. elif [ "$i" -eq "$cursor" ]; then
  187. pre='->'
  188. fi
  189. c="${#n}"; wdth="$((cols-54-c))"
  190. set -- "$pre" "$c" "$i" "$name" "$date" "$size" "$wdth" "$subj"
  191. printf '%s%*s %-22s %s %-8s "%.*s"\n' "$@"
  192. }
  193. headline () {
  194. unset match sort
  195. test "$filter" && match=" matching \"$pat\""
  196. test "$bysize" && sort=', sorted by size'
  197. case "$n" in
  198. 0)
  199. printf '%s: no messages%s%s\n' "$dir" "${match:-}" "${sort:-}"
  200. cursor=1; return ;;
  201. 1)
  202. printf '%s: %s messages%s%s\n' "$dir" "$n" "${match:-}"\
  203. "${sort:-}" ;;
  204. *)
  205. printf '%s: %s messages%s%s\n' "$dir" "$n" "${match:-}"\
  206. "${sort:-}" ;;
  207. esac
  208. }
  209. list () {
  210. update; term
  211. if [ "$cmd" = 'z' ]; then
  212. wdth="$((wdth+2))"
  213. headline; loop 'brief' | pager
  214. else
  215. headline; loop 'brief'
  216. fi
  217. }
  218. part () {
  219. file="$(awk -v i="$cursor" 'NR==i{print $1;exit}' "$statlist")"
  220. head="$(awk 'BEGIN{RS=""}NR==1{print;exit}' "$file")"
  221. body="$(awk '/^$/?i++:i' "$file")"
  222. }
  223. retain () {
  224. date="Date: $(field 'Date')"
  225. addr="From: $(field 'From' | tr -d \")"
  226. to="To: $(field 'To')"
  227. cc="$(field 'Cc')"
  228. subj="Subject: $(field 'Subject')"
  229. if [ -n "$cc" ]; then
  230. set -- "$date" "$addr" "$to" "Cc: $cc" "$subj"
  231. else
  232. set -- "$date" "$addr" "$to" "$subj"
  233. fi
  234. retain="$(printf '%s\n' "$@")"
  235. }
  236. page () {
  237. if [ "$n" -eq 0 ]; then
  238. printf 'EOF\n'
  239. return 1
  240. fi
  241. cursor="${cmd:-"$cursor"}"
  242. part
  243. retain
  244. tput clear
  245. printf '%s\n' "$retain" '' "$body" | pager
  246. }
  247. int () {
  248. if ([ "$1" -gt 0 ] && [ "$1" -le "$n" ]) 2>'/dev/null'; then
  249. return 0
  250. fi
  251. return 1
  252. }
  253. valid () {
  254. arg="${cmd#?}"
  255. if [ -n "$arg" ]; then
  256. lower="${arg%-*}"
  257. upper="${arg#*-}"
  258. if [ "$arg" = '*' ]; then
  259. lower=1
  260. upper="$n"
  261. elif [ "$arg" = '-.' ]; then
  262. lower=1
  263. upper="$cursor"
  264. elif [ "$arg" = '.-' ]; then
  265. lower="$cursor"
  266. upper="$n"
  267. elif [ "$arg" = ',' ]; then
  268. lower="$n"
  269. upper="$n"
  270. elif [ "$arg" = '-' ]; then
  271. printf '?\n'
  272. return 1
  273. fi
  274. test -z "$upper" && upper="$n"
  275. test -z "$lower" && lower=1
  276. elif [ "$n" -eq 0 ]; then
  277. printf 'EOF\n'
  278. return 1
  279. else
  280. lower="$cursor"
  281. upper="$cursor"
  282. return 0
  283. fi
  284. if int "$lower" && int "$upper"; then
  285. case "$cmd" in
  286. 'd'*|'y'*|'@'*)
  287. if [ "$upper" -ge "$lower" ]; then
  288. true
  289. else
  290. printf 'invalid range\n'
  291. return 1
  292. fi ;;
  293. *)
  294. if [ "$lower" -ne "$upper" ]; then
  295. printf 'this command supports no ranges\n'
  296. return 1
  297. else
  298. cursor="$lower"
  299. fi ;;
  300. esac
  301. else
  302. printf '?\n'
  303. return 1
  304. fi
  305. }
  306. header () {
  307. valid || return 1
  308. part
  309. tput clear
  310. printf '%s\n' "$head" | pager
  311. }
  312. pipe () {
  313. valid || return 1
  314. part
  315. test "$args" || args="$pipeto"
  316. tput clear
  317. printf '%s\n' "$body" | sh -c "$args" | pager
  318. }
  319. save () {
  320. # TODO: save MIME attachments
  321. valid || return 1
  322. if [ -z "$args" ]; then
  323. printf 'provide a filename\n'
  324. return 1
  325. fi
  326. part
  327. retain
  328. printf '%s\n\n%s\n' "$retain" "$body" > "$HOME/$args"
  329. printf 'message %s saved to %s/%s\n' "$cursor" "$HOME" "$args"
  330. }
  331. cpdo () {
  332. file="${1%% *}"
  333. filename="${file##*/}"
  334. cp "$file" "$args/${filename%:*}:2,S"
  335. }
  336. copy () {
  337. valid || return 1
  338. if [ -z "$args" ]; then
  339. printf 'provide a directory to copy into\n'
  340. return 1
  341. fi
  342. case "$args" in
  343. '/'*)
  344. true ;;
  345. 'i')
  346. args="$new" ;;
  347. 'c')
  348. args="$cur" ;;
  349. 't')
  350. args="$tmp" ;;
  351. 'o')
  352. if [ -n "$record" ]; then
  353. args="$record"
  354. else
  355. args="$HOME/$args/o"
  356. fi ;;
  357. *)
  358. args="$HOME/$args" ;;
  359. esac
  360. if [ ! -d "$args" ]; then
  361. printf '%s does not exist, create it? [Y/n]: ' "$args"
  362. read -r mkd
  363. case "$mkd" in
  364. ''|'Y'|'y')
  365. mkdir -p "$args" ;;
  366. *)
  367. printf 'aborting\n'
  368. return 1 ;;
  369. esac
  370. fi
  371. loop 'cpdo'
  372. count="$((upper-lower+1))"
  373. if [ "$count" -eq 1 ]; then
  374. printf 'one message copied to %s\n' "$args"
  375. else
  376. printf '%s messages copied to %s\n' "$count" "$args"
  377. fi
  378. }
  379. printfile () {
  380. printf '%s\n' "${1%% *}" >> "$templist"
  381. }
  382. delete () {
  383. valid || return 1
  384. count="$((upper-lower+1))"
  385. true > "$templist"
  386. loop 'printfile'
  387. while read -r file; do
  388. filename="${file##*/}"
  389. printf '/%s/d\n%s\n' "$filename" 'wq' | ed -s "$filelist"
  390. printf '/%s/d\n%s\n' "$filename" 'wq' | ed -s "$statlist"
  391. if [ "$dir" = "$del" ]; then
  392. rm "$file"
  393. else
  394. mv "$file" "${del}/${filename%:*}:2,T"
  395. fi
  396. done < "$templist"
  397. cp "$statlist" "$tempstat"
  398. n="$(awk 'END{print NR}' "$filelist")"
  399. if [ "$lower" -lt "$cursor" ]; then
  400. if [ "$upper" -ge "$cursor" ]; then
  401. cursor="$lower"
  402. else
  403. cursor="$((cursor-upper+lower-1))"
  404. fi
  405. fi
  406. test "$cursor" -gt "$n" && cursor="$n"
  407. test "$cursor" -lt 1 && cursor=1
  408. if [ "$count" -gt 1 ]; then
  409. printf '%s messages deleted\n' "$count"
  410. fi
  411. }
  412. parsemsg () {
  413. mid="$(field 'Message-ID')"
  414. addr="$(field 'From')"
  415. subj="$(field 'Subject')"
  416. replyto="$(field 'Reply-To')"
  417. to="${replyto:-"$addr"}"
  418. to="${to#*<}"; to="${to%>*}"
  419. subj="Re: ${subj#Re: *}"
  420. ref="$(printf '%s\n' "$head" | awk '/:/{i=0}/^References:/{i=1}i')"
  421. irt="$(printf '%s\n' "$head" | awk '/:/{i=0}/^In-Reply-To:/{i=1}i')"
  422. irtl="$(printf '%s\n' "$irt" | awk 'END{print NR}')"
  423. }
  424. toed () {
  425. printf '%s\n' "$@" | ed -s "$msg"
  426. }
  427. reply () {
  428. valid || return 1
  429. part
  430. parsemsg
  431. printf 'To [%s]: ' "$to"
  432. read -r newto
  433. printf 'Subject [%s]: ' "$subj"
  434. read -r newsubj
  435. subj="${newsubj:-"$subj"}"
  436. to="${newto:-"$to"}"
  437. msg="${tmp}/$(date '+%s')-edmail@$(hostname)"
  438. printf '%s\n' "$body" > "$msg"
  439. toed ',s/^/> /' '0a' "$addr wrote:" '' '.' 'w'
  440. editor "$msg"
  441. hdrs
  442. if [ -n "$ref" ]; then
  443. toed '0a' "$ref" " $mid" '.' 'w'
  444. elif [ -n "$irt" ] && [ "$irtl" -eq 1 ]; then
  445. toed '0a' "References: ${irt#* }" " $mid" '.' 'w'
  446. fi
  447. if [ -n "$mid" ]; then
  448. toed '0a' "In-Reply-To: $mid" '.' 'w'
  449. fi
  450. }
  451. edit () {
  452. if [ "${dir##*/}" != 'tmp' ] && [ "$cmd" != '#' ]; then
  453. printf 'will not comply outside tmp/\n'
  454. return 1
  455. fi
  456. valid || return 1
  457. part
  458. if [ "$cmd" = '#' ]; then
  459. editor "$file"
  460. return 0
  461. fi
  462. to="$(field 'To')"
  463. subj="$(field 'Subject')"
  464. printf 'To [%s]: ' "$to"
  465. read -r newto
  466. printf 'Subject [%s]: ' "$subj"
  467. read -r newsubj
  468. subj="${newsubj:-"$subj"}"
  469. to="${newto:-"$to"}"
  470. msg="${tmp}/$(date '+%s')-edmail@$(hostname)"
  471. printf '%s\n' "$body" > "$msg"
  472. editor "$msg"
  473. hdrs
  474. rm "$file"
  475. update
  476. }
  477. compose () {
  478. if [ -n "$args" ]; then
  479. to="$args"
  480. else
  481. printf 'To: '
  482. read -r to
  483. fi
  484. printf 'Subject: '
  485. read -r subj
  486. msg="${tmp}/$(date '+%s')-edmail@$(hostname)"
  487. editor "$msg"
  488. if [ -f "$msg" ]; then
  489. hdrs
  490. test -f "$sign" && cat "$sign" >> "$msg"
  491. else
  492. printf 'nothing written\n'
  493. fi
  494. }
  495. hdrs () {
  496. h1="Message-ID: <${msg##*/}>"
  497. h2="Date: $(date '+%a, %d %b %Y %T %z')"
  498. h3="From: $from"
  499. h4="To: $to"
  500. h5="Subject: $subj"
  501. toed '0a' "$h1" "$h2" "$h3" "$h4" "$h5" '' '.' 'w'
  502. }
  503. send () {
  504. if [ -z "$(find "$tmp" -type f)" ]; then
  505. printf '%s contains no messages\n' "$tmp"
  506. return 1
  507. fi
  508. i=0; j=0
  509. for file in "$tmp"/*; do
  510. j="$((j+1))"
  511. addr="$(awk '/^To: /{print;exit}' "$file" | cut -d' ' -f2-)"
  512. if sendmail "$addr" < "$file"; then
  513. i="$((i+1))"
  514. if [ -n "$record" ]; then
  515. mv "$file" "$record"
  516. else
  517. rm "$file"
  518. fi
  519. else
  520. printf 'edmail: could not send %s\n\n' "$file"
  521. fi
  522. done
  523. if [ "$i" -eq 0 ]; then
  524. printf 'no messages sent\n'
  525. elif [ "$j" -eq 1 ]; then
  526. printf '%s/%s message successfully sent\n' "$i" "$j"
  527. else
  528. printf '%s/%s messages successfully sent\n' "$i" "$j"
  529. fi
  530. test "${dir##*/}" = 'tmp' && update
  531. }
  532. parserc () {
  533. printf '%s\n' ',s/ *$//' "/^set $1=" | ed -s "$mailrc"\
  534. 2>'/dev/null' | cut -d'=' -f2- | tr -d \"
  535. }
  536. statfold () {
  537. for fold in "$@"; do
  538. if [ ! -d "$fold" ]; then
  539. if [ -z "$said" ]; then
  540. printf 'could not find the following, required, directories:\n'
  541. said=0
  542. fi
  543. printf ' %s\n' "$fold"
  544. notfound=0
  545. fi
  546. done
  547. test "$notfound" && return 0
  548. }
  549. folders () {
  550. new="$maildir/new"
  551. cur="$maildir/cur"
  552. tmp="$maildir/tmp"
  553. del="$maildir/.del"
  554. }
  555. init () {
  556. test "${tempdir:="$(mktemp -dt edmail.XXXXXX)"}" || return 1
  557. filelist="$tempdir/filelist"; templist="$tempdir/templist"
  558. statlist="$tempdir/statlist"; tempstat="$tempdir/tempstat"
  559. trap "rm -r $tempdir; exit" 0
  560. trap "printf '\n'; continue" 2
  561. if [ ! -f "${mailrc:="$HOME/.mailrc"}" ]; then
  562. printf 'could not find %s\n' "$mailrc"
  563. return 1
  564. fi
  565. fetchmail="$(parserc 'fetchmail')"
  566. sendmail="$(parserc 'sendmail')"
  567. maildir="$(parserc 'folder')"
  568. record="$(parserc 'record')"
  569. from="$(parserc 'from')"
  570. sign="$(parserc 'signature')"
  571. pager="$(parserc 'PAGER')"
  572. editor="$(parserc 'EDITOR')"
  573. pipeto="$(parserc 'cmd')"
  574. test "$maildir" || maildir="$HOME/.maildir"
  575. folders
  576. set -- "$new" "$cur" "$tmp" "$del"
  577. if [ -n "$record" ]; then
  578. case "$record" in
  579. '+'*)
  580. record="$maildir/${record#+}" ;;
  581. *)
  582. true ;;
  583. esac
  584. set -- "$@" "$record"
  585. fi
  586. if [ -z "$from" ]; then
  587. printf "set the string option 'from' in %s\\n" "$mailrc"
  588. return 1
  589. elif statfold "$@"; then
  590. printf 'create them? [Y/n]: '
  591. read -r mkd
  592. case "$mkd" in
  593. ''|'Y'|'y')
  594. if mkdir -p "$@"; then
  595. folders
  596. printf '#\n' > "$templist"
  597. return 0
  598. fi ;;
  599. *)
  600. printf 'exiting\n' ;;
  601. esac
  602. else
  603. folders
  604. printf '#\n' > "$templist"
  605. return 0
  606. fi
  607. return 1
  608. }
  609. fetchmail () {
  610. if [ -n "$fetchmail" ]; then
  611. if [ ! -x "$fetchmail" ]; then
  612. printf '%s is not executable\n' "$fetchmail"
  613. return 1
  614. else
  615. "$fetchmail"
  616. fi
  617. else
  618. mpop
  619. fi
  620. }
  621. sendmail () {
  622. if [ -n "$sendmail" ]; then
  623. if [ ! -x "$sendmail" ]; then
  624. printf '%s is not executable\n' "$sendmail"
  625. return 1
  626. else
  627. "$sendmail" "$@" || return 1
  628. fi
  629. else
  630. msmtp -- "$@" || return 1
  631. fi
  632. }
  633. pager () {
  634. if [ -n "$pager" ]; then
  635. if [ ! -x "$pager" ]; then
  636. printf '%s is not executable\n' "$pager"
  637. return 1
  638. else
  639. "$pager"
  640. fi
  641. else
  642. more
  643. fi
  644. }
  645. editor () {
  646. if [ -n "$editor" ]; then
  647. if [ ! -x "$editor" ]; then
  648. printf '%s is not executable\n' "$editor"
  649. return 1
  650. else
  651. "$editor" "$@"
  652. fi
  653. else
  654. ed -sp \* "$@"
  655. fi
  656. }
  657. serv () {
  658. case "$cmd" in
  659. '')
  660. page ;;
  661. ',')
  662. cmd="$n"
  663. page ;;
  664. 'f'|'+')
  665. if [ "$cursor" -lt "$n" ]; then
  666. cursor="$((cursor+1))"
  667. cmd="$cursor"
  668. page
  669. else
  670. printf 'EOF\n'
  671. fi ;;
  672. 'b'|'-')
  673. if [ "$cursor" -gt 1 ]; then
  674. cursor="$((cursor-1))"
  675. cmd="$cursor"
  676. page
  677. else
  678. printf 'BOF\n'
  679. fi ;;
  680. 'l'|'.')
  681. list ;;
  682. 'z')
  683. list ;;
  684. 'g')
  685. cursor=1
  686. list ;;
  687. 'g'*)
  688. valid || return 1
  689. list ;;
  690. 'G')
  691. cursor="$n"
  692. list ;;
  693. 'k')
  694. k="$((cursor-20))"
  695. if [ "$k" -le 1 ]; then
  696. cursor=1
  697. else
  698. cursor="$k"
  699. fi
  700. list ;;
  701. 'j')
  702. j="$((cursor+20))"
  703. if [ "$j" -le "$n" ]; then
  704. cursor="$j"
  705. fi
  706. list ;;
  707. 'i')
  708. dir="$new"
  709. cursor=1
  710. list ;;
  711. 'c')
  712. dir="$cur"
  713. cursor=1
  714. list ;;
  715. 't')
  716. dir="$tmp"
  717. cursor=1
  718. list ;;
  719. '%')
  720. dir="$del"
  721. cursor=1
  722. list ;;
  723. 'o')
  724. if [ -z "$record" ]; then
  725. printf '?\n'
  726. return 1
  727. fi
  728. dir="$record"
  729. cursor=1
  730. list ;;
  731. 'm')
  732. compose ;;
  733. 'h'*)
  734. header ;;
  735. 'd'*)
  736. delete ;;
  737. 'r'*)
  738. reply ;;
  739. 'p'*)
  740. pipe ;;
  741. 'y'*)
  742. copy ;;
  743. 'e'*|'#'*)
  744. edit ;;
  745. 's'*)
  746. save ;;
  747. 'w')
  748. send ;;
  749. 'n')
  750. fetchmail
  751. test "$dir" = "$new" && update ;;
  752. 'v')
  753. printf 'edmail version 0.1.0\n' ;;
  754. 'a')
  755. if archive; then
  756. printf '%s new messages moved to %s\n' "$newn" "$cur"
  757. else
  758. printf 'nothing to be done\n'
  759. fi
  760. test "$dir" = "$new" && update ;;
  761. 'q')
  762. find "$del" -type f -delete
  763. archive; exit 0 ;;
  764. 'x')
  765. exit 0 ;;
  766. 'u')
  767. if [ -z "$bysize" ]; then
  768. bysize=0
  769. else
  770. unset bysize
  771. fi
  772. list ;;
  773. '/')
  774. if [ -z "$filter" ]; then
  775. filter=0
  776. else
  777. unset filter
  778. fi
  779. list ;;
  780. '/'*)
  781. pat="${cmd#?}${args:+" $args"}"
  782. filter=0
  783. cursor=1
  784. list ;;
  785. '@'*)
  786. valid || return 1
  787. awk -v a="$lower" -v b="$upper" 'NR>=a&&NR<=b{print $1}'\
  788. "$statlist" ;;
  789. ';')
  790. tput clear ;;
  791. '?')
  792. man edmail ;;
  793. '!'|'$')
  794. sh ;;
  795. '!'*)
  796. sh -c "${cmd#?} $args" ;;
  797. *)
  798. if int "$cmd"; then
  799. page
  800. else
  801. printf '?\n'
  802. fi ;;
  803. esac
  804. }
  805. main () {
  806. init || exit 1
  807. cmd='i'; serv
  808. while true; do
  809. printf '& '
  810. read -r cmd args
  811. serv
  812. done
  813. }
  814. main