]> git.proxmox.com Git - mirror_acme.sh.git/blobdiff - notify/smtp.sh
Merge pull request #4820 from acmesh-official/dev
[mirror_acme.sh.git] / notify / smtp.sh
index 43536cd223845a6cbbf5e1493af27a6d6fd66e12..f5ebebca049407cc420fee14f1107d05b49b6cbd 100644 (file)
 # SMTP_TO="to@example.com"  # required
 # SMTP_HOST="smtp.example.com"  # required
 # SMTP_PORT="25"  # defaults to 25, 465 or 587 depending on SMTP_SECURE
-# SMTP_SECURE="none"  # one of "none", "ssl" (implicit TLS, TLS Wrapper), "tls" (explicit TLS, STARTTLS)
+# SMTP_SECURE="tls"  # one of "none", "ssl" (implicit TLS, TLS Wrapper), "tls" (explicit TLS, STARTTLS)
 # SMTP_USERNAME=""  # set if SMTP server requires login
 # SMTP_PASSWORD=""  # set if SMTP server requires login
 # SMTP_TIMEOUT="30"  # seconds for SMTP operations to timeout
-# SMTP_BIN="/path/to/curl_or_python"  # default finds first of curl, python3, or python on PATH
+# SMTP_BIN="/path/to/python_or_curl"  # default finds first of python3, python2.7, python, pypy3, pypy, curl on PATH
 
-SMTP_SECURE_DEFAULT="none"
+SMTP_SECURE_DEFAULT="tls"
 SMTP_TIMEOUT_DEFAULT="30"
 
 # subject content statuscode
@@ -36,7 +36,7 @@ smtp_send() {
     # Look for a command that can communicate with an SMTP server.
     # (Please don't add sendmail, ssmtp, mutt, mail, or msmtp here.
     # Those are already handled by the "mail" notify hook.)
-    for cmd in curl python3 python2.7 python pypy3 pypy; do
+    for cmd in python3 python2.7 python pypy3 pypy curl; do
       if _exists "$cmd"; then
         SMTP_BIN="$cmd"
         break
@@ -53,16 +53,28 @@ smtp_send() {
   _saveaccountconf_mutable_default SMTP_BIN "$SMTP_BIN"
 
   SMTP_FROM="$(_readaccountconf_mutable_default SMTP_FROM)"
+  SMTP_FROM="$(_clean_email_header "$SMTP_FROM")"
   if [ -z "$SMTP_FROM" ]; then
     _err "You must define SMTP_FROM as the sender email address."
     return 1
   fi
+  if _email_has_display_name "$SMTP_FROM"; then
+    _err "SMTP_FROM must be only a simple email address (sender@example.com)."
+    _err "Change your SMTP_FROM='$SMTP_FROM' to remove the display name."
+    return 1
+  fi
   _debug SMTP_FROM "$SMTP_FROM"
   _saveaccountconf_mutable_default SMTP_FROM "$SMTP_FROM"
 
   SMTP_TO="$(_readaccountconf_mutable_default SMTP_TO)"
+  SMTP_TO="$(_clean_email_header "$SMTP_TO")"
   if [ -z "$SMTP_TO" ]; then
-    _err "You must define SMTP_TO as the recipient email address."
+    _err "You must define SMTP_TO as the recipient email address(es)."
+    return 1
+  fi
+  if _email_has_display_name "$SMTP_TO"; then
+    _err "SMTP_TO must be only simple email addresses (to@example.com,to2@example.com)."
+    _err "Change your SMTP_TO='$SMTP_TO' to remove the display name(s)."
     return 1
   fi
   _debug SMTP_TO "$SMTP_TO"
@@ -111,7 +123,7 @@ smtp_send() {
   _debug SMTP_TIMEOUT "$SMTP_TIMEOUT"
   _saveaccountconf_mutable_default SMTP_TIMEOUT "$SMTP_TIMEOUT" "$SMTP_TIMEOUT_DEFAULT"
 
-  SMTP_X_MAILER="${PROJECT_NAME} ${VER} --notify-hook smtp"
+  SMTP_X_MAILER="$(_clean_email_header "$PROJECT_NAME $VER --notify-hook smtp")"
 
   # Run with --debug 2 (or above) to echo the transcript of the SMTP session.
   # Careful: this may include SMTP_PASSWORD in plaintext!
@@ -121,6 +133,7 @@ smtp_send() {
     SMTP_SHOW_TRANSCRIPT=""
   fi
 
+  SMTP_SUBJECT=$(_clean_email_header "$SMTP_SUBJECT")
   _debug SMTP_SUBJECT "$SMTP_SUBJECT"
   _debug SMTP_CONTENT "$SMTP_CONTENT"
 
@@ -146,28 +159,26 @@ smtp_send() {
   return 0
 }
 
+# Strip CR and NL from text to prevent MIME header injection
+# text
+_clean_email_header() {
+  printf "%s" "$(echo "$1" | tr -d "\r\n")"
+}
+
+# Simple check for display name in an email address (< > or ")
+# email
+_email_has_display_name() {
+  _email="$1"
+  echo "$_email" | grep -q -E '^.*[<>"]'
+}
+
 ##
 ## curl smtp sending
 ##
 
 # Send the message via curl using SMTP_* variables
 _smtp_send_curl() {
-  # curl passes --mail-from and --mail-rcpt directly to the SMTP protocol without
-  # additional parsing, and SMTP requires addr-spec only (no display names).
-  # In the future, maybe try to parse the addr-spec out for curl args (non-trivial).
-  if _email_has_display_name "$SMTP_FROM"; then
-    _err "curl smtp only allows a simple email address in SMTP_FROM."
-    _err "Change your SMTP_FROM='$SMTP_FROM' to remove the display name."
-    return 1
-  fi
-  if _email_has_display_name "$SMTP_TO"; then
-    _err "curl smtp only allows simple email addresses in SMTP_TO."
-    _err "Change your SMTP_TO='$SMTP_TO' to remove the display name(s)."
-    return 1
-  fi
-
   # Build curl args in $@
-
   case "$SMTP_SECURE" in
   none)
     set -- --url "smtp://${SMTP_HOST}:${SMTP_PORT}"
@@ -219,7 +230,8 @@ _smtp_send_curl() {
   echo "$raw_message" | "$SMTP_BIN" "$@"
 }
 
-# Output an RFC-822 / RFC-5322 email message using SMTP_* variables
+# Output an RFC-822 / RFC-5322 email message using SMTP_* variables.
+# (This assumes variables have already been cleaned for use in email headers.)
 _smtp_raw_message() {
   echo "From: $SMTP_FROM"
   echo "To: $SMTP_TO"
@@ -237,7 +249,7 @@ _mime_encoded_word() {
   _text="$1"
   # (regex character ranges like [a-z] can be locale-dependent; enumerate ASCII chars to avoid that)
   _ascii='] $`"'"[!#%&'()*+,./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ~^_abcdefghijklmnopqrstuvwxyz{|}~-"
-  if expr "$_text" : "^.*[^$_ascii]" >/dev/null; then
+  if echo "$_text" | grep -q -E "^.*[^$_ascii]"; then
     # At least one non-ASCII char; convert entire thing to encoded word
     printf "%s" "=?UTF-8?B?$(printf "%s" "$_text" | _base64)?="
   else
@@ -259,13 +271,6 @@ _rfc2822_date() {
   LC_TIME="$_old_lc_time"
 }
 
-# Simple check for display name in an email address (< > or ")
-# email
-_email_has_display_name() {
-  _email="$1"
-  expr "$_email" : '^.*[<>"]' >/dev/null
-}
-
 ##
 ## Python smtp sending
 ##
@@ -280,8 +285,12 @@ _smtp_send_python() {
 try:
     try:
         from email.message import EmailMessage
+        from email.policy import default as email_policy_default
     except ImportError:
-        from email.mime.text import MIMEText as EmailMessage  # Python 2
+        # Python 2 (or < 3.3)
+        from email.mime.text import MIMEText as EmailMessage
+        email_policy_default = None
+    from email.utils import formatdate as rfc2822_date
     from smtplib import SMTP, SMTP_SSL, SMTPException
     from socket import error as SocketError
 except ImportError as err:
@@ -305,7 +314,7 @@ subject="""$SMTP_SUBJECT"""
 content="""$SMTP_CONTENT"""
 
 try:
-    msg = EmailMessage()
+    msg = EmailMessage(policy=email_policy_default)
     msg.set_content(content)
 except (AttributeError, TypeError):
     # Python 2 MIMEText
@@ -313,6 +322,7 @@ except (AttributeError, TypeError):
 msg["Subject"] = subject
 msg["From"] = from_email
 msg["To"] = to_emails
+msg["Date"] = rfc2822_date(localtime=True)
 msg["X-Mailer"] = x_mailer
 
 smtp = None
@@ -353,7 +363,7 @@ PYTHON
 #   - if MY_CONF is set _empty_, output $default_value
 #     (lets user `export MY_CONF=` to clear previous saved value
 #     and return to default, without user having to know default)
-#   - otherwise if _readaccountconf_mutable $name is non-empty, return that
+#   - otherwise if _readaccountconf_mutable MY_CONF is non-empty, return that
 #     (value of SAVED_MY_CONF from account.conf)
 #   - otherwise output $default_value
 _readaccountconf_mutable_default() {
@@ -361,8 +371,9 @@ _readaccountconf_mutable_default() {
   _default_value="$2"
 
   eval "_value=\"\$$_name\""
-  eval "_explicit_empty_value=\"\${${_name}+empty}\""
-  if [ -z "${_value}" ] && [ "${_explicit_empty_value:-}" != "empty" ]; then
+  eval "_name_is_set=\"\${${_name}+true}\""
+  # ($_name_is_set is "true" if $$_name is set to anything, including empty)
+  if [ -z "${_value}" ] && [ "${_name_is_set:-}" != "true" ]; then
     _value="$(_readaccountconf_mutable "$_name")"
   fi
   if [ -z "${_value}" ]; then