comparison roundup/mailer.py @ 6020:aa26a260e81c

flake8 fixes: fix typoed variable; remove unused imports; format fixes fix typo: crypto_to type -> crypt_to other format fixes
author John Rouillard <rouilj@ieee.org>
date Thu, 02 Jan 2020 20:39:19 -0500
parents b3905faf58ea
children 087cae2fbcea
comparison
equal deleted inserted replaced
6019:a328f9e3307b 6020:aa26a260e81c
1 """Sending Roundup-specific mail over SMTP. 1 """Sending Roundup-specific mail over SMTP.
2 """ 2 """
3 __docformat__ = 'restructuredtext' 3 __docformat__ = 'restructuredtext'
4 4
5 import time, quopri, os, socket, smtplib, re, sys, traceback, email, logging 5 import time, os, socket, smtplib, sys, traceback, logging
6 6
7 from roundup import __version__ 7 from roundup import __version__
8 from roundup.date import get_timezone, Date 8 from roundup.date import get_timezone, Date
9 9
10 from email import charset 10 from email import charset
11 from email.utils import formatdate, formataddr, specialsre, escapesre 11 from email.utils import formatdate, specialsre, escapesre
12 from email.charset import Charset 12 from email.charset import Charset
13 from email.message import Message
14 from email.header import Header 13 from email.header import Header
15 from email.mime.base import MIMEBase 14 from email.mime.base import MIMEBase
16 from email.mime.text import MIMEText 15 from email.mime.text import MIMEText
17 from email.mime.multipart import MIMEMultipart 16 from email.mime.multipart import MIMEMultipart
18 from email.mime.nonmultipart import MIMENonMultipart 17 from email.mime.nonmultipart import MIMENonMultipart
19 18
20 from roundup.anypy import email_ 19 from roundup.anypy import email_
21 from roundup.anypy.strings import b2s, s2b, s2u 20 from roundup.anypy.strings import b2s, s2u
22 21
23 try: 22 try:
24 import gpg, gpg.core 23 import gpg, gpg.core
25 except ImportError: 24 except ImportError:
26 gpg = None 25 gpg = None
27 26
28 27
29 class MessageSendError(RuntimeError): 28 class MessageSendError(RuntimeError):
30 pass 29 pass
30
31 31
32 def nice_sender_header(name, address, charset): 32 def nice_sender_header(name, address, charset):
33 # construct an address header so it's as human-readable as possible 33 # construct an address header so it's as human-readable as possible
34 # even in the presence of a non-ASCII name part 34 # even in the presence of a non-ASCII name part
35 if not name: 35 if not name:
40 # use Header to encode correctly. 40 # use Header to encode correctly.
41 encname = Header(name, charset=charset).encode() 41 encname = Header(name, charset=charset).encode()
42 42
43 # the important bits of formataddr() 43 # the important bits of formataddr()
44 if specialsre.search(encname): 44 if specialsre.search(encname):
45 encname = '"%s"'%escapesre.sub(r'\\\g<0>', encname) 45 encname = '"%s"' % escapesre.sub(r'\\\g<0>', encname)
46 46
47 # now format the header as a string - don't return a Header as anonymous 47 # now format the header as a string - don't return a Header as anonymous
48 # headers play poorly with Messages (eg. won't get wrapped properly) 48 # headers play poorly with Messages (eg. won't get wrapped properly)
49 return '%s <%s>'%(encname, address) 49 return '%s <%s>' % (encname, address)
50
50 51
51 class Mailer: 52 class Mailer:
52 """Roundup-specific mail sending.""" 53 """Roundup-specific mail sending."""
53 def __init__(self, config): 54 def __init__(self, config):
54 self.config = config 55 self.config = config
125 Returns a Message object. 126 Returns a Message object.
126 ''' 127 '''
127 if multipart: 128 if multipart:
128 message = MIMEMultipart() 129 message = MIMEMultipart()
129 else: 130 else:
130 message = self.get_text_message(getattr(self.config, 'EMAIL_CHARSET', 'utf-8')) 131 message = self.get_text_message(getattr(self.config,
132 'EMAIL_CHARSET', 'utf-8'))
131 133
132 return message 134 return message
133 135
134 def standard_message(self, to, subject, content, author=None): 136 def standard_message(self, to, subject, content, author=None):
135 """Send a standard message. 137 """Send a standard message.
167 if crypt: 169 if crypt:
168 crypt_to = to 170 crypt_to = to
169 to = None 171 to = None
170 # see whether we should send to the dispatcher or not 172 # see whether we should send to the dispatcher or not
171 dispatcher_email = getattr(self.config, "DISPATCHER_EMAIL", 173 dispatcher_email = getattr(self.config, "DISPATCHER_EMAIL",
172 getattr(self.config, "ADMIN_EMAIL")) 174 getattr(self.config, "ADMIN_EMAIL"))
173 error_messages_to = getattr(self.config, "ERROR_MESSAGES_TO", "user") 175 error_messages_to = getattr(self.config, "ERROR_MESSAGES_TO", "user")
174 if error_messages_to == "dispatcher": 176 if error_messages_to == "dispatcher":
175 to = [dispatcher_email] 177 to = [dispatcher_email]
176 crypt = False 178 crypt = False
177 crypt_to = None 179 crypt_to = None
225 crypt_to) 227 crypt_to)
226 crypt_to = adrs 228 crypt_to = adrs
227 if crypt_to: 229 if crypt_to:
228 try: 230 try:
229 ctx.op_encrypt(keys, 1, plain, cipher) 231 ctx.op_encrypt(keys, 1, plain, cipher)
230 cipher.seek(0,0) 232 cipher.seek(0, 0)
231 message=MIMEMultipart('encrypted', boundary=None, 233 message = MIMEMultipart('encrypted', boundary=None,
232 _subparts=None, protocol="application/pgp-encrypted") 234 _subparts=None,
233 part=MIMEBase('application', 'pgp-encrypted') 235 protocol="application/pgp-encrypted")
236 part = MIMEBase('application', 'pgp-encrypted')
234 part.set_payload("Version: 1\r\n") 237 part.set_payload("Version: 1\r\n")
235 message.attach(part) 238 message.attach(part)
236 part=MIMEBase('application', 'octet-stream') 239 part = MIMEBase('application', 'octet-stream')
237 part.set_payload(cipher.read()) 240 part.set_payload(cipher.read())
238 message.attach(part) 241 message.attach(part)
239 except gpg.GPGMEError: 242 except gpg.GPGMEError:
240 self.logger.debug("bounce_message: Cannot encrypt to %s", 243 self.logger.debug("bounce_message: Cannot encrypt to %s",
241 str(crypto_to)) 244 str(crypt_to))
242 crypt_to = None 245 crypt_to = None
243 if crypt_to: 246 if crypt_to:
244 self.set_message_attributes(message, crypt_to, subject) 247 self.set_message_attributes(message, crypt_to, subject)
245 try: 248 try:
246 self.smtp_send(crypt_to, message.as_string()) 249 self.smtp_send(crypt_to, message.as_string())
251 254
252 def exception_message(self): 255 def exception_message(self):
253 '''Send a message to the admins with information about the latest 256 '''Send a message to the admins with information about the latest
254 traceback. 257 traceback.
255 ''' 258 '''
256 subject = '%s: %s'%(self.config.TRACKER_NAME, sys.exc_info()[1]) 259 subject = '%s: %s' % (self.config.TRACKER_NAME, sys.exc_info()[1])
257 to = [self.config.ADMIN_EMAIL] 260 to = [self.config.ADMIN_EMAIL]
258 content = '\n'.join(traceback.format_exception(*sys.exc_info())) 261 content = '\n'.join(traceback.format_exception(*sys.exc_info()))
259 self.standard_message(to, subject, content) 262 self.standard_message(to, subject, content)
260 263
261 def smtp_send(self, to, message, sender=None): 264 def smtp_send(self, to, message, sender=None):
272 sender = self.config.ADMIN_EMAIL 275 sender = self.config.ADMIN_EMAIL
273 if self.debug: 276 if self.debug:
274 # don't send - just write to a file, use unix from line so 277 # don't send - just write to a file, use unix from line so
275 # that resulting file can be openened in a mailer 278 # that resulting file can be openened in a mailer
276 fmt = '%a %b %m %H:%M:%S %Y' 279 fmt = '%a %b %m %H:%M:%S %Y'
277 unixfrm = 'From %s %s' % (sender, Date ('.').pretty (fmt)) 280 unixfrm = 'From %s %s' % (sender, Date('.').pretty(fmt))
278 open(self.debug, 'a').write('%s\nFROM: %s\nTO: %s\n%s\n\n' % 281 open(self.debug, 'a').write('%s\nFROM: %s\nTO: %s\n%s\n\n' %
279 (unixfrm, sender, 282 (unixfrm, sender,
280 ', '.join(to), message)) 283 ', '.join(to), message))
281 else: 284 else:
282 # now try to send the message 285 # now try to send the message
285 # instead of to roundup 288 # instead of to roundup
286 smtp = SMTPConnection(self.config) 289 smtp = SMTPConnection(self.config)
287 smtp.sendmail(sender, to, message) 290 smtp.sendmail(sender, to, message)
288 except socket.error as value: 291 except socket.error as value:
289 raise MessageSendError("Error: couldn't send email: " 292 raise MessageSendError("Error: couldn't send email: "
290 "mailhost %s"%value) 293 "mailhost %s" % value)
291 except smtplib.SMTPException as msg: 294 except smtplib.SMTPException as msg:
292 raise MessageSendError("Error: couldn't send email: %s"%msg) 295 raise MessageSendError("Error: couldn't send email: %s" % msg)
296
293 297
294 class SMTPConnection(smtplib.SMTP): 298 class SMTPConnection(smtplib.SMTP):
295 ''' Open an SMTP connection to the mailhost specified in the config 299 ''' Open an SMTP connection to the mailhost specified in the config
296 ''' 300 '''
297 def __init__(self, config): 301 def __init__(self, config):
300 304
301 # start the TLS if requested 305 # start the TLS if requested
302 if config["MAIL_TLS"]: 306 if config["MAIL_TLS"]:
303 self.ehlo() 307 self.ehlo()
304 self.starttls(config["MAIL_TLS_KEYFILE"], 308 self.starttls(config["MAIL_TLS_KEYFILE"],
305 config["MAIL_TLS_CERTFILE"]) 309 config["MAIL_TLS_CERTFILE"])
306 310
307 # ok, now do we also need to log in? 311 # ok, now do we also need to log in?
308 mailuser = config["MAIL_USERNAME"] 312 mailuser = config["MAIL_USERNAME"]
309 if mailuser: 313 if mailuser:
310 self.login(mailuser, config["MAIL_PASSWORD"]) 314 self.login(mailuser, config["MAIL_PASSWORD"])

Roundup Issue Tracker: http://roundup-tracker.org/