You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

399 lines
12 KiB

  1. #!/usr/bin/env sh
  2. # support smtp
  3. # Please report bugs to https://github.com/acmesh-official/acme.sh/issues/3358
  4. # This implementation uses either curl or Python (3 or 2.7).
  5. # (See also the "mail" notify hook, which supports other ways to send mail.)
  6. # SMTP_FROM="from@example.com" # required
  7. # SMTP_TO="to@example.com" # required
  8. # SMTP_HOST="smtp.example.com" # required
  9. # SMTP_PORT="25" # defaults to 25, 465 or 587 depending on SMTP_SECURE
  10. # SMTP_SECURE="tls" # one of "none", "ssl" (implicit TLS, TLS Wrapper), "tls" (explicit TLS, STARTTLS)
  11. # SMTP_USERNAME="" # set if SMTP server requires login
  12. # SMTP_PASSWORD="" # set if SMTP server requires login
  13. # SMTP_TIMEOUT="30" # seconds for SMTP operations to timeout
  14. # SMTP_BIN="/path/to/python_or_curl" # default finds first of python3, python2.7, python, pypy3, pypy, curl on PATH
  15. SMTP_SECURE_DEFAULT="tls"
  16. SMTP_TIMEOUT_DEFAULT="30"
  17. # subject content statuscode
  18. smtp_send() {
  19. SMTP_SUBJECT="$1"
  20. SMTP_CONTENT="$2"
  21. # UNUSED: _statusCode="$3" # 0: success, 1: error 2($RENEW_SKIP): skipped
  22. # Load and validate config:
  23. SMTP_BIN="$(_readaccountconf_mutable_default SMTP_BIN)"
  24. if [ -n "$SMTP_BIN" ] && ! _exists "$SMTP_BIN"; then
  25. _err "SMTP_BIN '$SMTP_BIN' does not exist."
  26. return 1
  27. fi
  28. if [ -z "$SMTP_BIN" ]; then
  29. # Look for a command that can communicate with an SMTP server.
  30. # (Please don't add sendmail, ssmtp, mutt, mail, or msmtp here.
  31. # Those are already handled by the "mail" notify hook.)
  32. for cmd in python3 python2.7 python pypy3 pypy curl; do
  33. if _exists "$cmd"; then
  34. SMTP_BIN="$cmd"
  35. break
  36. fi
  37. done
  38. if [ -z "$SMTP_BIN" ]; then
  39. _err "The smtp notify-hook requires curl or Python, but can't find any."
  40. _err 'If you have one of them, define SMTP_BIN="/path/to/curl_or_python".'
  41. _err 'Otherwise, see if you can use the "mail" notify-hook instead.'
  42. return 1
  43. fi
  44. fi
  45. _debug SMTP_BIN "$SMTP_BIN"
  46. _saveaccountconf_mutable_default SMTP_BIN "$SMTP_BIN"
  47. SMTP_FROM="$(_readaccountconf_mutable_default SMTP_FROM)"
  48. SMTP_FROM="$(_clean_email_header "$SMTP_FROM")"
  49. if [ -z "$SMTP_FROM" ]; then
  50. _err "You must define SMTP_FROM as the sender email address."
  51. return 1
  52. fi
  53. if _email_has_display_name "$SMTP_FROM"; then
  54. _err "SMTP_FROM must be only a simple email address (sender@example.com)."
  55. _err "Change your SMTP_FROM='$SMTP_FROM' to remove the display name."
  56. return 1
  57. fi
  58. _debug SMTP_FROM "$SMTP_FROM"
  59. _saveaccountconf_mutable_default SMTP_FROM "$SMTP_FROM"
  60. SMTP_TO="$(_readaccountconf_mutable_default SMTP_TO)"
  61. SMTP_TO="$(_clean_email_header "$SMTP_TO")"
  62. if [ -z "$SMTP_TO" ]; then
  63. _err "You must define SMTP_TO as the recipient email address(es)."
  64. return 1
  65. fi
  66. if _email_has_display_name "$SMTP_TO"; then
  67. _err "SMTP_TO must be only simple email addresses (to@example.com,to2@example.com)."
  68. _err "Change your SMTP_TO='$SMTP_TO' to remove the display name(s)."
  69. return 1
  70. fi
  71. _debug SMTP_TO "$SMTP_TO"
  72. _saveaccountconf_mutable_default SMTP_TO "$SMTP_TO"
  73. SMTP_HOST="$(_readaccountconf_mutable_default SMTP_HOST)"
  74. if [ -z "$SMTP_HOST" ]; then
  75. _err "You must define SMTP_HOST as the SMTP server hostname."
  76. return 1
  77. fi
  78. _debug SMTP_HOST "$SMTP_HOST"
  79. _saveaccountconf_mutable_default SMTP_HOST "$SMTP_HOST"
  80. SMTP_SECURE="$(_readaccountconf_mutable_default SMTP_SECURE "$SMTP_SECURE_DEFAULT")"
  81. case "$SMTP_SECURE" in
  82. "none") smtp_port_default="25" ;;
  83. "ssl") smtp_port_default="465" ;;
  84. "tls") smtp_port_default="587" ;;
  85. *)
  86. _err "Invalid SMTP_SECURE='$SMTP_SECURE'. It must be 'ssl', 'tls' or 'none'."
  87. return 1
  88. ;;
  89. esac
  90. _debug SMTP_SECURE "$SMTP_SECURE"
  91. _saveaccountconf_mutable_default SMTP_SECURE "$SMTP_SECURE" "$SMTP_SECURE_DEFAULT"
  92. SMTP_PORT="$(_readaccountconf_mutable_default SMTP_PORT "$smtp_port_default")"
  93. case "$SMTP_PORT" in
  94. *[!0-9]*)
  95. _err "Invalid SMTP_PORT='$SMTP_PORT'. It must be a port number."
  96. return 1
  97. ;;
  98. esac
  99. _debug SMTP_PORT "$SMTP_PORT"
  100. _saveaccountconf_mutable_default SMTP_PORT "$SMTP_PORT" "$smtp_port_default"
  101. SMTP_USERNAME="$(_readaccountconf_mutable_default SMTP_USERNAME)"
  102. _debug SMTP_USERNAME "$SMTP_USERNAME"
  103. _saveaccountconf_mutable_default SMTP_USERNAME "$SMTP_USERNAME"
  104. SMTP_PASSWORD="$(_readaccountconf_mutable_default SMTP_PASSWORD)"
  105. _secure_debug SMTP_PASSWORD "$SMTP_PASSWORD"
  106. _saveaccountconf_mutable_default SMTP_PASSWORD "$SMTP_PASSWORD"
  107. SMTP_TIMEOUT="$(_readaccountconf_mutable_default SMTP_TIMEOUT "$SMTP_TIMEOUT_DEFAULT")"
  108. _debug SMTP_TIMEOUT "$SMTP_TIMEOUT"
  109. _saveaccountconf_mutable_default SMTP_TIMEOUT "$SMTP_TIMEOUT" "$SMTP_TIMEOUT_DEFAULT"
  110. SMTP_X_MAILER="$(_clean_email_header "$PROJECT_NAME $VER --notify-hook smtp")"
  111. # Run with --debug 2 (or above) to echo the transcript of the SMTP session.
  112. # Careful: this may include SMTP_PASSWORD in plaintext!
  113. if [ "${DEBUG:-$DEBUG_LEVEL_NONE}" -ge "$DEBUG_LEVEL_2" ]; then
  114. SMTP_SHOW_TRANSCRIPT="True"
  115. else
  116. SMTP_SHOW_TRANSCRIPT=""
  117. fi
  118. SMTP_SUBJECT=$(_clean_email_header "$SMTP_SUBJECT")
  119. _debug SMTP_SUBJECT "$SMTP_SUBJECT"
  120. _debug SMTP_CONTENT "$SMTP_CONTENT"
  121. # Send the message:
  122. case "$(basename "$SMTP_BIN")" in
  123. curl) _smtp_send=_smtp_send_curl ;;
  124. py*) _smtp_send=_smtp_send_python ;;
  125. *)
  126. _err "Can't figure out how to invoke '$SMTP_BIN'."
  127. _err "Check your SMTP_BIN setting."
  128. return 1
  129. ;;
  130. esac
  131. if ! smtp_output="$($_smtp_send)"; then
  132. _err "Error sending message with $SMTP_BIN."
  133. if [ -n "$smtp_output" ]; then
  134. _err "$smtp_output"
  135. fi
  136. return 1
  137. fi
  138. return 0
  139. }
  140. # Strip CR and NL from text to prevent MIME header injection
  141. # text
  142. _clean_email_header() {
  143. printf "%s" "$(echo "$1" | tr -d "\r\n")"
  144. }
  145. # Simple check for display name in an email address (< > or ")
  146. # email
  147. _email_has_display_name() {
  148. _email="$1"
  149. expr "$_email" : '^.*[<>"]' >/dev/null
  150. }
  151. ##
  152. ## curl smtp sending
  153. ##
  154. # Send the message via curl using SMTP_* variables
  155. _smtp_send_curl() {
  156. # Build curl args in $@
  157. case "$SMTP_SECURE" in
  158. none)
  159. set -- --url "smtp://${SMTP_HOST}:${SMTP_PORT}"
  160. ;;
  161. ssl)
  162. set -- --url "smtps://${SMTP_HOST}:${SMTP_PORT}"
  163. ;;
  164. tls)
  165. set -- --url "smtp://${SMTP_HOST}:${SMTP_PORT}" --ssl-reqd
  166. ;;
  167. *)
  168. # This will only occur if someone adds a new SMTP_SECURE option above
  169. # without updating this code for it.
  170. _err "Unhandled SMTP_SECURE='$SMTP_SECURE' in _smtp_send_curl"
  171. _err "Please re-run with --debug and report a bug."
  172. return 1
  173. ;;
  174. esac
  175. set -- "$@" \
  176. --upload-file - \
  177. --mail-from "$SMTP_FROM" \
  178. --max-time "$SMTP_TIMEOUT"
  179. # Burst comma-separated $SMTP_TO into individual --mail-rcpt args.
  180. _to="${SMTP_TO},"
  181. while [ -n "$_to" ]; do
  182. _rcpt="${_to%%,*}"
  183. _to="${_to#*,}"
  184. set -- "$@" --mail-rcpt "$_rcpt"
  185. done
  186. _smtp_login="${SMTP_USERNAME}:${SMTP_PASSWORD}"
  187. if [ "$_smtp_login" != ":" ]; then
  188. set -- "$@" --user "$_smtp_login"
  189. fi
  190. if [ "$SMTP_SHOW_TRANSCRIPT" = "True" ]; then
  191. set -- "$@" --verbose
  192. else
  193. set -- "$@" --silent --show-error
  194. fi
  195. raw_message="$(_smtp_raw_message)"
  196. _debug2 "curl command:" "$SMTP_BIN" "$*"
  197. _debug2 "raw_message:\n$raw_message"
  198. echo "$raw_message" | "$SMTP_BIN" "$@"
  199. }
  200. # Output an RFC-822 / RFC-5322 email message using SMTP_* variables.
  201. # (This assumes variables have already been cleaned for use in email headers.)
  202. _smtp_raw_message() {
  203. echo "From: $SMTP_FROM"
  204. echo "To: $SMTP_TO"
  205. echo "Subject: $(_mime_encoded_word "$SMTP_SUBJECT")"
  206. echo "Date: $(_rfc2822_date)"
  207. echo "Content-Type: text/plain; charset=utf-8"
  208. echo "X-Mailer: $SMTP_X_MAILER"
  209. echo
  210. echo "$SMTP_CONTENT"
  211. }
  212. # Convert text to RFC-2047 MIME "encoded word" format if it contains non-ASCII chars
  213. # text
  214. _mime_encoded_word() {
  215. _text="$1"
  216. # (regex character ranges like [a-z] can be locale-dependent; enumerate ASCII chars to avoid that)
  217. _ascii='] $`"'"[!#%&'()*+,./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ~^_abcdefghijklmnopqrstuvwxyz{|}~-"
  218. if expr "$_text" : "^.*[^$_ascii]" >/dev/null; then
  219. # At least one non-ASCII char; convert entire thing to encoded word
  220. printf "%s" "=?UTF-8?B?$(printf "%s" "$_text" | _base64)?="
  221. else
  222. # Just printable ASCII, no conversion needed
  223. printf "%s" "$_text"
  224. fi
  225. }
  226. # Output current date in RFC-2822 Section 3.3 format as required in email headers
  227. # (e.g., "Mon, 15 Feb 2021 14:22:01 -0800")
  228. _rfc2822_date() {
  229. # Notes:
  230. # - this is deliberately not UTC, because it "SHOULD express local time" per spec
  231. # - the spec requires weekday and month in the C locale (English), not localized
  232. # - this date format specifier has been tested on Linux, Mac, Solaris and FreeBSD
  233. _old_lc_time="$LC_TIME"
  234. LC_TIME=C
  235. date +'%a, %-d %b %Y %H:%M:%S %z'
  236. LC_TIME="$_old_lc_time"
  237. }
  238. ##
  239. ## Python smtp sending
  240. ##
  241. # Send the message via Python using SMTP_* variables
  242. _smtp_send_python() {
  243. _debug "Python version" "$("$SMTP_BIN" --version 2>&1)"
  244. # language=Python
  245. "$SMTP_BIN" <<PYTHON
  246. # This code is meant to work with either Python 2.7.x or Python 3.4+.
  247. try:
  248. try:
  249. from email.message import EmailMessage
  250. from email.policy import default as email_policy_default
  251. except ImportError:
  252. # Python 2 (or < 3.3)
  253. from email.mime.text import MIMEText as EmailMessage
  254. email_policy_default = None
  255. from email.utils import formatdate as rfc2822_date
  256. from smtplib import SMTP, SMTP_SSL, SMTPException
  257. from socket import error as SocketError
  258. except ImportError as err:
  259. print("A required Python standard package is missing. This system may have"
  260. " a reduced version of Python unsuitable for sending mail: %s" % err)
  261. exit(1)
  262. show_transcript = """$SMTP_SHOW_TRANSCRIPT""" == "True"
  263. smtp_host = """$SMTP_HOST"""
  264. smtp_port = int("""$SMTP_PORT""")
  265. smtp_secure = """$SMTP_SECURE"""
  266. username = """$SMTP_USERNAME"""
  267. password = """$SMTP_PASSWORD"""
  268. timeout=int("""$SMTP_TIMEOUT""") # seconds
  269. x_mailer="""$SMTP_X_MAILER"""
  270. from_email="""$SMTP_FROM"""
  271. to_emails="""$SMTP_TO""" # can be comma-separated
  272. subject="""$SMTP_SUBJECT"""
  273. content="""$SMTP_CONTENT"""
  274. try:
  275. msg = EmailMessage(policy=email_policy_default)
  276. msg.set_content(content)
  277. except (AttributeError, TypeError):
  278. # Python 2 MIMEText
  279. msg = EmailMessage(content)
  280. msg["Subject"] = subject
  281. msg["From"] = from_email
  282. msg["To"] = to_emails
  283. msg["Date"] = rfc2822_date(localtime=True)
  284. msg["X-Mailer"] = x_mailer
  285. smtp = None
  286. try:
  287. if smtp_secure == "ssl":
  288. smtp = SMTP_SSL(smtp_host, smtp_port, timeout=timeout)
  289. else:
  290. smtp = SMTP(smtp_host, smtp_port, timeout=timeout)
  291. smtp.set_debuglevel(show_transcript)
  292. if smtp_secure == "tls":
  293. smtp.starttls()
  294. if username or password:
  295. smtp.login(username, password)
  296. smtp.sendmail(msg["From"], msg["To"].split(","), msg.as_string())
  297. except SMTPException as err:
  298. # Output just the error (skip the Python stack trace) for SMTP errors
  299. print("Error sending: %r" % err)
  300. exit(1)
  301. except SocketError as err:
  302. print("Error connecting to %s:%d: %r" % (smtp_host, smtp_port, err))
  303. exit(1)
  304. finally:
  305. if smtp is not None:
  306. smtp.quit()
  307. PYTHON
  308. }
  309. ##
  310. ## Conf helpers
  311. ##
  312. #_readaccountconf_mutable_default name default_value
  313. # Given a name like MY_CONF:
  314. # - if MY_CONF is set and non-empty, output $MY_CONF
  315. # - if MY_CONF is set _empty_, output $default_value
  316. # (lets user `export MY_CONF=` to clear previous saved value
  317. # and return to default, without user having to know default)
  318. # - otherwise if _readaccountconf_mutable MY_CONF is non-empty, return that
  319. # (value of SAVED_MY_CONF from account.conf)
  320. # - otherwise output $default_value
  321. _readaccountconf_mutable_default() {
  322. _name="$1"
  323. _default_value="$2"
  324. eval "_value=\"\$$_name\""
  325. eval "_name_is_set=\"\${${_name}+true}\""
  326. # ($_name_is_set is "true" if $$_name is set to anything, including empty)
  327. if [ -z "${_value}" ] && [ "${_name_is_set:-}" != "true" ]; then
  328. _value="$(_readaccountconf_mutable "$_name")"
  329. fi
  330. if [ -z "${_value}" ]; then
  331. _value="$_default_value"
  332. fi
  333. printf "%s" "$_value"
  334. }
  335. #_saveaccountconf_mutable_default name value default_value base64encode
  336. # Like _saveaccountconf_mutable, but if value is default_value
  337. # then _clearaccountconf_mutable instead
  338. _saveaccountconf_mutable_default() {
  339. _name="$1"
  340. _value="$2"
  341. _default_value="$3"
  342. _base64encode="$4"
  343. if [ "$_value" != "$_default_value" ]; then
  344. _saveaccountconf_mutable "$_name" "$_value" "$_base64encode"
  345. else
  346. _clearaccountconf_mutable "$_name"
  347. fi
  348. }