1 ##============================================================================
5 ## This library is free software; you can redistribute it and/or
6 ## modify it under the terms of the GNU General Public
7 ## License as published by the Free Software Foundation; either
8 ## version 2 of the License, or (at your option) any later version.
10 ## This library is distributed in the hope that it will be useful,
11 ## but WITHOUT ANY WARRANTY; without even the implied warranty of
12 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 ## General Public License for more details.
15 ## You should have received a copy of the GNU General Public
16 ## License along with this library; if not, write to the Free Software
17 ## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 021-1307
20 ## Copyright (C) 2002-2003 Dave Smith (dizzyd@jabber.org)
21 ## Copyright (C) 2005-2006 James Bunton (james@delx.cjb.net)
23 ##============================================================================
25 from twisted
.internet
import protocol
, reactor
32 STATE_AUTH_USERPASS
= 4
35 STATE_CONNECT_PENDING
= STATE_LAST
+ 1
40 ADDR_DOMAINNAME
= 0x03
48 AUTHMECH_USERPASS
= 0x02
49 AUTHMECH_INVALID
= 0xFF
52 REPLY_GENERAL_FAILUR
= 0x01
53 REPLY_CONN_NOT_ALLOWED
= 0x02
54 REPLY_NETWORK_UNREACHABLE
= 0x03
55 REPLY_HOST_UNREACHABLE
= 0x04
56 REPLY_CONN_REFUSED
= 0x05
57 REPLY_TTL_EXPIRED
= 0x06
58 REPLY_CMD_NOT_SUPPORTED
= 0x07
59 REPLY_ADDR_NOT_SUPPORTED
= 0x08
61 class SOCKSv5Outgoing(protocol
.Protocol
):
62 def __init__(self
, peersock
):
63 self
.peersock
= peersock
64 self
.peersock
.peersock
= self
66 def connectionMade(self
):
67 _invalid_
, hostname
, port
= self
.transport
.getPeer()
68 self
.peersock
.connectCompleted(hostname
, port
)
70 def connectionLost(self
, reason
):
71 self
.peersock
.transport
.loseConnection()
72 self
.peersock
.peersock
= None
75 def dataReceived(self
, buf
):
76 self
.peersock
.transport
.write(buf
)
78 class SOCKSv5(protocol
.Protocol
):
80 self
.state
= STATE_INITIAL
82 self
.supportedAuthMechs
= [ AUTHMECH_USERPASS
]
83 self
.supportedAddrs
= [ ADDR_IPV4
, ADDR_DOMAINNAME
]
84 self
.enabledCommands
= [ CMD_CONNECT
, CMD_BIND
]
89 def _parseNegotiation(self
):
92 ver
, nmethod
= struct
.unpack('!BB', self
.buf
[:2])
93 methods
= struct
.unpack('%dB' % nmethod
, self
.buf
[2:nmethod
+2])
95 # Ensure version is correct
97 self
.transport
.write(struct
.pack('!BB', SOCKS5_VER
, AUTHMECH_INVALID
))
98 self
.transport
.loseConnection()
101 # Trim off front of the buffer
102 self
.buf
= self
.buf
[nmethod
+2:]
104 # Check for supported auth mechs
105 for m
in self
.supportedAuthMechs
:
107 # Update internal state, according to selected method
108 if m
== AUTHMECH_ANON
:
109 self
.state
= STATE_REQUEST
110 elif m
== AUTHMECH_USERPASS
:
111 self
.state
= STATE_AUTH_USERPASS
112 # Complete negotiation w/ this method
113 self
.transport
.write(struct
.pack('!BB', SOCKS5_VER
, m
))
116 # No supported mechs found, notify client and close the connection
117 self
.transport
.write(struct
.pack('!BB', SOCKS5_VER
, AUTHMECH_INVALID
))
118 self
.transport
.loseConnection()
122 def _parseUserPass(self
):
125 ver
, ulen
= struct
.unpack('BB', self
.buf
[:2])
126 uname
, = struct
.unpack('%ds' % ulen
, self
.buf
[2:ulen
+ 2])
127 plen
, = struct
.unpack('B', self
.buf
[ulen
+ 2])
128 password
, = struct
.unpack('%ds' % plen
, self
.buf
[ulen
+ 3:ulen
+ 3 + plen
])
129 # Trim off fron of the buffer
130 self
.buf
= self
.buf
[3 + ulen
+ plen
:]
131 # Fire event to authenticate user
132 if self
.authenticateUserPass(uname
, password
):
134 self
.state
= STATE_REQUEST
135 self
.transport
.write(struct
.pack('!BB', SOCKS5_VER
, 0x00))
138 self
.transport
.write(struct
.pack('!BB', SOCKS5_VER
, 0x01))
139 self
.transport
.loseConnection()
143 def sendErrorReply(self
, errorcode
):
144 # Any other address types are not supported
145 result
= struct
.pack('!BBBBIH', SOCKS5_VER
, errorcode
, 0, 1, 0, 0)
146 self
.transport
.write(result
)
147 self
.transport
.loseConnection()
149 def _parseRequest(self
):
151 # Parse out data and trim buffer accordingly
152 ver
, cmd
, rsvd
, self
.addressType
= struct
.unpack('!BBBB', self
.buf
[:4])
154 # Ensure we actually support the requested address type
155 if self
.addressType
not in self
.supportedAddrs
:
156 self
.sendErrorReply(REPLY_ADDR_NOT_SUPPORTED
)
159 # Deal with addresses
160 if self
.addressType
== ADDR_IPV4
:
161 addr
, port
= struct
.unpack('!IH', self
.buf
[4:10])
162 self
.buf
= self
.buf
[10:]
163 elif self
.addressType
== ADDR_DOMAINNAME
:
164 nlen
= ord(self
.buf
[4])
165 addr
, port
= struct
.unpack('!%dsH' % nlen
, self
.buf
[5:])
166 self
.buf
= self
.buf
[7 + len(addr
):]
168 # Any other address types are not supported
169 self
.sendErrorReply(REPLY_ADDR_NOT_SUPPORTED
)
172 # Ensure command is supported
173 if cmd
not in self
.enabledCommands
:
174 # Send a not supported error
175 self
.sendErrorReply(REPLY_CMD_NOT_SUPPORTED
)
178 # Process the command
179 if cmd
== CMD_CONNECT
:
180 self
.connectRequested(addr
, port
)
181 elif cmd
== CMD_BIND
:
182 self
.bindRequested(addr
, port
)
184 # Any other command is not supported
185 self
.sendErrorReply(REPLY_CMD_NOT_SUPPORTED
)
187 except struct
.error
, why
:
191 def connectRequested(self
, addr
, port
):
192 self
.transport
.stopReading()
193 self
.state
= STATE_CONNECT_PENDING
194 protocol
.ClientCreator(reactor
, SOCKSv5Outgoing
, self
).connectTCP(addr
, port
)
196 def connectCompleted(self
, remotehost
, remoteport
):
197 if self
.addressType
== ADDR_IPV4
:
198 result
= struct
.pack('!BBBBIH', SOCKS5_VER
, REPLY_SUCCESS
, 0, 1, remotehost
, remoteport
)
199 elif self
.addressType
== ADDR_DOMAINNAME
:
200 result
= struct
.pack('!BBBBB%dsH' % len(remotehost
), SOCKS5_VER
, REPLY_SUCCESS
, 0,
201 ADDR_DOMAINNAME
, len(remotehost
), remotehost
, remoteport
)
202 self
.transport
.write(result
)
203 self
.state
= STATE_READY
204 self
.transport
.startReading()
206 def bindRequested(self
, addr
, port
):
209 def authenticateUserPass(self
, user
, passwd
):
210 print "User/pass: ", user
, passwd
213 def dataReceived(self
, buf
):
214 if self
.state
== STATE_READY
:
215 self
.peersock
.transport
.write(buf
)
218 self
.buf
= self
.buf
+ buf
219 if self
.state
== STATE_INITIAL
:
220 self
._parseNegotiation
()
221 if self
.state
== STATE_AUTH_USERPASS
:
222 self
._parseUserPass
()
223 if self
.state
== STATE_REQUEST
:
228 factory
= protocol
.Factory()
229 factory
.protocol
= SOCKSv5
231 if __name__
== "__main__":
232 reactor
.listenTCP(8888, factory
)