#!/usr/bin/env python

import socket
try:
    import SocketServer as socketserver
    import SimpleHTTPServer as server
    import httplib
except:
    import socketserver
    import http.server as server
    import http.client as httplib
import re, os
import argparse
import tempfile
import random
import string
import time
import RXTools as rx # In GPSTools/pythonTools/

# Copyright Trimble Inc 2022
#
# This is a very quick proof-of-concept based on the web debug proxy written by Will:
#
# coreBuild/ecos/web/source/web_redir.py
#
# The code sets a proxy up and this code is then a "man in the middle", the code parses
# each request, and if there are parameters, it "fuzzes" the requests to see if 
# we can force a crash. This could be vastly improved. 
#
# The fuzzing works as follows:
#
# Test 1
# If the request has N parameters, in turn drop a parameter and only output N-1
#
# Test 2
# For each of the N parameters, one at a time tweak the value after the "=". Currently,
# this outputs a set of 8 different values, including a very large number, and a 500 
# byte random string
# 
# After the "fuzz" it outputs the original requested URL to clean up the receiver.
#
# Each "fuzzed" URL is written to a file. Once we have a set of URLs, a second script
# "sendFuzz.py" can be used to send that data to the receiver. 
#
# This code was able to crash the receiver *before* Tom's code, the same set of URLs
# sent to a receiver *after* Tom's update did *not* crash the receiver.
#


BASE_PORT = 1234
curr_dir = os.getcwd()
do_debug = False

parser = argparse.ArgumentParser(description=
"""Receiver web redirect."""
)
parser.add_argument('rcvr_IP', metavar='my_receiver_IP',
                    help='receiver IP address - e.g., 10.1.150.10')
parser.add_argument('-p', dest='proxy', default=None,
                    help='proxy to receiver - e.g., http://proxy.trimble.com:3128 (default: None)')

args = parser.parse_args()

if args.proxy is not None:
    m = re.match(r"http://(.*?):([0-9]+)",args.proxy)
    if not m:
        print("Invalid proxy - must be of form http://some-proxy.com:8888")
        sys.exit(2)
    conn_args = [m.group(1), int(m.group(2))]
    rcvr_path = "http://" + args.rcvr_IP
else:
    conn_args = [args.rcvr_IP]
    rcvr_path = ""

def is_fs_case_sensitive():
    #
    # Force case with the prefix
    #
    with tempfile.NamedTemporaryFile(prefix='TmP') as tmp_file:
        return(not os.path.exists(tmp_file.name.lower()))

class Proxy(server.SimpleHTTPRequestHandler):
    if not do_debug:
        def log_message(self, format, *args):
            pass

    def send_remote_file(self, new_path):
        conn = httplib.HTTPConnection( *conn_args )
        conn.connect()
        hdrs = {"Connection":"Keep-Alive"}
        for x in self.headers:
            if x.lower() == 'cookie':
                hdrs['Cookie'] = self.headers[x]
                break
        if do_debug:
            print('remote:',rcvr_path + new_path,hdrs)
        conn.request('GET', rcvr_path + new_path, headers=hdrs)
        response = conn.getresponse()
        self.send_response(response.status)
        if response.status != httplib.OK:
            print('*********** bad status',response.status)
            return
        self.send_header("content-type", response.getheader('content-type'))
        if response.getheader('content-length'):
            self.send_header("content-length", response.getheader('content-length'))
        self.send_header("cache-control", "no-cache, must-revalidate")
        self.send_header("connection", "close")
        self.end_headers()
        while True:
            body = response.read(128)
            if len(body) == 0:
                break
            self.wfile.write(body)
        response.close()
        conn.close()

    def do_GET(self):
        ctype = self.guess_type(self.path)
        new_path = self.path

        # We now have a URL to send to the receiver - see if it looks like
        # it might have parameters (in which case we will fuzz it)
        if(new_path.startswith('/cgi') or ( ('=' in new_path) and ('&' in new_path))):
          print('Fuzz it')
          token = new_path.split('?')
          fields = token[1].split('&')

          # We are going to manipulate one parameter at a time
          for i in range(len(fields)):
            url = token[0] + '?'
            for index in range(len(fields)):
              if(index != i):
                url += fields[index] + '&'

            # URL contains the original URL minus one parameter
            # Send the URL with one parameter missing to see if that causes an issue
            try:
              # Write to the file first in case the receiver crashes
              fid.write(url[:-1] + '\n')
              fid.flush()
              # Send this URL using a different socket, sending on this socket caused a problem
              ret = rx.SendHttpGet(args.rcvr_IP,url[:-1],'admin','password',verbose=False)
              time.sleep(0.1)
            except:
              pass

            # Now add back the missing parameter, but mess with the value.
            if(True):            
                base = fields[i].split('=')
                for fuzzNum in range(8):
                  urlFuzz = url + base[0] + '='
                  if(fuzzNum == 0):
                    urlFuzz += (''.join(random.choice(string.ascii_lowercase) for _ in range(500)))
                  elif(fuzzNum == 1):
                    urlFuzz += 'off'
                  elif(fuzzNum == 2):
                    urlFuzz += (''.join(random.choice(string.digits) for _ in range(50)))
                  elif(fuzzNum == 3):
                    urlFuzz += 'on'
                  elif(fuzzNum == 4):
                    urlFuzz += 'off999999'
                  elif(fuzzNum == 5):
                    urlFuzz += 'on777'
                  elif(fuzzNum == 6):
                    urlFuzz += str(-1)
                  elif(fuzzNum == 7):
                    urlFuzz += str(10000000000000)
                  try:
                    # Write to the file first in case the receiver crashes
                    fid.write(urlFuzz + '\n')
                    fid.flush()
                    ret = rx.SendHttpGet(args.rcvr_IP,urlFuzz,'admin','password',verbose=False)
                  except:
                    pass


        print('Finished Fuzz')
        # Now clean up by sending the requested URL
        print(new_path)
        self.send_remote_file( new_path )


fid = open('FuzzURLs.txt','w')
# Serve on first available port after BASE_PORT
port = BASE_PORT
while port < BASE_PORT+10:
    try:
        socketserver.ThreadingTCPServer.allow_reuse_address = True
        httpd = socketserver.ThreadingTCPServer(('', port), Proxy)
        print("serving at port %d" % port)
        httpd.serve_forever()
    except socket.error:
        print('port %d in use' % port)
    port += 1
