]> code.delx.au - pymsnt/blob - src/socks5.py
Recursively ignore *.pyc
[pymsnt] / src / socks5.py
1 ##============================================================================
2 ##
3 ## License:
4 ##
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.
9 ##
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.
14 ##
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
18 ## USA
19 ##
20 ## Copyright (C) 2002-2003 Dave Smith (dizzyd@jabber.org)
21 ## Copyright (C) 2005-2006 James Bunton (james@delx.cjb.net)
22 ##
23 ##============================================================================
24
25 from twisted.internet import protocol, reactor
26 import struct
27
28 STATE_INITIAL = 0
29 STATE_AUTH = 1
30 STATE_REQUEST = 2
31 STATE_READY = 3
32 STATE_AUTH_USERPASS = 4
33 STATE_LAST = 5
34
35 STATE_CONNECT_PENDING = STATE_LAST + 1
36
37 SOCKS5_VER = 0x05
38
39 ADDR_IPV4 = 0x01
40 ADDR_DOMAINNAME = 0x03
41 ADDR_IPV6 = 0x04
42
43 CMD_CONNECT = 0x01
44 CMD_BIND = 0x02
45 CMD_UDPASSOC = 0x03
46
47 AUTHMECH_ANON = 0x00
48 AUTHMECH_USERPASS = 0x02
49 AUTHMECH_INVALID = 0xFF
50
51 REPLY_SUCCESS = 0x00
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
60
61 class SOCKSv5Outgoing(protocol.Protocol):
62 def __init__(self, peersock):
63 self.peersock = peersock
64 self.peersock.peersock = self
65
66 def connectionMade(self):
67 _invalid_, hostname, port = self.transport.getPeer()
68 self.peersock.connectCompleted(hostname, port)
69
70 def connectionLost(self, reason):
71 self.peersock.transport.loseConnection()
72 self.peersock.peersock = None
73 self.peersock = None
74
75 def dataReceived(self, buf):
76 self.peersock.transport.write(buf)
77
78 class SOCKSv5(protocol.Protocol):
79 def __init__(self):
80 self.state = STATE_INITIAL
81 self.buf = ""
82 self.supportedAuthMechs = [ AUTHMECH_USERPASS ]
83 self.supportedAddrs = [ ADDR_IPV4, ADDR_DOMAINNAME ]
84 self.enabledCommands = [ CMD_CONNECT, CMD_BIND ]
85 self.peersock = None
86 self.addressType = 0
87 self.requestType = 0
88
89 def _parseNegotiation(self):
90 try:
91 # Parse out data
92 ver, nmethod = struct.unpack('!BB', self.buf[:2])
93 methods = struct.unpack('%dB' % nmethod, self.buf[2:nmethod+2])
94
95 # Ensure version is correct
96 if ver != 5:
97 self.transport.write(struct.pack('!BB', SOCKS5_VER, AUTHMECH_INVALID))
98 self.transport.loseConnection()
99 return
100
101 # Trim off front of the buffer
102 self.buf = self.buf[nmethod+2:]
103
104 # Check for supported auth mechs
105 for m in self.supportedAuthMechs:
106 if m in methods:
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))
114 return
115
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()
119 except struct.error:
120 pass
121
122 def _parseUserPass(self):
123 try:
124 # Parse out data
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):
133 # Signal success
134 self.state = STATE_REQUEST
135 self.transport.write(struct.pack('!BB', SOCKS5_VER, 0x00))
136 else:
137 # Signal failure
138 self.transport.write(struct.pack('!BB', SOCKS5_VER, 0x01))
139 self.transport.loseConnection()
140 except struct.error:
141 pass
142
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()
148
149 def _parseRequest(self):
150 try:
151 # Parse out data and trim buffer accordingly
152 ver, cmd, rsvd, self.addressType = struct.unpack('!BBBB', self.buf[:4])
153
154 # Ensure we actually support the requested address type
155 if self.addressType not in self.supportedAddrs:
156 self.sendErrorReply(REPLY_ADDR_NOT_SUPPORTED)
157 return
158
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):]
167 else:
168 # Any other address types are not supported
169 self.sendErrorReply(REPLY_ADDR_NOT_SUPPORTED)
170 return
171
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)
176 return
177
178 # Process the command
179 if cmd == CMD_CONNECT:
180 self.connectRequested(addr, port)
181 elif cmd == CMD_BIND:
182 self.bindRequested(addr, port)
183 else:
184 # Any other command is not supported
185 self.sendErrorReply(REPLY_CMD_NOT_SUPPORTED)
186
187 except struct.error, why:
188 return None
189
190
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)
195
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()
205
206 def bindRequested(self, addr, port):
207 pass
208
209 def authenticateUserPass(self, user, passwd):
210 print "User/pass: ", user, passwd
211 return True
212
213 def dataReceived(self, buf):
214 if self.state == STATE_READY:
215 self.peersock.transport.write(buf)
216 return
217
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:
224 self._parseRequest()
225
226
227
228 factory = protocol.Factory()
229 factory.protocol = SOCKSv5
230
231 if __name__ == "__main__":
232 reactor.listenTCP(8888, factory)
233 reactor.run()