blob: 732066ef9a5a35814590435ae7c0a2de0af06455 [file] [log] [blame]
Benjamin Peterson90f5ba52010-03-11 22:53:45 +00001#! /usr/bin/env python3
R David Murray2539e672014-08-09 16:40:49 -04002"""An RFC 5321 smtp proxy with optional RFC 1870 and RFC 6531 extensions.
Barry Warsaw7e0d9562001-01-31 22:51:35 +00003
Barry Warsaw0e8427e2001-10-04 16:27:04 +00004Usage: %(program)s [options] [localhost:localport [remotehost:remoteport]]
Barry Warsaw7e0d9562001-01-31 22:51:35 +00005
6Options:
7
8 --nosetuid
9 -n
10 This program generally tries to setuid `nobody', unless this flag is
11 set. The setuid call will fail if this program is not run as root (in
12 which case, use this flag).
13
14 --version
15 -V
16 Print the version number and exit.
17
18 --class classname
19 -c classname
Barry Warsawf267b622004-10-09 21:44:13 +000020 Use `classname' as the concrete SMTP proxy class. Uses `PureProxy' by
Barry Warsaw7e0d9562001-01-31 22:51:35 +000021 default.
22
R David Murrayd1a30c92012-05-26 14:33:59 -040023 --size limit
24 -s limit
25 Restrict the total size of the incoming message to "limit" number of
26 bytes via the RFC 1870 SIZE extension. Defaults to 33554432 bytes.
27
R David Murray2539e672014-08-09 16:40:49 -040028 --smtputf8
29 -u
30 Enable the SMTPUTF8 extension and behave as an RFC 6531 smtp proxy.
31
Barry Warsaw7e0d9562001-01-31 22:51:35 +000032 --debug
33 -d
34 Turn on debugging prints.
35
36 --help
37 -h
38 Print this message and exit.
39
40Version: %(__version__)s
41
Barry Warsaw0e8427e2001-10-04 16:27:04 +000042If localhost is not given then `localhost' is used, and if localport is not
43given then 8025 is used. If remotehost is not given then `localhost' is used,
44and if remoteport is not given, then 25 is used.
Barry Warsaw7e0d9562001-01-31 22:51:35 +000045"""
46
47# Overview:
48#
R David Murrayd1a30c92012-05-26 14:33:59 -040049# This file implements the minimal SMTP protocol as defined in RFC 5321. It
Barry Warsaw7e0d9562001-01-31 22:51:35 +000050# has a hierarchy of classes which implement the backend functionality for the
51# smtpd. A number of classes are provided:
52#
Guido van Rossumb8b45ea2001-04-15 13:06:04 +000053# SMTPServer - the base class for the backend. Raises NotImplementedError
Barry Warsaw7e0d9562001-01-31 22:51:35 +000054# if you try to use it.
55#
56# DebuggingServer - simply prints each message it receives on stdout.
57#
58# PureProxy - Proxies all messages to a real smtpd which does final
59# delivery. One known problem with this class is that it doesn't handle
60# SMTP errors from the backend server at all. This should be fixed
61# (contributions are welcome!).
62#
63# MailmanProxy - An experimental hack to work with GNU Mailman
64# <www.list.org>. Using this server as your real incoming smtpd, your
65# mailhost will automatically recognize and accept mail destined to Mailman
66# lists when those lists are created. Every message not destined for a list
67# gets forwarded to a real backend smtpd, as with PureProxy. Again, errors
68# are not handled correctly yet.
69#
Barry Warsaw7e0d9562001-01-31 22:51:35 +000070#
Barry Warsawb1027642004-07-12 23:10:08 +000071# Author: Barry Warsaw <barry@python.org>
Barry Warsaw7e0d9562001-01-31 22:51:35 +000072#
73# TODO:
74#
75# - support mailbox delivery
76# - alias files
R David Murrayd1a30c92012-05-26 14:33:59 -040077# - Handle more ESMTP extensions
Barry Warsaw7e0d9562001-01-31 22:51:35 +000078# - handle error codes from the backend smtpd
79
80import sys
81import os
82import errno
83import getopt
84import time
85import socket
86import asyncore
87import asynchat
R David Murrayd1a30c92012-05-26 14:33:59 -040088import collections
Richard Jones803ef8a2010-07-24 09:51:40 +000089from warnings import warn
R David Murrayd1a30c92012-05-26 14:33:59 -040090from email._header_value_parser import get_addr_spec, get_angle_addr
Barry Warsaw7e0d9562001-01-31 22:51:35 +000091
Skip Montanaro0de65802001-02-15 22:15:14 +000092__all__ = ["SMTPServer","DebuggingServer","PureProxy","MailmanProxy"]
Barry Warsaw7e0d9562001-01-31 22:51:35 +000093
94program = sys.argv[0]
R David Murrayd1a30c92012-05-26 14:33:59 -040095__version__ = 'Python SMTP proxy version 0.3'
Barry Warsaw7e0d9562001-01-31 22:51:35 +000096
97
98class Devnull:
99 def write(self, msg): pass
100 def flush(self): pass
101
102
103DEBUGSTREAM = Devnull()
104NEWLINE = '\n'
Barry Warsaw0e8427e2001-10-04 16:27:04 +0000105COMMASPACE = ', '
R David Murrayd1a30c92012-05-26 14:33:59 -0400106DATA_SIZE_DEFAULT = 33554432
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000107
108
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000109def usage(code, msg=''):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000110 print(__doc__ % globals(), file=sys.stderr)
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000111 if msg:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000112 print(msg, file=sys.stderr)
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000113 sys.exit(code)
114
115
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000116class SMTPChannel(asynchat.async_chat):
117 COMMAND = 0
118 DATA = 1
119
Georg Brandl1e5c5f82010-12-03 07:38:22 +0000120 command_size_limit = 512
R David Murrayd1a30c92012-05-26 14:33:59 -0400121 command_size_limits = collections.defaultdict(lambda x=command_size_limit: x)
R David Murray2539e672014-08-09 16:40:49 -0400122
123 @property
124 def max_command_size_limit(self):
125 try:
126 return max(self.command_size_limits.values())
127 except ValueError:
128 return self.command_size_limit
Georg Brandl1e5c5f82010-12-03 07:38:22 +0000129
Vinay Sajip30298b42013-06-07 15:21:41 +0100130 def __init__(self, server, conn, addr, data_size_limit=DATA_SIZE_DEFAULT,
R David Murray2539e672014-08-09 16:40:49 -0400131 map=None, enable_SMTPUTF8=False, decode_data=None):
Vinay Sajip30298b42013-06-07 15:21:41 +0100132 asynchat.async_chat.__init__(self, conn, map=map)
Richard Jones803ef8a2010-07-24 09:51:40 +0000133 self.smtp_server = server
134 self.conn = conn
135 self.addr = addr
R David Murrayd1a30c92012-05-26 14:33:59 -0400136 self.data_size_limit = data_size_limit
R David Murray2539e672014-08-09 16:40:49 -0400137 self.enable_SMTPUTF8 = enable_SMTPUTF8
138 if enable_SMTPUTF8:
139 if decode_data:
R David Murray1a815382015-10-09 10:19:33 -0400140 raise ValueError("decode_data and enable_SMTPUTF8 cannot"
141 " be set to True at the same time")
R David Murray2539e672014-08-09 16:40:49 -0400142 decode_data = False
R David Murray554bcbf2014-06-11 11:18:08 -0400143 if decode_data is None:
144 warn("The decode_data default of True will change to False in 3.6;"
145 " specify an explicit value for this keyword",
146 DeprecationWarning, 2)
147 decode_data = True
148 self._decode_data = decode_data
149 if decode_data:
150 self._emptystring = ''
151 self._linesep = '\r\n'
152 self._dotsep = '.'
153 self._newline = NEWLINE
154 else:
155 self._emptystring = b''
156 self._linesep = b'\r\n'
Serhiy Storchakaee4c0b92015-03-20 16:48:02 +0200157 self._dotsep = ord(b'.')
R David Murray554bcbf2014-06-11 11:18:08 -0400158 self._newline = b'\n'
R David Murray2539e672014-08-09 16:40:49 -0400159 self._set_rset_state()
Richard Jones803ef8a2010-07-24 09:51:40 +0000160 self.seen_greeting = ''
R David Murray2539e672014-08-09 16:40:49 -0400161 self.extended_smtp = False
162 self.command_size_limits.clear()
Richard Jones803ef8a2010-07-24 09:51:40 +0000163 self.fqdn = socket.getfqdn()
Giampaolo Rodolà9cf5ef42010-08-23 22:28:13 +0000164 try:
165 self.peer = conn.getpeername()
Andrew Svetlov0832af62012-12-18 23:10:48 +0200166 except OSError as err:
Giampaolo Rodolà9cf5ef42010-08-23 22:28:13 +0000167 # a race condition may occur if the other end is closing
168 # before we can get the peername
169 self.close()
170 if err.args[0] != errno.ENOTCONN:
171 raise
172 return
Richard Jones803ef8a2010-07-24 09:51:40 +0000173 print('Peer:', repr(self.peer), file=DEBUGSTREAM)
174 self.push('220 %s %s' % (self.fqdn, __version__))
R David Murray2539e672014-08-09 16:40:49 -0400175
176 def _set_post_data_state(self):
177 """Reset state variables to their post-DATA state."""
178 self.smtp_state = self.COMMAND
179 self.mailfrom = None
180 self.rcpttos = []
181 self.require_SMTPUTF8 = False
182 self.num_bytes = 0
Josiah Carlsond74900e2008-07-07 04:15:08 +0000183 self.set_terminator(b'\r\n')
R David Murray2539e672014-08-09 16:40:49 -0400184
185 def _set_rset_state(self):
186 """Reset all state variables except the greeting."""
187 self._set_post_data_state()
188 self.received_data = ''
189 self.received_lines = []
190
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000191
Richard Jones803ef8a2010-07-24 09:51:40 +0000192 # properties for backwards-compatibility
193 @property
194 def __server(self):
195 warn("Access to __server attribute on SMTPChannel is deprecated, "
Florent Xicluna67317752011-12-10 11:07:42 +0100196 "use 'smtp_server' instead", DeprecationWarning, 2)
Richard Jones803ef8a2010-07-24 09:51:40 +0000197 return self.smtp_server
198 @__server.setter
199 def __server(self, value):
200 warn("Setting __server attribute on SMTPChannel is deprecated, "
Florent Xicluna67317752011-12-10 11:07:42 +0100201 "set 'smtp_server' instead", DeprecationWarning, 2)
Richard Jones803ef8a2010-07-24 09:51:40 +0000202 self.smtp_server = value
203
204 @property
205 def __line(self):
206 warn("Access to __line attribute on SMTPChannel is deprecated, "
Florent Xicluna67317752011-12-10 11:07:42 +0100207 "use 'received_lines' instead", DeprecationWarning, 2)
Richard Jones803ef8a2010-07-24 09:51:40 +0000208 return self.received_lines
209 @__line.setter
210 def __line(self, value):
211 warn("Setting __line attribute on SMTPChannel is deprecated, "
Florent Xicluna67317752011-12-10 11:07:42 +0100212 "set 'received_lines' instead", DeprecationWarning, 2)
Richard Jones803ef8a2010-07-24 09:51:40 +0000213 self.received_lines = value
214
215 @property
216 def __state(self):
217 warn("Access to __state attribute on SMTPChannel is deprecated, "
Florent Xicluna67317752011-12-10 11:07:42 +0100218 "use 'smtp_state' instead", DeprecationWarning, 2)
Richard Jones803ef8a2010-07-24 09:51:40 +0000219 return self.smtp_state
220 @__state.setter
221 def __state(self, value):
222 warn("Setting __state attribute on SMTPChannel is deprecated, "
Florent Xicluna67317752011-12-10 11:07:42 +0100223 "set 'smtp_state' instead", DeprecationWarning, 2)
Richard Jones803ef8a2010-07-24 09:51:40 +0000224 self.smtp_state = value
225
226 @property
227 def __greeting(self):
228 warn("Access to __greeting attribute on SMTPChannel is deprecated, "
Florent Xicluna67317752011-12-10 11:07:42 +0100229 "use 'seen_greeting' instead", DeprecationWarning, 2)
Richard Jones803ef8a2010-07-24 09:51:40 +0000230 return self.seen_greeting
231 @__greeting.setter
232 def __greeting(self, value):
233 warn("Setting __greeting attribute on SMTPChannel is deprecated, "
Florent Xicluna67317752011-12-10 11:07:42 +0100234 "set 'seen_greeting' instead", DeprecationWarning, 2)
Richard Jones803ef8a2010-07-24 09:51:40 +0000235 self.seen_greeting = value
236
237 @property
238 def __mailfrom(self):
239 warn("Access to __mailfrom attribute on SMTPChannel is deprecated, "
Florent Xicluna67317752011-12-10 11:07:42 +0100240 "use 'mailfrom' instead", DeprecationWarning, 2)
Richard Jones803ef8a2010-07-24 09:51:40 +0000241 return self.mailfrom
242 @__mailfrom.setter
243 def __mailfrom(self, value):
244 warn("Setting __mailfrom attribute on SMTPChannel is deprecated, "
Florent Xicluna67317752011-12-10 11:07:42 +0100245 "set 'mailfrom' instead", DeprecationWarning, 2)
Richard Jones803ef8a2010-07-24 09:51:40 +0000246 self.mailfrom = value
247
248 @property
249 def __rcpttos(self):
250 warn("Access to __rcpttos attribute on SMTPChannel is deprecated, "
Florent Xicluna67317752011-12-10 11:07:42 +0100251 "use 'rcpttos' instead", DeprecationWarning, 2)
Richard Jones803ef8a2010-07-24 09:51:40 +0000252 return self.rcpttos
253 @__rcpttos.setter
254 def __rcpttos(self, value):
255 warn("Setting __rcpttos attribute on SMTPChannel is deprecated, "
Florent Xicluna67317752011-12-10 11:07:42 +0100256 "set 'rcpttos' instead", DeprecationWarning, 2)
Richard Jones803ef8a2010-07-24 09:51:40 +0000257 self.rcpttos = value
258
259 @property
260 def __data(self):
261 warn("Access to __data attribute on SMTPChannel is deprecated, "
Florent Xicluna67317752011-12-10 11:07:42 +0100262 "use 'received_data' instead", DeprecationWarning, 2)
Richard Jones803ef8a2010-07-24 09:51:40 +0000263 return self.received_data
264 @__data.setter
265 def __data(self, value):
266 warn("Setting __data attribute on SMTPChannel is deprecated, "
Florent Xicluna67317752011-12-10 11:07:42 +0100267 "set 'received_data' instead", DeprecationWarning, 2)
Richard Jones803ef8a2010-07-24 09:51:40 +0000268 self.received_data = value
269
270 @property
271 def __fqdn(self):
272 warn("Access to __fqdn attribute on SMTPChannel is deprecated, "
Florent Xicluna67317752011-12-10 11:07:42 +0100273 "use 'fqdn' instead", DeprecationWarning, 2)
Richard Jones803ef8a2010-07-24 09:51:40 +0000274 return self.fqdn
275 @__fqdn.setter
276 def __fqdn(self, value):
277 warn("Setting __fqdn attribute on SMTPChannel is deprecated, "
Florent Xicluna67317752011-12-10 11:07:42 +0100278 "set 'fqdn' instead", DeprecationWarning, 2)
Richard Jones803ef8a2010-07-24 09:51:40 +0000279 self.fqdn = value
280
281 @property
282 def __peer(self):
283 warn("Access to __peer attribute on SMTPChannel is deprecated, "
Florent Xicluna67317752011-12-10 11:07:42 +0100284 "use 'peer' instead", DeprecationWarning, 2)
Richard Jones803ef8a2010-07-24 09:51:40 +0000285 return self.peer
286 @__peer.setter
287 def __peer(self, value):
288 warn("Setting __peer attribute on SMTPChannel is deprecated, "
Florent Xicluna67317752011-12-10 11:07:42 +0100289 "set 'peer' instead", DeprecationWarning, 2)
Richard Jones803ef8a2010-07-24 09:51:40 +0000290 self.peer = value
291
292 @property
293 def __conn(self):
294 warn("Access to __conn attribute on SMTPChannel is deprecated, "
Florent Xicluna67317752011-12-10 11:07:42 +0100295 "use 'conn' instead", DeprecationWarning, 2)
Richard Jones803ef8a2010-07-24 09:51:40 +0000296 return self.conn
297 @__conn.setter
298 def __conn(self, value):
299 warn("Setting __conn attribute on SMTPChannel is deprecated, "
Florent Xicluna67317752011-12-10 11:07:42 +0100300 "set 'conn' instead", DeprecationWarning, 2)
Richard Jones803ef8a2010-07-24 09:51:40 +0000301 self.conn = value
302
303 @property
304 def __addr(self):
305 warn("Access to __addr attribute on SMTPChannel is deprecated, "
Florent Xicluna67317752011-12-10 11:07:42 +0100306 "use 'addr' instead", DeprecationWarning, 2)
Richard Jones803ef8a2010-07-24 09:51:40 +0000307 return self.addr
308 @__addr.setter
309 def __addr(self, value):
310 warn("Setting __addr attribute on SMTPChannel is deprecated, "
Florent Xicluna67317752011-12-10 11:07:42 +0100311 "set 'addr' instead", DeprecationWarning, 2)
Richard Jones803ef8a2010-07-24 09:51:40 +0000312 self.addr = value
313
R David Murray2539e672014-08-09 16:40:49 -0400314 # Overrides base class for convenience.
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000315 def push(self, msg):
R David Murray2539e672014-08-09 16:40:49 -0400316 asynchat.async_chat.push(self, bytes(
317 msg + '\r\n', 'utf-8' if self.require_SMTPUTF8 else 'ascii'))
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000318
319 # Implementation of base class abstract method
320 def collect_incoming_data(self, data):
Georg Brandl1e5c5f82010-12-03 07:38:22 +0000321 limit = None
322 if self.smtp_state == self.COMMAND:
R David Murrayd1a30c92012-05-26 14:33:59 -0400323 limit = self.max_command_size_limit
Georg Brandl1e5c5f82010-12-03 07:38:22 +0000324 elif self.smtp_state == self.DATA:
325 limit = self.data_size_limit
326 if limit and self.num_bytes > limit:
327 return
328 elif limit:
329 self.num_bytes += len(data)
R David Murray554bcbf2014-06-11 11:18:08 -0400330 if self._decode_data:
331 self.received_lines.append(str(data, 'utf-8'))
332 else:
333 self.received_lines.append(data)
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000334
335 # Implementation of base class abstract method
336 def found_terminator(self):
R David Murray554bcbf2014-06-11 11:18:08 -0400337 line = self._emptystring.join(self.received_lines)
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000338 print('Data:', repr(line), file=DEBUGSTREAM)
Richard Jones803ef8a2010-07-24 09:51:40 +0000339 self.received_lines = []
340 if self.smtp_state == self.COMMAND:
R David Murrayd1a30c92012-05-26 14:33:59 -0400341 sz, self.num_bytes = self.num_bytes, 0
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000342 if not line:
343 self.push('500 Error: bad syntax')
344 return
R David Murray554bcbf2014-06-11 11:18:08 -0400345 if not self._decode_data:
346 line = str(line, 'utf-8')
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000347 i = line.find(' ')
348 if i < 0:
349 command = line.upper()
350 arg = None
351 else:
352 command = line[:i].upper()
353 arg = line[i+1:].strip()
R David Murrayd1a30c92012-05-26 14:33:59 -0400354 max_sz = (self.command_size_limits[command]
355 if self.extended_smtp else self.command_size_limit)
356 if sz > max_sz:
357 self.push('500 Error: line too long')
358 return
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000359 method = getattr(self, 'smtp_' + command, None)
360 if not method:
R David Murrayd1a30c92012-05-26 14:33:59 -0400361 self.push('500 Error: command "%s" not recognized' % command)
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000362 return
363 method(arg)
364 return
365 else:
Richard Jones803ef8a2010-07-24 09:51:40 +0000366 if self.smtp_state != self.DATA:
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000367 self.push('451 Internal confusion')
Georg Brandl1e5c5f82010-12-03 07:38:22 +0000368 self.num_bytes = 0
369 return
R David Murrayd1a30c92012-05-26 14:33:59 -0400370 if self.data_size_limit and self.num_bytes > self.data_size_limit:
Georg Brandl1e5c5f82010-12-03 07:38:22 +0000371 self.push('552 Error: Too much mail data')
372 self.num_bytes = 0
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000373 return
374 # Remove extraneous carriage returns and de-transparency according
R David Murrayd1a30c92012-05-26 14:33:59 -0400375 # to RFC 5321, Section 4.5.2.
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000376 data = []
R David Murray554bcbf2014-06-11 11:18:08 -0400377 for text in line.split(self._linesep):
378 if text and text[0] == self._dotsep:
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000379 data.append(text[1:])
380 else:
381 data.append(text)
R David Murray554bcbf2014-06-11 11:18:08 -0400382 self.received_data = self._newline.join(data)
R David Murray2539e672014-08-09 16:40:49 -0400383 args = (self.peer, self.mailfrom, self.rcpttos, self.received_data)
R David Murraya33df312015-05-11 12:11:40 -0400384 kwargs = {}
385 if not self._decode_data:
386 kwargs = {
387 'mail_options': self.mail_options,
388 'rcpt_options': self.rcpt_options,
389 }
390 status = self.smtp_server.process_message(*args, **kwargs)
R David Murray2539e672014-08-09 16:40:49 -0400391 self._set_post_data_state()
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000392 if not status:
R David Murrayd1a30c92012-05-26 14:33:59 -0400393 self.push('250 OK')
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000394 else:
395 self.push(status)
396
397 # SMTP and ESMTP commands
398 def smtp_HELO(self, arg):
399 if not arg:
400 self.push('501 Syntax: HELO hostname')
401 return
R David Murray2539e672014-08-09 16:40:49 -0400402 # See issue #21783 for a discussion of this behavior.
Richard Jones803ef8a2010-07-24 09:51:40 +0000403 if self.seen_greeting:
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000404 self.push('503 Duplicate HELO/EHLO')
R David Murray2539e672014-08-09 16:40:49 -0400405 return
406 self._set_rset_state()
407 self.seen_greeting = arg
408 self.push('250 %s' % self.fqdn)
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000409
R David Murrayd1a30c92012-05-26 14:33:59 -0400410 def smtp_EHLO(self, arg):
411 if not arg:
412 self.push('501 Syntax: EHLO hostname')
413 return
R David Murray2539e672014-08-09 16:40:49 -0400414 # See issue #21783 for a discussion of this behavior.
R David Murrayd1a30c92012-05-26 14:33:59 -0400415 if self.seen_greeting:
416 self.push('503 Duplicate HELO/EHLO')
R David Murray2539e672014-08-09 16:40:49 -0400417 return
418 self._set_rset_state()
419 self.seen_greeting = arg
420 self.extended_smtp = True
421 self.push('250-%s' % self.fqdn)
422 if self.data_size_limit:
423 self.push('250-SIZE %s' % self.data_size_limit)
424 self.command_size_limits['MAIL'] += 26
R David Murraya33df312015-05-11 12:11:40 -0400425 if not self._decode_data:
R David Murray2539e672014-08-09 16:40:49 -0400426 self.push('250-8BITMIME')
R David Murraya33df312015-05-11 12:11:40 -0400427 if self.enable_SMTPUTF8:
R David Murray2539e672014-08-09 16:40:49 -0400428 self.push('250-SMTPUTF8')
429 self.command_size_limits['MAIL'] += 10
430 self.push('250 HELP')
R David Murrayd1a30c92012-05-26 14:33:59 -0400431
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000432 def smtp_NOOP(self, arg):
433 if arg:
434 self.push('501 Syntax: NOOP')
435 else:
R David Murrayd1a30c92012-05-26 14:33:59 -0400436 self.push('250 OK')
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000437
438 def smtp_QUIT(self, arg):
439 # args is ignored
440 self.push('221 Bye')
441 self.close_when_done()
442
R David Murrayd1a30c92012-05-26 14:33:59 -0400443 def _strip_command_keyword(self, keyword, arg):
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000444 keylen = len(keyword)
445 if arg[:keylen].upper() == keyword:
R David Murrayd1a30c92012-05-26 14:33:59 -0400446 return arg[keylen:].strip()
447 return ''
448
449 def _getaddr(self, arg):
450 if not arg:
451 return '', ''
452 if arg.lstrip().startswith('<'):
453 address, rest = get_angle_addr(arg)
454 else:
455 address, rest = get_addr_spec(arg)
456 if not address:
457 return address, rest
458 return address.addr_spec, rest
459
460 def _getparams(self, params):
R David Murraya33df312015-05-11 12:11:40 -0400461 # Return params as dictionary. Return None if not all parameters
462 # appear to be syntactically valid according to RFC 1869.
463 result = {}
464 for param in params:
465 param, eq, value = param.partition('=')
466 if not param.isalnum() or eq and not value:
467 return None
468 result[param] = value if eq else True
469 return result
R David Murrayd1a30c92012-05-26 14:33:59 -0400470
471 def smtp_HELP(self, arg):
472 if arg:
Benjamin Peterson0c803312015-04-05 10:01:48 -0400473 extended = ' [SP <mail-parameters>]'
R David Murrayd1a30c92012-05-26 14:33:59 -0400474 lc_arg = arg.upper()
475 if lc_arg == 'EHLO':
476 self.push('250 Syntax: EHLO hostname')
477 elif lc_arg == 'HELO':
478 self.push('250 Syntax: HELO hostname')
479 elif lc_arg == 'MAIL':
480 msg = '250 Syntax: MAIL FROM: <address>'
481 if self.extended_smtp:
482 msg += extended
483 self.push(msg)
484 elif lc_arg == 'RCPT':
485 msg = '250 Syntax: RCPT TO: <address>'
486 if self.extended_smtp:
487 msg += extended
488 self.push(msg)
489 elif lc_arg == 'DATA':
490 self.push('250 Syntax: DATA')
491 elif lc_arg == 'RSET':
492 self.push('250 Syntax: RSET')
493 elif lc_arg == 'NOOP':
494 self.push('250 Syntax: NOOP')
495 elif lc_arg == 'QUIT':
496 self.push('250 Syntax: QUIT')
497 elif lc_arg == 'VRFY':
498 self.push('250 Syntax: VRFY <address>')
499 else:
500 self.push('501 Supported commands: EHLO HELO MAIL RCPT '
501 'DATA RSET NOOP QUIT VRFY')
502 else:
503 self.push('250 Supported commands: EHLO HELO MAIL RCPT DATA '
504 'RSET NOOP QUIT VRFY')
505
506 def smtp_VRFY(self, arg):
507 if arg:
508 address, params = self._getaddr(arg)
509 if address:
510 self.push('252 Cannot VRFY user, but will accept message '
511 'and attempt delivery')
512 else:
513 self.push('502 Could not VRFY %s' % arg)
514 else:
515 self.push('501 Syntax: VRFY <address>')
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000516
517 def smtp_MAIL(self, arg):
R David Murray669b7552012-03-20 16:16:29 -0400518 if not self.seen_greeting:
R David Murraya33df312015-05-11 12:11:40 -0400519 self.push('503 Error: send HELO first')
R David Murray669b7552012-03-20 16:16:29 -0400520 return
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000521 print('===> MAIL', arg, file=DEBUGSTREAM)
R David Murrayd1a30c92012-05-26 14:33:59 -0400522 syntaxerr = '501 Syntax: MAIL FROM: <address>'
523 if self.extended_smtp:
524 syntaxerr += ' [SP <mail-parameters>]'
525 if arg is None:
526 self.push(syntaxerr)
527 return
528 arg = self._strip_command_keyword('FROM:', arg)
529 address, params = self._getaddr(arg)
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000530 if not address:
R David Murrayd1a30c92012-05-26 14:33:59 -0400531 self.push(syntaxerr)
532 return
533 if not self.extended_smtp and params:
534 self.push(syntaxerr)
535 return
Richard Jones803ef8a2010-07-24 09:51:40 +0000536 if self.mailfrom:
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000537 self.push('503 Error: nested MAIL command')
538 return
R David Murraya33df312015-05-11 12:11:40 -0400539 self.mail_options = params.upper().split()
540 params = self._getparams(self.mail_options)
R David Murrayd1a30c92012-05-26 14:33:59 -0400541 if params is None:
542 self.push(syntaxerr)
543 return
R David Murraya33df312015-05-11 12:11:40 -0400544 if not self._decode_data:
545 body = params.pop('BODY', '7BIT')
546 if body not in ['7BIT', '8BITMIME']:
547 self.push('501 Error: BODY can only be one of 7BIT, 8BITMIME')
R David Murray2539e672014-08-09 16:40:49 -0400548 return
R David Murraya33df312015-05-11 12:11:40 -0400549 if self.enable_SMTPUTF8:
550 smtputf8 = params.pop('SMTPUTF8', False)
551 if smtputf8 is True:
R David Murray2539e672014-08-09 16:40:49 -0400552 self.require_SMTPUTF8 = True
R David Murraya33df312015-05-11 12:11:40 -0400553 elif smtputf8 is not False:
554 self.push('501 Error: SMTPUTF8 takes no arguments')
555 return
R David Murrayd1a30c92012-05-26 14:33:59 -0400556 size = params.pop('SIZE', None)
557 if size:
558 if not size.isdigit():
559 self.push(syntaxerr)
560 return
561 elif self.data_size_limit and int(size) > self.data_size_limit:
562 self.push('552 Error: message size exceeds fixed maximum message size')
563 return
564 if len(params.keys()) > 0:
565 self.push('555 MAIL FROM parameters not recognized or not implemented')
566 return
Richard Jones803ef8a2010-07-24 09:51:40 +0000567 self.mailfrom = address
568 print('sender:', self.mailfrom, file=DEBUGSTREAM)
R David Murrayd1a30c92012-05-26 14:33:59 -0400569 self.push('250 OK')
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000570
571 def smtp_RCPT(self, arg):
R David Murray669b7552012-03-20 16:16:29 -0400572 if not self.seen_greeting:
573 self.push('503 Error: send HELO first');
574 return
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000575 print('===> RCPT', arg, file=DEBUGSTREAM)
Richard Jones803ef8a2010-07-24 09:51:40 +0000576 if not self.mailfrom:
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000577 self.push('503 Error: need MAIL command')
578 return
R David Murrayd1a30c92012-05-26 14:33:59 -0400579 syntaxerr = '501 Syntax: RCPT TO: <address>'
580 if self.extended_smtp:
581 syntaxerr += ' [SP <mail-parameters>]'
582 if arg is None:
583 self.push(syntaxerr)
584 return
585 arg = self._strip_command_keyword('TO:', arg)
586 address, params = self._getaddr(arg)
587 if not address:
588 self.push(syntaxerr)
589 return
R David Murraya33df312015-05-11 12:11:40 -0400590 if not self.extended_smtp and params:
591 self.push(syntaxerr)
592 return
593 self.rcpt_options = params.upper().split()
594 params = self._getparams(self.rcpt_options)
595 if params is None:
596 self.push(syntaxerr)
597 return
598 # XXX currently there are no options we recognize.
599 if len(params.keys()) > 0:
R David Murrayd1a30c92012-05-26 14:33:59 -0400600 self.push('555 RCPT TO parameters not recognized or not implemented')
601 return
Richard Jones803ef8a2010-07-24 09:51:40 +0000602 self.rcpttos.append(address)
603 print('recips:', self.rcpttos, file=DEBUGSTREAM)
R David Murrayd1a30c92012-05-26 14:33:59 -0400604 self.push('250 OK')
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000605
606 def smtp_RSET(self, arg):
607 if arg:
608 self.push('501 Syntax: RSET')
609 return
R David Murray2539e672014-08-09 16:40:49 -0400610 self._set_rset_state()
R David Murrayd1a30c92012-05-26 14:33:59 -0400611 self.push('250 OK')
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000612
613 def smtp_DATA(self, arg):
R David Murray669b7552012-03-20 16:16:29 -0400614 if not self.seen_greeting:
615 self.push('503 Error: send HELO first');
616 return
Richard Jones803ef8a2010-07-24 09:51:40 +0000617 if not self.rcpttos:
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000618 self.push('503 Error: need RCPT command')
619 return
620 if arg:
621 self.push('501 Syntax: DATA')
622 return
Richard Jones803ef8a2010-07-24 09:51:40 +0000623 self.smtp_state = self.DATA
Josiah Carlsond74900e2008-07-07 04:15:08 +0000624 self.set_terminator(b'\r\n.\r\n')
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000625 self.push('354 End data with <CR><LF>.<CR><LF>')
626
R David Murrayd1a30c92012-05-26 14:33:59 -0400627 # Commands that have not been implemented
628 def smtp_EXPN(self, arg):
629 self.push('502 EXPN not implemented')
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000630
R David Murrayd1a30c92012-05-26 14:33:59 -0400631
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000632class SMTPServer(asyncore.dispatcher):
Richard Jones803ef8a2010-07-24 09:51:40 +0000633 # SMTPChannel class to use for managing client connections
634 channel_class = SMTPChannel
635
R David Murrayd1a30c92012-05-26 14:33:59 -0400636 def __init__(self, localaddr, remoteaddr,
R David Murray554bcbf2014-06-11 11:18:08 -0400637 data_size_limit=DATA_SIZE_DEFAULT, map=None,
R David Murray2539e672014-08-09 16:40:49 -0400638 enable_SMTPUTF8=False, decode_data=None):
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000639 self._localaddr = localaddr
640 self._remoteaddr = remoteaddr
R David Murrayd1a30c92012-05-26 14:33:59 -0400641 self.data_size_limit = data_size_limit
R David Murray2539e672014-08-09 16:40:49 -0400642 self.enable_SMTPUTF8 = enable_SMTPUTF8
643 if enable_SMTPUTF8:
644 if decode_data:
645 raise ValueError("The decode_data and enable_SMTPUTF8"
646 " parameters cannot be set to True at the"
647 " same time.")
648 decode_data = False
R David Murray554bcbf2014-06-11 11:18:08 -0400649 if decode_data is None:
650 warn("The decode_data default of True will change to False in 3.6;"
651 " specify an explicit value for this keyword",
652 DeprecationWarning, 2)
653 decode_data = True
654 self._decode_data = decode_data
Vinay Sajip30298b42013-06-07 15:21:41 +0100655 asyncore.dispatcher.__init__(self, map=map)
Giampaolo Rodolà610aa4f2010-06-30 17:47:39 +0000656 try:
R David Murray012a83a2014-06-11 15:17:50 -0400657 gai_results = socket.getaddrinfo(*localaddr,
658 type=socket.SOCK_STREAM)
R David Murray6fe56a32014-06-11 13:48:58 -0400659 self.create_socket(gai_results[0][0], gai_results[0][1])
Giampaolo Rodolà610aa4f2010-06-30 17:47:39 +0000660 # try to re-use a server port if possible
661 self.set_reuse_addr()
662 self.bind(localaddr)
663 self.listen(5)
664 except:
665 self.close()
666 raise
667 else:
668 print('%s started at %s\n\tLocal addr: %s\n\tRemote addr:%s' % (
669 self.__class__.__name__, time.ctime(time.time()),
670 localaddr, remoteaddr), file=DEBUGSTREAM)
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000671
Giampaolo Rodolà977c7072010-10-04 21:08:36 +0000672 def handle_accepted(self, conn, addr):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000673 print('Incoming connection from %s' % repr(addr), file=DEBUGSTREAM)
R David Murray2539e672014-08-09 16:40:49 -0400674 channel = self.channel_class(self,
675 conn,
676 addr,
677 self.data_size_limit,
678 self._map,
679 self.enable_SMTPUTF8,
680 self._decode_data)
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000681
682 # API for "doing something useful with the message"
R David Murraya33df312015-05-11 12:11:40 -0400683 def process_message(self, peer, mailfrom, rcpttos, data, **kwargs):
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000684 """Override this abstract method to handle messages from the client.
685
686 peer is a tuple containing (ipaddr, port) of the client that made the
687 socket connection to our smtp port.
688
689 mailfrom is the raw address the client claims the message is coming
690 from.
691
692 rcpttos is a list of raw addresses the client wishes to deliver the
693 message to.
694
695 data is a string containing the entire full text of the message,
696 headers (if supplied) and all. It has been `de-transparencied'
697 according to RFC 821, Section 4.5.2. In other words, a line
698 containing a `.' followed by other text has had the leading dot
699 removed.
700
R David Murraya33df312015-05-11 12:11:40 -0400701 kwargs is a dictionary containing additional information. It is empty
702 unless decode_data=False or enable_SMTPUTF8=True was given as init
703 parameter, in which case ut will contain the following keys:
704 'mail_options': list of parameters to the mail command. All
705 elements are uppercase strings. Example:
706 ['BODY=8BITMIME', 'SMTPUTF8'].
707 'rcpt_options': same, for the rcpt command.
708
R David Murray2539e672014-08-09 16:40:49 -0400709 This function should return None for a normal `250 Ok' response;
710 otherwise, it should return the desired response string in RFC 821
711 format.
712
713 """
714 raise NotImplementedError
715
Tim Peters658cba62001-02-09 20:06:00 +0000716
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000717class DebuggingServer(SMTPServer):
R David Murray2539e672014-08-09 16:40:49 -0400718
719 def _print_message_content(self, peer, data):
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000720 inheaders = 1
R David Murray2539e672014-08-09 16:40:49 -0400721 lines = data.splitlines()
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000722 for line in lines:
723 # headers first
724 if inheaders and not line:
R David Murray2539e672014-08-09 16:40:49 -0400725 peerheader = 'X-Peer: ' + peer[0]
726 if not isinstance(data, str):
727 # decoded_data=false; make header match other binary output
728 peerheader = repr(peerheader.encode('utf-8'))
729 print(peerheader)
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000730 inheaders = 0
R David Murray2539e672014-08-09 16:40:49 -0400731 if not isinstance(data, str):
732 # Avoid spurious 'str on bytes instance' warning.
733 line = repr(line)
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000734 print(line)
R David Murray2539e672014-08-09 16:40:49 -0400735
R David Murraya33df312015-05-11 12:11:40 -0400736 def process_message(self, peer, mailfrom, rcpttos, data, **kwargs):
R David Murray2539e672014-08-09 16:40:49 -0400737 print('---------- MESSAGE FOLLOWS ----------')
R David Murraya33df312015-05-11 12:11:40 -0400738 if kwargs:
739 if kwargs.get('mail_options'):
740 print('mail options: %s' % kwargs['mail_options'])
741 if kwargs.get('rcpt_options'):
742 print('rcpt options: %s\n' % kwargs['rcpt_options'])
R David Murray2539e672014-08-09 16:40:49 -0400743 self._print_message_content(peer, data)
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000744 print('------------ END MESSAGE ------------')
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000745
746
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000747class PureProxy(SMTPServer):
R David Murray2539e672014-08-09 16:40:49 -0400748 def __init__(self, *args, **kwargs):
749 if 'enable_SMTPUTF8' in kwargs and kwargs['enable_SMTPUTF8']:
750 raise ValueError("PureProxy does not support SMTPUTF8.")
751 super(PureProxy, self).__init__(*args, **kwargs)
752
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000753 def process_message(self, peer, mailfrom, rcpttos, data):
754 lines = data.split('\n')
755 # Look for the last header
756 i = 0
757 for line in lines:
758 if not line:
759 break
760 i += 1
761 lines.insert(i, 'X-Peer: %s' % peer[0])
762 data = NEWLINE.join(lines)
763 refused = self._deliver(mailfrom, rcpttos, data)
764 # TBD: what to do with refused addresses?
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000765 print('we got some refusals:', refused, file=DEBUGSTREAM)
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000766
767 def _deliver(self, mailfrom, rcpttos, data):
768 import smtplib
769 refused = {}
770 try:
771 s = smtplib.SMTP()
772 s.connect(self._remoteaddr[0], self._remoteaddr[1])
773 try:
774 refused = s.sendmail(mailfrom, rcpttos, data)
775 finally:
776 s.quit()
Guido van Rossumb940e112007-01-10 16:19:56 +0000777 except smtplib.SMTPRecipientsRefused as e:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000778 print('got SMTPRecipientsRefused', file=DEBUGSTREAM)
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000779 refused = e.recipients
Andrew Svetlov0832af62012-12-18 23:10:48 +0200780 except (OSError, smtplib.SMTPException) as e:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000781 print('got', e.__class__, file=DEBUGSTREAM)
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000782 # All recipients were refused. If the exception had an associated
783 # error code, use it. Otherwise,fake it with a non-triggering
784 # exception code.
785 errcode = getattr(e, 'smtp_code', -1)
786 errmsg = getattr(e, 'smtp_error', 'ignore')
787 for r in rcpttos:
788 refused[r] = (errcode, errmsg)
789 return refused
790
791
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000792class MailmanProxy(PureProxy):
R David Murray2539e672014-08-09 16:40:49 -0400793 def __init__(self, *args, **kwargs):
794 if 'enable_SMTPUTF8' in kwargs and kwargs['enable_SMTPUTF8']:
795 raise ValueError("MailmanProxy does not support SMTPUTF8.")
796 super(PureProxy, self).__init__(*args, **kwargs)
797
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000798 def process_message(self, peer, mailfrom, rcpttos, data):
Guido van Rossum68937b42007-05-18 00:51:22 +0000799 from io import StringIO
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000800 from Mailman import Utils
801 from Mailman import Message
802 from Mailman import MailList
803 # If the message is to a Mailman mailing list, then we'll invoke the
804 # Mailman script directly, without going through the real smtpd.
805 # Otherwise we'll forward it to the local proxy for disposition.
806 listnames = []
807 for rcpt in rcpttos:
808 local = rcpt.lower().split('@')[0]
809 # We allow the following variations on the theme
810 # listname
811 # listname-admin
812 # listname-owner
813 # listname-request
814 # listname-join
815 # listname-leave
816 parts = local.split('-')
817 if len(parts) > 2:
818 continue
819 listname = parts[0]
820 if len(parts) == 2:
821 command = parts[1]
822 else:
823 command = ''
824 if not Utils.list_exists(listname) or command not in (
825 '', 'admin', 'owner', 'request', 'join', 'leave'):
826 continue
827 listnames.append((rcpt, listname, command))
828 # Remove all list recipients from rcpttos and forward what we're not
829 # going to take care of ourselves. Linear removal should be fine
830 # since we don't expect a large number of recipients.
831 for rcpt, listname, command in listnames:
832 rcpttos.remove(rcpt)
Tim Peters658cba62001-02-09 20:06:00 +0000833 # If there's any non-list destined recipients left,
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000834 print('forwarding recips:', ' '.join(rcpttos), file=DEBUGSTREAM)
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000835 if rcpttos:
836 refused = self._deliver(mailfrom, rcpttos, data)
837 # TBD: what to do with refused addresses?
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000838 print('we got refusals:', refused, file=DEBUGSTREAM)
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000839 # Now deliver directly to the list commands
840 mlists = {}
841 s = StringIO(data)
842 msg = Message.Message(s)
843 # These headers are required for the proper execution of Mailman. All
Mark Dickinson934896d2009-02-21 20:59:32 +0000844 # MTAs in existence seem to add these if the original message doesn't
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000845 # have them.
Barry Warsaw820c1202008-06-12 04:06:45 +0000846 if not msg.get('from'):
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000847 msg['From'] = mailfrom
Barry Warsaw820c1202008-06-12 04:06:45 +0000848 if not msg.get('date'):
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000849 msg['Date'] = time.ctime(time.time())
850 for rcpt, listname, command in listnames:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000851 print('sending message to', rcpt, file=DEBUGSTREAM)
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000852 mlist = mlists.get(listname)
853 if not mlist:
854 mlist = MailList.MailList(listname, lock=0)
855 mlists[listname] = mlist
856 # dispatch on the type of command
857 if command == '':
858 # post
859 msg.Enqueue(mlist, tolist=1)
860 elif command == 'admin':
861 msg.Enqueue(mlist, toadmin=1)
862 elif command == 'owner':
863 msg.Enqueue(mlist, toowner=1)
864 elif command == 'request':
865 msg.Enqueue(mlist, torequest=1)
866 elif command in ('join', 'leave'):
867 # TBD: this is a hack!
868 if command == 'join':
869 msg['Subject'] = 'subscribe'
870 else:
871 msg['Subject'] = 'unsubscribe'
872 msg.Enqueue(mlist, torequest=1)
873
874
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000875class Options:
R David Murray2539e672014-08-09 16:40:49 -0400876 setuid = True
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000877 classname = 'PureProxy'
R David Murrayd1a30c92012-05-26 14:33:59 -0400878 size_limit = None
R David Murray2539e672014-08-09 16:40:49 -0400879 enable_SMTPUTF8 = False
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000880
881
882def parseargs():
883 global DEBUGSTREAM
884 try:
885 opts, args = getopt.getopt(
R David Murray2539e672014-08-09 16:40:49 -0400886 sys.argv[1:], 'nVhc:s:du',
887 ['class=', 'nosetuid', 'version', 'help', 'size=', 'debug',
888 'smtputf8'])
Guido van Rossumb940e112007-01-10 16:19:56 +0000889 except getopt.error as e:
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000890 usage(1, e)
891
892 options = Options()
893 for opt, arg in opts:
894 if opt in ('-h', '--help'):
895 usage(0)
896 elif opt in ('-V', '--version'):
Serhiy Storchakac56894d2013-09-05 17:44:53 +0300897 print(__version__)
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000898 sys.exit(0)
899 elif opt in ('-n', '--nosetuid'):
R David Murray2539e672014-08-09 16:40:49 -0400900 options.setuid = False
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000901 elif opt in ('-c', '--class'):
902 options.classname = arg
903 elif opt in ('-d', '--debug'):
904 DEBUGSTREAM = sys.stderr
R David Murray2539e672014-08-09 16:40:49 -0400905 elif opt in ('-u', '--smtputf8'):
906 options.enable_SMTPUTF8 = True
R David Murrayd1a30c92012-05-26 14:33:59 -0400907 elif opt in ('-s', '--size'):
908 try:
909 int_size = int(arg)
910 options.size_limit = int_size
911 except:
912 print('Invalid size: ' + arg, file=sys.stderr)
913 sys.exit(1)
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000914
915 # parse the rest of the arguments
Barry Warsaw0e8427e2001-10-04 16:27:04 +0000916 if len(args) < 1:
917 localspec = 'localhost:8025'
918 remotespec = 'localhost:25'
919 elif len(args) < 2:
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000920 localspec = args[0]
Barry Warsaw0e8427e2001-10-04 16:27:04 +0000921 remotespec = 'localhost:25'
Barry Warsawebf54272001-11-04 03:04:25 +0000922 elif len(args) < 3:
923 localspec = args[0]
924 remotespec = args[1]
Barry Warsaw0e8427e2001-10-04 16:27:04 +0000925 else:
926 usage(1, 'Invalid arguments: %s' % COMMASPACE.join(args))
927
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000928 # split into host/port pairs
929 i = localspec.find(':')
930 if i < 0:
Barry Warsaw0e8427e2001-10-04 16:27:04 +0000931 usage(1, 'Bad local spec: %s' % localspec)
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000932 options.localhost = localspec[:i]
933 try:
934 options.localport = int(localspec[i+1:])
935 except ValueError:
Barry Warsaw0e8427e2001-10-04 16:27:04 +0000936 usage(1, 'Bad local port: %s' % localspec)
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000937 i = remotespec.find(':')
938 if i < 0:
Barry Warsaw0e8427e2001-10-04 16:27:04 +0000939 usage(1, 'Bad remote spec: %s' % remotespec)
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000940 options.remotehost = remotespec[:i]
941 try:
942 options.remoteport = int(remotespec[i+1:])
943 except ValueError:
Barry Warsaw0e8427e2001-10-04 16:27:04 +0000944 usage(1, 'Bad remote port: %s' % remotespec)
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000945 return options
946
947
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000948if __name__ == '__main__':
949 options = parseargs()
950 # Become nobody
Florent Xicluna711f87c2011-10-20 23:03:43 +0200951 classname = options.classname
952 if "." in classname:
953 lastdot = classname.rfind(".")
954 mod = __import__(classname[:lastdot], globals(), locals(), [""])
955 classname = classname[lastdot+1:]
956 else:
957 import __main__ as mod
958 class_ = getattr(mod, classname)
959 proxy = class_((options.localhost, options.localport),
R David Murrayd1a30c92012-05-26 14:33:59 -0400960 (options.remotehost, options.remoteport),
R David Murray2539e672014-08-09 16:40:49 -0400961 options.size_limit, enable_SMTPUTF8=options.enable_SMTPUTF8)
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000962 if options.setuid:
963 try:
964 import pwd
Brett Cannoncd171c82013-07-04 17:43:24 -0400965 except ImportError:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000966 print('Cannot import module "pwd"; try running with -n option.', file=sys.stderr)
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000967 sys.exit(1)
968 nobody = pwd.getpwnam('nobody')[2]
969 try:
970 os.setuid(nobody)
Giampaolo Rodola'0166a282013-02-12 15:14:17 +0100971 except PermissionError:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000972 print('Cannot setuid "nobody"; try running with -n option.', file=sys.stderr)
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000973 sys.exit(1)
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000974 try:
975 asyncore.loop()
976 except KeyboardInterrupt:
977 pass