|
@@ -0,0 +1,334 @@
|
|
|
+#!/usr/bin/python
|
|
|
+
|
|
|
+# Copyright (c) 2013 by Peter_Siegrist(SystemLoesungen) (PSS @ ZweierNet.ch)
|
|
|
+# www.IPv6Tech.ch
|
|
|
+#
|
|
|
+# All Rights reserved.
|
|
|
+# This program is free software; you can redistribute it and/or
|
|
|
+# modify it under the terms of the GNU General Public License as
|
|
|
+# published by the Free Software Foundation.
|
|
|
+#
|
|
|
+# This program is distributed in the hope that it will be useful,
|
|
|
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
+# GNU General Public License for more details.
|
|
|
+#
|
|
|
+#
|
|
|
+# This is a simple IPv4-IPv6 non-caching HTTP/HTTPS Proxy/Gateway.
|
|
|
+# The Server can make both IPv6, primarily, or IPv4 requestst on behalf of the pure IPv4 clients.
|
|
|
+#
|
|
|
+# WebProxy46 does not filter any messages apart from chanching HTTP/1.1 to HTTP/1.0
|
|
|
+# because we do not want to filter out subrequests in persistent connections.
|
|
|
+#
|
|
|
+# All standard methodes are supported: GET, HEAD, POST, PUT, OPTIONS, TRACE and DELETE.
|
|
|
+# The CONNECT method is used for SSL connections. Only port 443 is allowed with this method (HTTP CONNECT Vulnerability)
|
|
|
+#
|
|
|
+# For using this proxy configure your browser to use proxy service.
|
|
|
+# The servers standard binding is port 9090 on address 0.0.0.0
|
|
|
+#
|
|
|
+# Accepted schemes are HTTP HTTPS and FTP.
|
|
|
+# Hostnames in URL's may be given as FQDN, IPv4 dotted address or IPv6 address in square brackets form [xx:xx:xx::x]
|
|
|
+#
|
|
|
+
|
|
|
+
|
|
|
+import socket
|
|
|
+import getopt
|
|
|
+import sys
|
|
|
+from select import select
|
|
|
+import logging
|
|
|
+import thread
|
|
|
+import struct
|
|
|
+import string
|
|
|
+
|
|
|
+#-- Vars
|
|
|
+
|
|
|
+version = 'WebProxy46/v0.7'
|
|
|
+buflen = 8192
|
|
|
+listen_host = '0.0.0.0'
|
|
|
+listen_port = 9090
|
|
|
+debug = False
|
|
|
+lfnr = 0
|
|
|
+
|
|
|
+
|
|
|
+#-- Threaded Main Object
|
|
|
+
|
|
|
+class ProxyHandler:
|
|
|
+ def __init__(self, local_conn, local_address):
|
|
|
+ global lfnr
|
|
|
+ lfnr += 1
|
|
|
+ self.lfnr = lfnr
|
|
|
+ log(self.lfnr,'NEW Connect ...')
|
|
|
+ self.local_client = local_conn
|
|
|
+ self.local_client_address = local_address
|
|
|
+ self.local_client_buffer = ''
|
|
|
+ self.timeout = 60
|
|
|
+ self.handle()
|
|
|
+ self.local_client.close()
|
|
|
+
|
|
|
+ def _set_target_sockopts_ssl(self):
|
|
|
+ self.target.setsockopt(socket.SOL_SOCKET,socket.MSG_DONTWAIT,1)
|
|
|
+ self.target.setblocking(1)
|
|
|
+
|
|
|
+ def handle(self):
|
|
|
+ try:
|
|
|
+ self.local_client.setblocking(1)
|
|
|
+ self.local_client.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER, struct.pack('ii', 1, 20))
|
|
|
+ #self.local_client.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 0)
|
|
|
+
|
|
|
+ clt_count = 0
|
|
|
+ while 1:
|
|
|
+ clt_count += 1
|
|
|
+ (client_data, _, client_error) = select([self.local_client], [], [], 1)
|
|
|
+ if client_data:
|
|
|
+ data = self.local_client.recv(buflen)
|
|
|
+ if len(data) == 0:
|
|
|
+ break
|
|
|
+ self.local_client_buffer += data
|
|
|
+ log2(self.lfnr,"data:%s"%data)
|
|
|
+ if client_error:
|
|
|
+ err = select.error
|
|
|
+ log(self.lfnr,"%s - Client_Error select: %s on %s " % (self.af, err, self.local_client_address))
|
|
|
+ if clt_count == 1:
|
|
|
+ break
|
|
|
+
|
|
|
+
|
|
|
+ if self.local_client_buffer:
|
|
|
+ end_head = self.local_client_buffer.find('\n')
|
|
|
+ log(self.lfnr,'%s'%self.local_client_buffer[:end_head])
|
|
|
+ self.method, self.path, self.protocol = (self.local_client_buffer[:end_head+1]).split()
|
|
|
+ self.local_client_buffer = self.local_client_buffer[end_head+1:]
|
|
|
+ # we can't handle 1.1 persistent connections in proxys, thanks mozilla :(
|
|
|
+ self.protocol = string.replace(self.protocol, 'HTTP/1.1', 'HTTP/1.0')
|
|
|
+
|
|
|
+ if self.method=='CONNECT':
|
|
|
+ self.method_ssl()
|
|
|
+ elif self.method in ('OPTIONS', 'GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'TRACE'):
|
|
|
+ self.method_get()
|
|
|
+ else:
|
|
|
+ self.method_invalid()
|
|
|
+ try:
|
|
|
+ self.target.close()
|
|
|
+ except:
|
|
|
+ pass
|
|
|
+ except Exception as uerr:
|
|
|
+ log(self.lfnr,'__Unexpected Error__: %s'%uerr)
|
|
|
+
|
|
|
+
|
|
|
+ def method_invalid(self):
|
|
|
+ log(self.lfnr,'Invalid HTTP Method: %s'%self.method)
|
|
|
+ self.local_client.send('HTTP/1.1 501 Not Implemented\r\n'+'Unknown method: %s\r\n\r\n'%self.method)
|
|
|
+ self.local_client_buffer = ''
|
|
|
+ self.local_client.close()
|
|
|
+
|
|
|
+ def method_ssl(self):
|
|
|
+ if not self.connect_target(self.path):
|
|
|
+ self.local_client.send('HTTP/1.1 500\r\n\r\n')
|
|
|
+ self.local_client.send('HTTP/1.1 500 %s\r\nProxy: %s\r\n\r\n'%(self.conn_error,version))
|
|
|
+ return False
|
|
|
+ #self.local_client.send('HTTP/1.1 200 OK\r\n\r\n')
|
|
|
+ self.local_client.send('HTTP/1.1 200 Connection established\r\n'+'Proxy: %s\r\n\r\n'%version)
|
|
|
+ self.local_client_buffer = ''
|
|
|
+ self.handle_select()
|
|
|
+
|
|
|
+
|
|
|
+ def method_get(self):
|
|
|
+ j = self.path.find('://')
|
|
|
+ self.scheme = self.path[:j]
|
|
|
+ self.path = self.path[j+3:]
|
|
|
+ i = self.path.find('/')
|
|
|
+ host = self.path[:i]
|
|
|
+ path = self.path[i:]
|
|
|
+ if not self.connect_target(host):
|
|
|
+ self.local_client.send('HTTP/1.1 500\r\n\r\n')
|
|
|
+ self.local_client.send('HTTP/1.1 500 %s\r\nProxy: %s\r\n\r\n'%(self.conn_error,version))
|
|
|
+ return False
|
|
|
+ s1 = self.target.send('%s %s %s\r\n'%(self.method, path, self.protocol)+self.local_client_buffer)
|
|
|
+ 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)
|
|
|
+ log(self.lfnr,'SEND_REQUEST(to %s): %s %s %s\n'%(self.target_addr[0:2], self.method, path, self.protocol))
|
|
|
+ self.local_client_buffer = ''
|
|
|
+ self.handle_select()
|
|
|
+
|
|
|
+
|
|
|
+ def connect_target(self, host):
|
|
|
+ if host.find('[') != -1:
|
|
|
+ j = host.find(']:')
|
|
|
+ if j!=-1:
|
|
|
+ port = int(host[j+2:])
|
|
|
+ host = host[1:j]
|
|
|
+ else:
|
|
|
+ host = host[1:-1]
|
|
|
+ if self.scheme.lower() == 'ftp':
|
|
|
+ port = 21
|
|
|
+ else:
|
|
|
+ port = 80
|
|
|
+ else:
|
|
|
+ i = host.find(':')
|
|
|
+ if i!=-1:
|
|
|
+ port = int(host[i+1:])
|
|
|
+ host = host[:i]
|
|
|
+ else:
|
|
|
+ if self.scheme.lower() == 'ftp':
|
|
|
+ port = 21
|
|
|
+ else:
|
|
|
+ port = 80
|
|
|
+
|
|
|
+ if self.method=='CONNECT':
|
|
|
+ if port != 443:
|
|
|
+ self.conn_error = 'Connection refused. Port %d not allowed in CONNECT method'%port
|
|
|
+ return False
|
|
|
+ try:
|
|
|
+ addrinfo = socket.getaddrinfo(host, port)
|
|
|
+ except socket.error as msg:
|
|
|
+ log(self.lfnr,"Error resolving %s:%d: %s" % (host, port, msg))
|
|
|
+ self.conn_error = 'Error resolving %s:%d: %s'%(host, port,msg[1])
|
|
|
+ return False
|
|
|
+ for (afm, _, _, _, address) in addrinfo:
|
|
|
+ if afm == socket.AF_INET6:
|
|
|
+ self.target = socket.socket(socket.AF_INET6)
|
|
|
+ self.target_addr = address
|
|
|
+ self.af = 'IPv6'
|
|
|
+ break
|
|
|
+ elif afm == socket.AF_INET:
|
|
|
+ self.target = socket.socket(socket.AF_INET)
|
|
|
+ self.target_addr = address
|
|
|
+ self.af = 'IPv4'
|
|
|
+ else:
|
|
|
+ log(self.lfnr,"EEERRRRRRRRRRRRRRRRRRRR\n")
|
|
|
+ self.target.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 0)
|
|
|
+ self.target.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER, struct.pack('ii', 1, 10))
|
|
|
+ try:
|
|
|
+ self.target.connect(self.target_addr)
|
|
|
+ except socket.error as msg:
|
|
|
+ log(self.lfnr,"%s - Error connecting %s:%d (Addr: %s): %s" % (self.af, host, port, self.target_addr, msg))
|
|
|
+ self.conn_error = 'Connection failed: %s'%msg[1]
|
|
|
+ return False
|
|
|
+ else:
|
|
|
+ log(self.lfnr,"%s - connect to %s:%d (%s)" % (self.af, host, port, self.target_addr[0:2]))
|
|
|
+
|
|
|
+ return True
|
|
|
+
|
|
|
+ def handle_select(self):
|
|
|
+ self.soc_pair = [self.local_client, self.target]
|
|
|
+ count = 0
|
|
|
+ rf = 'undefined'
|
|
|
+ while 1:
|
|
|
+ count += 1
|
|
|
+ (recv_data, _, error) = select(self.soc_pair, [], self.soc_pair, 2)
|
|
|
+ if recv_data:
|
|
|
+ cnt = 0
|
|
|
+ for i in recv_data:
|
|
|
+ cnt +=1
|
|
|
+ if i is self.local_client:
|
|
|
+ rf = self.local_client_address
|
|
|
+ else:
|
|
|
+ rf = self.target_addr
|
|
|
+ try:
|
|
|
+ data = i.recv(buflen)
|
|
|
+ except socket.error as err:
|
|
|
+ log(self.lfnr,'recv error (%s):%s' % (rf, err))
|
|
|
+ if err[0] == 104: # connection reset by peer
|
|
|
+ continue
|
|
|
+ log2(self.lfnr,'%d %d select handle from: %s LEN: %d' % (count,cnt,rf,len(data)))
|
|
|
+ if len(data) == 0:
|
|
|
+ continue
|
|
|
+ if i is self.local_client:
|
|
|
+ #self.view_sockopts(self.local_client)
|
|
|
+ if 'HTTP/1.' in data:
|
|
|
+ bs = data.find('\n')
|
|
|
+ log(self.lfnr,'%s - Request from %s: %s'%(self.af,self.local_client_address[0:2],data[:bs-1]))
|
|
|
+ rf = self.local_client_address
|
|
|
+ log2(self.lfnr,'DATA(to %s from %s):%s'% (self.target_addr,rf,data[:260]))
|
|
|
+ out = self.target
|
|
|
+ elif i is self.target:
|
|
|
+ if 'HTTP/1.' in data[0:8]:
|
|
|
+ bs = data[0:256].find('\n')
|
|
|
+ log(self.lfnr,'%s - Answer from %s: %s'%(self.af,self.target_addr[0:2],data[:bs-1]))
|
|
|
+
|
|
|
+ rf = self.target_addr
|
|
|
+ log2(self.lfnr,'DATA(to %s from %s):%s'% (self.local_client_address,rf,data[:260]))
|
|
|
+ out = self.local_client
|
|
|
+ else:
|
|
|
+ log(self.lfnr,"EEEEEERRRRRROOOORRRRRRRRR: %s"% i)
|
|
|
+
|
|
|
+ if data:
|
|
|
+ try:
|
|
|
+ sent_byte = out.send(data)
|
|
|
+ except socket.error as err:
|
|
|
+ log(self.lfnr,'send error (%s):%s' % (rf, err))
|
|
|
+ else:
|
|
|
+ log2(self.lfnr,'Sent %d Bytes Data to %s' % (sent_byte,out.getpeername()[0:2]))
|
|
|
+ count = 0
|
|
|
+ if error:
|
|
|
+ err = select.error
|
|
|
+ log(self.lfnr,"%s - Error select: %s on %s:%s " % (self.af, err, self.local_client_address, self.target_addr))
|
|
|
+ if count == 10:
|
|
|
+ break
|
|
|
+
|
|
|
+ def view_sockopts(self,s):
|
|
|
+ socket_options = [ (getattr(socket, opt), opt) for opt in dir(socket) if opt.startswith('SO_') ]
|
|
|
+ socket_options.sort()
|
|
|
+ for num, opt in socket_options:
|
|
|
+ try:
|
|
|
+ val = s.getsockopt(socket.SOL_SOCKET, num)
|
|
|
+ log2(self.lfnr,'%s(%d) defaults to %d' % (opt, num, val))
|
|
|
+ except (socket.error) as e:
|
|
|
+ log2(self.lfnr,'%s(%d) can\'t help you out there: %s' % (opt, num, str(e)))
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+# -- Main functions
|
|
|
+
|
|
|
+def log(lfnr,msg):
|
|
|
+ logging.warn('[%s] %s'%(lfnr,msg))
|
|
|
+
|
|
|
+def log2(lfnr,msg):
|
|
|
+ if debug:
|
|
|
+ logging.warn('[%s] %s'%(lfnr,msg))
|
|
|
+
|
|
|
+def usage():
|
|
|
+ print(sys.argv[0]),
|
|
|
+ print(" [-p port] [-s host] [-l logfile] [-d] [-h]")
|
|
|
+ print('')
|
|
|
+ print(" -p - Port to listen on")
|
|
|
+ print(" -s - Host to listen on. If not specified, 0.0.0.0 is used")
|
|
|
+ print(" -l - Path to logfile. If not specified, STDOUT is used")
|
|
|
+ print(" -d - debug messages")
|
|
|
+ print('')
|
|
|
+
|
|
|
+def main(host, port):
|
|
|
+ logfile = None
|
|
|
+ try:
|
|
|
+ opts, args = getopt.getopt(sys.argv[1:], "l:hdp:s:", [])
|
|
|
+ except getopt.GetoptError:
|
|
|
+ usage()
|
|
|
+ return 1
|
|
|
+ for opt, value in opts:
|
|
|
+ if opt == "-p": port = int(value)
|
|
|
+ if opt == "-s": host = value
|
|
|
+ if opt == "-l": logfile = value
|
|
|
+ if opt == "-d":
|
|
|
+ global debug
|
|
|
+ debug = True
|
|
|
+ if opt == "-h":
|
|
|
+ usage()
|
|
|
+ return 0
|
|
|
+ if not socket.has_ipv6:
|
|
|
+ print("This machine is not IPv6 capable. Exiting.\n")
|
|
|
+ exit(1)
|
|
|
+
|
|
|
+ logging.basicConfig(format='%(asctime)s %(thread)s: %(message)s',
|
|
|
+ filename=logfile)
|
|
|
+
|
|
|
+ server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
|
+ server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
|
+ server_socket.bind((host, port))
|
|
|
+ log(lfnr,"Start Server")
|
|
|
+ log(lfnr,"Listen on %s:%d"%(host, port))
|
|
|
+ server_socket.listen(10)
|
|
|
+ while 1:
|
|
|
+ thread.start_new_thread(ProxyHandler, server_socket.accept())
|
|
|
+
|
|
|
+
|
|
|
+if __name__ == '__main__':
|
|
|
+ main(listen_host, listen_port)
|
|
|
+
|