TDTS06NetworkCourse/lab2/proxy.py
2021-08-22 13:22:37 +02:00

253 lines
8.1 KiB
Python
Executable file

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
TDTS06 - Computer networks 2020/21
Last modification: 2020-10-02 (rev 3)
First release: 2020-09-25 (rev 1)
Websites to try our very basic proxy:
http://zebroid.ida.liu.se/fakenews/test2.html
http://zebroid.ida.liu.se/fakenews/test4.html
Note to the corrector:
""" """ comments are used to describe the general operation of a passage
while # comments describe the operation of a particular line.
Usage: proxy.py [-h] [--debug] [--port PORT]
Optional arguments:
-h, --help show the help message and exit
--debug print debug information
--port PORT set the port to use for the client side connection
"""
import argparse # For program's argument management
import re # To find patterns
import signal # To implement a signal handler
import socket # A low level socket programming library
import time # To manage timeout
import _thread # Library for multi-threading
""" Proxy general parameters (default values) """
HOST = '127.0.0.1' # Localhost
INTERNAL_PORT = 8080 # HTTP proxy internal port (can be change by user using --port PORT)
EXTERNAL_PORT = 80 # Port for server connection
""" Alteration parameters """
REPLACE1 = "Trolly"
REPLACE2 = "Linköping"
PATTERN1 = re.compile("Smiley", re.IGNORECASE)
PATTERN2 = re.compile("Stockholm", re.IGNORECASE)
""" Dev. parameters """
DEBUG = False # Can be change by passing --debug arg
""" A non-blocking recv function """
def recv_non_blocking(socket):
total_data = []
first_pkt = True
content_length = 0
while True:
# Try to receive data
try:
print("Receiving data segment...")
data = socket.recv(8192)
# Trying to find the Content_Length field
if first_pkt:
first_pkt = False
pos = data.find(b'Content-Length:')
pos_end = data.find(b'\r', pos, len(data) - 1)
content_length = int(data[pos+16:pos_end].decode("utf-8"))
# If we have new data, we add it to the total
if data:
total_data.append(data)
# If the total exceed the Content_Length field, we are done.
if len(b''.join(total_data)) >= content_length and content_length:
break
else:
time.sleep(0.05)
# Except no data
except:
pass
return b''.join(total_data)
"""
A function which alter a request
This function is particularly complex since it is necessary
to replace words by others while ensuring that these
replaced words are not image addresses.
"""
def altered(request):
try:
# Decoding the request to analyse it
altered_request = request.decode("utf-8")
except UnicodeDecodeError:
return request
"""
The purpose of this part is to remplace 'Smiley' by 'Trolly'
and 'Stockholm' by 'Linköping' everywhere except in image names.
"""
final_request = ""
tmp = ""
analyse_start = 0
r_end = len(altered_request) - 1
# Searching for image
img_pos = altered_request.find('<', analyse_start, r_end)
while img_pos > -1:
# We alter until the start of <img ...>
final_request += PATTERN2.sub(REPLACE2, PATTERN1.sub(REPLACE1, altered_request[analyse_start:img_pos]))
analyse_start = altered_request.find('>', img_pos, r_end)
# We do not modify the inside of <img ...>
final_request += altered_request[img_pos:analyse_start]
# We search for another image
img_pos = altered_request.find('<', analyse_start, r_end)
# We alter the end of the request
final_request += PATTERN2.sub(REPLACE2, PATTERN1.sub(REPLACE1, altered_request[analyse_start:img_pos]))
return bytes(final_request, "utf-8")
""" A basic SIGINT handler"""
def signal_handler(sig, frame):
print('Stopping...')
try:
# Try closing client socket
client_socket.close()
except NameError:
pass
try:
# Try closing server socket
server_socket.close()
except NameError:
pass
exit(0)
def send_server(t_url, t_protocol, t_server, t_client_connection):
""" Attempt to resend the request to the server """
print("Trying to reach the website:")
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
t_server_ip = socket.gethostbyname(t_server)
server_socket.connect((t_server_ip, EXTERNAL_PORT))
print("Connected to server...")
print("Sending request...")
new_request = bytes("GET " + t_url + " " + t_protocol + "\nHost: " + t_server + "\r\n\r\n", "utf-8")
server_socket.sendall(new_request)
""" Waiting fo response """
print("Request sent.")
server_response = recv_non_blocking(server_socket)
#server_response = recv_timeout(server_socket)
print("Response from the server.")
""" Request analysis """
status_code = server_response[:25].decode("utf-8").split("\n")[0].split(' ')[1]
if status_code != "200":
print("The server returned an error, status code: ", status_code)
print("Error bypass attempt...")
""" Text alteration """
print("Server response alteration")
server_response = altered(server_response)
""" We just resend the altered server response to the client """
print("Transmitting the altered response to the client")
t_client_connection.sendall(server_response)
server_socket.close()
# DEBUG
if DEBUG:
print("")
print("DEBUG:")
print(data)
print(new_request)
print(server_response)
print("")
# END DEBUG
def manage_request(data, client_connection):
""" Analysis of the request and identification """
request = data.decode("utf-8").split("\n")
header = request[0].split(' ')
method = header[0]
if "detectportal.firefox.com/success.txt" in request:
pass
elif method == "GET":
""" If it is a GET request, we extract any useful information """
url = header[1]
server = url.split('/')[2]
server_ip = socket.gethostbyname(server)
protocol = header[2]
print("Interception of a GET method requesting the URL: ", url)
print("Destination server: ", server, " (", server_ip, ")")
""" Image alteration """
print("Checking for alteration...")
if "smiley.jpg" in url:
url = "http://zebroid.ida.liu.se/fakenews/trolly.jpg"
_thread.start_new_thread(send_server, (url, protocol, server, client_connection))
""" If this is not a GET request """
else:
print("Receiving an unsupported method: ", method)
if DEBUG:
print("")
print("DEBUG:")
print(data)
print("")
""" Code entry """
""" We check for arguments """
parser = argparse.ArgumentParser(description='A very basic HTTP proxy.')
parser.add_argument('--debug', help='print debug information', action='store_true')
parser.add_argument('--port', help='set the port to use for the client side connection')
args = parser.parse_args()
if args.debug:
DEBUG = True
if args.port:
INTERNAL_PORT = int(args.port)
""" Starting proxy initialisation """
print("Init proxy...")
# Launching signal handler
signal.signal(signal.SIGINT, signal_handler)
if DEBUG:
print("DEBUG: Debug mode enabled.")
""" We create a local proxy on the port INTERNAL_PORT and we listen, waiting for a request """
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
client_socket.bind((HOST, INTERNAL_PORT))
while True:
client_socket.listen()
print("Proxy ready and waiting...")
""" We accept any connection on the port and we keep the data sent """
client_connection, addr = client_socket.accept()
print("Connected by", addr)
print("Receiving data...")
data = client_connection.recv(2048)
_thread.start_new_thread(manage_request, (data, client_connection))