#! /usr/bin/env python3"""An RFC 5321 smtp proxy with optional RFC 1870 and RFC 6531 extensions.Usage: %(program)s [options] [localhost:localport [remotehost:remoteport]]Options:--nosetuid-nThis program generally tries to setuid `nobody', unless this flag isset. The setuid call will fail if this program is not run as root (inwhich case, use this flag).--version-VPrint the version number and exit.--class classname-c classnameUse `classname' as the concrete SMTP proxy class. Uses `PureProxy' bydefault.--size limit-s limitRestrict the total size of the incoming message to "limit" number ofbytes via the RFC 1870 SIZE extension. Defaults to 33554432 bytes.--smtputf8-uEnable the SMTPUTF8 extension and behave as an RFC 6531 smtp proxy.--debug-dTurn on debugging prints.--help-hPrint this message and exit.Version: %(__version__)sIf localhost is not given then `localhost' is used, and if localport is notgiven then 8025 is used. If remotehost is not given then `localhost' is used,and if remoteport is not given, then 25 is used."""# Overview:## This file implements the minimal SMTP protocol as defined in RFC 5321. It# has a hierarchy of classes which implement the backend functionality for the# smtpd. A number of classes are provided:## SMTPServer - the base class for the backend. Raises NotImplementedError# if you try to use it.## DebuggingServer - simply prints each message it receives on stdout.## PureProxy - Proxies all messages to a real smtpd which does final# delivery. One known problem with this class is that it doesn't handle# SMTP errors from the backend server at all. This should be fixed# (contributions are welcome!).## MailmanProxy - An experimental hack to work with GNU Mailman# <www.list.org>. Using this server as your real incoming smtpd, your# mailhost will automatically recognize and accept mail destined to Mailman# lists when those lists are created. Every message not destined for a list# gets forwarded to a real backend smtpd, as with PureProxy. Again, errors# are not handled correctly yet.### Author: Barry Warsaw <barry@python.org>## TODO:## - support mailbox delivery# - alias files# - Handle more ESMTP extensions# - handle error codes from the backend smtpdimport sysimport osimport errnoimport getoptimport timeimport socketimport asyncoreimport asynchatimport collectionsfrom warnings import warnfrom email._header_value_parser import get_addr_spec, get_angle_addr__all__ = ["SMTPChannel", "SMTPServer", "DebuggingServer", "PureProxy","MailmanProxy",]program = sys.argv[0]__version__ = 'Python SMTP proxy version 0.3'class Devnull:def write(self, msg): passdef flush(self): passDEBUGSTREAM = Devnull()NEWLINE = '\n'COMMASPACE = ', 'DATA_SIZE_DEFAULT = 33554432def usage(code, msg=''):print(__doc__ % globals(), file=sys.stderr)if msg:print(msg, file=sys.stderr)sys.exit(code)class SMTPChannel(asynchat.async_chat):COMMAND = 0DATA = 1command_size_limit = 512command_size_limits = collections.defaultdict(lambda x=command_size_limit: x)@propertydef max_command_size_limit(self):try:return max(self.command_size_limits.values())except ValueError:return self.command_size_limitdef __init__(self, server, conn, addr, data_size_limit=DATA_SIZE_DEFAULT,map=None, enable_SMTPUTF8=False, decode_data=False):asynchat.async_chat.__init__(self, conn, map=map)self.smtp_server = serverself.conn = connself.addr = addrself.data_size_limit = data_size_limitself.enable_SMTPUTF8 = enable_SMTPUTF8self._decode_data = decode_dataif enable_SMTPUTF8 and decode_data:raise ValueError("decode_data and enable_SMTPUTF8 cannot"" be set to True at the same time")if decode_data:self._emptystring = ''self._linesep = '\r\n'self._dotsep = '.'self._newline = NEWLINEelse:self._emptystring = b''self._linesep = b'\r\n'self._dotsep = ord(b'.')self._newline = b'\n'self._set_rset_state()self.seen_greeting = ''self.extended_smtp = Falseself.command_size_limits.clear()self.fqdn = socket.getfqdn()try:self.peer = conn.getpeername()except OSError as err:# a race condition may occur if the other end is closing# before we can get the peernameself.close()if err.args[0] != errno.ENOTCONN:raisereturnprint('Peer:', repr(self.peer), file=DEBUGSTREAM)self.push('220 %s %s' % (self.fqdn, __version__))def _set_post_data_state(self):"""Reset state variables to their post-DATA state."""self.smtp_state = self.COMMANDself.mailfrom = Noneself.rcpttos = []self.require_SMTPUTF8 = Falseself.num_bytes = 0self.set_terminator(b'\r\n')def _set_rset_state(self):"""Reset all state variables except the greeting."""self._set_post_data_state()self.received_data = ''self.received_lines = []# properties for backwards-compatibility@propertydef __server(self):warn("Access to __server attribute on SMTPChannel is deprecated, ""use 'smtp_server' instead", DeprecationWarning, 2)return self.smtp_server@__server.setterdef __server(self, value):warn("Setting __server attribute on SMTPChannel is deprecated, ""set 'smtp_server' instead", DeprecationWarning, 2)self.smtp_server = value@propertydef __line(self):warn("Access to __line attribute on SMTPChannel is deprecated, ""use 'received_lines' instead", DeprecationWarning, 2)return self.received_lines@__line.setterdef __line(self, value):warn("Setting __line attribute on SMTPChannel is deprecated, ""set 'received_lines' instead", DeprecationWarning, 2)self.received_lines = value@propertydef __state(self):warn("Access to __state attribute on SMTPChannel is deprecated, ""use 'smtp_state' instead", DeprecationWarning, 2)return self.smtp_state@__state.setterdef __state(self, value):warn("Setting __state attribute on SMTPChannel is deprecated, ""set 'smtp_state' instead", DeprecationWarning, 2)self.smtp_state = value@propertydef __greeting(self):warn("Access to __greeting attribute on SMTPChannel is deprecated, ""use 'seen_greeting' instead", DeprecationWarning, 2)return self.seen_greeting@__greeting.setterdef __greeting(self, value):warn("Setting __greeting attribute on SMTPChannel is deprecated, ""set 'seen_greeting' instead", DeprecationWarning, 2)self.seen_greeting = value@propertydef __mailfrom(self):warn("Access to __mailfrom attribute on SMTPChannel is deprecated, ""use 'mailfrom' instead", DeprecationWarning, 2)return self.mailfrom@__mailfrom.setterdef __mailfrom(self, value):warn("Setting __mailfrom attribute on SMTPChannel is deprecated, ""set 'mailfrom' instead", DeprecationWarning, 2)self.mailfrom = value@propertydef __rcpttos(self):warn("Access to __rcpttos attribute on SMTPChannel is deprecated, ""use 'rcpttos' instead", DeprecationWarning, 2)return self.rcpttos@__rcpttos.setterdef __rcpttos(self, value):warn("Setting __rcpttos attribute on SMTPChannel is deprecated, ""set 'rcpttos' instead", DeprecationWarning, 2)self.rcpttos = value@propertydef __data(self):warn("Access to __data attribute on SMTPChannel is deprecated, ""use 'received_data' instead", DeprecationWarning, 2)return self.received_data@__data.setterdef __data(self, value):warn("Setting __data attribute on SMTPChannel is deprecated, ""set 'received_data' instead", DeprecationWarning, 2)self.received_data = value@propertydef __fqdn(self):warn("Access to __fqdn attribute on SMTPChannel is deprecated, ""use 'fqdn' instead", DeprecationWarning, 2)return self.fqdn@__fqdn.setterdef __fqdn(self, value):warn("Setting __fqdn attribute on SMTPChannel is deprecated, ""set 'fqdn' instead", DeprecationWarning, 2)self.fqdn = value@propertydef __peer(self):warn("Access to __peer attribute on SMTPChannel is deprecated, ""use 'peer' instead", DeprecationWarning, 2)return self.peer@__peer.setterdef __peer(self, value):warn("Setting __peer attribute on SMTPChannel is deprecated, ""set 'peer' instead", DeprecationWarning, 2)self.peer = value@propertydef __conn(self):warn("Access to __conn attribute on SMTPChannel is deprecated, ""use 'conn' instead", DeprecationWarning, 2)return self.conn@__conn.setterdef __conn(self, value):warn("Setting __conn attribute on SMTPChannel is deprecated, ""set 'conn' instead", DeprecationWarning, 2)self.conn = value@propertydef __addr(self):warn("Access to __addr attribute on SMTPChannel is deprecated, ""use 'addr' instead", DeprecationWarning, 2)return self.addr@__addr.setterdef __addr(self, value):warn("Setting __addr attribute on SMTPChannel is deprecated, ""set 'addr' instead", DeprecationWarning, 2)self.addr = value# Overrides base class for convenience.def push(self, msg):asynchat.async_chat.push(self, bytes(msg + '\r\n', 'utf-8' if self.require_SMTPUTF8 else 'ascii'))# Implementation of base class abstract methoddef collect_incoming_data(self, data):limit = Noneif self.smtp_state == self.COMMAND:limit = self.max_command_size_limitelif self.smtp_state == self.DATA:limit = self.data_size_limitif limit and self.num_bytes > limit:returnelif limit:self.num_bytes += len(data)if self._decode_data:self.received_lines.append(str(data, 'utf-8'))else:self.received_lines.append(data)# Implementation of base class abstract methoddef found_terminator(self):line = self._emptystring.join(self.received_lines)print('Data:', repr(line), file=DEBUGSTREAM)self.received_lines = []if self.smtp_state == self.COMMAND:sz, self.num_bytes = self.num_bytes, 0if not line:self.push('500 Error: bad syntax')returnif not self._decode_data:line = str(line, 'utf-8')i = line.find(' ')if i < 0:command = line.upper()arg = Noneelse:command = line[:i].upper()arg = line[i+1:].strip()max_sz = (self.command_size_limits[command]if self.extended_smtp else self.command_size_limit)if sz > max_sz:self.push('500 Error: line too long')returnmethod = getattr(self, 'smtp_' + command, None)if not method:self.push('500 Error: command "%s" not recognized' % command)returnmethod(arg)returnelse:if self.smtp_state != self.DATA:self.push('451 Internal confusion')self.num_bytes = 0returnif self.data_size_limit and self.num_bytes > self.data_size_limit:self.push('552 Error: Too much mail data')self.num_bytes = 0return# Remove extraneous carriage returns and de-transparency according# to RFC 5321, Section 4.5.2.data = []for text in line.split(self._linesep):if text and text[0] == self._dotsep:data.append(text[1:])else:data.append(text)self.received_data = self._newline.join(data)args = (self.peer, self.mailfrom, self.rcpttos, self.received_data)kwargs = {}if not self._decode_data:kwargs = {'mail_options': self.mail_options,'rcpt_options': self.rcpt_options,}status = self.smtp_server.process_message(*args, **kwargs)self._set_post_data_state()if not status:self.push('250 OK')else:self.push(status)# SMTP and ESMTP commandsdef smtp_HELO(self, arg):if not arg:self.push('501 Syntax: HELO hostname')return# See issue #21783 for a discussion of this behavior.if self.seen_greeting:self.push('503 Duplicate HELO/EHLO')returnself._set_rset_state()self.seen_greeting = argself.push('250 %s' % self.fqdn)def smtp_EHLO(self, arg):if not arg:self.push('501 Syntax: EHLO hostname')return# See issue #21783 for a discussion of this behavior.if self.seen_greeting:self.push('503 Duplicate HELO/EHLO')returnself._set_rset_state()self.seen_greeting = argself.extended_smtp = Trueself.push('250-%s' % self.fqdn)if self.data_size_limit:self.push('250-SIZE %s' % self.data_size_limit)self.command_size_limits['MAIL'] += 26if not self._decode_data:self.push('250-8BITMIME')if self.enable_SMTPUTF8:self.push('250-SMTPUTF8')self.command_size_limits['MAIL'] += 10self.push('250 HELP')def smtp_NOOP(self, arg):if arg:self.push('501 Syntax: NOOP')else:self.push('250 OK')def smtp_QUIT(self, arg):# args is ignoredself.push('221 Bye')self.close_when_done()def _strip_command_keyword(self, keyword, arg):keylen = len(keyword)if arg[:keylen].upper() == keyword:return arg[keylen:].strip()return ''def _getaddr(self, arg):if not arg:return '', ''if arg.lstrip().startswith('<'):address, rest = get_angle_addr(arg)else:address, rest = get_addr_spec(arg)if not address:return address, restreturn address.addr_spec, restdef _getparams(self, params):# Return params as dictionary. Return None if not all parameters# appear to be syntactically valid according to RFC 1869.result = {}for param in params:param, eq, value = param.partition('=')if not param.isalnum() or eq and not value:return Noneresult[param] = value if eq else Truereturn resultdef smtp_HELP(self, arg):if arg:extended = ' [SP <mail-parameters>]'lc_arg = arg.upper()if lc_arg == 'EHLO':self.push('250 Syntax: EHLO hostname')elif lc_arg == 'HELO':self.push('250 Syntax: HELO hostname')elif lc_arg == 'MAIL':msg = '250 Syntax: MAIL FROM: <address>'if self.extended_smtp:msg += extendedself.push(msg)elif lc_arg == 'RCPT':msg = '250 Syntax: RCPT TO: <address>'if self.extended_smtp:msg += extendedself.push(msg)elif lc_arg == 'DATA':self.push('250 Syntax: DATA')elif lc_arg == 'RSET':self.push('250 Syntax: RSET')elif lc_arg == 'NOOP':self.push('250 Syntax: NOOP')elif lc_arg == 'QUIT':self.push('250 Syntax: QUIT')elif lc_arg == 'VRFY':self.push('250 Syntax: VRFY <address>')else:self.push('501 Supported commands: EHLO HELO MAIL RCPT ''DATA RSET NOOP QUIT VRFY')else:self.push('250 Supported commands: EHLO HELO MAIL RCPT DATA ''RSET NOOP QUIT VRFY')def smtp_VRFY(self, arg):if arg:address, params = self._getaddr(arg)if address:self.push('252 Cannot VRFY user, but will accept message ''and attempt delivery')else:self.push('502 Could not VRFY %s' % arg)else:self.push('501 Syntax: VRFY <address>')def smtp_MAIL(self, arg):if not self.seen_greeting:self.push('503 Error: send HELO first')returnprint('===> MAIL', arg, file=DEBUGSTREAM)syntaxerr = '501 Syntax: MAIL FROM: <address>'if self.extended_smtp:syntaxerr += ' [SP <mail-parameters>]'if arg is None:self.push(syntaxerr)returnarg = self._strip_command_keyword('FROM:', arg)address, params = self._getaddr(arg)if not address:self.push(syntaxerr)returnif not self.extended_smtp and params:self.push(syntaxerr)returnif self.mailfrom:self.push('503 Error: nested MAIL command')returnself.mail_options = params.upper().split()params = self._getparams(self.mail_options)if params is None:self.push(syntaxerr)returnif not self._decode_data:body = params.pop('BODY', '7BIT')if body not in ['7BIT', '8BITMIME']:self.push('501 Error: BODY can only be one of 7BIT, 8BITMIME')returnif self.enable_SMTPUTF8:smtputf8 = params.pop('SMTPUTF8', False)if smtputf8 is True:self.require_SMTPUTF8 = Trueelif smtputf8 is not False:self.push('501 Error: SMTPUTF8 takes no arguments')returnsize = params.pop('SIZE', None)if size:if not size.isdigit():self.push(syntaxerr)returnelif self.data_size_limit and int(size) > self.data_size_limit:self.push('552 Error: message size exceeds fixed maximum message size')returnif len(params.keys()) > 0:self.push('555 MAIL FROM parameters not recognized or not implemented')returnself.mailfrom = addressprint('sender:', self.mailfrom, file=DEBUGSTREAM)self.push('250 OK')def smtp_RCPT(self, arg):if not self.seen_greeting:self.push('503 Error: send HELO first');returnprint('===> RCPT', arg, file=DEBUGSTREAM)if not self.mailfrom:self.push('503 Error: need MAIL command')returnsyntaxerr = '501 Syntax: RCPT TO: <address>'if self.extended_smtp:syntaxerr += ' [SP <mail-parameters>]'if arg is None:self.push(syntaxerr)returnarg = self._strip_command_keyword('TO:', arg)address, params = self._getaddr(arg)if not address:self.push(syntaxerr)returnif not self.extended_smtp and params:self.push(syntaxerr)returnself.rcpt_options = params.upper().split()params = self._getparams(self.rcpt_options)if params is None:self.push(syntaxerr)return# XXX currently there are no options we recognize.if len(params.keys()) > 0:self.push('555 RCPT TO parameters not recognized or not implemented')returnself.rcpttos.append(address)print('recips:', self.rcpttos, file=DEBUGSTREAM)self.push('250 OK')def smtp_RSET(self, arg):if arg:self.push('501 Syntax: RSET')returnself._set_rset_state()self.push('250 OK')def smtp_DATA(self, arg):if not self.seen_greeting:self.push('503 Error: send HELO first');returnif not self.rcpttos:self.push('503 Error: need RCPT command')returnif arg:self.push('501 Syntax: DATA')returnself.smtp_state = self.DATAself.set_terminator(b'\r\n.\r\n')self.push('354 End data with <CR><LF>.<CR><LF>')# Commands that have not been implementeddef smtp_EXPN(self, arg):self.push('502 EXPN not implemented')class SMTPServer(asyncore.dispatcher):# SMTPChannel class to use for managing client connectionschannel_class = SMTPChanneldef __init__(self, localaddr, remoteaddr,data_size_limit=DATA_SIZE_DEFAULT, map=None,enable_SMTPUTF8=False, decode_data=False):self._localaddr = localaddrself._remoteaddr = remoteaddrself.data_size_limit = data_size_limitself.enable_SMTPUTF8 = enable_SMTPUTF8self._decode_data = decode_dataif enable_SMTPUTF8 and decode_data:raise ValueError("decode_data and enable_SMTPUTF8 cannot"" be set to True at the same time")asyncore.dispatcher.__init__(self, map=map)try:gai_results = socket.getaddrinfo(*localaddr,type=socket.SOCK_STREAM)self.create_socket(gai_results[0][0], gai_results[0][1])# try to re-use a server port if possibleself.set_reuse_addr()self.bind(localaddr)self.listen(5)except:self.close()raiseelse:print('%s started at %s\n\tLocal addr: %s\n\tRemote addr:%s' % (self.__class__.__name__, time.ctime(time.time()),localaddr, remoteaddr), file=DEBUGSTREAM)def handle_accepted(self, conn, addr):print('Incoming connection from %s' % repr(addr), file=DEBUGSTREAM)channel = self.channel_class(self,conn,addr,self.data_size_limit,self._map,self.enable_SMTPUTF8,self._decode_data)# API for "doing something useful with the message"def process_message(self, peer, mailfrom, rcpttos, data, **kwargs):"""Override this abstract method to handle messages from the client.peer is a tuple containing (ipaddr, port) of the client that made thesocket connection to our smtp port.mailfrom is the raw address the client claims the message is comingfrom.rcpttos is a list of raw addresses the client wishes to deliver themessage to.data is a string containing the entire full text of the message,headers (if supplied) and all. It has been `de-transparencied'according to RFC 821, Section 4.5.2. In other words, a linecontaining a `.' followed by other text has had the leading dotremoved.kwargs is a dictionary containing additional information. It isempty if decode_data=True was given as init parameter, otherwiseit will contain the following keys:'mail_options': list of parameters to the mail command. Allelements are uppercase strings. Example:['BODY=8BITMIME', 'SMTPUTF8'].'rcpt_options': same, for the rcpt command.This function should return None for a normal `250 Ok' response;otherwise, it should return the desired response string in RFC 821format."""raise NotImplementedErrorclass DebuggingServer(SMTPServer):def _print_message_content(self, peer, data):inheaders = 1lines = data.splitlines()for line in lines:# headers firstif inheaders and not line:peerheader = 'X-Peer: ' + peer[0]if not isinstance(data, str):# decoded_data=false; make header match other binary outputpeerheader = repr(peerheader.encode('utf-8'))print(peerheader)inheaders = 0if not isinstance(data, str):# Avoid spurious 'str on bytes instance' warning.line = repr(line)print(line)def process_message(self, peer, mailfrom, rcpttos, data, **kwargs):print('---------- MESSAGE FOLLOWS ----------')if kwargs:if kwargs.get('mail_options'):print('mail options: %s' % kwargs['mail_options'])if kwargs.get('rcpt_options'):print('rcpt options: %s\n' % kwargs['rcpt_options'])self._print_message_content(peer, data)print('------------ END MESSAGE ------------')class PureProxy(SMTPServer):def __init__(self, *args, **kwargs):if 'enable_SMTPUTF8' in kwargs and kwargs['enable_SMTPUTF8']:raise ValueError("PureProxy does not support SMTPUTF8.")super(PureProxy, self).__init__(*args, **kwargs)def process_message(self, peer, mailfrom, rcpttos, data):lines = data.split('\n')# Look for the last headeri = 0for line in lines:if not line:breaki += 1lines.insert(i, 'X-Peer: %s' % peer[0])data = NEWLINE.join(lines)refused = self._deliver(mailfrom, rcpttos, data)# TBD: what to do with refused addresses?print('we got some refusals:', refused, file=DEBUGSTREAM)def _deliver(self, mailfrom, rcpttos, data):import smtplibrefused = {}try:s = smtplib.SMTP()s.connect(self._remoteaddr[0], self._remoteaddr[1])try:refused = s.sendmail(mailfrom, rcpttos, data)finally:s.quit()except smtplib.SMTPRecipientsRefused as e:print('got SMTPRecipientsRefused', file=DEBUGSTREAM)refused = e.recipientsexcept (OSError, smtplib.SMTPException) as e:print('got', e.__class__, file=DEBUGSTREAM)# All recipients were refused. If the exception had an associated# error code, use it. Otherwise,fake it with a non-triggering# exception code.errcode = getattr(e, 'smtp_code', -1)errmsg = getattr(e, 'smtp_error', 'ignore')for r in rcpttos:refused[r] = (errcode, errmsg)return refusedclass MailmanProxy(PureProxy):def __init__(self, *args, **kwargs):if 'enable_SMTPUTF8' in kwargs and kwargs['enable_SMTPUTF8']:raise ValueError("MailmanProxy does not support SMTPUTF8.")super(PureProxy, self).__init__(*args, **kwargs)def process_message(self, peer, mailfrom, rcpttos, data):from io import StringIOfrom Mailman import Utilsfrom Mailman import Messagefrom Mailman import MailList# If the message is to a Mailman mailing list, then we'll invoke the# Mailman script directly, without going through the real smtpd.# Otherwise we'll forward it to the local proxy for disposition.listnames = []for rcpt in rcpttos:local = rcpt.lower().split('@')[0]# We allow the following variations on the theme# listname# listname-admin# listname-owner# listname-request# listname-join# listname-leaveparts = local.split('-')if len(parts) > 2:continuelistname = parts[0]if len(parts) == 2:command = parts[1]else:command = ''if not Utils.list_exists(listname) or command not in ('', 'admin', 'owner', 'request', 'join', 'leave'):continuelistnames.append((rcpt, listname, command))# Remove all list recipients from rcpttos and forward what we're not# going to take care of ourselves. Linear removal should be fine# since we don't expect a large number of recipients.for rcpt, listname, command in listnames:rcpttos.remove(rcpt)# If there's any non-list destined recipients left,print('forwarding recips:', ' '.join(rcpttos), file=DEBUGSTREAM)if rcpttos:refused = self._deliver(mailfrom, rcpttos, data)# TBD: what to do with refused addresses?print('we got refusals:', refused, file=DEBUGSTREAM)# Now deliver directly to the list commandsmlists = {}s = StringIO(data)msg = Message.Message(s)# These headers are required for the proper execution of Mailman. All# MTAs in existence seem to add these if the original message doesn't# have them.if not msg.get('from'):msg['From'] = mailfromif not msg.get('date'):msg['Date'] = time.ctime(time.time())for rcpt, listname, command in listnames:print('sending message to', rcpt, file=DEBUGSTREAM)mlist = mlists.get(listname)if not mlist:mlist = MailList.MailList(listname, lock=0)mlists[listname] = mlist# dispatch on the type of commandif command == '':# postmsg.Enqueue(mlist, tolist=1)elif command == 'admin':msg.Enqueue(mlist, toadmin=1)elif command == 'owner':msg.Enqueue(mlist, toowner=1)elif command == 'request':msg.Enqueue(mlist, torequest=1)elif command in ('join', 'leave'):# TBD: this is a hack!if command == 'join':msg['Subject'] = 'subscribe'else:msg['Subject'] = 'unsubscribe'msg.Enqueue(mlist, torequest=1)class Options:setuid = Trueclassname = 'PureProxy'size_limit = Noneenable_SMTPUTF8 = Falsedef parseargs():global DEBUGSTREAMtry:opts, args = getopt.getopt(sys.argv[1:], 'nVhc:s:du',['class=', 'nosetuid', 'version', 'help', 'size=', 'debug','smtputf8'])except getopt.error as e:usage(1, e)options = Options()for opt, arg in opts:if opt in ('-h', '--help'):usage(0)elif opt in ('-V', '--version'):print(__version__)sys.exit(0)elif opt in ('-n', '--nosetuid'):options.setuid = Falseelif opt in ('-c', '--class'):options.classname = argelif opt in ('-d', '--debug'):DEBUGSTREAM = sys.stderrelif opt in ('-u', '--smtputf8'):options.enable_SMTPUTF8 = Trueelif opt in ('-s', '--size'):try:int_size = int(arg)options.size_limit = int_sizeexcept:print('Invalid size: ' + arg, file=sys.stderr)sys.exit(1)# parse the rest of the argumentsif len(args) < 1:localspec = 'localhost:8025'remotespec = 'localhost:25'elif len(args) < 2:localspec = args[0]remotespec = 'localhost:25'elif len(args) < 3:localspec = args[0]remotespec = args[1]else:usage(1, 'Invalid arguments: %s' % COMMASPACE.join(args))# split into host/port pairsi = localspec.find(':')if i < 0:usage(1, 'Bad local spec: %s' % localspec)options.localhost = localspec[:i]try:options.localport = int(localspec[i+1:])except ValueError:usage(1, 'Bad local port: %s' % localspec)i = remotespec.find(':')if i < 0:usage(1, 'Bad remote spec: %s' % remotespec)options.remotehost = remotespec[:i]try:options.remoteport = int(remotespec[i+1:])except ValueError:usage(1, 'Bad remote port: %s' % remotespec)return optionsif __name__ == '__main__':options = parseargs()# Become nobodyclassname = options.classnameif "." in classname:lastdot = classname.rfind(".")mod = __import__(classname[:lastdot], globals(), locals(), [""])classname = classname[lastdot+1:]else:import __main__ as modclass_ = getattr(mod, classname)proxy = class_((options.localhost, options.localport),(options.remotehost, options.remoteport),options.size_limit, enable_SMTPUTF8=options.enable_SMTPUTF8)if options.setuid:try:import pwdexcept ImportError:print('Cannot import module "pwd"; try running with -n option.', file=sys.stderr)sys.exit(1)nobody = pwd.getpwnam('nobody')[2]try:os.setuid(nobody)except PermissionError:print('Cannot setuid "nobody"; try running with -n option.', file=sys.stderr)sys.exit(1)try:asyncore.loop()except KeyboardInterrupt:pass
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。