1
|
#!/usr/bin/env python3
|
2
|
#
|
3
|
# Copyright (c) 2015, inaz2
|
4
|
# Copyright (C) 2021 jahoti <jahoti@tilde.team>
|
5
|
# Licensing information is collated in the `copyright` file
|
6
|
|
7
|
"""
|
8
|
The core for a "virtual network" proxy
|
9
|
|
10
|
Be sure to run this inside your intended certificates directory.
|
11
|
"""
|
12
|
|
13
|
import os, socket, ssl, sys, threading, time
|
14
|
from http.server import HTTPServer, BaseHTTPRequestHandler
|
15
|
from socketserver import ThreadingMixIn
|
16
|
from subprocess import Popen, PIPE
|
17
|
|
18
|
gen_cert_req, lock = 'openssl req -new -key cert.key -subj /CN=%s', threading.Lock()
|
19
|
sign_cert_req = 'openssl x509 -req -days 3650 -CA ca.crt -CAkey ca.key -set_serial %d -out %s'
|
20
|
|
21
|
|
22
|
class ProxyRequestHandler(BaseHTTPRequestHandler):
|
23
|
"""Handles a network request made to the proxy"""
|
24
|
|
25
|
def log_error(self, format, *args):
|
26
|
# suppress "Request timed out: timeout('timed out',)"
|
27
|
if isinstance(args[0], socket.timeout):
|
28
|
return
|
29
|
|
30
|
self.log_message(format, *args)
|
31
|
|
32
|
def do_CONNECT(self):
|
33
|
hostname = self.path.split(':')[0]
|
34
|
certpath = '%s.crt' % (hostname if hostname != 'ca' else 'CA')
|
35
|
|
36
|
with lock:
|
37
|
if not os.path.isfile(certpath):
|
38
|
p1 = Popen((gen_cert_req % hostname).split(' '), stdout=PIPE).stdout
|
39
|
Popen((sign_cert_req % (time.time() * 1000, certpath)).split(' '), stdin=p1, stderr=PIPE).communicate()
|
40
|
|
41
|
self.wfile.write('HTTP/1.1 200 Connection Established\r\n')
|
42
|
self.end_headers()
|
43
|
|
44
|
self.connection = ssl.wrap_socket(self.connection, keyfile='cert.key', certfile=certpath, server_side=True)
|
45
|
self.rfile = self.connection.makefile('rb', self.rbufsize)
|
46
|
self.wfile = self.connection.makefile('wb', self.wbufsize)
|
47
|
|
48
|
self.close_connection = int(self.headers.get('Proxy-Connection', '').lower() == 'close')
|
49
|
|
50
|
def proxy(self):
|
51
|
if self.path == 'http://hachette.test/':
|
52
|
with open('ca.crt', 'rb') as f:
|
53
|
data = f.read()
|
54
|
|
55
|
self.wfile.write('HTTP/1.1 200 OK\r\n')
|
56
|
self.send_header('Content-Type', 'application/x-x509-ca-cert')
|
57
|
self.send_header('Content-Length', len(data))
|
58
|
self.send_header('Connection', 'close')
|
59
|
self.end_headers()
|
60
|
self.wfile.write(data)
|
61
|
return
|
62
|
|
63
|
content_length = int(self.headers.get('Content-Length', 0))
|
64
|
req_body = self.rfile.read(content_length) if content_length else None
|
65
|
|
66
|
if self.path[0] == '/':
|
67
|
if isinstance(self.connection, ssl.SSLSocket):
|
68
|
self.path = 'https://%s%s' % (self.headers['Host'], self.path)
|
69
|
else:
|
70
|
self.path = 'http://%s%s' % (self.headers['Host'], self.path)
|
71
|
|
72
|
self.handle_request(req_body)
|
73
|
|
74
|
do_OPTIONS = do_DELETE = do_PUT = do_HEAD = do_POST = do_GET = proxy
|
75
|
|
76
|
def handle_request(self, req_body):
|
77
|
pass
|
78
|
|
79
|
|
80
|
class ThreadingHTTPServer(ThreadingMixIn, HTTPServer):
|
81
|
"""The actual proxy server"""
|
82
|
address_family, daemon_threads, handler = socket.AF_INET6, True, ProxyRequestHandler
|
83
|
|
84
|
def handle_error(self, request, client_address):
|
85
|
# suppress socket/ssl related errors
|
86
|
cls, e = sys.exc_info()[:2]
|
87
|
if not (cls is socket.error or cls is ssl.SSLError):
|
88
|
return HTTPServer.handle_error(self, request, client_address)
|
89
|
|
90
|
|
91
|
def start(server_class, port=1337):
|
92
|
"""Start up the proxy/server"""
|
93
|
|
94
|
print('Serving HTTP Proxy')
|
95
|
httpd = server_class(('::1', port), ProxyRequestHandler)
|
96
|
httpd.serve_forever()
|