Update timestamps in headers of modified files.
[portage.git] / pym / portage / mail.py
1 # Copyright 1998-2011 Gentoo Foundation
2 # Distributed under the terms of the GNU General Public License v2
3
4 # Since python ebuilds remove the 'email' module when USE=build
5 # is enabled, use a local import so that
6 # portage.proxy.lazyimport._preload_portage_submodules()
7 # can load this module even though the 'email' module is missing.
8 # The elog mail modules won't work, but at least an ImportError
9 # won't cause portage to crash during stage builds. Since the
10 # 'smtlib' module imports the 'email' module, that's imported
11 # locally as well.
12
13 import socket
14 import sys
15 import time
16
17 from portage import os
18 from portage import _encodings
19 from portage import _unicode_decode, _unicode_encode
20 from portage.localization import _
21 import portage
22
23 if sys.hexversion >= 0x3000000:
24         basestring = str
25
26         def _force_ascii_if_necessary(s):
27                 # Force ascii encoding in order to avoid UnicodeEncodeError
28                 # from smtplib.sendmail with python3 (bug #291331).
29                 s = _unicode_encode(s,
30                         encoding='ascii', errors='backslashreplace')
31                 s = _unicode_decode(s,
32                         encoding='ascii', errors='replace')
33                 return s
34
35 else:
36
37         def _force_ascii_if_necessary(s):
38                 return s
39
40 def TextMessage(_text):
41         from email.mime.text import MIMEText
42         mimetext = MIMEText(_text)
43         if sys.hexversion >= 0x3000000:
44                 mimetext.set_charset("UTF-8")
45         return mimetext
46
47 def create_message(sender, recipient, subject, body, attachments=None):
48
49         from email.header import Header
50         from email.mime.base import MIMEBase as BaseMessage
51         from email.mime.multipart import MIMEMultipart as MultipartMessage
52
53         if sys.hexversion < 0x3000000:
54                 sender = _unicode_encode(sender,
55                         encoding=_encodings['content'], errors='strict')
56                 recipient = _unicode_encode(recipient,
57                         encoding=_encodings['content'], errors='strict')
58                 subject = _unicode_encode(subject,
59                         encoding=_encodings['content'], errors='backslashreplace')
60                 body = _unicode_encode(body,
61                         encoding=_encodings['content'], errors='backslashreplace')
62
63         if attachments == None:
64                 mymessage = TextMessage(body)
65         else:
66                 mymessage = MultipartMessage()
67                 mymessage.attach(TextMessage(body))
68                 for x in attachments:
69                         if isinstance(x, BaseMessage):
70                                 mymessage.attach(x)
71                         elif isinstance(x, basestring):
72                                 if sys.hexversion < 0x3000000:
73                                         x = _unicode_encode(x,
74                                                 encoding=_encodings['content'],
75                                                 errors='backslashreplace')
76                                 mymessage.attach(TextMessage(x))
77                         else:
78                                 raise portage.exception.PortageException(_("Can't handle type of attachment: %s") % type(x))
79
80         mymessage.set_unixfrom(sender)
81         mymessage["To"] = recipient
82         mymessage["From"] = sender
83
84         # Use Header as a workaround so that long subject lines are wrapped
85         # correctly by <=python-2.6 (gentoo bug #263370, python issue #1974).
86         # Also, need to force ascii for python3, in order to avoid
87         # UnicodeEncodeError with non-ascii characters:
88         #  File "/usr/lib/python3.1/email/header.py", line 189, in __init__
89         #    self.append(s, charset, errors)
90         #  File "/usr/lib/python3.1/email/header.py", line 262, in append
91         #    input_bytes = s.encode(input_charset, errors)
92         #UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-9: ordinal not in range(128)
93         mymessage["Subject"] = Header(_force_ascii_if_necessary(subject))
94         mymessage["Date"] = time.strftime("%a, %d %b %Y %H:%M:%S %z")
95         
96         return mymessage
97
98 def send_mail(mysettings, message):
99
100         import smtplib
101
102         mymailhost = "localhost"
103         mymailport = 25
104         mymailuser = ""
105         mymailpasswd = ""
106         myrecipient = "root@localhost"
107         
108         # Syntax for PORTAGE_ELOG_MAILURI (if defined):
109         # address [[user:passwd@]mailserver[:port]]
110         # where address:    recipient address
111         #       user:       username for smtp auth (defaults to none)
112         #       passwd:     password for smtp auth (defaults to none)
113         #       mailserver: smtp server that should be used to deliver the mail (defaults to localhost)
114         #                                       alternatively this can also be the absolute path to a sendmail binary if you don't want to use smtp
115         #       port:       port to use on the given smtp server (defaults to 25, values > 100000 indicate that starttls should be used on (port-100000))
116         if " " in mysettings.get("PORTAGE_ELOG_MAILURI", ""):
117                 myrecipient, mymailuri = mysettings["PORTAGE_ELOG_MAILURI"].split()
118                 if "@" in mymailuri:
119                         myauthdata, myconndata = mymailuri.rsplit("@", 1)
120                         try:
121                                 mymailuser,mymailpasswd = myauthdata.split(":")
122                         except ValueError:
123                                 print(_("!!! invalid SMTP AUTH configuration, trying unauthenticated ..."))
124                 else:
125                         myconndata = mymailuri
126                 if ":" in myconndata:
127                         mymailhost,mymailport = myconndata.split(":")
128                 else:
129                         mymailhost = myconndata
130         else:
131                 myrecipient = mysettings.get("PORTAGE_ELOG_MAILURI", "")
132         
133         myfrom = message.get("From")
134
135         if sys.hexversion < 0x3000000:
136                 myrecipient = _unicode_encode(myrecipient,
137                         encoding=_encodings['content'], errors='strict')
138                 mymailhost = _unicode_encode(mymailhost,
139                         encoding=_encodings['content'], errors='strict')
140                 mymailport = _unicode_encode(mymailport,
141                         encoding=_encodings['content'], errors='strict')
142                 myfrom = _unicode_encode(myfrom,
143                         encoding=_encodings['content'], errors='strict')
144                 mymailuser = _unicode_encode(mymailuser,
145                         encoding=_encodings['content'], errors='strict')
146                 mymailpasswd = _unicode_encode(mymailpasswd,
147                         encoding=_encodings['content'], errors='strict')
148
149         # user wants to use a sendmail binary instead of smtp
150         if mymailhost[0] == os.sep and os.path.exists(mymailhost):
151                 fd = os.popen(mymailhost+" -f "+myfrom+" "+myrecipient, "w")
152                 fd.write(_force_ascii_if_necessary(message.as_string()))
153                 if fd.close() != None:
154                         sys.stderr.write(_("!!! %s returned with a non-zero exit code. This generally indicates an error.\n") % mymailhost)
155         else:
156                 try:
157                         if int(mymailport) > 100000:
158                                 myconn = smtplib.SMTP(mymailhost, int(mymailport) - 100000)
159                                 myconn.ehlo()
160                                 if not myconn.has_extn("STARTTLS"):
161                                         raise portage.exception.PortageException(_("!!! TLS support requested for logmail but not supported by server"))
162                                 myconn.starttls()
163                                 myconn.ehlo()
164                         else:
165                                 myconn = smtplib.SMTP(mymailhost, mymailport)
166                         if mymailuser != "" and mymailpasswd != "":
167                                 myconn.login(mymailuser, mymailpasswd)
168
169                         message_str = _force_ascii_if_necessary(message.as_string())
170                         myconn.sendmail(myfrom, myrecipient, message_str)
171                         myconn.quit()
172                 except smtplib.SMTPException as e:
173                         raise portage.exception.PortageException(_("!!! An error occurred while trying to send logmail:\n")+str(e))
174                 except socket.error as e:
175                         raise portage.exception.PortageException(_("!!! A network error occurred while trying to send logmail:\n%s\nSure you configured PORTAGE_ELOG_MAILURI correctly?") % str(e))
176         return
177