This commit is contained in:
Mai Gillmann
2019-12-17 14:09:10 +01:00
parent 66e908fc8a
commit 4791d00a43
2122 changed files with 423791 additions and 0 deletions
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,42 @@
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""The Finger User Information Protocol (RFC 1288)"""
from twisted.protocols import basic
class Finger(basic.LineReceiver):
def lineReceived(self, line):
parts = line.split()
if not parts:
parts = [b'']
if len(parts) == 1:
slash_w = 0
else:
slash_w = 1
user = parts[-1]
if b'@' in user:
hostPlace = user.rfind(b'@')
user = user[:hostPlace]
host = user[hostPlace+1:]
return self.forwardQuery(slash_w, user, host)
if user:
return self.getUser(slash_w, user)
else:
return self.getDomain(slash_w)
def _refuseMessage(self, message):
self.transport.write(message + b"\n")
self.transport.loseConnection()
def forwardQuery(self, slash_w, user, host):
self._refuseMessage(b'Finger forwarding service denied')
def getDomain(self, slash_w):
self._refuseMessage(b'Finger online list denied')
def getUser(self, slash_w, user):
self.transport.write(b'Login: ' + user + b'\n')
self._refuseMessage(b'No such user')
@@ -0,0 +1,71 @@
# -*- test-case-name: twisted.protocols.haproxy.test.test_parser -*-
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Parser for 'haproxy:' string endpoint.
"""
from zope.interface import implementer
from twisted.plugin import IPlugin
from twisted.internet.endpoints import (
quoteStringArgument, serverFromString, IStreamServerEndpointStringParser
)
from twisted.python.compat import iteritems
from . import proxyEndpoint
def unparseEndpoint(args, kwargs):
"""
Un-parse the already-parsed args and kwargs back into endpoint syntax.
@param args: C{:}-separated arguments
@type args: L{tuple} of native L{str}
@param kwargs: C{:} and then C{=}-separated keyword arguments
@type arguments: L{tuple} of native L{str}
@return: a string equivalent to the original format which this was parsed
as.
@rtype: native L{str}
"""
description = ':'.join(
[quoteStringArgument(str(arg)) for arg in args] +
sorted(['%s=%s' % (quoteStringArgument(str(key)),
quoteStringArgument(str(value)))
for key, value in iteritems(kwargs)
]))
return description
@implementer(IPlugin, IStreamServerEndpointStringParser)
class HAProxyServerParser(object):
"""
Stream server endpoint string parser for the HAProxyServerEndpoint type.
@ivar prefix: See L{IStreamServerEndpointStringParser.prefix}.
"""
prefix = "haproxy"
def parseStreamServer(self, reactor, *args, **kwargs):
"""
Parse a stream server endpoint from a reactor and string-only arguments
and keyword arguments.
@param reactor: The reactor.
@param args: The parsed string arguments.
@param kwargs: The parsed keyword arguments.
@return: a stream server endpoint
@rtype: L{IStreamServerEndpoint}
"""
subdescription = unparseEndpoint(args, kwargs)
wrappedEndpoint = serverFromString(reactor, subdescription)
return proxyEndpoint(wrappedEndpoint)
@@ -0,0 +1,143 @@
# -*- test-case-name: twisted.protocols.haproxy.test.test_v1parser -*-
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
IProxyParser implementation for version one of the PROXY protocol.
"""
from zope.interface import implementer
from twisted.internet import address
from ._exceptions import (
convertError, InvalidProxyHeader, InvalidNetworkProtocol,
MissingAddressData
)
from . import _info
from . import _interfaces
@implementer(_interfaces.IProxyParser)
class V1Parser(object):
"""
PROXY protocol version one header parser.
Version one of the PROXY protocol is a human readable format represented
by a single, newline delimited binary string that contains all of the
relevant source and destination data.
"""
PROXYSTR = b'PROXY'
UNKNOWN_PROTO = b'UNKNOWN'
TCP4_PROTO = b'TCP4'
TCP6_PROTO = b'TCP6'
ALLOWED_NET_PROTOS = (
TCP4_PROTO,
TCP6_PROTO,
UNKNOWN_PROTO,
)
NEWLINE = b'\r\n'
def __init__(self):
self.buffer = b''
def feed(self, data):
"""
Consume a chunk of data and attempt to parse it.
@param data: A bytestring.
@type data: L{bytes}
@return: A two-tuple containing, in order, a
L{_interfaces.IProxyInfo} and any bytes fed to the
parser that followed the end of the header. Both of these values
are None until a complete header is parsed.
@raises InvalidProxyHeader: If the bytes fed to the parser create an
invalid PROXY header.
"""
self.buffer += data
if len(self.buffer) > 107 and self.NEWLINE not in self.buffer:
raise InvalidProxyHeader()
lines = (self.buffer).split(self.NEWLINE, 1)
if not len(lines) > 1:
return (None, None)
self.buffer = b''
remaining = lines.pop()
header = lines.pop()
info = self.parse(header)
return (info, remaining)
@classmethod
def parse(cls, line):
"""
Parse a bytestring as a full PROXY protocol header line.
@param line: A bytestring that represents a valid HAProxy PROXY
protocol header line.
@type line: bytes
@return: A L{_interfaces.IProxyInfo} containing the parsed data.
@raises InvalidProxyHeader: If the bytestring does not represent a
valid PROXY header.
@raises InvalidNetworkProtocol: When no protocol can be parsed or is
not one of the allowed values.
@raises MissingAddressData: When the protocol is TCP* but the header
does not contain a complete set of addresses and ports.
"""
originalLine = line
proxyStr = None
networkProtocol = None
sourceAddr = None
sourcePort = None
destAddr = None
destPort = None
with convertError(ValueError, InvalidProxyHeader):
proxyStr, line = line.split(b' ', 1)
if proxyStr != cls.PROXYSTR:
raise InvalidProxyHeader()
with convertError(ValueError, InvalidNetworkProtocol):
networkProtocol, line = line.split(b' ', 1)
if networkProtocol not in cls.ALLOWED_NET_PROTOS:
raise InvalidNetworkProtocol()
if networkProtocol == cls.UNKNOWN_PROTO:
return _info.ProxyInfo(originalLine, None, None)
with convertError(ValueError, MissingAddressData):
sourceAddr, line = line.split(b' ', 1)
with convertError(ValueError, MissingAddressData):
destAddr, line = line.split(b' ', 1)
with convertError(ValueError, MissingAddressData):
sourcePort, line = line.split(b' ', 1)
with convertError(ValueError, MissingAddressData):
destPort = line.split(b' ')[0]
if networkProtocol == cls.TCP4_PROTO:
return _info.ProxyInfo(
originalLine,
address.IPv4Address('TCP', sourceAddr, int(sourcePort)),
address.IPv4Address('TCP', destAddr, int(destPort)),
)
return _info.ProxyInfo(
originalLine,
address.IPv6Address('TCP', sourceAddr, int(sourcePort)),
address.IPv6Address('TCP', destAddr, int(destPort)),
)
@@ -0,0 +1,106 @@
# -*- test-case-name: twisted.protocols.haproxy.test.test_wrapper -*-
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Protocol wrapper that provides HAProxy PROXY protocol support.
"""
from twisted.protocols import policies
from twisted.internet import interfaces
from twisted.internet.endpoints import _WrapperServerEndpoint
from ._exceptions import InvalidProxyHeader
from ._v1parser import V1Parser
from ._v2parser import V2Parser
class HAProxyProtocolWrapper(policies.ProtocolWrapper, object):
"""
A Protocol wrapper that provides HAProxy support.
This protocol reads the PROXY stream header, v1 or v2, parses the provided
connection data, and modifies the behavior of getPeer and getHost to return
the data provided by the PROXY header.
"""
def __init__(self, factory, wrappedProtocol):
policies.ProtocolWrapper.__init__(self, factory, wrappedProtocol)
self._proxyInfo = None
self._parser = None
def dataReceived(self, data):
if self._proxyInfo is not None:
return self.wrappedProtocol.dataReceived(data)
if self._parser is None:
if (
len(data) >= 16 and
data[:12] == V2Parser.PREFIX and
ord(data[12:13]) & 0b11110000 == 0x20
):
self._parser = V2Parser()
elif len(data) >= 8 and data[:5] == V1Parser.PROXYSTR:
self._parser = V1Parser()
else:
self.loseConnection()
return None
try:
self._proxyInfo, remaining = self._parser.feed(data)
if remaining:
self.wrappedProtocol.dataReceived(remaining)
except InvalidProxyHeader:
self.loseConnection()
def getPeer(self):
if self._proxyInfo and self._proxyInfo.source:
return self._proxyInfo.source
return self.transport.getPeer()
def getHost(self):
if self._proxyInfo and self._proxyInfo.destination:
return self._proxyInfo.destination
return self.transport.getHost()
class HAProxyWrappingFactory(policies.WrappingFactory):
"""
A Factory wrapper that adds PROXY protocol support to connections.
"""
protocol = HAProxyProtocolWrapper
def logPrefix(self):
"""
Annotate the wrapped factory's log prefix with some text indicating
the PROXY protocol is in use.
@rtype: C{str}
"""
if interfaces.ILoggingContext.providedBy(self.wrappedFactory):
logPrefix = self.wrappedFactory.logPrefix()
else:
logPrefix = self.wrappedFactory.__class__.__name__
return "%s (PROXY)" % (logPrefix,)
def proxyEndpoint(wrappedEndpoint):
"""
Wrap an endpoint with PROXY protocol support, so that the transport's
C{getHost} and C{getPeer} methods reflect the attributes of the proxied
connection rather than the underlying connection.
@param wrappedEndpoint: The underlying listening endpoint.
@type wrappedEndpoint: L{IStreamServerEndpoint}
@return: a new listening endpoint that speaks the PROXY protocol.
@rtype: L{IStreamServerEndpoint}
"""
return _WrapperServerEndpoint(wrappedEndpoint, HAProxyWrappingFactory)
@@ -0,0 +1,7 @@
# -*- test-case-name: twisted.protocols.haproxy.test -*-
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Unit tests for L{twisted.protocols.haproxy}.
"""
@@ -0,0 +1,295 @@
# -*- test-case-name: twisted.test.test_htb -*-
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Hierarchical Token Bucket traffic shaping.
Patterned after U{Martin Devera's Hierarchical Token Bucket traffic
shaper for the Linux kernel<http://luxik.cdi.cz/~devik/qos/htb/>}.
@seealso: U{HTB Linux queuing discipline manual - user guide
<http://luxik.cdi.cz/~devik/qos/htb/manual/userg.htm>}
@seealso: U{Token Bucket Filter in Linux Advanced Routing & Traffic Control
HOWTO<http://lartc.org/howto/lartc.qdisc.classless.html#AEN682>}
"""
# TODO: Investigate whether we should be using os.times()[-1] instead of
# time.time. time.time, it has been pointed out, can go backwards. Is
# the same true of os.times?
from time import time
from zope.interface import implementer, Interface
from twisted.protocols import pcp
class Bucket:
"""
Implementation of a Token bucket.
A bucket can hold a certain number of tokens and it drains over time.
@cvar maxburst: The maximum number of tokens that the bucket can
hold at any given time. If this is L{None}, the bucket has
an infinite size.
@type maxburst: C{int}
@cvar rate: The rate at which the bucket drains, in number
of tokens per second. If the rate is L{None}, the bucket
drains instantaneously.
@type rate: C{int}
"""
maxburst = None
rate = None
_refcount = 0
def __init__(self, parentBucket=None):
"""
Create a L{Bucket} that may have a parent L{Bucket}.
@param parentBucket: If a parent Bucket is specified,
all L{add} and L{drip} operations on this L{Bucket}
will be applied on the parent L{Bucket} as well.
@type parentBucket: L{Bucket}
"""
self.content = 0
self.parentBucket = parentBucket
self.lastDrip = time()
def add(self, amount):
"""
Adds tokens to the L{Bucket} and its C{parentBucket}.
This will add as many of the C{amount} tokens as will fit into both
this L{Bucket} and its C{parentBucket}.
@param amount: The number of tokens to try to add.
@type amount: C{int}
@returns: The number of tokens that actually fit.
@returntype: C{int}
"""
self.drip()
if self.maxburst is None:
allowable = amount
else:
allowable = min(amount, self.maxburst - self.content)
if self.parentBucket is not None:
allowable = self.parentBucket.add(allowable)
self.content += allowable
return allowable
def drip(self):
"""
Let some of the bucket drain.
The L{Bucket} drains at the rate specified by the class
variable C{rate}.
@returns: C{True} if the bucket is empty after this drip.
@returntype: C{bool}
"""
if self.parentBucket is not None:
self.parentBucket.drip()
if self.rate is None:
self.content = 0
else:
now = time()
deltaTime = now - self.lastDrip
deltaTokens = deltaTime * self.rate
self.content = max(0, self.content - deltaTokens)
self.lastDrip = now
return self.content == 0
class IBucketFilter(Interface):
def getBucketFor(*somethings, **some_kw):
"""
Return a L{Bucket} corresponding to the provided parameters.
@returntype: L{Bucket}
"""
@implementer(IBucketFilter)
class HierarchicalBucketFilter:
"""
Filter things into buckets that can be nested.
@cvar bucketFactory: Class of buckets to make.
@type bucketFactory: L{Bucket}
@cvar sweepInterval: Seconds between sweeping out the bucket cache.
@type sweepInterval: C{int}
"""
bucketFactory = Bucket
sweepInterval = None
def __init__(self, parentFilter=None):
self.buckets = {}
self.parentFilter = parentFilter
self.lastSweep = time()
def getBucketFor(self, *a, **kw):
"""
Find or create a L{Bucket} corresponding to the provided parameters.
Any parameters are passed on to L{getBucketKey}, from them it
decides which bucket you get.
@returntype: L{Bucket}
"""
if ((self.sweepInterval is not None)
and ((time() - self.lastSweep) > self.sweepInterval)):
self.sweep()
if self.parentFilter:
parentBucket = self.parentFilter.getBucketFor(self, *a, **kw)
else:
parentBucket = None
key = self.getBucketKey(*a, **kw)
bucket = self.buckets.get(key)
if bucket is None:
bucket = self.bucketFactory(parentBucket)
self.buckets[key] = bucket
return bucket
def getBucketKey(self, *a, **kw):
"""
Construct a key based on the input parameters to choose a L{Bucket}.
The default implementation returns the same key for all
arguments. Override this method to provide L{Bucket} selection.
@returns: Something to be used as a key in the bucket cache.
"""
return None
def sweep(self):
"""
Remove empty buckets.
"""
for key, bucket in self.buckets.items():
bucket_is_empty = bucket.drip()
if (bucket._refcount == 0) and bucket_is_empty:
del self.buckets[key]
self.lastSweep = time()
class FilterByHost(HierarchicalBucketFilter):
"""
A Hierarchical Bucket filter with a L{Bucket} for each host.
"""
sweepInterval = 60 * 20
def getBucketKey(self, transport):
return transport.getPeer()[1]
class FilterByServer(HierarchicalBucketFilter):
"""
A Hierarchical Bucket filter with a L{Bucket} for each service.
"""
sweepInterval = None
def getBucketKey(self, transport):
return transport.getHost()[2]
class ShapedConsumer(pcp.ProducerConsumerProxy):
"""
Wraps a C{Consumer} and shapes the rate at which it receives data.
"""
# Providing a Pull interface means I don't have to try to schedule
# traffic with callLaters.
iAmStreaming = False
def __init__(self, consumer, bucket):
pcp.ProducerConsumerProxy.__init__(self, consumer)
self.bucket = bucket
self.bucket._refcount += 1
def _writeSomeData(self, data):
# In practice, this actually results in obscene amounts of
# overhead, as a result of generating lots and lots of packets
# with twelve-byte payloads. We may need to do a version of
# this with scheduled writes after all.
amount = self.bucket.add(len(data))
return pcp.ProducerConsumerProxy._writeSomeData(self, data[:amount])
def stopProducing(self):
pcp.ProducerConsumerProxy.stopProducing(self)
self.bucket._refcount -= 1
class ShapedTransport(ShapedConsumer):
"""
Wraps a C{Transport} and shapes the rate at which it receives data.
This is a L{ShapedConsumer} with a little bit of magic to provide for
the case where the consumer it wraps is also a C{Transport} and people
will be attempting to access attributes this does not proxy as a
C{Consumer} (e.g. C{loseConnection}).
"""
# Ugh. We only wanted to filter IConsumer, not ITransport.
iAmStreaming = False
def __getattr__(self, name):
# Because people will be doing things like .getPeer and
# .loseConnection on me.
return getattr(self.consumer, name)
class ShapedProtocolFactory:
"""
Dispense C{Protocols} with traffic shaping on their transports.
Usage::
myserver = SomeFactory()
myserver.protocol = ShapedProtocolFactory(myserver.protocol,
bucketFilter)
Where C{SomeServerFactory} is a L{twisted.internet.protocol.Factory}, and
C{bucketFilter} is an instance of L{HierarchicalBucketFilter}.
"""
def __init__(self, protoClass, bucketFilter):
"""
Tell me what to wrap and where to get buckets.
@param protoClass: The class of C{Protocol} this will generate
wrapped instances of.
@type protoClass: L{Protocol<twisted.internet.interfaces.IProtocol>}
class
@param bucketFilter: The filter which will determine how
traffic is shaped.
@type bucketFilter: L{HierarchicalBucketFilter}.
"""
# More precisely, protoClass can be any callable that will return
# instances of something that implements IProtocol.
self.protocol = protoClass
self.bucketFilter = bucketFilter
def __call__(self, *a, **kw):
"""
Make a C{Protocol} instance with a shaped transport.
Any parameters will be passed on to the protocol's initializer.
@returns: A C{Protocol} instance with a L{ShapedTransport}.
"""
proto = self.protocol(*a, **kw)
origMakeConnection = proto.makeConnection
def makeConnection(transport):
bucket = self.bucketFilter.getBucketFor(transport)
shapedTransport = ShapedTransport(transport, bucket)
return origMakeConnection(shapedTransport)
proto.makeConnection = makeConnection
return proto
@@ -0,0 +1,158 @@
# -*- test-case-name: twisted.test.test_postfix -*-
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Postfix mail transport agent related protocols.
"""
import sys
try:
# Python 2
from UserDict import UserDict
except ImportError:
# Python 3
from collections import UserDict
try:
# Python 2
from urllib import quote as _quote, unquote as _unquote
except ImportError:
# Python 3
from urllib.parse import quote as _quote, unquote as _unquote
from twisted.protocols import basic
from twisted.protocols import policies
from twisted.internet import protocol, defer
from twisted.python import log
from twisted.python.compat import unicode
# urllib's quote functions just happen to match
# the postfix semantics.
def quote(s):
quoted = _quote(s)
if isinstance(quoted, unicode):
quoted = quoted.encode("ascii")
return quoted
def unquote(s):
if isinstance(s, bytes):
s = s.decode("ascii")
quoted = _unquote(s)
return quoted.encode("ascii")
class PostfixTCPMapServer(basic.LineReceiver, policies.TimeoutMixin):
"""
Postfix mail transport agent TCP map protocol implementation.
Receive requests for data matching given key via lineReceived,
asks it's factory for the data with self.factory.get(key), and
returns the data to the requester. None means no entry found.
You can use postfix's postmap to test the map service::
/usr/sbin/postmap -q KEY tcp:localhost:4242
"""
timeout = 600
delimiter = b'\n'
def connectionMade(self):
self.setTimeout(self.timeout)
def sendCode(self, code, message=b''):
"""
Send an SMTP-like code with a message.
"""
self.sendLine(str(code).encode("ascii") + b' ' + message)
def lineReceived(self, line):
self.resetTimeout()
try:
request, params = line.split(None, 1)
except ValueError:
request = line
params = None
try:
f = getattr(self, u'do_' + request.decode("ascii"))
except AttributeError:
self.sendCode(400, b'unknown command')
else:
try:
f(params)
except:
excInfo = str(sys.exc_info()[1]).encode("ascii")
self.sendCode(400, b'Command ' + request + b' failed: ' +
excInfo)
def do_get(self, key):
if key is None:
self.sendCode(400, b"Command 'get' takes 1 parameters.")
else:
d = defer.maybeDeferred(self.factory.get, key)
d.addCallbacks(self._cbGot, self._cbNot)
d.addErrback(log.err)
def _cbNot(self, fail):
msg = fail.getErrorMessage().encode("ascii")
self.sendCode(400, msg)
def _cbGot(self, value):
if value is None:
self.sendCode(500)
else:
self.sendCode(200, quote(value))
def do_put(self, keyAndValue):
if keyAndValue is None:
self.sendCode(400, b"Command 'put' takes 2 parameters.")
else:
try:
key, value = keyAndValue.split(None, 1)
except ValueError:
self.sendCode(400, b"Command 'put' takes 2 parameters.")
else:
self.sendCode(500, b'put is not implemented yet.')
class PostfixTCPMapDictServerFactory(UserDict, protocol.ServerFactory):
"""
An in-memory dictionary factory for PostfixTCPMapServer.
"""
protocol = PostfixTCPMapServer
class PostfixTCPMapDeferringDictServerFactory(protocol.ServerFactory):
"""
An in-memory dictionary factory for PostfixTCPMapServer.
"""
protocol = PostfixTCPMapServer
def __init__(self, data=None):
self.data = {}
if data is not None:
self.data.update(data)
def get(self, key):
return defer.succeed(self.data.get(key))
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,255 @@
# -*- test-case-name: twisted.test.test_socks -*-
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Implementation of the SOCKSv4 protocol.
"""
# python imports
import struct
import string
import socket
import time
# twisted imports
from twisted.internet import reactor, protocol, defer
from twisted.python import log
class SOCKSv4Outgoing(protocol.Protocol):
def __init__(self, socks):
self.socks=socks
def connectionMade(self):
peer = self.transport.getPeer()
self.socks.makeReply(90, 0, port=peer.port, ip=peer.host)
self.socks.otherConn=self
def connectionLost(self, reason):
self.socks.transport.loseConnection()
def dataReceived(self, data):
self.socks.write(data)
def write(self,data):
self.socks.log(self,data)
self.transport.write(data)
class SOCKSv4Incoming(protocol.Protocol):
def __init__(self,socks):
self.socks=socks
self.socks.otherConn=self
def connectionLost(self, reason):
self.socks.transport.loseConnection()
def dataReceived(self,data):
self.socks.write(data)
def write(self, data):
self.socks.log(self,data)
self.transport.write(data)
class SOCKSv4(protocol.Protocol):
"""
An implementation of the SOCKSv4 protocol.
@type logging: L{str} or L{None}
@ivar logging: If not L{None}, the name of the logfile to which connection
information will be written.
@type reactor: object providing L{twisted.internet.interfaces.IReactorTCP}
@ivar reactor: The reactor used to create connections.
@type buf: L{str}
@ivar buf: Part of a SOCKSv4 connection request.
@type otherConn: C{SOCKSv4Incoming}, C{SOCKSv4Outgoing} or L{None}
@ivar otherConn: Until the connection has been established, C{otherConn} is
L{None}. After that, it is the proxy-to-destination protocol instance
along which the client's connection is being forwarded.
"""
def __init__(self, logging=None, reactor=reactor):
self.logging = logging
self.reactor = reactor
def connectionMade(self):
self.buf = b""
self.otherConn = None
def dataReceived(self, data):
"""
Called whenever data is received.
@type data: L{bytes}
@param data: Part or all of a SOCKSv4 packet.
"""
if self.otherConn:
self.otherConn.write(data)
return
self.buf = self.buf + data
completeBuffer = self.buf
if b"\000" in self.buf[8:]:
head, self.buf = self.buf[:8], self.buf[8:]
version, code, port = struct.unpack("!BBH", head[:4])
user, self.buf = self.buf.split(b"\000", 1)
if head[4:7] == b"\000\000\000" and head[7:8] != b"\000":
# An IP address of the form 0.0.0.X, where X is non-zero,
# signifies that this is a SOCKSv4a packet.
# If the complete packet hasn't been received, restore the
# buffer and wait for it.
if b"\000" not in self.buf:
self.buf = completeBuffer
return
server, self.buf = self.buf.split(b"\000", 1)
d = self.reactor.resolve(server)
d.addCallback(self._dataReceived2, user,
version, code, port)
d.addErrback(lambda result, self = self: self.makeReply(91))
return
else:
server = socket.inet_ntoa(head[4:8])
self._dataReceived2(server, user, version, code, port)
def _dataReceived2(self, server, user, version, code, port):
"""
The second half of the SOCKS connection setup. For a SOCKSv4 packet this
is after the server address has been extracted from the header. For a
SOCKSv4a packet this is after the host name has been resolved.
@type server: L{str}
@param server: The IP address of the destination, represented as a
dotted quad.
@type user: L{str}
@param user: The username associated with the connection.
@type version: L{int}
@param version: The SOCKS protocol version number.
@type code: L{int}
@param code: The comand code. 1 means establish a TCP/IP stream
connection, and 2 means establish a TCP/IP port binding.
@type port: L{int}
@param port: The port number associated with the connection.
"""
assert version == 4, "Bad version code: %s" % version
if not self.authorize(code, server, port, user):
self.makeReply(91)
return
if code == 1: # CONNECT
d = self.connectClass(server, port, SOCKSv4Outgoing, self)
d.addErrback(lambda result, self = self: self.makeReply(91))
elif code == 2: # BIND
d = self.listenClass(0, SOCKSv4IncomingFactory, self, server)
d.addCallback(lambda x,
self = self: self.makeReply(90, 0, x[1], x[0]))
else:
raise RuntimeError("Bad Connect Code: %s" % (code,))
assert self.buf == b"", "hmm, still stuff in buffer... %s" % repr(
self.buf)
def connectionLost(self, reason):
if self.otherConn:
self.otherConn.transport.loseConnection()
def authorize(self,code,server,port,user):
log.msg("code %s connection to %s:%s (user %s) authorized" % (code,server,port,user))
return 1
def connectClass(self, host, port, klass, *args):
return protocol.ClientCreator(reactor, klass, *args).connectTCP(host,port)
def listenClass(self, port, klass, *args):
serv = reactor.listenTCP(port, klass(*args))
return defer.succeed(serv.getHost()[1:])
def makeReply(self,reply,version=0,port=0,ip="0.0.0.0"):
self.transport.write(struct.pack("!BBH",version,reply,port)+socket.inet_aton(ip))
if reply!=90: self.transport.loseConnection()
def write(self,data):
self.log(self,data)
self.transport.write(data)
def log(self,proto,data):
if not self.logging: return
peer = self.transport.getPeer()
their_peer = self.otherConn.transport.getPeer()
f=open(self.logging,"a")
f.write("%s\t%s:%d %s %s:%d\n"%(time.ctime(),
peer.host,peer.port,
((proto==self and '<') or '>'),
their_peer.host,their_peer.port))
while data:
p,data=data[:16],data[16:]
f.write(string.join(map(lambda x:'%02X'%ord(x),p),' ')+' ')
f.write((16-len(p))*3*' ')
for c in p:
if len(repr(c))>3: f.write('.')
else: f.write(c)
f.write('\n')
f.write('\n')
f.close()
class SOCKSv4Factory(protocol.Factory):
"""
A factory for a SOCKSv4 proxy.
Constructor accepts one argument, a log file name.
"""
def __init__(self, log):
self.logging = log
def buildProtocol(self, addr):
return SOCKSv4(self.logging, reactor)
class SOCKSv4IncomingFactory(protocol.Factory):
"""
A utility class for building protocols for incoming connections.
"""
def __init__(self, socks, ip):
self.socks = socks
self.ip = ip
def buildProtocol(self, addr):
if addr[0] == self.ip:
self.ip = ""
self.socks.makeReply(90, 0)
return SOCKSv4Incoming(self.socks)
elif self.ip == "":
return None
else:
self.socks.makeReply(91, 0)
self.ip = ""
return None
@@ -0,0 +1,124 @@
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""Implement standard (and unused) TCP protocols.
These protocols are either provided by inetd, or are not provided at all.
"""
from __future__ import absolute_import, division
import time
import struct
from zope.interface import implementer
from twisted.internet import protocol, interfaces
class Echo(protocol.Protocol):
"""
As soon as any data is received, write it back (RFC 862).
"""
def dataReceived(self, data):
self.transport.write(data)
class Discard(protocol.Protocol):
"""
Discard any received data (RFC 863).
"""
def dataReceived(self, data):
# I'm ignoring you, nyah-nyah
pass
@implementer(interfaces.IProducer)
class Chargen(protocol.Protocol):
"""
Generate repeating noise (RFC 864).
"""
noise = b'@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ !"#$%&?'
def connectionMade(self):
self.transport.registerProducer(self, 0)
def resumeProducing(self):
self.transport.write(self.noise)
def pauseProducing(self):
pass
def stopProducing(self):
pass
class QOTD(protocol.Protocol):
"""
Return a quote of the day (RFC 865).
"""
def connectionMade(self):
self.transport.write(self.getQuote())
self.transport.loseConnection()
def getQuote(self):
"""
Return a quote. May be overrriden in subclasses.
"""
return b"An apple a day keeps the doctor away.\r\n"
class Who(protocol.Protocol):
"""
Return list of active users (RFC 866)
"""
def connectionMade(self):
self.transport.write(self.getUsers())
self.transport.loseConnection()
def getUsers(self):
"""
Return active users. Override in subclasses.
"""
return b"root\r\n"
class Daytime(protocol.Protocol):
"""
Send back the daytime in ASCII form (RFC 867).
"""
def connectionMade(self):
self.transport.write(time.asctime(time.gmtime(time.time())) + b'\r\n')
self.transport.loseConnection()
class Time(protocol.Protocol):
"""
Send back the time in machine readable form (RFC 868).
"""
def connectionMade(self):
# is this correct only for 32-bit machines?
result = struct.pack("!i", int(time.time()))
self.transport.write(result)
self.transport.loseConnection()
__all__ = ["Echo", "Discard", "Chargen", "QOTD", "Who", "Daytime", "Time"]