WebProxy46

Aus Si:Wiki von Siegrist SystemLösungen - Informatik und Rezepte
Wechseln zu: Navigation, Suche

Die Intention ein solches Programm zu schreiben war ganz einfach, nämlich zu schauen wie viel ich mit Python anfangen kann und, noch wichtiger, zu lernen einen Proxy für unterschiedliche IP-Protokolle in Python zu realisieren.

Es war eine meiner grösseren Progrämmli mit Python im 2013 und ich habe es auch hier geschafft einen "Spaghetticode" zu produzieren. Man sieht, ich liebe Spaghetticode, ganz im Gegensatz zu vielen Python Programmierern die hier Objektorientalismus reinbringen.

Ein Hoch auf eine Programmiersprache die beides locker beherrscht und auch mit Beidem zufrieden ist.

Aber wie erstellt man Spaghetticode mit Python ? Ganz einfach: Oben eine Klasse definieren, worin sich Funktion und interne Funktionen befinden, und ausserhalb, falls nötig, einfach die externen Funktionen und alles verhält sich so wie überal wo Namensräume geschaffen werden. Voilà.


Dieses Programm nimmt HTTP, HTTPS und FTP Anfragen von IPv4 Clients entgegen und leitet diese, wenn möglich, an IPv6 Server weiter.
Es löst die Namen der GET Anfragen selbst auf um, wenn möglich, eine IPv6 Adresse zu erhalten. Ist keine solche vorhanden, nehmen wir halt IPv4 um möglichst alle Requests einer Seite zu befriedigen.
Alle gängigen Methoden wie GET, HEAD, POST, PUT, OPTIONS, TRACE und DELETE sowie CONNECT für SSL Verbindungen werden unterstützt.


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