blob: 723da04b84765cd4a33322334c5ad676309fee0b [file] [log] [blame] [edit]
# Copyright 1998-2014 Gentoo Foundation
# Distributed under the terms of the GNU General Public License v2
# Since python ebuilds remove the 'email' module when USE=build
# is enabled, use a local import so that
# portage.proxy.lazyimport._preload_portage_submodules()
# can load this module even though the 'email' module is missing.
# The elog mail modules won't work, but at least an ImportError
# won't cause portage to crash during stage builds. Since the
# 'smtlib' module imports the 'email' module, that's imported
# locally as well.
import socket
import sys
import time
from portage import os
from portage import _encodings
from portage import _unicode_decode, _unicode_encode
from portage.localization import _
import portage
if sys.hexversion >= 0x3000000:
# pylint: disable=W0622
basestring = str
def _force_ascii_if_necessary(s):
# Force ascii encoding in order to avoid UnicodeEncodeError
# from smtplib.sendmail with python3 (bug #291331).
s = _unicode_encode(s,
encoding='ascii', errors='backslashreplace')
s = _unicode_decode(s,
encoding='ascii', errors='replace')
return s
else:
def _force_ascii_if_necessary(s):
return s
def TextMessage(_text):
from email.mime.text import MIMEText
mimetext = MIMEText(_text)
mimetext.set_charset("UTF-8")
return mimetext
def create_message(sender, recipient, subject, body, attachments=None):
from email.header import Header
from email.mime.base import MIMEBase as BaseMessage
from email.mime.multipart import MIMEMultipart as MultipartMessage
if sys.hexversion < 0x3000000:
sender = _unicode_encode(sender,
encoding=_encodings['content'], errors='strict')
recipient = _unicode_encode(recipient,
encoding=_encodings['content'], errors='strict')
subject = _unicode_encode(subject,
encoding=_encodings['content'], errors='backslashreplace')
body = _unicode_encode(body,
encoding=_encodings['content'], errors='backslashreplace')
if attachments == None:
mymessage = TextMessage(body)
else:
mymessage = MultipartMessage()
mymessage.attach(TextMessage(body))
for x in attachments:
if isinstance(x, BaseMessage):
mymessage.attach(x)
elif isinstance(x, basestring):
if sys.hexversion < 0x3000000:
x = _unicode_encode(x,
encoding=_encodings['content'],
errors='backslashreplace')
mymessage.attach(TextMessage(x))
else:
raise portage.exception.PortageException(_("Can't handle type of attachment: %s") % type(x))
mymessage.set_unixfrom(sender)
mymessage["To"] = recipient
mymessage["From"] = sender
# Use Header as a workaround so that long subject lines are wrapped
# correctly by <=python-2.6 (gentoo bug #263370, python issue #1974).
# Also, need to force ascii for python3, in order to avoid
# UnicodeEncodeError with non-ascii characters:
# File "/usr/lib/python3.1/email/header.py", line 189, in __init__
# self.append(s, charset, errors)
# File "/usr/lib/python3.1/email/header.py", line 262, in append
# input_bytes = s.encode(input_charset, errors)
#UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-9: ordinal not in range(128)
mymessage["Subject"] = Header(_force_ascii_if_necessary(subject))
mymessage["Date"] = time.strftime("%a, %d %b %Y %H:%M:%S %z")
return mymessage
def send_mail(mysettings, message):
import smtplib
mymailhost = "localhost"
mymailport = 25
mymailuser = ""
mymailpasswd = ""
myrecipient = "root@localhost"
# Syntax for PORTAGE_ELOG_MAILURI (if defined):
# address [[user:passwd@]mailserver[:port]]
# where address: recipient address
# user: username for smtp auth (defaults to none)
# passwd: password for smtp auth (defaults to none)
# mailserver: smtp server that should be used to deliver the mail (defaults to localhost)
# alternatively this can also be the absolute path to a sendmail binary if you don't want to use smtp
# port: port to use on the given smtp server (defaults to 25, values > 100000 indicate that starttls should be used on (port-100000))
if " " in mysettings.get("PORTAGE_ELOG_MAILURI", ""):
myrecipient, mymailuri = mysettings["PORTAGE_ELOG_MAILURI"].split()
if "@" in mymailuri:
myauthdata, myconndata = mymailuri.rsplit("@", 1)
try:
mymailuser, mymailpasswd = myauthdata.split(":")
except ValueError:
print(_("!!! invalid SMTP AUTH configuration, trying unauthenticated ..."))
else:
myconndata = mymailuri
if ":" in myconndata:
mymailhost, mymailport = myconndata.split(":")
else:
mymailhost = myconndata
else:
myrecipient = mysettings.get("PORTAGE_ELOG_MAILURI", "")
myfrom = message.get("From")
if sys.hexversion < 0x3000000:
myrecipient = _unicode_encode(myrecipient,
encoding=_encodings['content'], errors='strict')
mymailhost = _unicode_encode(mymailhost,
encoding=_encodings['content'], errors='strict')
mymailport = _unicode_encode(mymailport,
encoding=_encodings['content'], errors='strict')
myfrom = _unicode_encode(myfrom,
encoding=_encodings['content'], errors='strict')
mymailuser = _unicode_encode(mymailuser,
encoding=_encodings['content'], errors='strict')
mymailpasswd = _unicode_encode(mymailpasswd,
encoding=_encodings['content'], errors='strict')
# user wants to use a sendmail binary instead of smtp
if mymailhost[0] == os.sep and os.path.exists(mymailhost):
fd = os.popen(mymailhost+" -f "+myfrom+" "+myrecipient, "w")
fd.write(_force_ascii_if_necessary(message.as_string()))
if fd.close() != None:
sys.stderr.write(_("!!! %s returned with a non-zero exit code. This generally indicates an error.\n") % mymailhost)
else:
try:
if int(mymailport) > 100000:
myconn = smtplib.SMTP(mymailhost, int(mymailport) - 100000)
myconn.ehlo()
if not myconn.has_extn("STARTTLS"):
raise portage.exception.PortageException(_("!!! TLS support requested for logmail but not supported by server"))
myconn.starttls()
myconn.ehlo()
else:
myconn = smtplib.SMTP(mymailhost, mymailport)
if mymailuser != "" and mymailpasswd != "":
myconn.login(mymailuser, mymailpasswd)
message_str = _force_ascii_if_necessary(message.as_string())
myconn.sendmail(myfrom, myrecipient, message_str)
myconn.quit()
except smtplib.SMTPException as e:
raise portage.exception.PortageException(_("!!! An error occurred while trying to send logmail:\n")+str(e))
except socket.error as e:
raise portage.exception.PortageException(_("!!! A network error occurred while trying to send logmail:\n%s\nSure you configured PORTAGE_ELOG_MAILURI correctly?") % str(e))
return