1 | #! /usr/bin/env python
|
---|
2 | """An RFC 2821 smtp proxy.
|
---|
3 |
|
---|
4 | Usage: %(program)s [options] [localhost:localport [remotehost:remoteport]]
|
---|
5 |
|
---|
6 | Options:
|
---|
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
|
---|
20 | Use `classname' as the concrete SMTP proxy class. Uses `PureProxy' by
|
---|
21 | default.
|
---|
22 |
|
---|
23 | --debug
|
---|
24 | -d
|
---|
25 | Turn on debugging prints.
|
---|
26 |
|
---|
27 | --help
|
---|
28 | -h
|
---|
29 | Print this message and exit.
|
---|
30 |
|
---|
31 | Version: %(__version__)s
|
---|
32 |
|
---|
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.
|
---|
36 | """
|
---|
37 |
|
---|
38 | # Overview:
|
---|
39 | #
|
---|
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:
|
---|
43 | #
|
---|
44 | # SMTPServer - the base class for the backend. Raises NotImplementedError
|
---|
45 | # if you try to use it.
|
---|
46 | #
|
---|
47 | # DebuggingServer - simply prints each message it receives on stdout.
|
---|
48 | #
|
---|
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!).
|
---|
53 | #
|
---|
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.
|
---|
60 | #
|
---|
61 | # Please note that this script requires Python 2.0
|
---|
62 | #
|
---|
63 | # Author: Barry Warsaw <barry@python.org>
|
---|
64 | #
|
---|
65 | # TODO:
|
---|
66 | #
|
---|
67 | # - support mailbox delivery
|
---|
68 | # - alias files
|
---|
69 | # - ESMTP
|
---|
70 | # - handle error codes from the backend smtpd
|
---|
71 |
|
---|
72 | import sys
|
---|
73 | import os
|
---|
74 | import errno
|
---|
75 | import getopt
|
---|
76 | import time
|
---|
77 | import socket
|
---|
78 | import asyncore
|
---|
79 | import asynchat
|
---|
80 |
|
---|
81 | __all__ = ["SMTPServer","DebuggingServer","PureProxy","MailmanProxy"]
|
---|
82 |
|
---|
83 | program = sys.argv[0]
|
---|
84 | __version__ = 'Python SMTP proxy version 0.2'
|
---|
85 |
|
---|
86 |
|
---|
87 | class Devnull:
|
---|
88 | def write(self, msg): pass
|
---|
89 | def flush(self): pass
|
---|
90 |
|
---|
91 |
|
---|
92 | DEBUGSTREAM = Devnull()
|
---|
93 | NEWLINE = '\n'
|
---|
94 | EMPTYSTRING = ''
|
---|
95 | COMMASPACE = ', '
|
---|
96 |
|
---|
97 |
|
---|
98 | def usage(code, msg=''):
|
---|
99 | print >> sys.stderr, __doc__ % globals()
|
---|
100 | if msg:
|
---|
101 | print >> sys.stderr, msg
|
---|
102 | sys.exit(code)
|
---|
103 |
|
---|
104 |
|
---|
105 | class SMTPChannel(asynchat.async_chat):
|
---|
106 | COMMAND = 0
|
---|
107 | DATA = 1
|
---|
108 |
|
---|
109 | def __init__(self, server, conn, addr):
|
---|
110 | asynchat.async_chat.__init__(self, conn)
|
---|
111 | self.__server = server
|
---|
112 | self.__conn = conn
|
---|
113 | self.__addr = addr
|
---|
114 | self.__line = []
|
---|
115 | self.__state = self.COMMAND
|
---|
116 | self.__greeting = 0
|
---|
117 | self.__mailfrom = None
|
---|
118 | self.__rcpttos = []
|
---|
119 | self.__data = ''
|
---|
120 | self.__fqdn = socket.getfqdn()
|
---|
121 | try:
|
---|
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
|
---|
126 | self.close()
|
---|
127 | if err[0] != errno.ENOTCONN:
|
---|
128 | raise
|
---|
129 | return
|
---|
130 | print >> DEBUGSTREAM, 'Peer:', repr(self.__peer)
|
---|
131 | self.push('220 %s %s' % (self.__fqdn, __version__))
|
---|
132 | self.set_terminator('\r\n')
|
---|
133 |
|
---|
134 | # Overrides base class for convenience
|
---|
135 | def push(self, msg):
|
---|
136 | asynchat.async_chat.push(self, msg + '\r\n')
|
---|
137 |
|
---|
138 | # Implementation of base class abstract method
|
---|
139 | def collect_incoming_data(self, data):
|
---|
140 | self.__line.append(data)
|
---|
141 |
|
---|
142 | # Implementation of base class abstract method
|
---|
143 | def found_terminator(self):
|
---|
144 | line = EMPTYSTRING.join(self.__line)
|
---|
145 | print >> DEBUGSTREAM, 'Data:', repr(line)
|
---|
146 | self.__line = []
|
---|
147 | if self.__state == self.COMMAND:
|
---|
148 | if not line:
|
---|
149 | self.push('500 Error: bad syntax')
|
---|
150 | return
|
---|
151 | method = None
|
---|
152 | i = line.find(' ')
|
---|
153 | if i < 0:
|
---|
154 | command = line.upper()
|
---|
155 | arg = None
|
---|
156 | else:
|
---|
157 | command = line[:i].upper()
|
---|
158 | arg = line[i+1:].strip()
|
---|
159 | method = getattr(self, 'smtp_' + command, None)
|
---|
160 | if not method:
|
---|
161 | self.push('502 Error: command "%s" not implemented' % command)
|
---|
162 | return
|
---|
163 | method(arg)
|
---|
164 | return
|
---|
165 | else:
|
---|
166 | if self.__state != self.DATA:
|
---|
167 | self.push('451 Internal confusion')
|
---|
168 | return
|
---|
169 | # Remove extraneous carriage returns and de-transparency according
|
---|
170 | # to RFC 821, Section 4.5.2.
|
---|
171 | data = []
|
---|
172 | for text in line.split('\r\n'):
|
---|
173 | if text and text[0] == '.':
|
---|
174 | data.append(text[1:])
|
---|
175 | else:
|
---|
176 | data.append(text)
|
---|
177 | self.__data = NEWLINE.join(data)
|
---|
178 | status = self.__server.process_message(self.__peer,
|
---|
179 | self.__mailfrom,
|
---|
180 | self.__rcpttos,
|
---|
181 | self.__data)
|
---|
182 | self.__rcpttos = []
|
---|
183 | self.__mailfrom = None
|
---|
184 | self.__state = self.COMMAND
|
---|
185 | self.set_terminator('\r\n')
|
---|
186 | if not status:
|
---|
187 | self.push('250 Ok')
|
---|
188 | else:
|
---|
189 | self.push(status)
|
---|
190 |
|
---|
191 | # SMTP and ESMTP commands
|
---|
192 | def smtp_HELO(self, arg):
|
---|
193 | if not arg:
|
---|
194 | self.push('501 Syntax: HELO hostname')
|
---|
195 | return
|
---|
196 | if self.__greeting:
|
---|
197 | self.push('503 Duplicate HELO/EHLO')
|
---|
198 | else:
|
---|
199 | self.__greeting = arg
|
---|
200 | self.push('250 %s' % self.__fqdn)
|
---|
201 |
|
---|
202 | def smtp_NOOP(self, arg):
|
---|
203 | if arg:
|
---|
204 | self.push('501 Syntax: NOOP')
|
---|
205 | else:
|
---|
206 | self.push('250 Ok')
|
---|
207 |
|
---|
208 | def smtp_QUIT(self, arg):
|
---|
209 | # args is ignored
|
---|
210 | self.push('221 Bye')
|
---|
211 | self.close_when_done()
|
---|
212 |
|
---|
213 | # factored
|
---|
214 | def __getaddr(self, keyword, arg):
|
---|
215 | address = None
|
---|
216 | keylen = len(keyword)
|
---|
217 | if arg[:keylen].upper() == keyword:
|
---|
218 | address = arg[keylen:].strip()
|
---|
219 | if not address:
|
---|
220 | pass
|
---|
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]
|
---|
225 | return address
|
---|
226 |
|
---|
227 | def smtp_MAIL(self, arg):
|
---|
228 | print >> DEBUGSTREAM, '===> MAIL', arg
|
---|
229 | address = self.__getaddr('FROM:', arg) if arg else None
|
---|
230 | if not address:
|
---|
231 | self.push('501 Syntax: MAIL FROM:<address>')
|
---|
232 | return
|
---|
233 | if self.__mailfrom:
|
---|
234 | self.push('503 Error: nested MAIL command')
|
---|
235 | return
|
---|
236 | self.__mailfrom = address
|
---|
237 | print >> DEBUGSTREAM, 'sender:', self.__mailfrom
|
---|
238 | self.push('250 Ok')
|
---|
239 |
|
---|
240 | def smtp_RCPT(self, arg):
|
---|
241 | print >> DEBUGSTREAM, '===> RCPT', arg
|
---|
242 | if not self.__mailfrom:
|
---|
243 | self.push('503 Error: need MAIL command')
|
---|
244 | return
|
---|
245 | address = self.__getaddr('TO:', arg) if arg else None
|
---|
246 | if not address:
|
---|
247 | self.push('501 Syntax: RCPT TO: <address>')
|
---|
248 | return
|
---|
249 | self.__rcpttos.append(address)
|
---|
250 | print >> DEBUGSTREAM, 'recips:', self.__rcpttos
|
---|
251 | self.push('250 Ok')
|
---|
252 |
|
---|
253 | def smtp_RSET(self, arg):
|
---|
254 | if arg:
|
---|
255 | self.push('501 Syntax: RSET')
|
---|
256 | return
|
---|
257 | # Resets the sender, recipients, and data, but not the greeting
|
---|
258 | self.__mailfrom = None
|
---|
259 | self.__rcpttos = []
|
---|
260 | self.__data = ''
|
---|
261 | self.__state = self.COMMAND
|
---|
262 | self.push('250 Ok')
|
---|
263 |
|
---|
264 | def smtp_DATA(self, arg):
|
---|
265 | if not self.__rcpttos:
|
---|
266 | self.push('503 Error: need RCPT command')
|
---|
267 | return
|
---|
268 | if arg:
|
---|
269 | self.push('501 Syntax: DATA')
|
---|
270 | return
|
---|
271 | self.__state = self.DATA
|
---|
272 | self.set_terminator('\r\n.\r\n')
|
---|
273 | self.push('354 End data with <CR><LF>.<CR><LF>')
|
---|
274 |
|
---|
275 |
|
---|
276 | class SMTPServer(asyncore.dispatcher):
|
---|
277 | def __init__(self, localaddr, remoteaddr):
|
---|
278 | self._localaddr = localaddr
|
---|
279 | self._remoteaddr = remoteaddr
|
---|
280 | asyncore.dispatcher.__init__(self)
|
---|
281 | try:
|
---|
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()
|
---|
285 | self.bind(localaddr)
|
---|
286 | self.listen(5)
|
---|
287 | except:
|
---|
288 | # cleanup asyncore.socket_map before raising
|
---|
289 | self.close()
|
---|
290 | raise
|
---|
291 | else:
|
---|
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)
|
---|
296 |
|
---|
297 | def handle_accept(self):
|
---|
298 | pair = self.accept()
|
---|
299 | if pair is not None:
|
---|
300 | conn, addr = pair
|
---|
301 | print >> DEBUGSTREAM, 'Incoming connection from %s' % repr(addr)
|
---|
302 | channel = SMTPChannel(self, conn, addr)
|
---|
303 |
|
---|
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.
|
---|
307 |
|
---|
308 | peer is a tuple containing (ipaddr, port) of the client that made the
|
---|
309 | socket connection to our smtp port.
|
---|
310 |
|
---|
311 | mailfrom is the raw address the client claims the message is coming
|
---|
312 | from.
|
---|
313 |
|
---|
314 | rcpttos is a list of raw addresses the client wishes to deliver the
|
---|
315 | message to.
|
---|
316 |
|
---|
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
|
---|
321 | removed.
|
---|
322 |
|
---|
323 | This function should return None, for a normal `250 Ok' response;
|
---|
324 | otherwise it returns the desired response string in RFC 821 format.
|
---|
325 |
|
---|
326 | """
|
---|
327 | raise NotImplementedError
|
---|
328 |
|
---|
329 |
|
---|
330 | class DebuggingServer(SMTPServer):
|
---|
331 | # Do something with the gathered message
|
---|
332 | def process_message(self, peer, mailfrom, rcpttos, data):
|
---|
333 | inheaders = 1
|
---|
334 | lines = data.split('\n')
|
---|
335 | print '---------- MESSAGE FOLLOWS ----------'
|
---|
336 | for line in lines:
|
---|
337 | # headers first
|
---|
338 | if inheaders and not line:
|
---|
339 | print 'X-Peer:', peer[0]
|
---|
340 | inheaders = 0
|
---|
341 | print line
|
---|
342 | print '------------ END MESSAGE ------------'
|
---|
343 |
|
---|
344 |
|
---|
345 | class PureProxy(SMTPServer):
|
---|
346 | def process_message(self, peer, mailfrom, rcpttos, data):
|
---|
347 | lines = data.split('\n')
|
---|
348 | # Look for the last header
|
---|
349 | i = 0
|
---|
350 | for line in lines:
|
---|
351 | if not line:
|
---|
352 | break
|
---|
353 | i += 1
|
---|
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
|
---|
359 |
|
---|
360 | def _deliver(self, mailfrom, rcpttos, data):
|
---|
361 | import smtplib
|
---|
362 | refused = {}
|
---|
363 | try:
|
---|
364 | s = smtplib.SMTP()
|
---|
365 | s.connect(self._remoteaddr[0], self._remoteaddr[1])
|
---|
366 | try:
|
---|
367 | refused = s.sendmail(mailfrom, rcpttos, data)
|
---|
368 | finally:
|
---|
369 | s.quit()
|
---|
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
|
---|
377 | # exception code.
|
---|
378 | errcode = getattr(e, 'smtp_code', -1)
|
---|
379 | errmsg = getattr(e, 'smtp_error', 'ignore')
|
---|
380 | for r in rcpttos:
|
---|
381 | refused[r] = (errcode, errmsg)
|
---|
382 | return refused
|
---|
383 |
|
---|
384 |
|
---|
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.
|
---|
394 | listnames = []
|
---|
395 | for rcpt in rcpttos:
|
---|
396 | local = rcpt.lower().split('@')[0]
|
---|
397 | # We allow the following variations on the theme
|
---|
398 | # listname
|
---|
399 | # listname-admin
|
---|
400 | # listname-owner
|
---|
401 | # listname-request
|
---|
402 | # listname-join
|
---|
403 | # listname-leave
|
---|
404 | parts = local.split('-')
|
---|
405 | if len(parts) > 2:
|
---|
406 | continue
|
---|
407 | listname = parts[0]
|
---|
408 | if len(parts) == 2:
|
---|
409 | command = parts[1]
|
---|
410 | else:
|
---|
411 | command = ''
|
---|
412 | if not Utils.list_exists(listname) or command not in (
|
---|
413 | '', 'admin', 'owner', 'request', 'join', 'leave'):
|
---|
414 | continue
|
---|
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:
|
---|
420 | rcpttos.remove(rcpt)
|
---|
421 | # If there's any non-list destined recipients left,
|
---|
422 | print >> DEBUGSTREAM, 'forwarding recips:', ' '.join(rcpttos)
|
---|
423 | if 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
|
---|
428 | mlists = {}
|
---|
429 | s = StringIO(data)
|
---|
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
|
---|
433 | # have them.
|
---|
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)
|
---|
441 | if not mlist:
|
---|
442 | mlist = MailList.MailList(listname, lock=0)
|
---|
443 | mlists[listname] = mlist
|
---|
444 | # dispatch on the type of command
|
---|
445 | if command == '':
|
---|
446 | # post
|
---|
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'
|
---|
458 | else:
|
---|
459 | msg['Subject'] = 'unsubscribe'
|
---|
460 | msg.Enqueue(mlist, torequest=1)
|
---|
461 |
|
---|
462 |
|
---|
463 | class Options:
|
---|
464 | setuid = 1
|
---|
465 | classname = 'PureProxy'
|
---|
466 |
|
---|
467 |
|
---|
468 | def parseargs():
|
---|
469 | global DEBUGSTREAM
|
---|
470 | try:
|
---|
471 | opts, args = getopt.getopt(
|
---|
472 | sys.argv[1:], 'nVhc:d',
|
---|
473 | ['class=', 'nosetuid', 'version', 'help', 'debug'])
|
---|
474 | except getopt.error, e:
|
---|
475 | usage(1, e)
|
---|
476 |
|
---|
477 | options = Options()
|
---|
478 | for opt, arg in opts:
|
---|
479 | if opt in ('-h', '--help'):
|
---|
480 | usage(0)
|
---|
481 | elif opt in ('-V', '--version'):
|
---|
482 | print >> sys.stderr, __version__
|
---|
483 | sys.exit(0)
|
---|
484 | elif opt in ('-n', '--nosetuid'):
|
---|
485 | options.setuid = 0
|
---|
486 | elif opt in ('-c', '--class'):
|
---|
487 | options.classname = arg
|
---|
488 | elif opt in ('-d', '--debug'):
|
---|
489 | DEBUGSTREAM = sys.stderr
|
---|
490 |
|
---|
491 | # parse the rest of the arguments
|
---|
492 | if len(args) < 1:
|
---|
493 | localspec = 'localhost:8025'
|
---|
494 | remotespec = 'localhost:25'
|
---|
495 | elif len(args) < 2:
|
---|
496 | localspec = args[0]
|
---|
497 | remotespec = 'localhost:25'
|
---|
498 | elif len(args) < 3:
|
---|
499 | localspec = args[0]
|
---|
500 | remotespec = args[1]
|
---|
501 | else:
|
---|
502 | usage(1, 'Invalid arguments: %s' % COMMASPACE.join(args))
|
---|
503 |
|
---|
504 | # split into host/port pairs
|
---|
505 | i = localspec.find(':')
|
---|
506 | if i < 0:
|
---|
507 | usage(1, 'Bad local spec: %s' % localspec)
|
---|
508 | options.localhost = localspec[:i]
|
---|
509 | try:
|
---|
510 | options.localport = int(localspec[i+1:])
|
---|
511 | except ValueError:
|
---|
512 | usage(1, 'Bad local port: %s' % localspec)
|
---|
513 | i = remotespec.find(':')
|
---|
514 | if i < 0:
|
---|
515 | usage(1, 'Bad remote spec: %s' % remotespec)
|
---|
516 | options.remotehost = remotespec[:i]
|
---|
517 | try:
|
---|
518 | options.remoteport = int(remotespec[i+1:])
|
---|
519 | except ValueError:
|
---|
520 | usage(1, 'Bad remote port: %s' % remotespec)
|
---|
521 | return options
|
---|
522 |
|
---|
523 |
|
---|
524 | if __name__ == '__main__':
|
---|
525 | options = parseargs()
|
---|
526 | # Become nobody
|
---|
527 | classname = options.classname
|
---|
528 | if "." in classname:
|
---|
529 | lastdot = classname.rfind(".")
|
---|
530 | mod = __import__(classname[:lastdot], globals(), locals(), [""])
|
---|
531 | classname = classname[lastdot+1:]
|
---|
532 | else:
|
---|
533 | import __main__ as mod
|
---|
534 | class_ = getattr(mod, classname)
|
---|
535 | proxy = class_((options.localhost, options.localport),
|
---|
536 | (options.remotehost, options.remoteport))
|
---|
537 | if options.setuid:
|
---|
538 | try:
|
---|
539 | import pwd
|
---|
540 | except ImportError:
|
---|
541 | print >> sys.stderr, \
|
---|
542 | 'Cannot import module "pwd"; try running with -n option.'
|
---|
543 | sys.exit(1)
|
---|
544 | nobody = pwd.getpwnam('nobody')[2]
|
---|
545 | try:
|
---|
546 | os.setuid(nobody)
|
---|
547 | except OSError, e:
|
---|
548 | if e.errno != errno.EPERM: raise
|
---|
549 | print >> sys.stderr, \
|
---|
550 | 'Cannot setuid "nobody"; try running with -n option.'
|
---|
551 | sys.exit(1)
|
---|
552 | try:
|
---|
553 | asyncore.loop()
|
---|
554 | except KeyboardInterrupt:
|
---|
555 | pass
|
---|