WebProxy46.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335
  1. #!/usr/bin/python
  2. # Copyright (c) 2013 by Peter_Siegrist(SystemLoesungen) (PSS @ ZweierNet.ch)
  3. # www.IPv6Tech.ch
  4. #
  5. # All Rights reserved.
  6. # This program is free software; you can redistribute it and/or
  7. # modify it under the terms of the GNU General Public License as
  8. # published by the Free Software Foundation.
  9. #
  10. # This program 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
  13. # GNU General Public License for more details.
  14. #
  15. #
  16. # This is a simple IPv4-IPv6 non-caching HTTP/HTTPS Proxy/Gateway.
  17. # The Server can make both IPv6, primarily, or IPv4 requestst on behalf of the pure IPv4 clients.
  18. #
  19. # WebProxy46 does not filter any messages apart from chanching HTTP/1.1 to HTTP/1.0
  20. # because we do not want to filter out subrequests in persistent connections.
  21. #
  22. # All standard methodes are supported: GET, HEAD, POST, PUT, OPTIONS, TRACE and DELETE.
  23. # The CONNECT method is used for SSL connections. Only port 443 is allowed with this method (HTTP CONNECT Vulnerability)
  24. #
  25. # For using this proxy configure your browser to use proxy service.
  26. # The servers standard binding is port 9090 on address 0.0.0.0
  27. #
  28. # Accepted schemes are HTTP HTTPS and FTP.
  29. # Hostnames in URL's may be given as FQDN, IPv4 dotted address or IPv6 address in square brackets form [xx:xx:xx::x]
  30. #
  31. import socket
  32. import getopt
  33. import sys
  34. from select import select
  35. import logging
  36. import thread
  37. import struct
  38. import string
  39. #-- Vars
  40. version = 'WebProxy46/v0.7'
  41. buflen = 8192
  42. listen_host = '0.0.0.0'
  43. listen_port = 9090
  44. debug = False
  45. lfnr = 0
  46. #-- Threaded Main Object
  47. class ProxyHandler:
  48. def __init__(self, local_conn, local_address):
  49. global lfnr
  50. lfnr += 1
  51. self.lfnr = lfnr
  52. log(self.lfnr,'NEW Connect ...')
  53. self.local_client = local_conn
  54. self.local_client_address = local_address
  55. self.local_client_buffer = ''
  56. self.timeout = 60
  57. self.handle()
  58. self.local_client.close()
  59. def _set_target_sockopts_ssl(self):
  60. self.target.setsockopt(socket.SOL_SOCKET,socket.MSG_DONTWAIT,1)
  61. self.target.setblocking(1)
  62. def handle(self):
  63. try:
  64. self.local_client.setblocking(1)
  65. self.local_client.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER, struct.pack('ii', 1, 20))
  66. #self.local_client.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 0)
  67. clt_count = 0
  68. while 1:
  69. clt_count += 1
  70. (client_data, _, client_error) = select([self.local_client], [], [], 1)
  71. if client_data:
  72. data = self.local_client.recv(buflen)
  73. if len(data) == 0:
  74. break
  75. self.local_client_buffer += data
  76. log2(self.lfnr,"data:%s"%data)
  77. if client_error:
  78. err = select.error
  79. log(self.lfnr,"%s - Client_Error select: %s on %s " % (self.af, err, self.local_client_address))
  80. if clt_count == 1:
  81. break
  82. if self.local_client_buffer:
  83. end_head = self.local_client_buffer.find('\n')
  84. log(self.lfnr,'%s'%self.local_client_buffer[:end_head])
  85. self.method, self.path, self.protocol = (self.local_client_buffer[:end_head+1]).split()
  86. self.local_client_buffer = self.local_client_buffer[end_head+1:]
  87. # we can't handle 1.1 persistent connections in proxys, thanks mozilla :(
  88. self.protocol = string.replace(self.protocol, 'HTTP/1.1', 'HTTP/1.0')
  89. if self.method=='CONNECT':
  90. self.method_ssl()
  91. elif self.method in ('OPTIONS', 'GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'TRACE'):
  92. self.method_get()
  93. else:
  94. self.method_invalid()
  95. try:
  96. self.target.close()
  97. except:
  98. pass
  99. except Exception as uerr:
  100. log(self.lfnr,'__Unexpected Error__: %s'%uerr)
  101. def method_invalid(self):
  102. log(self.lfnr,'Invalid HTTP Method: %s'%self.method)
  103. self.local_client.send('HTTP/1.1 501 Not Implemented\r\n'+'Unknown method: %s\r\n\r\n'%self.method)
  104. self.local_client_buffer = ''
  105. self.local_client.close()
  106. def method_ssl(self):
  107. if not self.connect_target(self.path):
  108. self.local_client.send('HTTP/1.1 500\r\n\r\n')
  109. self.local_client.send('HTTP/1.1 500 %s\r\nProxy: %s\r\n\r\n'%(self.conn_error,version))
  110. return False
  111. #self.local_client.send('HTTP/1.1 200 OK\r\n\r\n')
  112. self.local_client.send('HTTP/1.1 200 Connection established\r\n'+'Proxy: %s\r\n\r\n'%version)
  113. self.local_client_buffer = ''
  114. self.handle_select()
  115. def method_get(self):
  116. j = self.path.find('://')
  117. self.scheme = self.path[:j]
  118. self.path = self.path[j+3:]
  119. i = self.path.find('/')
  120. host = self.path[:i]
  121. path = self.path[i:]
  122. if not self.connect_target(host):
  123. self.local_client.send('HTTP/1.1 500\r\n\r\n')
  124. self.local_client.send('HTTP/1.1 500 %s\r\nProxy: %s\r\n\r\n'%(self.conn_error,version))
  125. return False
  126. s1 = self.target.send('%s %s %s\r\n'%(self.method, path, self.protocol)+self.local_client_buffer)
  127. log2(self.lfnr,'SEND_RAW(to %s):%s %s %s\n'%(self.target_addr[0:2],self.method, path, self.protocol)+self.local_client_buffer)
  128. log(self.lfnr,'SEND_REQUEST(to %s): %s %s %s\n'%(self.target_addr[0:2], self.method, path, self.protocol))
  129. self.local_client_buffer = ''
  130. self.handle_select()
  131. def connect_target(self, host):
  132. if host.find('[') != -1:
  133. j = host.find(']:')
  134. if j!=-1:
  135. port = int(host[j+2:])
  136. host = host[1:j]
  137. else:
  138. host = host[1:-1]
  139. if self.scheme.lower() == 'ftp':
  140. port = 21
  141. else:
  142. port = 80
  143. else:
  144. i = host.find(':')
  145. if i!=-1:
  146. port = int(host[i+1:])
  147. host = host[:i]
  148. else:
  149. if self.scheme.lower() == 'ftp':
  150. port = 21
  151. else:
  152. port = 80
  153. if self.method=='CONNECT':
  154. if port != 443:
  155. self.conn_error = 'Connection refused. Port %d not allowed in CONNECT method'%port
  156. return False
  157. try:
  158. addrinfo = socket.getaddrinfo(host, port)
  159. except socket.error as msg:
  160. log(self.lfnr,"Error resolving %s:%d: %s" % (host, port, msg))
  161. self.conn_error = 'Error resolving %s:%d: %s'%(host, port,msg[1])
  162. return False
  163. for (afm, _, _, _, address) in addrinfo:
  164. if afm == socket.AF_INET6:
  165. self.target = socket.socket(socket.AF_INET6)
  166. self.target_addr = address
  167. self.af = 'IPv6'
  168. break
  169. elif afm == socket.AF_INET:
  170. self.target = socket.socket(socket.AF_INET)
  171. self.target_addr = address
  172. self.af = 'IPv4'
  173. else:
  174. log(self.lfnr,"EEERRRRRRRRRRRRRRRRRRRR\n")
  175. self.target.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 0)
  176. self.target.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER, struct.pack('ii', 1, 10))
  177. try:
  178. self.target.connect(self.target_addr)
  179. except socket.error as msg:
  180. log(self.lfnr,"%s - Error connecting %s:%d (Addr: %s): %s" % (self.af, host, port, self.target_addr, msg))
  181. self.conn_error = 'Connection failed: %s'%msg[1]
  182. return False
  183. else:
  184. log(self.lfnr,"%s - connect to %s:%d (%s)" % (self.af, host, port, self.target_addr[0:2]))
  185. return True
  186. def handle_select(self):
  187. self.soc_pair = [self.local_client, self.target]
  188. count = 0
  189. rf = 'undefined'
  190. while 1:
  191. count += 1
  192. (recv_data, _, error) = select(self.soc_pair, [], self.soc_pair, 2)
  193. if recv_data:
  194. cnt = 0
  195. for i in recv_data:
  196. cnt +=1
  197. if i is self.local_client:
  198. rf = self.local_client_address
  199. else:
  200. rf = self.target_addr
  201. try:
  202. data = i.recv(buflen)
  203. except socket.error as err:
  204. log(self.lfnr,'recv error (%s):%s' % (rf, err))
  205. if err[0] == 104: # connection reset by peer
  206. continue
  207. log2(self.lfnr,'%d %d select handle from: %s LEN: %d' % (count,cnt,rf,len(data)))
  208. if len(data) == 0:
  209. continue
  210. if i is self.local_client:
  211. #self.view_sockopts(self.local_client)
  212. if 'HTTP/1.' in data:
  213. bs = data.find('\n')
  214. log(self.lfnr,'%s - Request from %s: %s'%(self.af,self.local_client_address[0:2],data[:bs-1]))
  215. rf = self.local_client_address
  216. log2(self.lfnr,'DATA(to %s from %s):%s'% (self.target_addr,rf,data[:260]))
  217. out = self.target
  218. elif i is self.target:
  219. if 'HTTP/1.' in data[0:8]:
  220. bs = data[0:256].find('\n')
  221. log(self.lfnr,'%s - Answer from %s: %s'%(self.af,self.target_addr[0:2],data[:bs-1]))
  222. rf = self.target_addr
  223. log2(self.lfnr,'DATA(to %s from %s):%s'% (self.local_client_address,rf,data[:260]))
  224. out = self.local_client
  225. else:
  226. log(self.lfnr,"EEEEEERRRRRROOOORRRRRRRRR: %s"% i)
  227. if data:
  228. try:
  229. sent_byte = out.send(data)
  230. except socket.error as err:
  231. log(self.lfnr,'send error (%s):%s' % (rf, err))
  232. else:
  233. log2(self.lfnr,'Sent %d Bytes Data to %s' % (sent_byte,out.getpeername()[0:2]))
  234. count = 0
  235. if error:
  236. err = select.error
  237. log(self.lfnr,"%s - Error select: %s on %s:%s " % (self.af, err, self.local_client_address, self.target_addr))
  238. if count == 10:
  239. break
  240. def view_sockopts(self,s):
  241. socket_options = [ (getattr(socket, opt), opt) for opt in dir(socket) if opt.startswith('SO_') ]
  242. socket_options.sort()
  243. for num, opt in socket_options:
  244. try:
  245. val = s.getsockopt(socket.SOL_SOCKET, num)
  246. log2(self.lfnr,'%s(%d) defaults to %d' % (opt, num, val))
  247. except (socket.error) as e:
  248. log2(self.lfnr,'%s(%d) can\'t help you out there: %s' % (opt, num, str(e)))
  249. # -- Main functions
  250. def log(lfnr,msg):
  251. logging.warn('[%s] %s'%(lfnr,msg))
  252. def log2(lfnr,msg):
  253. if debug:
  254. logging.warn('[%s] %s'%(lfnr,msg))
  255. def usage():
  256. print(sys.argv[0]),
  257. print(" [-p port] [-s host] [-l logfile] [-d] [-h]")
  258. print('')
  259. print(" -p - Port to listen on")
  260. print(" -s - Host to listen on. If not specified, 0.0.0.0 is used")
  261. print(" -l - Path to logfile. If not specified, STDOUT is used")
  262. print(" -d - debug messages")
  263. print('')
  264. def main(host, port):
  265. logfile = None
  266. try:
  267. opts, args = getopt.getopt(sys.argv[1:], "l:hdp:s:", [])
  268. except getopt.GetoptError:
  269. usage()
  270. return 1
  271. for opt, value in opts:
  272. if opt == "-p": port = int(value)
  273. if opt == "-s": host = value
  274. if opt == "-l": logfile = value
  275. if opt == "-d":
  276. global debug
  277. debug = True
  278. if opt == "-h":
  279. usage()
  280. return 0
  281. if not socket.has_ipv6:
  282. print("This machine is not IPv6 capable. Exiting.\n")
  283. exit(1)
  284. logging.basicConfig(format='%(asctime)s %(thread)s: %(message)s',
  285. filename=logfile)
  286. server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  287. server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  288. server_socket.bind((host, port))
  289. log(lfnr,"Start Server")
  290. log(lfnr,"Listen on %s:%d"%(host, port))
  291. server_socket.listen(10)
  292. while 1:
  293. thread.start_new_thread(ProxyHandler, server_socket.accept())
  294. if __name__ == '__main__':
  295. main(listen_host, listen_port)