Mercurial > p > roundup > code
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"]) |
