|
1 #!/usr/bin/env python |
|
2 # |
|
3 # Copyright 2007 Google Inc. |
|
4 # |
|
5 # Licensed under the Apache License, Version 2.0 (the "License"); |
|
6 # you may not use this file except in compliance with the License. |
|
7 # You may obtain a copy of the License at |
|
8 # |
|
9 # http://www.apache.org/licenses/LICENSE-2.0 |
|
10 # |
|
11 # Unless required by applicable law or agreed to in writing, software |
|
12 # distributed under the License is distributed on an "AS IS" BASIS, |
|
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
14 # See the License for the specific language governing permissions and |
|
15 # limitations under the License. |
|
16 # |
|
17 |
|
18 """Stub version of the mail API, writes email to logs and can optionally |
|
19 send real email via SMTP or sendmail.""" |
|
20 |
|
21 |
|
22 |
|
23 |
|
24 |
|
25 from email import MIMEBase |
|
26 from email import MIMEMultipart |
|
27 from email import MIMEText |
|
28 import logging |
|
29 import mail |
|
30 import mimetypes |
|
31 import subprocess |
|
32 import smtplib |
|
33 |
|
34 |
|
35 class ServiceStub(object): |
|
36 """Service stub base class used to forward requests to methods. |
|
37 |
|
38 Use this base class to defined service stub classes. Instead of overriding |
|
39 MakeSyncCall, the default implementation forwards the call to appropriate |
|
40 sub-class method. |
|
41 |
|
42 If the sub class defines a static string 'SERVICE', it will also check |
|
43 to make sure that calls to this service stub are always made to that named |
|
44 service. |
|
45 """ |
|
46 |
|
47 def MakeSyncCall(self, service, call, request, response): |
|
48 """The main RPC entry point. |
|
49 |
|
50 Args: |
|
51 service: Must be name as defined by sub class variable SERVICE. |
|
52 call: A string representing the rpc to make. Must be part of |
|
53 MailService. |
|
54 request: A protocol buffer of the type corresponding to 'call'. |
|
55 response: A protocol buffer of the type corresponding to 'call'. |
|
56 """ |
|
57 assert not hasattr(self, 'SERVICE') or service == self.SERVICE |
|
58 explanation = [] |
|
59 assert request.IsInitialized(explanation), explanation |
|
60 |
|
61 attr = getattr(self, '_Dynamic_' + call) |
|
62 attr(request, response) |
|
63 |
|
64 |
|
65 class MailServiceStub(ServiceStub): |
|
66 """Python only mail service stub. |
|
67 |
|
68 This stub does not actually attempt to send email. instead it merely logs |
|
69 a description of the email to the developers console. |
|
70 |
|
71 Args: |
|
72 host: Host of SMTP server to use. Blank disables sending SMTP. |
|
73 port: Port of SMTP server to use. |
|
74 user: User to log in to SMTP server as. |
|
75 password: Password for SMTP server user. |
|
76 """ |
|
77 SERVICE = 'mail' |
|
78 |
|
79 def __init__(self, |
|
80 host=None, |
|
81 port=25, |
|
82 user='', |
|
83 password='', |
|
84 enable_sendmail=False, |
|
85 show_mail_body=False): |
|
86 self._smtp_host = host |
|
87 self._smtp_port = port |
|
88 self._smtp_user = user |
|
89 self._smtp_password = password |
|
90 self._enable_sendmail = enable_sendmail |
|
91 self._show_mail_body = show_mail_body |
|
92 |
|
93 def _GenerateLog(self, method, message, log): |
|
94 """Generate a list of log messages representing sent mail. |
|
95 |
|
96 Args: |
|
97 message: Message to write to log. |
|
98 log: Log function of type string -> None |
|
99 """ |
|
100 log('MailService.%s' % method) |
|
101 log(' From: %s' % message.sender()) |
|
102 |
|
103 for address in message.to_list(): |
|
104 log(' To: %s' % address) |
|
105 for address in message.cc_list(): |
|
106 log(' Cc: %s' % address) |
|
107 for address in message.bcc_list(): |
|
108 log(' Bcc: %s' % address) |
|
109 |
|
110 if message.replyto(): |
|
111 log(' Reply-to: %s' % message.replyto()) |
|
112 |
|
113 log(' Subject: %s' % message.subject()) |
|
114 |
|
115 if message.has_textbody(): |
|
116 log(' Body:') |
|
117 log(' Content-type: text/plain') |
|
118 log(' Data length: %d' % len(message.textbody())) |
|
119 if self._show_mail_body: |
|
120 log('-----\n' + message.textbody() + '\n-----') |
|
121 |
|
122 if message.has_htmlbody(): |
|
123 log(' Body:') |
|
124 log(' Content-type: text/html') |
|
125 log(' Data length: %d' % len(message.htmlbody())) |
|
126 if self._show_mail_body: |
|
127 log('-----\n' + message.htmlbody() + '\n-----') |
|
128 |
|
129 for attachment in message.attachment_list(): |
|
130 log(' Attachment:') |
|
131 log(' File name: %s' % attachment.filename()) |
|
132 log(' Data length: %s' % len(attachment.data())) |
|
133 |
|
134 def _SendSMTP(self, mime_message, smtp_lib=smtplib.SMTP): |
|
135 """Send MIME message via SMTP. |
|
136 |
|
137 Connects to SMTP server and sends MIME message. If user is supplied |
|
138 will try to login to that server to send as authenticated. Does not |
|
139 currently support encryption. |
|
140 |
|
141 Args: |
|
142 mime_message: MimeMessage to send. Create using ToMIMEMessage. |
|
143 smtp_lib: Class of SMTP library. Used for dependency injection. |
|
144 """ |
|
145 smtp = smtp_lib() |
|
146 try: |
|
147 smtp.connect(self._smtp_host, self._smtp_port) |
|
148 if self._smtp_user: |
|
149 smtp.login(self._smtp_user, self._smtp_password) |
|
150 |
|
151 tos = ', '.join([mime_message[to] for to in ['To', 'Cc', 'Bcc'] |
|
152 if mime_message[to]]) |
|
153 smtp.sendmail(mime_message['From'], tos, str(mime_message)) |
|
154 finally: |
|
155 smtp.quit() |
|
156 |
|
157 def _SendSendmail(self, mime_message, |
|
158 popen=subprocess.Popen, |
|
159 sendmail_command='sendmail'): |
|
160 """Send MIME message via sendmail, if exists on computer. |
|
161 |
|
162 Attempts to send email via sendmail. Any IO failure, including |
|
163 the program not being found is ignored. |
|
164 |
|
165 Args: |
|
166 mime_message: MimeMessage to send. Create using ToMIMEMessage. |
|
167 popen: popen function to create a new sub-process. |
|
168 """ |
|
169 try: |
|
170 tos = [mime_message[to] for to in ['To', 'Cc', 'Bcc'] if mime_message[to]] |
|
171 sendmail_command = '%s %s' % (sendmail_command, ' '.join(tos)) |
|
172 |
|
173 try: |
|
174 child = popen(sendmail_command, |
|
175 shell=True, |
|
176 stdin=subprocess.PIPE, |
|
177 stdout=subprocess.PIPE) |
|
178 except (IOError, OSError), e: |
|
179 logging.error('Unable to open pipe to sendmail') |
|
180 raise |
|
181 try: |
|
182 child.stdin.write(str(mime_message)) |
|
183 child.stdin.close() |
|
184 finally: |
|
185 while child.poll() is None: |
|
186 child.stdout.read(100) |
|
187 child.stdout.close() |
|
188 except (IOError, OSError), e: |
|
189 logging.error('Error sending mail using sendmail: ' + str(e)) |
|
190 |
|
191 def _Send(self, request, response, log=logging.info, |
|
192 smtp_lib=smtplib.SMTP, |
|
193 popen=subprocess.Popen, |
|
194 sendmail_command='sendmail'): |
|
195 """Implementation of MailServer::Send(). |
|
196 |
|
197 Logs email message. Contents of attachments are not shown, only |
|
198 their sizes. If SMTP is configured, will send via SMTP, else |
|
199 will use Sendmail if it is installed. |
|
200 |
|
201 Args: |
|
202 request: The message to send, a SendMailRequest. |
|
203 response: The send response, a SendMailResponse. |
|
204 log: Log function to send log information. Used for dependency |
|
205 injection. |
|
206 smtp_lib: Class of SMTP library. Used for dependency injection. |
|
207 popen2: popen2 function to use for opening pipe to other process. |
|
208 Used for dependency injection. |
|
209 """ |
|
210 self._GenerateLog('Send', request, log) |
|
211 |
|
212 if self._smtp_host and self._enable_sendmail: |
|
213 log('Both SMTP and sendmail are enabled. Ignoring sendmail.') |
|
214 |
|
215 mime_message = mail.MailMessageToMIMEMessage(request) |
|
216 if self._smtp_host: |
|
217 self._SendSMTP(mime_message, smtp_lib) |
|
218 elif self._enable_sendmail: |
|
219 self._SendSendmail(mime_message, popen, sendmail_command) |
|
220 else: |
|
221 logging.info('You are not currently sending out real email. ' |
|
222 'If you have sendmail installed you can use it ' |
|
223 'by using the server with --enable_sendmail') |
|
224 |
|
225 _Dynamic_Send = _Send |
|
226 |
|
227 def _SendToAdmins(self, request, response, log=logging.info): |
|
228 """Implementation of MailServer::SendToAdmins(). |
|
229 |
|
230 Logs email message. Contents of attachments are not shown, only |
|
231 their sizes. |
|
232 |
|
233 Given the difficulty of determining who the actual sender |
|
234 is, Sendmail and SMTP are disabled for this action. |
|
235 |
|
236 Args: |
|
237 request: The message to send, a SendMailRequest. |
|
238 response: The send response, a SendMailResponse. |
|
239 log: Log function to send log information. Used for dependency |
|
240 injection. |
|
241 """ |
|
242 self._GenerateLog('SendToAdmins', request, log) |
|
243 |
|
244 if self._smtp_host and self._enable_sendmail: |
|
245 log('Both SMTP and sendmail are enabled. Ignoring sendmail.') |
|
246 |
|
247 _Dynamic_SendToAdmins = _SendToAdmins |