PushService: Initial release
[enigma2-plugins.git] / pushservice / src / mail.py
1 #!/usr/bin/env python
2 # coding: utf-8
3 #
4 # Copyright 2010 Alexandre Fiori
5 # based on the original Tornado by Facebook
6 #
7 # Licensed under the Apache License, Version 2.0 (the "License"); you may
8 # not use this file except in compliance with the License. You may obtain
9 # a copy of the License at
10 #
11 #     http://www.apache.org/licenses/LICENSE-2.0
12 #
13 # Unless required by applicable law or agreed to in writing, software
14 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
15 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
16 # License for the specific language governing permissions and limitations
17 # under the License.
18 #
19 # Changes:
20 # 24.02.2012  betonme
21 #             Added retries and timeout parameter
22 #             Return deferred and connector
23   
24 """Implementation of e-mail Message and SMTP with and without SSL"""
25   
26 import types
27 import os.path
28 from cStringIO import StringIO
29 from OpenSSL.SSL import SSLv3_METHOD
30   
31 from email import Encoders
32 from email.MIMEText import MIMEText
33 from email.MIMEBase import MIMEBase
34 from email.MIMEMultipart import MIMEMultipart
35 from email.Utils import COMMASPACE, formatdate
36   
37 from twisted.internet import reactor
38 from twisted.internet.defer import Deferred
39 from twisted.internet.ssl import ClientContextFactory
40 from twisted.mail.smtp import ESMTPSenderFactory
41   
42 class Message(object):
43     def __init__(self, from_addr, to_addrs, subject, message, mime="text/plain", charset="utf-8"):
44         self.subject = subject
45         self.from_addr = from_addr
46         self.to_addrs = isinstance(to_addrs, types.StringType) and [to_addrs] or to_addrs
47   
48         self.msg = None
49         self.__cache = None
50         self.message = MIMEText(message)
51         self.message.set_charset(charset)
52         self.message.set_type(mime)
53   
54     def attach(self, filename, mime=None, charset=None, content=None):
55         base = os.path.basename(filename)
56         if content is None:
57             fd = open(filename)
58             content = fd.read()
59             fd.close()
60   
61         if not isinstance(content, types.StringType):
62             raise TypeError("don't know how to handle content: %s" % type(content))
63   
64         part = MIMEBase("application", "octet-stream")
65         part.set_payload(content)
66         Encoders.encode_base64(part)
67         part.add_header("Content-Disposition", "attachment; filename=\"%s\"" % base)
68   
69         if mime is not None:
70             part.set_type(mime)
71   
72         if charset is not None:
73             part.set_charset(charset)
74   
75         if self.msg is None:
76             self.msg = MIMEMultipart()
77             self.msg.attach(self.message)
78   
79         self.msg.attach(part)
80   
81     def __str__(self):
82         return self.__cache or "nuswit mail message: not rendered yet"
83   
84     def render(self):
85         if self.msg is None:
86             self.msg = self.message
87   
88         self.msg["Subject"] = self.subject
89         self.msg["From"] = self.from_addr
90         self.msg["To"] = COMMASPACE.join(self.to_addrs)
91         self.msg["Date"] = formatdate(localtime=True)
92   
93         if self.__cache is None:
94             self.__cache = self.msg.as_string()
95   
96         return StringIO(self.__cache)
97
98
99 def sendmail(mailconf, message):
100     """Takes a regular dictionary as mailconf, as follows:
101   
102     mailconf["host"] = "your.smtp.com" (required)
103     mailconf["port"] = 25 (optional, default 25 or 587 for TLS)
104     mailconf["username"] = "username" (optional)
105     mailconf["password"] = "password" (optional)
106     mailconf["tls"] = True | False (optional, default False)
107     mailconf["retries"] = 0 (optional, default 0)
108     mailconf["timeout"] = 30 (optional, default 30)
109     """
110     if not isinstance(mailconf, types.DictType):
111         raise TypeError("mailconf must be a regular python dictionary")
112   
113     if not isinstance(message, Message):
114         raise TypeError("message must be an instance of nuswit.mail.Message")
115   
116     host = mailconf.get("host")
117     if not isinstance(host, types.StringType):
118         raise ValueError("mailconf requires a 'host' configuration")
119   
120     if mailconf.get("tls", False) is True:
121         port = mailconf.get("port", 587)
122         contextFactory = ClientContextFactory()
123         contextFactory.method = SSLv3_METHOD
124     else:
125         port = mailconf.get("port", 25)
126         contextFactory = None
127     
128     retries = mailconf.get("retries", 0)
129     timeout = mailconf.get("timeout", 30)
130     
131     if not isinstance(port, types.IntType):
132         raise ValueError("mailconf requires a proper 'port' configuration")
133     
134     deferred = Deferred()
135     username, password = mailconf.get("username"), mailconf.get("password")
136     factory = ESMTPSenderFactory(
137         username, password,
138         message.from_addr, message.to_addrs, message.render(),
139         deferred, contextFactory=contextFactory,
140         requireAuthentication=(username and password),
141         retries=retries, timeout=timeout)
142     
143     connector = reactor.connectTCP(host, port, factory, timeout=timeout)
144     
145     return deferred, connector