+from __future__ import absolute_import
+from cStringIO import StringIO
+from email.generator import Generator
+from email.parser import Parser
+from email.mime.text import MIMEText
+from email.mime.multipart import MIMEMultipart
+from email.utils import getaddresses, formataddr, formatdate, make_msgid
+import os
+import smtplib
+import subprocess
+import sys
+import tempfile
+
+from ..base import Player, PlayerError
+
+
+# Configure alternative sendmail command in case smtplib is too
+# annoying. Set SENDMAIL to None to use smtplib.
+SENDMAIL = None
+#SENDMAIL = ['/usr/sbin/sendmail', '-t']
+#SENDMAIL = ['/usr/bin/msmtp', '-t']
+
+
+class IncomingEmailDispatcher (object):
+ """For reading reply messages.
+ """
+ def __init__(self, fifo_path=None):
+ self._cache = []
+ if fifo_path == None:
+ self.dir_path = tempfile.mkdtemp(suffix='.pyrisk')
+ self.fifo_path = os.path.join(self.dir_path, 'incoming')
+ else:
+ self.dir_path = None
+ self.fifo_path = os.path.abspath(fifo_path)
+ os.mkfifo(self.fifo_path)
+ def close(self):
+ os.remove(self.fifo_path)
+ if self.dir_path != None:
+ os.rmdir(self.dir_path)
+ def get(self, tag):
+ # FIFO blocks on open until a writer also opens
+ self.fifo = open(self.fifo_path, 'r')
+ for msg_tag, msg in self._cache:
+ if msg_tag == tag:
+ self._cache.remove(msg)
+ return msg
+ msg = self._get_msg()
+ msg_tag = self._msg_tag(msg['Subject'])
+ while msg_tag != tag:
+ self._cache.append((msg_tag, msg))
+ msg = self._get_msg()
+ msg_tag = self._msg_tag(msg['Subject'])
+ self.fifo.close()
+ return msg
+ def _msg_tag(self, subject):
+ """ Return the tag portion of a message subject.
+
+ >>> ied = IncomingEmailDispatcher()
+ >>> ied._msg_tag('[TAG] Hi there')
+ u'[TAG]'
+ >>> ied._msg_tag(' [tg] Hi there')
+ u'[tg]'
+ >>> ied._msg_tag(' Re: [t] Hi there')
+ u'[t]'
+ >>> ied.close()
+ """
+ subject = subject.strip()
+ if subject.startswith('Re:'):
+ subject = subject[len('Re:'):]
+ subject = subject.strip()
+ args = subject.split(u']',1)
+ if len(args) < 1:
+ return None
+ return args[0]+u']'
+ def _get_msg(self):
+ text = self.fifo.read()
+ p = Parser()
+ return p.parsestr(text)
+
+class OutgoingEmailDispatcher (object):
+ """For sending outgoing messages.
+ """
+ def __init__(self, return_address, return_name='PyRisk server',
+ sendmail=None, verbose_excute=False,
+ smtp_host=None, smtp_port=465,
+ smtp_user=None, smtp_password=None):
+ self.return_address = return_address
+ self.return_name = return_name
+ self.sendmail = sendmail
+ if self.sendmail == None:
+ self.sendmail = SENDMAIL
+ self.verbose_execute = verbose_execute
+ self.smtp_host = smtp_host
+ self.smtp_port = smtp_port
+ self.smtp_user = smtp_user
+ self.smtp_password = smtp_password
+ def send(self, msg):
+ """Send an email Message instance on its merry way.
+ """
+ msg['From'] = formataddr((self.return_name, self.return_address))
+ msg['Reply-to'] = msg['From']
+ msg['Date'] = formatdate()
+ msg['Message-id'] = make_msgid()
+
+ if self.sendmail != None:
+ execute(self.sendmail, stdin=self._flatten(msg))
+ return None
+ s = smtplib.SMTP_SSL(self.smtp_host, self.smtp_port)
+ s.connect()
+ s.login(self.smtp_user, self.smtp_password)
+ s.sendmail(from_addr=self._source_email(msg),
+ to_addrs=self._target_emails(msg),
+ msg=self._flatten(msg))
+ s.close()
+ def _execute(self, args, stdin=None, expect=(0,)):
+ """
+ Execute a command (allows us to drive gpg).
+ """
+ if self.verbose_execute == True:
+ print >> sys.stderr, '$ '+args
+ try:
+ p = subprocess.Popen(args,
+ stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ shell=False, close_fds=True)
+ except OSError, e:
+ strerror = '%s\nwhile executing %s' % (e.args[1], args)
+ raise Exception, strerror
+ output, error = p.communicate(input=stdin)
+ status = p.wait()
+ if self.verbose_execute == True:
+ print >> sys.stderr, '(status: %d)\n%s%s' % (status, output, error)
+ if status not in expect:
+ strerror = '%s\nwhile executing %s\n%s\n%d' % (args[1], args, error, status)
+ raise Exception, strerror
+ return status, output, error
+ def _source_email(self, msg, return_realname=False):
+ """
+ Search the header of an email Message instance to find the
+ sender's email address.
+ """
+ froms = msg.get_all('from', [])
+ from_tuples = getaddresses(froms) # [(realname, email_address), ...]
+ if return_realname == True:
+ return from_tuples[0] # (realname, email_address)
+ return from_tuples[0][1] # email_address
+ def _target_emails(self, msg):
+ """
+ Search the header of an email Message instance to find a
+ list of recipient's email addresses.
+ """
+ tos = msg.get_all('to', [])
+ ccs = msg.get_all('cc', [])
+ bccs = msg.get_all('bcc', [])
+ resent_tos = msg.get_all('resent-to', [])
+ resent_ccs = msg.get_all('resent-cc', [])
+ resent_bccs = msg.get_all('resent-bcc', [])
+ resent = resent_tos + resent_ccs + resent_bccs
+ if len(resent) > 0:
+ all_recipients = getaddresses(resent)
+ else:
+ all_recipients = getaddresses(tos + ccs + bccs)
+ return [addr[1] for addr in all_recipients]
+ def _flatten(self, msg, to_unicode=False):
+ """
+ Produce flat text output from an email Message instance.
+ """
+ assert msg != None
+ fp = StringIO()
+ g = Generator(fp, mangle_from_=False)
+ g.flatten(msg)
+ text = fp.getvalue()
+ if to_unicode == True:
+ encoding = msg.get_content_charset('utf-8')
+ text = unicode(text, encoding=encoding)
+ return text
+
+def encodedMIMEText(body, encoding='us-ascii', disposition='inline', filename=None):
+ if encoding == 'us-ascii':
+ part = MIMEText(body)
+ else:
+ # Create the message ('plain' stands for Content-Type: text/plain)
+ part = MIMEText(body.encode(encoding), 'plain', encoding)
+ if filename == None:
+ part.add_header('Content-Disposition', disposition)
+ else:
+ part.add_header('Content-Disposition', disposition, filename=filename)
+ return part
+