#!/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)