blob: 1cd004fbc6fe5b88a89775d520a4ea88eaf95e31 [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#
Barry Warsaw7e0d9562001-01-31 22:51:35 +000063#
Barry Warsawb1027642004-07-12 23:10:08 +000064# Author: Barry Warsaw <barry@python.org>
Barry Warsaw7e0d9562001-01-31 22:51:35 +000065#
66# TODO:
67#
68# - support mailbox delivery
69# - alias files
R David Murrayd1a30c92012-05-26 14:33:59 -040070# - Handle more ESMTP extensions
Barry Warsaw7e0d9562001-01-31 22:51:35 +000071# - handle error codes from the backend smtpd
72
73import sys
74import os
75import errno
76import getopt
77import time
78import socket
R David Murrayd1a30c92012-05-26 14:33:59 -040079import collections
Richard Jones803ef8a2010-07-24 09:51:40 +000080from warnings import warn
R David Murrayd1a30c92012-05-26 14:33:59 -040081from email._header_value_parser import get_addr_spec, get_angle_addr
Barry Warsaw7e0d9562001-01-31 22:51:35 +000082
Martin Panter380ef012016-06-06 02:03:11 +000083__all__ = [
84 "SMTPChannel", "SMTPServer", "DebuggingServer", "PureProxy",
Martin Panter380ef012016-06-06 02:03:11 +000085]
Barry Warsaw7e0d9562001-01-31 22:51:35 +000086
Barry Warsaw8488b852021-06-24 12:37:26 -070087warn(
88 'The smtpd module is deprecated and unmaintained. Please see aiosmtpd '
89 '(https://aiosmtpd.readthedocs.io/) for the recommended replacement.',
90 DeprecationWarning,
91 stacklevel=2)
92
93
94# These are imported after the above warning so that users get the correct
95# deprecation warning.
96import asyncore
97import asynchat
98
99
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000100program = sys.argv[0]
R David Murrayd1a30c92012-05-26 14:33:59 -0400101__version__ = 'Python SMTP proxy version 0.3'
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000102
103
104class Devnull:
105 def write(self, msg): pass
106 def flush(self): pass
107
108
109DEBUGSTREAM = Devnull()
110NEWLINE = '\n'
Barry Warsaw0e8427e2001-10-04 16:27:04 +0000111COMMASPACE = ', '
R David Murrayd1a30c92012-05-26 14:33:59 -0400112DATA_SIZE_DEFAULT = 33554432
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000113
114
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000115def usage(code, msg=''):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000116 print(__doc__ % globals(), file=sys.stderr)
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000117 if msg:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000118 print(msg, file=sys.stderr)
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000119 sys.exit(code)
120
121
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000122class SMTPChannel(asynchat.async_chat):
123 COMMAND = 0
124 DATA = 1
125
Georg Brandl1e5c5f82010-12-03 07:38:22 +0000126 command_size_limit = 512
R David Murrayd1a30c92012-05-26 14:33:59 -0400127 command_size_limits = collections.defaultdict(lambda x=command_size_limit: x)
R David Murray2539e672014-08-09 16:40:49 -0400128
129 @property
130 def max_command_size_limit(self):
131 try:
132 return max(self.command_size_limits.values())
133 except ValueError:
134 return self.command_size_limit
Georg Brandl1e5c5f82010-12-03 07:38:22 +0000135
Vinay Sajip30298b42013-06-07 15:21:41 +0100136 def __init__(self, server, conn, addr, data_size_limit=DATA_SIZE_DEFAULT,
Serhiy Storchakacbcc2fd2016-05-16 09:36:31 +0300137 map=None, enable_SMTPUTF8=False, decode_data=False):
Vinay Sajip30298b42013-06-07 15:21:41 +0100138 asynchat.async_chat.__init__(self, conn, map=map)
Richard Jones803ef8a2010-07-24 09:51:40 +0000139 self.smtp_server = server
140 self.conn = conn
141 self.addr = addr
R David Murrayd1a30c92012-05-26 14:33:59 -0400142 self.data_size_limit = data_size_limit
Serhiy Storchakaeb6cd742016-05-29 23:50:56 +0300143 self.enable_SMTPUTF8 = enable_SMTPUTF8
144 self._decode_data = decode_data
Serhiy Storchakacbcc2fd2016-05-16 09:36:31 +0300145 if enable_SMTPUTF8 and decode_data:
146 raise ValueError("decode_data and enable_SMTPUTF8 cannot"
147 " be set to True at the same time")
R David Murray554bcbf2014-06-11 11:18:08 -0400148 if decode_data:
149 self._emptystring = ''
150 self._linesep = '\r\n'
151 self._dotsep = '.'
152 self._newline = NEWLINE
153 else:
154 self._emptystring = b''
155 self._linesep = b'\r\n'
Serhiy Storchakaee4c0b92015-03-20 16:48:02 +0200156 self._dotsep = ord(b'.')
R David Murray554bcbf2014-06-11 11:18:08 -0400157 self._newline = b'\n'
R David Murray2539e672014-08-09 16:40:49 -0400158 self._set_rset_state()
Richard Jones803ef8a2010-07-24 09:51:40 +0000159 self.seen_greeting = ''
R David Murray2539e672014-08-09 16:40:49 -0400160 self.extended_smtp = False
161 self.command_size_limits.clear()
Richard Jones803ef8a2010-07-24 09:51:40 +0000162 self.fqdn = socket.getfqdn()
Giampaolo Rodolà9cf5ef42010-08-23 22:28:13 +0000163 try:
164 self.peer = conn.getpeername()
Andrew Svetlov0832af62012-12-18 23:10:48 +0200165 except OSError as err:
Giampaolo Rodolà9cf5ef42010-08-23 22:28:13 +0000166 # a race condition may occur if the other end is closing
167 # before we can get the peername
168 self.close()
Serhiy Storchakac4d45ee2020-11-22 10:28:34 +0200169 if err.errno != errno.ENOTCONN:
Giampaolo Rodolà9cf5ef42010-08-23 22:28:13 +0000170 raise
171 return
Richard Jones803ef8a2010-07-24 09:51:40 +0000172 print('Peer:', repr(self.peer), file=DEBUGSTREAM)
173 self.push('220 %s %s' % (self.fqdn, __version__))
R David Murray2539e672014-08-09 16:40:49 -0400174
175 def _set_post_data_state(self):
176 """Reset state variables to their post-DATA state."""
177 self.smtp_state = self.COMMAND
178 self.mailfrom = None
179 self.rcpttos = []
180 self.require_SMTPUTF8 = False
181 self.num_bytes = 0
Josiah Carlsond74900e2008-07-07 04:15:08 +0000182 self.set_terminator(b'\r\n')
R David Murray2539e672014-08-09 16:40:49 -0400183
184 def _set_rset_state(self):
185 """Reset all state variables except the greeting."""
186 self._set_post_data_state()
187 self.received_data = ''
188 self.received_lines = []
189
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000190
Richard Jones803ef8a2010-07-24 09:51:40 +0000191 # properties for backwards-compatibility
192 @property
193 def __server(self):
194 warn("Access to __server attribute on SMTPChannel is deprecated, "
Florent Xicluna67317752011-12-10 11:07:42 +0100195 "use 'smtp_server' instead", DeprecationWarning, 2)
Richard Jones803ef8a2010-07-24 09:51:40 +0000196 return self.smtp_server
197 @__server.setter
198 def __server(self, value):
199 warn("Setting __server attribute on SMTPChannel is deprecated, "
Florent Xicluna67317752011-12-10 11:07:42 +0100200 "set 'smtp_server' instead", DeprecationWarning, 2)
Richard Jones803ef8a2010-07-24 09:51:40 +0000201 self.smtp_server = value
202
203 @property
204 def __line(self):
205 warn("Access to __line attribute on SMTPChannel is deprecated, "
Florent Xicluna67317752011-12-10 11:07:42 +0100206 "use 'received_lines' instead", DeprecationWarning, 2)
Richard Jones803ef8a2010-07-24 09:51:40 +0000207 return self.received_lines
208 @__line.setter
209 def __line(self, value):
210 warn("Setting __line attribute on SMTPChannel is deprecated, "
Florent Xicluna67317752011-12-10 11:07:42 +0100211 "set 'received_lines' instead", DeprecationWarning, 2)
Richard Jones803ef8a2010-07-24 09:51:40 +0000212 self.received_lines = value
213
214 @property
215 def __state(self):
216 warn("Access to __state attribute on SMTPChannel is deprecated, "
Florent Xicluna67317752011-12-10 11:07:42 +0100217 "use 'smtp_state' instead", DeprecationWarning, 2)
Richard Jones803ef8a2010-07-24 09:51:40 +0000218 return self.smtp_state
219 @__state.setter
220 def __state(self, value):
221 warn("Setting __state attribute on SMTPChannel is deprecated, "
Florent Xicluna67317752011-12-10 11:07:42 +0100222 "set 'smtp_state' instead", DeprecationWarning, 2)
Richard Jones803ef8a2010-07-24 09:51:40 +0000223 self.smtp_state = value
224
225 @property
226 def __greeting(self):
227 warn("Access to __greeting attribute on SMTPChannel is deprecated, "
Florent Xicluna67317752011-12-10 11:07:42 +0100228 "use 'seen_greeting' instead", DeprecationWarning, 2)
Richard Jones803ef8a2010-07-24 09:51:40 +0000229 return self.seen_greeting
230 @__greeting.setter
231 def __greeting(self, value):
232 warn("Setting __greeting attribute on SMTPChannel is deprecated, "
Florent Xicluna67317752011-12-10 11:07:42 +0100233 "set 'seen_greeting' instead", DeprecationWarning, 2)
Richard Jones803ef8a2010-07-24 09:51:40 +0000234 self.seen_greeting = value
235
236 @property
237 def __mailfrom(self):
238 warn("Access to __mailfrom attribute on SMTPChannel is deprecated, "
Florent Xicluna67317752011-12-10 11:07:42 +0100239 "use 'mailfrom' instead", DeprecationWarning, 2)
Richard Jones803ef8a2010-07-24 09:51:40 +0000240 return self.mailfrom
241 @__mailfrom.setter
242 def __mailfrom(self, value):
243 warn("Setting __mailfrom attribute on SMTPChannel is deprecated, "
Florent Xicluna67317752011-12-10 11:07:42 +0100244 "set 'mailfrom' instead", DeprecationWarning, 2)
Richard Jones803ef8a2010-07-24 09:51:40 +0000245 self.mailfrom = value
246
247 @property
248 def __rcpttos(self):
249 warn("Access to __rcpttos attribute on SMTPChannel is deprecated, "
Florent Xicluna67317752011-12-10 11:07:42 +0100250 "use 'rcpttos' instead", DeprecationWarning, 2)
Richard Jones803ef8a2010-07-24 09:51:40 +0000251 return self.rcpttos
252 @__rcpttos.setter
253 def __rcpttos(self, value):
254 warn("Setting __rcpttos attribute on SMTPChannel is deprecated, "
Florent Xicluna67317752011-12-10 11:07:42 +0100255 "set 'rcpttos' instead", DeprecationWarning, 2)
Richard Jones803ef8a2010-07-24 09:51:40 +0000256 self.rcpttos = value
257
258 @property
259 def __data(self):
260 warn("Access to __data attribute on SMTPChannel is deprecated, "
Florent Xicluna67317752011-12-10 11:07:42 +0100261 "use 'received_data' instead", DeprecationWarning, 2)
Richard Jones803ef8a2010-07-24 09:51:40 +0000262 return self.received_data
263 @__data.setter
264 def __data(self, value):
265 warn("Setting __data attribute on SMTPChannel is deprecated, "
Florent Xicluna67317752011-12-10 11:07:42 +0100266 "set 'received_data' instead", DeprecationWarning, 2)
Richard Jones803ef8a2010-07-24 09:51:40 +0000267 self.received_data = value
268
269 @property
270 def __fqdn(self):
271 warn("Access to __fqdn attribute on SMTPChannel is deprecated, "
Florent Xicluna67317752011-12-10 11:07:42 +0100272 "use 'fqdn' instead", DeprecationWarning, 2)
Richard Jones803ef8a2010-07-24 09:51:40 +0000273 return self.fqdn
274 @__fqdn.setter
275 def __fqdn(self, value):
276 warn("Setting __fqdn attribute on SMTPChannel is deprecated, "
Florent Xicluna67317752011-12-10 11:07:42 +0100277 "set 'fqdn' instead", DeprecationWarning, 2)
Richard Jones803ef8a2010-07-24 09:51:40 +0000278 self.fqdn = value
279
280 @property
281 def __peer(self):
282 warn("Access to __peer attribute on SMTPChannel is deprecated, "
Florent Xicluna67317752011-12-10 11:07:42 +0100283 "use 'peer' instead", DeprecationWarning, 2)
Richard Jones803ef8a2010-07-24 09:51:40 +0000284 return self.peer
285 @__peer.setter
286 def __peer(self, value):
287 warn("Setting __peer attribute on SMTPChannel is deprecated, "
Florent Xicluna67317752011-12-10 11:07:42 +0100288 "set 'peer' instead", DeprecationWarning, 2)
Richard Jones803ef8a2010-07-24 09:51:40 +0000289 self.peer = value
290
291 @property
292 def __conn(self):
293 warn("Access to __conn attribute on SMTPChannel is deprecated, "
Florent Xicluna67317752011-12-10 11:07:42 +0100294 "use 'conn' instead", DeprecationWarning, 2)
Richard Jones803ef8a2010-07-24 09:51:40 +0000295 return self.conn
296 @__conn.setter
297 def __conn(self, value):
298 warn("Setting __conn attribute on SMTPChannel is deprecated, "
Florent Xicluna67317752011-12-10 11:07:42 +0100299 "set 'conn' instead", DeprecationWarning, 2)
Richard Jones803ef8a2010-07-24 09:51:40 +0000300 self.conn = value
301
302 @property
303 def __addr(self):
304 warn("Access to __addr attribute on SMTPChannel is deprecated, "
Florent Xicluna67317752011-12-10 11:07:42 +0100305 "use 'addr' instead", DeprecationWarning, 2)
Richard Jones803ef8a2010-07-24 09:51:40 +0000306 return self.addr
307 @__addr.setter
308 def __addr(self, value):
309 warn("Setting __addr attribute on SMTPChannel is deprecated, "
Florent Xicluna67317752011-12-10 11:07:42 +0100310 "set 'addr' instead", DeprecationWarning, 2)
Richard Jones803ef8a2010-07-24 09:51:40 +0000311 self.addr = value
312
R David Murray2539e672014-08-09 16:40:49 -0400313 # Overrides base class for convenience.
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000314 def push(self, msg):
R David Murray2539e672014-08-09 16:40:49 -0400315 asynchat.async_chat.push(self, bytes(
316 msg + '\r\n', 'utf-8' if self.require_SMTPUTF8 else 'ascii'))
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000317
318 # Implementation of base class abstract method
319 def collect_incoming_data(self, data):
Georg Brandl1e5c5f82010-12-03 07:38:22 +0000320 limit = None
321 if self.smtp_state == self.COMMAND:
R David Murrayd1a30c92012-05-26 14:33:59 -0400322 limit = self.max_command_size_limit
Georg Brandl1e5c5f82010-12-03 07:38:22 +0000323 elif self.smtp_state == self.DATA:
324 limit = self.data_size_limit
325 if limit and self.num_bytes > limit:
326 return
327 elif limit:
328 self.num_bytes += len(data)
R David Murray554bcbf2014-06-11 11:18:08 -0400329 if self._decode_data:
330 self.received_lines.append(str(data, 'utf-8'))
331 else:
332 self.received_lines.append(data)
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000333
334 # Implementation of base class abstract method
335 def found_terminator(self):
R David Murray554bcbf2014-06-11 11:18:08 -0400336 line = self._emptystring.join(self.received_lines)
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000337 print('Data:', repr(line), file=DEBUGSTREAM)
Richard Jones803ef8a2010-07-24 09:51:40 +0000338 self.received_lines = []
339 if self.smtp_state == self.COMMAND:
R David Murrayd1a30c92012-05-26 14:33:59 -0400340 sz, self.num_bytes = self.num_bytes, 0
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000341 if not line:
342 self.push('500 Error: bad syntax')
343 return
R David Murray554bcbf2014-06-11 11:18:08 -0400344 if not self._decode_data:
345 line = str(line, 'utf-8')
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000346 i = line.find(' ')
347 if i < 0:
348 command = line.upper()
349 arg = None
350 else:
351 command = line[:i].upper()
352 arg = line[i+1:].strip()
R David Murrayd1a30c92012-05-26 14:33:59 -0400353 max_sz = (self.command_size_limits[command]
354 if self.extended_smtp else self.command_size_limit)
355 if sz > max_sz:
356 self.push('500 Error: line too long')
357 return
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000358 method = getattr(self, 'smtp_' + command, None)
359 if not method:
R David Murrayd1a30c92012-05-26 14:33:59 -0400360 self.push('500 Error: command "%s" not recognized' % command)
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000361 return
362 method(arg)
363 return
364 else:
Richard Jones803ef8a2010-07-24 09:51:40 +0000365 if self.smtp_state != self.DATA:
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000366 self.push('451 Internal confusion')
Georg Brandl1e5c5f82010-12-03 07:38:22 +0000367 self.num_bytes = 0
368 return
R David Murrayd1a30c92012-05-26 14:33:59 -0400369 if self.data_size_limit and self.num_bytes > self.data_size_limit:
Georg Brandl1e5c5f82010-12-03 07:38:22 +0000370 self.push('552 Error: Too much mail data')
371 self.num_bytes = 0
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000372 return
373 # Remove extraneous carriage returns and de-transparency according
R David Murrayd1a30c92012-05-26 14:33:59 -0400374 # to RFC 5321, Section 4.5.2.
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000375 data = []
R David Murray554bcbf2014-06-11 11:18:08 -0400376 for text in line.split(self._linesep):
377 if text and text[0] == self._dotsep:
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000378 data.append(text[1:])
379 else:
380 data.append(text)
R David Murray554bcbf2014-06-11 11:18:08 -0400381 self.received_data = self._newline.join(data)
R David Murray2539e672014-08-09 16:40:49 -0400382 args = (self.peer, self.mailfrom, self.rcpttos, self.received_data)
R David Murraya33df312015-05-11 12:11:40 -0400383 kwargs = {}
384 if not self._decode_data:
385 kwargs = {
386 'mail_options': self.mail_options,
387 'rcpt_options': self.rcpt_options,
388 }
389 status = self.smtp_server.process_message(*args, **kwargs)
R David Murray2539e672014-08-09 16:40:49 -0400390 self._set_post_data_state()
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000391 if not status:
R David Murrayd1a30c92012-05-26 14:33:59 -0400392 self.push('250 OK')
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000393 else:
394 self.push(status)
395
396 # SMTP and ESMTP commands
397 def smtp_HELO(self, arg):
398 if not arg:
399 self.push('501 Syntax: HELO hostname')
400 return
R David Murray2539e672014-08-09 16:40:49 -0400401 # See issue #21783 for a discussion of this behavior.
Richard Jones803ef8a2010-07-24 09:51:40 +0000402 if self.seen_greeting:
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000403 self.push('503 Duplicate HELO/EHLO')
R David Murray2539e672014-08-09 16:40:49 -0400404 return
405 self._set_rset_state()
406 self.seen_greeting = arg
407 self.push('250 %s' % self.fqdn)
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000408
R David Murrayd1a30c92012-05-26 14:33:59 -0400409 def smtp_EHLO(self, arg):
410 if not arg:
411 self.push('501 Syntax: EHLO hostname')
412 return
R David Murray2539e672014-08-09 16:40:49 -0400413 # See issue #21783 for a discussion of this behavior.
R David Murrayd1a30c92012-05-26 14:33:59 -0400414 if self.seen_greeting:
415 self.push('503 Duplicate HELO/EHLO')
R David Murray2539e672014-08-09 16:40:49 -0400416 return
417 self._set_rset_state()
418 self.seen_greeting = arg
419 self.extended_smtp = True
420 self.push('250-%s' % self.fqdn)
421 if self.data_size_limit:
422 self.push('250-SIZE %s' % self.data_size_limit)
423 self.command_size_limits['MAIL'] += 26
R David Murraya33df312015-05-11 12:11:40 -0400424 if not self._decode_data:
R David Murray2539e672014-08-09 16:40:49 -0400425 self.push('250-8BITMIME')
R David Murraya33df312015-05-11 12:11:40 -0400426 if self.enable_SMTPUTF8:
R David Murray2539e672014-08-09 16:40:49 -0400427 self.push('250-SMTPUTF8')
428 self.command_size_limits['MAIL'] += 10
429 self.push('250 HELP')
R David Murrayd1a30c92012-05-26 14:33:59 -0400430
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000431 def smtp_NOOP(self, arg):
432 if arg:
433 self.push('501 Syntax: NOOP')
434 else:
R David Murrayd1a30c92012-05-26 14:33:59 -0400435 self.push('250 OK')
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000436
437 def smtp_QUIT(self, arg):
438 # args is ignored
439 self.push('221 Bye')
440 self.close_when_done()
441
R David Murrayd1a30c92012-05-26 14:33:59 -0400442 def _strip_command_keyword(self, keyword, arg):
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000443 keylen = len(keyword)
444 if arg[:keylen].upper() == keyword:
R David Murrayd1a30c92012-05-26 14:33:59 -0400445 return arg[keylen:].strip()
446 return ''
447
448 def _getaddr(self, arg):
449 if not arg:
450 return '', ''
451 if arg.lstrip().startswith('<'):
452 address, rest = get_angle_addr(arg)
453 else:
454 address, rest = get_addr_spec(arg)
455 if not address:
456 return address, rest
457 return address.addr_spec, rest
458
459 def _getparams(self, params):
R David Murraya33df312015-05-11 12:11:40 -0400460 # Return params as dictionary. Return None if not all parameters
461 # appear to be syntactically valid according to RFC 1869.
462 result = {}
463 for param in params:
464 param, eq, value = param.partition('=')
465 if not param.isalnum() or eq and not value:
466 return None
467 result[param] = value if eq else True
468 return result
R David Murrayd1a30c92012-05-26 14:33:59 -0400469
470 def smtp_HELP(self, arg):
471 if arg:
Benjamin Peterson0c803312015-04-05 10:01:48 -0400472 extended = ' [SP <mail-parameters>]'
R David Murrayd1a30c92012-05-26 14:33:59 -0400473 lc_arg = arg.upper()
474 if lc_arg == 'EHLO':
475 self.push('250 Syntax: EHLO hostname')
476 elif lc_arg == 'HELO':
477 self.push('250 Syntax: HELO hostname')
478 elif lc_arg == 'MAIL':
479 msg = '250 Syntax: MAIL FROM: <address>'
480 if self.extended_smtp:
481 msg += extended
482 self.push(msg)
483 elif lc_arg == 'RCPT':
484 msg = '250 Syntax: RCPT TO: <address>'
485 if self.extended_smtp:
486 msg += extended
487 self.push(msg)
488 elif lc_arg == 'DATA':
489 self.push('250 Syntax: DATA')
490 elif lc_arg == 'RSET':
491 self.push('250 Syntax: RSET')
492 elif lc_arg == 'NOOP':
493 self.push('250 Syntax: NOOP')
494 elif lc_arg == 'QUIT':
495 self.push('250 Syntax: QUIT')
496 elif lc_arg == 'VRFY':
497 self.push('250 Syntax: VRFY <address>')
498 else:
499 self.push('501 Supported commands: EHLO HELO MAIL RCPT '
500 'DATA RSET NOOP QUIT VRFY')
501 else:
502 self.push('250 Supported commands: EHLO HELO MAIL RCPT DATA '
503 'RSET NOOP QUIT VRFY')
504
505 def smtp_VRFY(self, arg):
506 if arg:
507 address, params = self._getaddr(arg)
508 if address:
509 self.push('252 Cannot VRFY user, but will accept message '
510 'and attempt delivery')
511 else:
512 self.push('502 Could not VRFY %s' % arg)
513 else:
514 self.push('501 Syntax: VRFY <address>')
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000515
516 def smtp_MAIL(self, arg):
R David Murray669b7552012-03-20 16:16:29 -0400517 if not self.seen_greeting:
R David Murraya33df312015-05-11 12:11:40 -0400518 self.push('503 Error: send HELO first')
R David Murray669b7552012-03-20 16:16:29 -0400519 return
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000520 print('===> MAIL', arg, file=DEBUGSTREAM)
R David Murrayd1a30c92012-05-26 14:33:59 -0400521 syntaxerr = '501 Syntax: MAIL FROM: <address>'
522 if self.extended_smtp:
523 syntaxerr += ' [SP <mail-parameters>]'
524 if arg is None:
525 self.push(syntaxerr)
526 return
527 arg = self._strip_command_keyword('FROM:', arg)
528 address, params = self._getaddr(arg)
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000529 if not address:
R David Murrayd1a30c92012-05-26 14:33:59 -0400530 self.push(syntaxerr)
531 return
532 if not self.extended_smtp and params:
533 self.push(syntaxerr)
534 return
Richard Jones803ef8a2010-07-24 09:51:40 +0000535 if self.mailfrom:
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000536 self.push('503 Error: nested MAIL command')
537 return
R David Murraya33df312015-05-11 12:11:40 -0400538 self.mail_options = params.upper().split()
539 params = self._getparams(self.mail_options)
R David Murrayd1a30c92012-05-26 14:33:59 -0400540 if params is None:
541 self.push(syntaxerr)
542 return
R David Murraya33df312015-05-11 12:11:40 -0400543 if not self._decode_data:
544 body = params.pop('BODY', '7BIT')
545 if body not in ['7BIT', '8BITMIME']:
546 self.push('501 Error: BODY can only be one of 7BIT, 8BITMIME')
R David Murray2539e672014-08-09 16:40:49 -0400547 return
R David Murraya33df312015-05-11 12:11:40 -0400548 if self.enable_SMTPUTF8:
549 smtputf8 = params.pop('SMTPUTF8', False)
550 if smtputf8 is True:
R David Murray2539e672014-08-09 16:40:49 -0400551 self.require_SMTPUTF8 = True
R David Murraya33df312015-05-11 12:11:40 -0400552 elif smtputf8 is not False:
553 self.push('501 Error: SMTPUTF8 takes no arguments')
554 return
R David Murrayd1a30c92012-05-26 14:33:59 -0400555 size = params.pop('SIZE', None)
556 if size:
557 if not size.isdigit():
558 self.push(syntaxerr)
559 return
560 elif self.data_size_limit and int(size) > self.data_size_limit:
561 self.push('552 Error: message size exceeds fixed maximum message size')
562 return
563 if len(params.keys()) > 0:
564 self.push('555 MAIL FROM parameters not recognized or not implemented')
565 return
Richard Jones803ef8a2010-07-24 09:51:40 +0000566 self.mailfrom = address
567 print('sender:', self.mailfrom, file=DEBUGSTREAM)
R David Murrayd1a30c92012-05-26 14:33:59 -0400568 self.push('250 OK')
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000569
570 def smtp_RCPT(self, arg):
R David Murray669b7552012-03-20 16:16:29 -0400571 if not self.seen_greeting:
572 self.push('503 Error: send HELO first');
573 return
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000574 print('===> RCPT', arg, file=DEBUGSTREAM)
Richard Jones803ef8a2010-07-24 09:51:40 +0000575 if not self.mailfrom:
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000576 self.push('503 Error: need MAIL command')
577 return
R David Murrayd1a30c92012-05-26 14:33:59 -0400578 syntaxerr = '501 Syntax: RCPT TO: <address>'
579 if self.extended_smtp:
580 syntaxerr += ' [SP <mail-parameters>]'
581 if arg is None:
582 self.push(syntaxerr)
583 return
584 arg = self._strip_command_keyword('TO:', arg)
585 address, params = self._getaddr(arg)
586 if not address:
587 self.push(syntaxerr)
588 return
R David Murraya33df312015-05-11 12:11:40 -0400589 if not self.extended_smtp and params:
590 self.push(syntaxerr)
591 return
592 self.rcpt_options = params.upper().split()
593 params = self._getparams(self.rcpt_options)
594 if params is None:
595 self.push(syntaxerr)
596 return
597 # XXX currently there are no options we recognize.
598 if len(params.keys()) > 0:
R David Murrayd1a30c92012-05-26 14:33:59 -0400599 self.push('555 RCPT TO parameters not recognized or not implemented')
600 return
Richard Jones803ef8a2010-07-24 09:51:40 +0000601 self.rcpttos.append(address)
602 print('recips:', self.rcpttos, file=DEBUGSTREAM)
R David Murrayd1a30c92012-05-26 14:33:59 -0400603 self.push('250 OK')
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000604
605 def smtp_RSET(self, arg):
606 if arg:
607 self.push('501 Syntax: RSET')
608 return
R David Murray2539e672014-08-09 16:40:49 -0400609 self._set_rset_state()
R David Murrayd1a30c92012-05-26 14:33:59 -0400610 self.push('250 OK')
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000611
612 def smtp_DATA(self, arg):
R David Murray669b7552012-03-20 16:16:29 -0400613 if not self.seen_greeting:
614 self.push('503 Error: send HELO first');
615 return
Richard Jones803ef8a2010-07-24 09:51:40 +0000616 if not self.rcpttos:
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000617 self.push('503 Error: need RCPT command')
618 return
619 if arg:
620 self.push('501 Syntax: DATA')
621 return
Richard Jones803ef8a2010-07-24 09:51:40 +0000622 self.smtp_state = self.DATA
Josiah Carlsond74900e2008-07-07 04:15:08 +0000623 self.set_terminator(b'\r\n.\r\n')
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000624 self.push('354 End data with <CR><LF>.<CR><LF>')
625
R David Murrayd1a30c92012-05-26 14:33:59 -0400626 # Commands that have not been implemented
627 def smtp_EXPN(self, arg):
628 self.push('502 EXPN not implemented')
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000629
R David Murrayd1a30c92012-05-26 14:33:59 -0400630
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000631class SMTPServer(asyncore.dispatcher):
Richard Jones803ef8a2010-07-24 09:51:40 +0000632 # SMTPChannel class to use for managing client connections
633 channel_class = SMTPChannel
634
R David Murrayd1a30c92012-05-26 14:33:59 -0400635 def __init__(self, localaddr, remoteaddr,
R David Murray554bcbf2014-06-11 11:18:08 -0400636 data_size_limit=DATA_SIZE_DEFAULT, map=None,
Serhiy Storchakacbcc2fd2016-05-16 09:36:31 +0300637 enable_SMTPUTF8=False, decode_data=False):
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000638 self._localaddr = localaddr
639 self._remoteaddr = remoteaddr
R David Murrayd1a30c92012-05-26 14:33:59 -0400640 self.data_size_limit = data_size_limit
Serhiy Storchakaeb6cd742016-05-29 23:50:56 +0300641 self.enable_SMTPUTF8 = enable_SMTPUTF8
642 self._decode_data = decode_data
Serhiy Storchakacbcc2fd2016-05-16 09:36:31 +0300643 if enable_SMTPUTF8 and decode_data:
644 raise ValueError("decode_data and enable_SMTPUTF8 cannot"
645 " be set to True at the same time")
Vinay Sajip30298b42013-06-07 15:21:41 +0100646 asyncore.dispatcher.__init__(self, map=map)
Giampaolo Rodolà610aa4f2010-06-30 17:47:39 +0000647 try:
R David Murray012a83a2014-06-11 15:17:50 -0400648 gai_results = socket.getaddrinfo(*localaddr,
649 type=socket.SOCK_STREAM)
R David Murray6fe56a32014-06-11 13:48:58 -0400650 self.create_socket(gai_results[0][0], gai_results[0][1])
Giampaolo Rodolà610aa4f2010-06-30 17:47:39 +0000651 # try to re-use a server port if possible
652 self.set_reuse_addr()
653 self.bind(localaddr)
654 self.listen(5)
655 except:
656 self.close()
657 raise
658 else:
659 print('%s started at %s\n\tLocal addr: %s\n\tRemote addr:%s' % (
660 self.__class__.__name__, time.ctime(time.time()),
661 localaddr, remoteaddr), file=DEBUGSTREAM)
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000662
Giampaolo Rodolà977c7072010-10-04 21:08:36 +0000663 def handle_accepted(self, conn, addr):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000664 print('Incoming connection from %s' % repr(addr), file=DEBUGSTREAM)
R David Murray2539e672014-08-09 16:40:49 -0400665 channel = self.channel_class(self,
666 conn,
667 addr,
668 self.data_size_limit,
669 self._map,
670 self.enable_SMTPUTF8,
671 self._decode_data)
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000672
673 # API for "doing something useful with the message"
R David Murraya33df312015-05-11 12:11:40 -0400674 def process_message(self, peer, mailfrom, rcpttos, data, **kwargs):
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000675 """Override this abstract method to handle messages from the client.
676
677 peer is a tuple containing (ipaddr, port) of the client that made the
678 socket connection to our smtp port.
679
680 mailfrom is the raw address the client claims the message is coming
681 from.
682
683 rcpttos is a list of raw addresses the client wishes to deliver the
684 message to.
685
686 data is a string containing the entire full text of the message,
687 headers (if supplied) and all. It has been `de-transparencied'
688 according to RFC 821, Section 4.5.2. In other words, a line
689 containing a `.' followed by other text has had the leading dot
690 removed.
691
Serhiy Storchakacbcc2fd2016-05-16 09:36:31 +0300692 kwargs is a dictionary containing additional information. It is
693 empty if decode_data=True was given as init parameter, otherwise
694 it will contain the following keys:
R David Murraya33df312015-05-11 12:11:40 -0400695 'mail_options': list of parameters to the mail command. All
696 elements are uppercase strings. Example:
697 ['BODY=8BITMIME', 'SMTPUTF8'].
698 'rcpt_options': same, for the rcpt command.
699
R David Murray2539e672014-08-09 16:40:49 -0400700 This function should return None for a normal `250 Ok' response;
701 otherwise, it should return the desired response string in RFC 821
702 format.
703
704 """
705 raise NotImplementedError
706
Tim Peters658cba62001-02-09 20:06:00 +0000707
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000708class DebuggingServer(SMTPServer):
R David Murray2539e672014-08-09 16:40:49 -0400709
710 def _print_message_content(self, peer, data):
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000711 inheaders = 1
R David Murray2539e672014-08-09 16:40:49 -0400712 lines = data.splitlines()
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000713 for line in lines:
714 # headers first
715 if inheaders and not line:
R David Murray2539e672014-08-09 16:40:49 -0400716 peerheader = 'X-Peer: ' + peer[0]
717 if not isinstance(data, str):
718 # decoded_data=false; make header match other binary output
719 peerheader = repr(peerheader.encode('utf-8'))
720 print(peerheader)
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000721 inheaders = 0
R David Murray2539e672014-08-09 16:40:49 -0400722 if not isinstance(data, str):
723 # Avoid spurious 'str on bytes instance' warning.
724 line = repr(line)
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000725 print(line)
R David Murray2539e672014-08-09 16:40:49 -0400726
R David Murraya33df312015-05-11 12:11:40 -0400727 def process_message(self, peer, mailfrom, rcpttos, data, **kwargs):
R David Murray2539e672014-08-09 16:40:49 -0400728 print('---------- MESSAGE FOLLOWS ----------')
R David Murraya33df312015-05-11 12:11:40 -0400729 if kwargs:
730 if kwargs.get('mail_options'):
731 print('mail options: %s' % kwargs['mail_options'])
732 if kwargs.get('rcpt_options'):
733 print('rcpt options: %s\n' % kwargs['rcpt_options'])
R David Murray2539e672014-08-09 16:40:49 -0400734 self._print_message_content(peer, data)
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000735 print('------------ END MESSAGE ------------')
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000736
737
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000738class PureProxy(SMTPServer):
R David Murray2539e672014-08-09 16:40:49 -0400739 def __init__(self, *args, **kwargs):
740 if 'enable_SMTPUTF8' in kwargs and kwargs['enable_SMTPUTF8']:
741 raise ValueError("PureProxy does not support SMTPUTF8.")
742 super(PureProxy, self).__init__(*args, **kwargs)
743
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000744 def process_message(self, peer, mailfrom, rcpttos, data):
745 lines = data.split('\n')
746 # Look for the last header
747 i = 0
748 for line in lines:
749 if not line:
750 break
751 i += 1
752 lines.insert(i, 'X-Peer: %s' % peer[0])
753 data = NEWLINE.join(lines)
754 refused = self._deliver(mailfrom, rcpttos, data)
755 # TBD: what to do with refused addresses?
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000756 print('we got some refusals:', refused, file=DEBUGSTREAM)
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000757
758 def _deliver(self, mailfrom, rcpttos, data):
759 import smtplib
760 refused = {}
761 try:
762 s = smtplib.SMTP()
763 s.connect(self._remoteaddr[0], self._remoteaddr[1])
764 try:
765 refused = s.sendmail(mailfrom, rcpttos, data)
766 finally:
767 s.quit()
Guido van Rossumb940e112007-01-10 16:19:56 +0000768 except smtplib.SMTPRecipientsRefused as e:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000769 print('got SMTPRecipientsRefused', file=DEBUGSTREAM)
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000770 refused = e.recipients
Andrew Svetlov0832af62012-12-18 23:10:48 +0200771 except (OSError, smtplib.SMTPException) as e:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000772 print('got', e.__class__, file=DEBUGSTREAM)
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000773 # All recipients were refused. If the exception had an associated
774 # error code, use it. Otherwise,fake it with a non-triggering
775 # exception code.
776 errcode = getattr(e, 'smtp_code', -1)
777 errmsg = getattr(e, 'smtp_error', 'ignore')
778 for r in rcpttos:
779 refused[r] = (errcode, errmsg)
780 return refused
781
782
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000783class Options:
R David Murray2539e672014-08-09 16:40:49 -0400784 setuid = True
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000785 classname = 'PureProxy'
R David Murrayd1a30c92012-05-26 14:33:59 -0400786 size_limit = None
R David Murray2539e672014-08-09 16:40:49 -0400787 enable_SMTPUTF8 = False
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000788
789
790def parseargs():
791 global DEBUGSTREAM
792 try:
793 opts, args = getopt.getopt(
R David Murray2539e672014-08-09 16:40:49 -0400794 sys.argv[1:], 'nVhc:s:du',
795 ['class=', 'nosetuid', 'version', 'help', 'size=', 'debug',
796 'smtputf8'])
Guido van Rossumb940e112007-01-10 16:19:56 +0000797 except getopt.error as e:
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000798 usage(1, e)
799
800 options = Options()
801 for opt, arg in opts:
802 if opt in ('-h', '--help'):
803 usage(0)
804 elif opt in ('-V', '--version'):
Serhiy Storchakac56894d2013-09-05 17:44:53 +0300805 print(__version__)
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000806 sys.exit(0)
807 elif opt in ('-n', '--nosetuid'):
R David Murray2539e672014-08-09 16:40:49 -0400808 options.setuid = False
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000809 elif opt in ('-c', '--class'):
810 options.classname = arg
811 elif opt in ('-d', '--debug'):
812 DEBUGSTREAM = sys.stderr
R David Murray2539e672014-08-09 16:40:49 -0400813 elif opt in ('-u', '--smtputf8'):
814 options.enable_SMTPUTF8 = True
R David Murrayd1a30c92012-05-26 14:33:59 -0400815 elif opt in ('-s', '--size'):
816 try:
817 int_size = int(arg)
818 options.size_limit = int_size
819 except:
820 print('Invalid size: ' + arg, file=sys.stderr)
821 sys.exit(1)
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000822
823 # parse the rest of the arguments
Barry Warsaw0e8427e2001-10-04 16:27:04 +0000824 if len(args) < 1:
825 localspec = 'localhost:8025'
826 remotespec = 'localhost:25'
827 elif len(args) < 2:
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000828 localspec = args[0]
Barry Warsaw0e8427e2001-10-04 16:27:04 +0000829 remotespec = 'localhost:25'
Barry Warsawebf54272001-11-04 03:04:25 +0000830 elif len(args) < 3:
831 localspec = args[0]
832 remotespec = args[1]
Barry Warsaw0e8427e2001-10-04 16:27:04 +0000833 else:
834 usage(1, 'Invalid arguments: %s' % COMMASPACE.join(args))
835
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000836 # split into host/port pairs
837 i = localspec.find(':')
838 if i < 0:
Barry Warsaw0e8427e2001-10-04 16:27:04 +0000839 usage(1, 'Bad local spec: %s' % localspec)
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000840 options.localhost = localspec[:i]
841 try:
842 options.localport = int(localspec[i+1:])
843 except ValueError:
Barry Warsaw0e8427e2001-10-04 16:27:04 +0000844 usage(1, 'Bad local port: %s' % localspec)
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000845 i = remotespec.find(':')
846 if i < 0:
Barry Warsaw0e8427e2001-10-04 16:27:04 +0000847 usage(1, 'Bad remote spec: %s' % remotespec)
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000848 options.remotehost = remotespec[:i]
849 try:
850 options.remoteport = int(remotespec[i+1:])
851 except ValueError:
Barry Warsaw0e8427e2001-10-04 16:27:04 +0000852 usage(1, 'Bad remote port: %s' % remotespec)
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000853 return options
854
855
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000856if __name__ == '__main__':
857 options = parseargs()
858 # Become nobody
Florent Xicluna711f87c2011-10-20 23:03:43 +0200859 classname = options.classname
860 if "." in classname:
861 lastdot = classname.rfind(".")
862 mod = __import__(classname[:lastdot], globals(), locals(), [""])
863 classname = classname[lastdot+1:]
864 else:
865 import __main__ as mod
866 class_ = getattr(mod, classname)
867 proxy = class_((options.localhost, options.localport),
R David Murrayd1a30c92012-05-26 14:33:59 -0400868 (options.remotehost, options.remoteport),
R David Murray2539e672014-08-09 16:40:49 -0400869 options.size_limit, enable_SMTPUTF8=options.enable_SMTPUTF8)
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000870 if options.setuid:
871 try:
872 import pwd
Brett Cannoncd171c82013-07-04 17:43:24 -0400873 except ImportError:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000874 print('Cannot import module "pwd"; try running with -n option.', file=sys.stderr)
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000875 sys.exit(1)
876 nobody = pwd.getpwnam('nobody')[2]
877 try:
878 os.setuid(nobody)
Giampaolo Rodola'0166a282013-02-12 15:14:17 +0100879 except PermissionError:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000880 print('Cannot setuid "nobody"; try running with -n option.', file=sys.stderr)
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000881 sys.exit(1)
Barry Warsaw7e0d9562001-01-31 22:51:35 +0000882 try:
883 asyncore.loop()
884 except KeyboardInterrupt:
885 pass