]>
git.proxmox.com Git - mirror_edk2.git/blob - AppPkg/Applications/Python/Python-2.7.2/Lib/smtpd.py
2 """An RFC 2821 smtp proxy.
4 Usage: %(program)s [options] [localhost:localport [remotehost:remoteport]]
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).
16 Print the version number and exit.
20 Use `classname' as the concrete SMTP proxy class. Uses `PureProxy' by
25 Turn on debugging prints.
29 Print this message and exit.
31 Version: %(__version__)s
33 If localhost is not given then `localhost' is used, and if localport is not
34 given then 8025 is used. If remotehost is not given then `localhost' is used,
35 and if remoteport is not given, then 25 is used.
40 # This file implements the minimal SMTP protocol as defined in RFC 821. It
41 # has a hierarchy of classes which implement the backend functionality for the
42 # smtpd. A number of classes are provided:
44 # SMTPServer - the base class for the backend. Raises NotImplementedError
45 # if you try to use it.
47 # DebuggingServer - simply prints each message it receives on stdout.
49 # PureProxy - Proxies all messages to a real smtpd which does final
50 # delivery. One known problem with this class is that it doesn't handle
51 # SMTP errors from the backend server at all. This should be fixed
52 # (contributions are welcome!).
54 # MailmanProxy - An experimental hack to work with GNU Mailman
55 # <www.list.org>. Using this server as your real incoming smtpd, your
56 # mailhost will automatically recognize and accept mail destined to Mailman
57 # lists when those lists are created. Every message not destined for a list
58 # gets forwarded to a real backend smtpd, as with PureProxy. Again, errors
59 # are not handled correctly yet.
61 # Please note that this script requires Python 2.0
63 # Author: Barry Warsaw <barry@python.org>
67 # - support mailbox delivery
70 # - handle error codes from the backend smtpd
81 __all__
= ["SMTPServer","DebuggingServer","PureProxy","MailmanProxy"]
84 __version__
= 'Python SMTP proxy version 0.2'
88 def write(self
, msg
): pass
92 DEBUGSTREAM
= Devnull()
98 def usage(code
, msg
=''):
99 print >> sys
.stderr
, __doc__
% globals()
101 print >> sys
.stderr
, msg
105 class SMTPChannel(asynchat
.async_chat
):
109 def __init__(self
, server
, conn
, addr
):
110 asynchat
.async_chat
.__init
__(self
, conn
)
111 self
.__server
= server
115 self
.__state
= self
.COMMAND
117 self
.__mailfrom
= None
120 self
.__fqdn
= socket
.getfqdn()
122 self
.__peer
= conn
.getpeername()
123 except socket
.error
, err
:
124 # a race condition may occur if the other end is closing
125 # before we can get the peername
127 if err
[0] != errno
.ENOTCONN
:
130 print >> DEBUGSTREAM
, 'Peer:', repr(self
.__peer
)
131 self
.push('220 %s %s' % (self
.__fqdn
, __version__
))
132 self
.set_terminator('\r\n')
134 # Overrides base class for convenience
136 asynchat
.async_chat
.push(self
, msg
+ '\r\n')
138 # Implementation of base class abstract method
139 def collect_incoming_data(self
, data
):
140 self
.__line
.append(data
)
142 # Implementation of base class abstract method
143 def found_terminator(self
):
144 line
= EMPTYSTRING
.join(self
.__line
)
145 print >> DEBUGSTREAM
, 'Data:', repr(line
)
147 if self
.__state
== self
.COMMAND
:
149 self
.push('500 Error: bad syntax')
154 command
= line
.upper()
157 command
= line
[:i
].upper()
158 arg
= line
[i
+1:].strip()
159 method
= getattr(self
, 'smtp_' + command
, None)
161 self
.push('502 Error: command "%s" not implemented' % command
)
166 if self
.__state
!= self
.DATA
:
167 self
.push('451 Internal confusion')
169 # Remove extraneous carriage returns and de-transparency according
170 # to RFC 821, Section 4.5.2.
172 for text
in line
.split('\r\n'):
173 if text
and text
[0] == '.':
174 data
.append(text
[1:])
177 self
.__data
= NEWLINE
.join(data
)
178 status
= self
.__server
.process_message(self
.__peer
,
183 self
.__mailfrom
= None
184 self
.__state
= self
.COMMAND
185 self
.set_terminator('\r\n')
191 # SMTP and ESMTP commands
192 def smtp_HELO(self
, arg
):
194 self
.push('501 Syntax: HELO hostname')
197 self
.push('503 Duplicate HELO/EHLO')
199 self
.__greeting
= arg
200 self
.push('250 %s' % self
.__fqdn
)
202 def smtp_NOOP(self
, arg
):
204 self
.push('501 Syntax: NOOP')
208 def smtp_QUIT(self
, arg
):
211 self
.close_when_done()
214 def __getaddr(self
, keyword
, arg
):
216 keylen
= len(keyword
)
217 if arg
[:keylen
].upper() == keyword
:
218 address
= arg
[keylen
:].strip()
221 elif address
[0] == '<' and address
[-1] == '>' and address
!= '<>':
222 # Addresses can be in the form <person@dom.com> but watch out
223 # for null address, e.g. <>
224 address
= address
[1:-1]
227 def smtp_MAIL(self
, arg
):
228 print >> DEBUGSTREAM
, '===> MAIL', arg
229 address
= self
.__getaddr
('FROM:', arg
) if arg
else None
231 self
.push('501 Syntax: MAIL FROM:<address>')
234 self
.push('503 Error: nested MAIL command')
236 self
.__mailfrom
= address
237 print >> DEBUGSTREAM
, 'sender:', self
.__mailfrom
240 def smtp_RCPT(self
, arg
):
241 print >> DEBUGSTREAM
, '===> RCPT', arg
242 if not self
.__mailfrom
:
243 self
.push('503 Error: need MAIL command')
245 address
= self
.__getaddr
('TO:', arg
) if arg
else None
247 self
.push('501 Syntax: RCPT TO: <address>')
249 self
.__rcpttos
.append(address
)
250 print >> DEBUGSTREAM
, 'recips:', self
.__rcpttos
253 def smtp_RSET(self
, arg
):
255 self
.push('501 Syntax: RSET')
257 # Resets the sender, recipients, and data, but not the greeting
258 self
.__mailfrom
= None
261 self
.__state
= self
.COMMAND
264 def smtp_DATA(self
, arg
):
265 if not self
.__rcpttos
:
266 self
.push('503 Error: need RCPT command')
269 self
.push('501 Syntax: DATA')
271 self
.__state
= self
.DATA
272 self
.set_terminator('\r\n.\r\n')
273 self
.push('354 End data with <CR><LF>.<CR><LF>')
276 class SMTPServer(asyncore
.dispatcher
):
277 def __init__(self
, localaddr
, remoteaddr
):
278 self
._localaddr
= localaddr
279 self
._remoteaddr
= remoteaddr
280 asyncore
.dispatcher
.__init
__(self
)
282 self
.create_socket(socket
.AF_INET
, socket
.SOCK_STREAM
)
283 # try to re-use a server port if possible
284 self
.set_reuse_addr()
288 # cleanup asyncore.socket_map before raising
292 print >> DEBUGSTREAM
, \
293 '%s started at %s\n\tLocal addr: %s\n\tRemote addr:%s' % (
294 self
.__class
__.__name
__, time
.ctime(time
.time()),
295 localaddr
, remoteaddr
)
297 def handle_accept(self
):
301 print >> DEBUGSTREAM
, 'Incoming connection from %s' % repr(addr
)
302 channel
= SMTPChannel(self
, conn
, addr
)
304 # API for "doing something useful with the message"
305 def process_message(self
, peer
, mailfrom
, rcpttos
, data
):
306 """Override this abstract method to handle messages from the client.
308 peer is a tuple containing (ipaddr, port) of the client that made the
309 socket connection to our smtp port.
311 mailfrom is the raw address the client claims the message is coming
314 rcpttos is a list of raw addresses the client wishes to deliver the
317 data is a string containing the entire full text of the message,
318 headers (if supplied) and all. It has been `de-transparencied'
319 according to RFC 821, Section 4.5.2. In other words, a line
320 containing a `.' followed by other text has had the leading dot
323 This function should return None, for a normal `250 Ok' response;
324 otherwise it returns the desired response string in RFC 821 format.
327 raise NotImplementedError
330 class DebuggingServer(SMTPServer
):
331 # Do something with the gathered message
332 def process_message(self
, peer
, mailfrom
, rcpttos
, data
):
334 lines
= data
.split('\n')
335 print '---------- MESSAGE FOLLOWS ----------'
338 if inheaders
and not line
:
339 print 'X-Peer:', peer
[0]
342 print '------------ END MESSAGE ------------'
345 class PureProxy(SMTPServer
):
346 def process_message(self
, peer
, mailfrom
, rcpttos
, data
):
347 lines
= data
.split('\n')
348 # Look for the last header
354 lines
.insert(i
, 'X-Peer: %s' % peer
[0])
355 data
= NEWLINE
.join(lines
)
356 refused
= self
._deliver
(mailfrom
, rcpttos
, data
)
357 # TBD: what to do with refused addresses?
358 print >> DEBUGSTREAM
, 'we got some refusals:', refused
360 def _deliver(self
, mailfrom
, rcpttos
, data
):
365 s
.connect(self
._remoteaddr
[0], self
._remoteaddr
[1])
367 refused
= s
.sendmail(mailfrom
, rcpttos
, data
)
370 except smtplib
.SMTPRecipientsRefused
, e
:
371 print >> DEBUGSTREAM
, 'got SMTPRecipientsRefused'
372 refused
= e
.recipients
373 except (socket
.error
, smtplib
.SMTPException
), e
:
374 print >> DEBUGSTREAM
, 'got', e
.__class
__
375 # All recipients were refused. If the exception had an associated
376 # error code, use it. Otherwise,fake it with a non-triggering
378 errcode
= getattr(e
, 'smtp_code', -1)
379 errmsg
= getattr(e
, 'smtp_error', 'ignore')
381 refused
[r
] = (errcode
, errmsg
)
385 class MailmanProxy(PureProxy
):
386 def process_message(self
, peer
, mailfrom
, rcpttos
, data
):
387 from cStringIO
import StringIO
388 from Mailman
import Utils
389 from Mailman
import Message
390 from Mailman
import MailList
391 # If the message is to a Mailman mailing list, then we'll invoke the
392 # Mailman script directly, without going through the real smtpd.
393 # Otherwise we'll forward it to the local proxy for disposition.
396 local
= rcpt
.lower().split('@')[0]
397 # We allow the following variations on the theme
404 parts
= local
.split('-')
412 if not Utils
.list_exists(listname
) or command
not in (
413 '', 'admin', 'owner', 'request', 'join', 'leave'):
415 listnames
.append((rcpt
, listname
, command
))
416 # Remove all list recipients from rcpttos and forward what we're not
417 # going to take care of ourselves. Linear removal should be fine
418 # since we don't expect a large number of recipients.
419 for rcpt
, listname
, command
in listnames
:
421 # If there's any non-list destined recipients left,
422 print >> DEBUGSTREAM
, 'forwarding recips:', ' '.join(rcpttos
)
424 refused
= self
._deliver
(mailfrom
, rcpttos
, data
)
425 # TBD: what to do with refused addresses?
426 print >> DEBUGSTREAM
, 'we got refusals:', refused
427 # Now deliver directly to the list commands
430 msg
= Message
.Message(s
)
431 # These headers are required for the proper execution of Mailman. All
432 # MTAs in existence seem to add these if the original message doesn't
434 if not msg
.getheader('from'):
435 msg
['From'] = mailfrom
436 if not msg
.getheader('date'):
437 msg
['Date'] = time
.ctime(time
.time())
438 for rcpt
, listname
, command
in listnames
:
439 print >> DEBUGSTREAM
, 'sending message to', rcpt
440 mlist
= mlists
.get(listname
)
442 mlist
= MailList
.MailList(listname
, lock
=0)
443 mlists
[listname
] = mlist
444 # dispatch on the type of command
447 msg
.Enqueue(mlist
, tolist
=1)
448 elif command
== 'admin':
449 msg
.Enqueue(mlist
, toadmin
=1)
450 elif command
== 'owner':
451 msg
.Enqueue(mlist
, toowner
=1)
452 elif command
== 'request':
453 msg
.Enqueue(mlist
, torequest
=1)
454 elif command
in ('join', 'leave'):
455 # TBD: this is a hack!
456 if command
== 'join':
457 msg
['Subject'] = 'subscribe'
459 msg
['Subject'] = 'unsubscribe'
460 msg
.Enqueue(mlist
, torequest
=1)
465 classname
= 'PureProxy'
471 opts
, args
= getopt
.getopt(
472 sys
.argv
[1:], 'nVhc:d',
473 ['class=', 'nosetuid', 'version', 'help', 'debug'])
474 except getopt
.error
, e
:
478 for opt
, arg
in opts
:
479 if opt
in ('-h', '--help'):
481 elif opt
in ('-V', '--version'):
482 print >> sys
.stderr
, __version__
484 elif opt
in ('-n', '--nosetuid'):
486 elif opt
in ('-c', '--class'):
487 options
.classname
= arg
488 elif opt
in ('-d', '--debug'):
489 DEBUGSTREAM
= sys
.stderr
491 # parse the rest of the arguments
493 localspec
= 'localhost:8025'
494 remotespec
= 'localhost:25'
497 remotespec
= 'localhost:25'
502 usage(1, 'Invalid arguments: %s' % COMMASPACE
.join(args
))
504 # split into host/port pairs
505 i
= localspec
.find(':')
507 usage(1, 'Bad local spec: %s' % localspec
)
508 options
.localhost
= localspec
[:i
]
510 options
.localport
= int(localspec
[i
+1:])
512 usage(1, 'Bad local port: %s' % localspec
)
513 i
= remotespec
.find(':')
515 usage(1, 'Bad remote spec: %s' % remotespec
)
516 options
.remotehost
= remotespec
[:i
]
518 options
.remoteport
= int(remotespec
[i
+1:])
520 usage(1, 'Bad remote port: %s' % remotespec
)
524 if __name__
== '__main__':
525 options
= parseargs()
531 print >> sys
.stderr
, \
532 'Cannot import module "pwd"; try running with -n option.'
534 nobody
= pwd
.getpwnam('nobody')[2]
538 if e
.errno
!= errno
.EPERM
: raise
539 print >> sys
.stderr
, \
540 'Cannot setuid "nobody"; try running with -n option.'
542 classname
= options
.classname
544 lastdot
= classname
.rfind(".")
545 mod
= __import__(classname
[:lastdot
], globals(), locals(), [""])
546 classname
= classname
[lastdot
+1:]
548 import __main__
as mod
549 class_
= getattr(mod
, classname
)
550 proxy
= class_((options
.localhost
, options
.localport
),
551 (options
.remotehost
, options
.remoteport
))
554 except KeyboardInterrupt: