Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- """
- author: _where
- PVG HTTP Server.
- Usage: python postviaget.py port e.g python postviaget.py 80 to run on port 80
- Implementation of popular HTTP protocols - GET, HEAD, POST, PUT, DELETE and CONNECT methods -
- via the GET protocol on BaseHTTPServer, thus acting as a HTTP server similar to Apache's httpd.
- You are strongly advised to read the HTTP documentation (rfc2616) in order to grasp what's being done here.
- Developed as a tunnel solution for organizations/isps where other protocols, except GET, are disabled.
- On a lot of occassions, most isps allow free access to browsing a specific site.
- This is achieved through GET.
- POST, CONNECT and other protocols are mostly disabled.
- In such situations, this script makes it possible to implement these protocols through a standard HTTP
- proxy on the client machine, by tunneling requests based on forbidden protocols, to a server running this script,
- as a GET request.
- To achieve this, some custom headers are introduced.
- The script assumes the organization/isp performs some form of response caching,
- thus chunked transfer-encoding for responses are not done. Responses are sent with
- a content-length.
- Client proxy originally developed for: Header Handler
- Header Definitions
- ======================
- H-Body: This header, has as its value, data regarding the headers and body(if present) of
- tunneled requests (forbidden protocols) encoded in base64. The decoded value of this
- header should take the form:
- hostValue + delimiter + port+ delimiter + requestCaptured
- An example of a request captured by a proxy on the client machine, to www.nairaland.com,
- assuming the delimiter is '@HH@' will be:
- www.nairaland.com@HH@80@HH@request_headers_and_body
- H-Chunked: GET protocol, is not without its limitations. A standard GET request is limited in
- size to ~12kb, since it's not allowed to have a body. What makes this fun, ;-)
- is implementing this, with this limitation in mind. To achieve this, the proxy
- appends this header to inform the script that requests will be sent in chunks.
- The value is 1 to indicate this, and 0 to indicate otherwise. For a large upload
- (POST request), of, for example 1mb, by the client, ~100 GET requests have to be made
- from the client proxy to this script.
- To implement ssl through the CONNECT protocol, requests are also sent in a similar fashion.
- H-Position: In order to hasten the upload process, tunneling is done in a fashion similar to the
- UDP protocol.Requests from the client proxy could be sent concurrently, without waiting
- for a response from this script. To achieve this, this header is appended to the request.
- The position serves to inform the script of the order in which requests are sent from the
- client proxy. The positional ordering starts from zero.
- So, even if the 10th request arrives before the 1st one, the script knows how to
- arrange and send accordingly. Since the script responds to each request received,
- lost packets could be resent, if no response is delivered to the client proxy.
- H-Secure: To support requests sent in chunks, an id of some sort is needed in order to multithread
- requests. The value of this header, is thus such id. Requests having same id, are treated
- with the same socket connection to the target host. A value of zero indicates that requests
- are neither sent in chunks, nor are https requests.
- H-Share: If authentication is to be performed, this header has to be appended to the original request format.
- The current form of authentication is crude, as it just compares the value of this header to
- preset strings. Implement appropriately, as desired.
- H-Mode: In supporting ssl/https, two modes are provided.
- If the value of this header is 0, it is assumed that https requests captured by the
- client proxy and tunneled to this script, are not being manipulated in any way
- - such as decryption. Thus, the script acts as a relay, forwarding requests
- to the target host, and forwarding responses back. Plain sockets are thus used.
- If the value of this header is 1, it is assumed that https requests have being decrypted
- by the client proxy. Thus, the script uses secure sockets to communicate with the
- target host.
- Thus a standard request to this server has to take the form:
- GET / HTTP/1.1
- Host: ip_address_of_machine_running_this_script_on_port_80
- H-Body: base64encoded_header_and_body_of_forbidden_protocols
- H-share: auth_string_value
- H-Chunked: 1
- H-Position: 0
- H-Secure: 43021
- H-Mode: 0
- """
- __version__ = "0.0.1"
- # client dictionaries, if authentication is needed. Only client is madara.
- g = {'madara': {}} # dictionary used to service client requests. stores stuff
- h = {'madara': {}} # dictionary used to service client requests. stores stuff
- num = {'madara': {}} # dictionary used to keep track of next expected request. starts from zero.
- pSoc = {'madara': {}} # dictionary used to service client requests. stores sockets.
- delimiter = '@HH@' # delimiter used for body (H-body) header
- #actualOrder = []
- import BaseHTTPServer, socket, SocketServer, sys, base64, ssl, math, random,datetime,time
- class ProxyHandler (BaseHTTPServer.BaseHTTPRequestHandler):
- __base = BaseHTTPServer.BaseHTTPRequestHandler
- __base_handle = __base.handle
- server_version = "HH Server/" + __version__
- rbufsize = 0 # self.rfile should be unbuffered
- username = ''
- def handle(self):
- (ip, port) = self.client_address
- self.__base_handle()
- def outConnection(self, host, port, soc):
- try: soc.connect((host, port))
- except socket.error, arg:
- self.send_error(404, "Connection error!")
- return 0
- return 1
- def do_GET(self):
- global g, h
- if 'h-share' in self.headers:
- self.username = str(self.headers['H-Share'])
- if self.username not in g:
- self.sendRes()
- self.connection.close()
- return
- if 'h-mode' in self.headers:
- ex = int(self.headers['H-mode'])
- if ex: self.mitm()
- else: self.ssloverhttp()
- else: self.ssloverhttp()
- def mitm(self): # MAN-IN-THE-MIDDLE https
- global g, h, pSoc, actualOrder
- secure = int(self.headers['H-Secure'])
- soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- requestBody = base64.b64decode(self.headers['H-Body'])
- request = requestBody.split(delimiter)
- host = request[0]
- port = int(request[1])
- dataSent = request[2]
- if not int(self.headers['H-Chunked']): # if requests are not chunked. normal requests less than 12kb
- if dataSent == 'REFRESH': # to be sent initially by client proxy before any tunneling is done
- for i in (g, h, pSoc):
- for j in i:
- if type(j) is type(soc): j.close()
- g[self.username] = {} # so as to clear dictionaries used to service previous session
- h[self.username] = {}
- pSoc[self.username] = {}
- self.wfile.write('HTTP/1.1 200 Continue\r\nContent-Length: 0\r\n\r\n')
- self.connection.close()
- return
- if secure:
- soc = ssl.wrap_socket(soc) # wrapper to convert plain socket to a secure socket
- try: # send request to target host and return response
- if self.outConnection(host, port, soc):
- soc.send(dataSent)
- self.read(soc)
- finally:
- soc.close()
- self.connection.close()
- else: # for requests sent in chunks: UDP style
- """
- The algo used here to keep track, is crude, but functional. Client proxy should
- ensure as much as possible that order zero comes first. Other requests could come
- in any particular order.
- Algorithm:
- Start from zero, expect request of position zero, if other position
- received, store value, and still expect position zero. Continue this way
- until position zero is received. When position zero is received,
- send zero, increment expected value to 1.
- If position 1 is already received, and stored, send 1, then increment expected
- to 2. If position 2 is not in store, wait till position 2 is received, then
- send, and look for three...
- For example, if five requests are sent by the client proxy and are
- received in the order: 5,3,0,4,1,2.
- It stores 5, stores 3, sends 0, stores 4, sends 1, sends 2, sends 3, sends 4
- and sends 5.
- """
- position = int(self.headers['H-Position'])
- if secure in g[self.username]: # if processing of request id is already started
- if position == num[self.username][secure]: # if request is the next expected one, send immediately
- pSoc[self.username][secure].send(dataSent)
- t = num[self.username][secure] + 1
- while 1: # loop to send stored requests in order
- if t in g[self.username][secure]: pSoc[self.username][secure].send(g[self.username][secure][t])
- else:
- num[self.username][secure] = t
- break
- t += 1
- else: g[self.username][secure][position] = dataSent; # store request body
- self.wfile.write('HTTP/1.1 200 Continue\r\nContent-Length: 0\r\n\r\n') # inform proxy of receipt, so there's
- self.connection.close() # no need resending
- return
- else: # if first request with that id
- g[self.username][secure] = {} # instantiate dictionary for that id
- split = dataSent.split(delimiter)
- self.outConnection(split[0], int(split[1]), soc)
- pSoc[self.username][secure] = soc # store socket for that id in dictionary
- pSoc[self.username][secure].send(split[2]) # send immediately since position is zero
- num[self.username][secure] = 1
- self.wfile.write('HTTP/1.1 200 Continue\r\nContent-Length: 0\r\n\r\n')
- self.connection.close()
- return
- def ssloverhttp(self): # ssl relay
- global g, h, pSoc
- secure = int(self.headers['H-Secure'])
- soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- requestBody = base64.b64decode(self.headers['H-Body'])
- if int(self.headers['H-Chunked']): # if requests are not chunked
- split = requestBody.split(delimiter)
- host = split[0]
- port = int(split[1])
- dataSent = split[2]
- if dataSent == 'REFRESH': # to be sent initially by client proxy before any tunneling is done
- g[self.username] = {} # so as to clear dictionaries used to service previous session
- h[self.username] = {}
- pSoc[self.username] = {}
- self.wfile.write('HTTP/1.1 200 Continue\r\nContent-Length: 0\r\n\r\n')
- self.connection.close()
- return
- if dataSent == 'PING': # used to inform script to send responses from id-specific socket
- if secure in pSoc[self.username]:
- #self.readResponse(pSoc[self.username][secure]) # if caching is done by isp/organization
- self.readwriteChunkResponse(pSoc[self.username][secure]) # if chunked transfer-encoding is permitted
- else: self.constructResp(None)
- self.connection.close()
- return
- # same algo used in other mode. Transfer is still in a fashion similar to UDP
- position = int(self.headers['H-Position'])
- if secure in pSoc[self.username]: # send ssl request to target host as is, if socket already created
- if position == num[self.username][secure]: # if request is the next expected one, send immediately
- pSoc[self.username][secure].send(dataSent)
- t = num[self.username][secure] + 1
- while 1: # loop to send stored requests in order
- if t in g[self.username][secure]: pSoc[self.username][secure].send(g[self.username][secure][t])
- else:
- num[self.username][secure] = t
- break
- t += 1
- else: g[self.username][secure][position] = dataSent; # store request body
- self.wfile.write('HTTP/1.1 200 Continue\r\nContent-Length: 0\r\n\r\n') # inform proxy of receipt, so there's
- self.connection.close()
- return
- else:
- """
- This is the first communication for ssl/https requests.
- Typically, this should not have a body, or if it does, it will be disregarded.
- When connection is successfully established to host, the script responds with a 200.
- The client could parse this, and modify, or send, as is to the browser.
- According to rfc2616, CONNECT requests should be handled this way.
- """
- if self.outConnection(host, port, soc):
- g[self.username][secure] = {}
- pSoc[self.username][secure] = soc
- if dataSent == 'CONNECT': pass
- else: pSoc[self.username][secure].send(dataSent)
- num[self.username][secure] = 1
- self.wfile.write('HTTP/1.1 200 Continue\r\nContent-Length: 0\r\n\r\n')
- self.connection.close()
- return
- else: # normal requests (POST, GET with body less that 12kb
- split = requestBody.split(delimiter)
- host = split[0]
- port = int(split[1])
- dataSent = split[2]
- if self.outConnection(host, port, soc):
- soc.send(dataSent)
- self.read(soc, 20, secure)
- soc.close()
- self.connection.close()
- def read(self, soc, idlingTimeout=20, secure=0): # normal response reading for GET/POST requests not sent in chunks
- global g
- count = 0
- while 1:
- count += 1
- data = ''
- try: data = soc.recv(8192)
- except: pass
- if data:
- self.wfile.write(data)
- count = 0
- if count >= idlingTimeout: break
- soc.close()
- if secure in g[self.username]: g[self.username].pop(secure)
- def sendRes(self): # response for unauthorized clients. (Header Handler implementation)
- # implement appropriately, to suit your needs.
- body = "<html><head><title>Error</title></head><body>Client not authorized</body></html>"
- self.wfile.write(self.protocol_version +
- " 200 OK\r\n")
- self.wfile.write("Proxy-agent: %s\r\n" % self.version_string())
- self.wfile.write('Content-Length: %s\r\n' % len(body))
- self.wfile.write("Cache-control: no-cache, no-store, no-transform\r\n")
- self.wfile.write("Pragma: no-cache\r\n")
- self.wfile.write("Connection: close\r\n")
- self.wfile.write("\r\n")
- self.wfile.write(body)
- def constructResp(self, response):
- self.wfile.write('HTTP/1.1 200 OK\r\n')
- self.wfile.write('Server: Header Handler\r\n')
- self.wfile.write('Date: %s\r\n' % time.strftime("%c"))
- self.wfile.write('Content-Type: text/html\r\n')
- if response is None:
- self.wfile.write('Content-Length: 0\r\n')
- else:
- self.wfile.write('Content-Length: %s\r\n' % len(response))
- self.wfile.write('Cache-Control: private\r\n')
- self.wfile.write('Connection: close\r\n')
- self.wfile.write('\r\n')
- if response is not None: self.wfile.write(response)
- def readResponse(self, soc): # if caching is done by isp
- data = ''
- try: data = soc.recv(4092)
- except: pass
- if data: self.constructResp(data)
- else: self.constructResp(None)
- def readwriteChunkResponse(self, soc, idlingTimeout=20): # else if chunked transfer-encoding is permitted by isp
- self.wfile.write('HTTP/1.1 200 OK\r\n')
- self.wfile.write('Server: Header Handler\r\n')
- self.wfile.write('Date: %s\r\n' % time.strftime("%c"))
- self.wfile.write('Content-Type: text/html\r\n')
- self.wfile.write('Transfer-Encoding: chunked\r\n')
- self.wfile.write('Cache-Control: private\r\n')
- self.wfile.write('Connection: close\r\n')
- self.wfile.write('\r\n')
- count = 0
- soc.setblocking(False)
- while 1:
- count += 1
- data = ''
- try: data = soc.recv(8192)
- except: pass
- if data:
- self.wfile.write('%x\r\n' % len(data))
- self.wfile.write(data)
- self.wfile.write('\r\n')
- count = 0
- if count >= idlingTimeout: break
- try: self.wfile.write('0\r\n\r\n')
- except: pass
- """
- Multi-threaded implementation of the PVG server.
- Spawns a new thread to handle each request.
- """
- class ThreadingHTTPServer (SocketServer.ThreadingMixIn,
- BaseHTTPServer.HTTPServer): pass
- if __name__ == '__main__':
- BaseHTTPServer.test(ProxyHandler, ThreadingHTTPServer)
Add Comment
Please, Sign In to add comment