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.
299 lines
10 KiB
299 lines
10 KiB
#!/usr/bin/python3 -OO
|
|
# Copyright 2007-2020 The SABnzbd-Team <team@sabnzbd.org>
|
|
#
|
|
# This program is free software; you can redistribute it and/or
|
|
# modify it under the terms of the GNU General Public License
|
|
# as published by the Free Software Foundation; either version 2
|
|
# of the License, or (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program; if not, write to the Free Software
|
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
|
|
"""
|
|
sabnzbd.emailer - Send notification emails
|
|
"""
|
|
|
|
import smtplib
|
|
import logging
|
|
import re
|
|
import time
|
|
import glob
|
|
|
|
from Cheetah.Template import Template
|
|
from email.message import EmailMessage
|
|
|
|
from sabnzbd.constants import *
|
|
import sabnzbd
|
|
from sabnzbd.misc import to_units, split_host, time_format
|
|
from sabnzbd.notifier import check_cat
|
|
import sabnzbd.cfg as cfg
|
|
|
|
RE_HEADER = re.compile(r"^([^:]+):(.*)")
|
|
|
|
|
|
def errormsg(msg):
|
|
logging.error(msg)
|
|
return msg
|
|
|
|
|
|
def get_email_date():
|
|
""" Return un-localized date string for the Date: field """
|
|
# Get locale independent date/time string: "Sun May 22 20:15:12 2011"
|
|
day, month, dayno, hms, year = time.asctime(time.gmtime()).split()
|
|
return "%s, %s %s %s %s +0000" % (day, dayno, month, year, hms)
|
|
|
|
|
|
def send_email(message, email_to, test=None):
|
|
""" Send message if message non-empty and email-parms are set """
|
|
# we should not use CFG if we are testing. we should use values
|
|
# from UI instead.
|
|
# email_to is replaced at send_with_template, since it can be an array
|
|
if test:
|
|
email_server = test.get("email_server")
|
|
email_from = test.get("email_from")
|
|
email_account = test.get("email_account")
|
|
email_pwd = test.get("email_pwd")
|
|
if email_pwd and not email_pwd.replace("*", ""):
|
|
# If all stars, get stored password instead
|
|
email_pwd = cfg.email_pwd()
|
|
else:
|
|
email_server = cfg.email_server()
|
|
email_from = cfg.email_from()
|
|
email_account = cfg.email_account()
|
|
email_pwd = cfg.email_pwd()
|
|
|
|
if not message.strip("\n\r\t "):
|
|
return "Skipped empty message"
|
|
|
|
# Prepare the email
|
|
email_message = _prepare_message(message)
|
|
|
|
if email_server and email_to and email_from:
|
|
server, port = split_host(email_server)
|
|
if not port:
|
|
port = 25
|
|
logging.debug("Connecting to server %s:%s", server, port)
|
|
|
|
try:
|
|
mailconn = smtplib.SMTP_SSL(server, port)
|
|
mailconn.ehlo()
|
|
logging.debug("Connected to server %s:%s", server, port)
|
|
except:
|
|
# Non SSL mail server
|
|
logging.debug("Non-SSL mail server detected reconnecting to server %s:%s", server, port)
|
|
|
|
try:
|
|
mailconn = smtplib.SMTP(server, port)
|
|
mailconn.ehlo()
|
|
except:
|
|
logging.info("Traceback: ", exc_info=True)
|
|
return errormsg(T("Failed to connect to mail server"))
|
|
|
|
# TLS support
|
|
if mailconn.ehlo_resp:
|
|
m = re.search(b"STARTTLS", mailconn.ehlo_resp, re.IGNORECASE)
|
|
if m:
|
|
logging.debug("TLS mail server detected")
|
|
try:
|
|
mailconn.starttls()
|
|
mailconn.ehlo()
|
|
except:
|
|
logging.info("Traceback: ", exc_info=True)
|
|
return errormsg(T("Failed to initiate TLS connection"))
|
|
|
|
# Authentication
|
|
if (email_account != "") and (email_pwd != ""):
|
|
try:
|
|
mailconn.login(email_account, email_pwd)
|
|
except smtplib.SMTPHeloError:
|
|
return errormsg(T("The server didn't reply properly to the helo greeting"))
|
|
except smtplib.SMTPAuthenticationError:
|
|
return errormsg(T("Failed to authenticate to mail server"))
|
|
except smtplib.SMTPException:
|
|
return errormsg(T("No suitable authentication method was found"))
|
|
except:
|
|
logging.info("Traceback: ", exc_info=True)
|
|
return errormsg(T("Unknown authentication failure in mail server"))
|
|
|
|
try:
|
|
mailconn.sendmail(email_from, email_to, email_message)
|
|
msg = None
|
|
except smtplib.SMTPHeloError:
|
|
msg = errormsg("The server didn't reply properly to the helo greeting.")
|
|
except smtplib.SMTPRecipientsRefused:
|
|
msg = errormsg("The server rejected ALL recipients (no mail was sent).")
|
|
except smtplib.SMTPSenderRefused:
|
|
msg = errormsg("The server didn't accept the from_addr.")
|
|
except smtplib.SMTPDataError:
|
|
msg = errormsg("The server replied with an unexpected error code (other than a refusal of a recipient).")
|
|
except:
|
|
logging.info("Traceback: ", exc_info=True)
|
|
msg = errormsg(T("Failed to send e-mail"))
|
|
|
|
try:
|
|
mailconn.close()
|
|
except:
|
|
logging.info("Traceback: ", exc_info=True)
|
|
errormsg(T("Failed to close mail connection"))
|
|
|
|
if msg:
|
|
return msg
|
|
else:
|
|
logging.info("Notification e-mail successfully sent")
|
|
return T("Email succeeded")
|
|
else:
|
|
return T("Cannot send, missing required data")
|
|
|
|
|
|
def send_with_template(prefix, parm, test=None):
|
|
""" Send an email using template """
|
|
parm["from"] = cfg.email_from()
|
|
parm["date"] = get_email_date()
|
|
|
|
ret = None
|
|
email_templates = []
|
|
path = cfg.email_dir.get_path()
|
|
if path and os.path.exists(path):
|
|
try:
|
|
email_templates = glob.glob(os.path.join(path, "%s-*.tmpl" % prefix))
|
|
except:
|
|
logging.error(T("Cannot find email templates in %s"), path)
|
|
else:
|
|
path = os.path.join(sabnzbd.DIR_PROG, DEF_EMAIL_TMPL)
|
|
tpath = os.path.join(path, "%s-%s.tmpl" % (prefix, cfg.language()))
|
|
if os.path.exists(tpath):
|
|
email_templates = [tpath]
|
|
else:
|
|
email_templates = [os.path.join(path, "%s-en.tmpl" % prefix)]
|
|
|
|
for template_file in email_templates:
|
|
logging.debug("Trying to send email using template %s", template_file)
|
|
if os.access(template_file, os.R_OK):
|
|
if test:
|
|
recipients = [test.get("email_to")]
|
|
else:
|
|
recipients = cfg.email_to()
|
|
|
|
if len(recipients):
|
|
for recipient in recipients:
|
|
# Force-open as UTF-8, otherwise Cheetah breaks it
|
|
with open(template_file, "r", encoding="utf-8") as template_fp:
|
|
parm["to"] = recipient
|
|
message = Template(file=template_fp, searchList=[parm], compilerSettings=CHEETAH_DIRECTIVES)
|
|
ret = send_email(message.respond(), recipient, test)
|
|
else:
|
|
ret = T("No recipients given, no email sent")
|
|
else:
|
|
# Can't open or read file, stop
|
|
return errormsg(T("Cannot read %s") % template_file)
|
|
|
|
# Did we send any emails at all?
|
|
if not ret:
|
|
ret = T("No email templates found")
|
|
return ret
|
|
|
|
|
|
def endjob(
|
|
filename, cat, status, path, bytes_downloaded, fail_msg, stages, script, script_output, script_ret, test=None
|
|
):
|
|
""" Send end-of-job email """
|
|
# Is it allowed?
|
|
if not check_cat("misc", cat, keyword="email") and not test:
|
|
return None
|
|
|
|
# Translate the stage names
|
|
tr = sabnzbd.api.Ttemplate
|
|
if not status and fail_msg:
|
|
xstages = {tr("stage-fail"): (fail_msg,)}
|
|
else:
|
|
xstages = {}
|
|
|
|
for stage in stages:
|
|
lines = []
|
|
for line in stages[stage]:
|
|
if "\n" in line or "<br/>" in line:
|
|
lines.extend(line.replace("<br/>", "\n").split("\n"))
|
|
else:
|
|
lines.append(line)
|
|
xstages[tr("stage-" + stage.lower())] = lines
|
|
|
|
parm = {}
|
|
parm["status"] = status
|
|
parm["name"] = filename
|
|
parm["path"] = path
|
|
parm["msgid"] = ""
|
|
parm["stages"] = xstages
|
|
parm["script"] = script
|
|
parm["script_output"] = script_output
|
|
parm["script_ret"] = script_ret
|
|
parm["cat"] = cat
|
|
parm["size"] = "%sB" % to_units(bytes_downloaded)
|
|
parm["end_time"] = time.strftime(time_format("%Y-%m-%d %H:%M:%S"), time.localtime(time.time()))
|
|
|
|
return send_with_template("email", parm, test)
|
|
|
|
|
|
def rss_mail(feed, jobs):
|
|
""" Send notification email containing list of files """
|
|
parm = {"amount": len(jobs), "feed": feed, "jobs": jobs}
|
|
return send_with_template("rss", parm)
|
|
|
|
|
|
def badfetch_mail(msg, url):
|
|
""" Send notification email about failed NZB fetch """
|
|
parm = {"url": url, "msg": msg}
|
|
return send_with_template("badfetch", parm)
|
|
|
|
|
|
def diskfull_mail():
|
|
""" Send email about disk full, no templates """
|
|
if cfg.email_full():
|
|
return send_email(
|
|
T(
|
|
"""To: %s
|
|
From: %s
|
|
Date: %s
|
|
Subject: SABnzbd reports Disk Full
|
|
|
|
Hi,
|
|
|
|
SABnzbd has stopped downloading, because the disk is almost full.
|
|
Please make room and resume SABnzbd manually.
|
|
|
|
"""
|
|
)
|
|
% (cfg.email_to.get_string(), cfg.email_from(), get_email_date()),
|
|
cfg.email_to(),
|
|
)
|
|
else:
|
|
return ""
|
|
|
|
|
|
def _prepare_message(txt):
|
|
""" Parse the headers in the template to real headers """
|
|
msg = EmailMessage()
|
|
payload = []
|
|
body = False
|
|
header = False
|
|
for line in txt.split("\n"):
|
|
if header and not line:
|
|
body = True
|
|
if body:
|
|
payload.append(line)
|
|
else:
|
|
# See if we match a header
|
|
m = RE_HEADER.search(line)
|
|
if m:
|
|
header = True
|
|
keyword = m.group(1).strip()
|
|
value = m.group(2).strip()
|
|
msg[keyword] = value
|
|
|
|
msg.set_content("\n".join(payload))
|
|
return msg.as_bytes()
|
|
|