#!/usr/bin/env python import sys sys.path.append('pysocks') import socket import thread import select import httplib import random import string import base64 import os import time import mimetools import logging logging.basicConfig(level=logging.DEBUG, filename='HTTor_OP.log', format='%(asctime)s %(levelname)s %(message)s') import common from PySocks import ThreadingSocks4Proxy, ReceiveSocksReq, Remote_Connection_Failed INACTIVITY_TIMEOUT = 360 DATA_BUF_SIZE = 8192 LOG_PAYLOAD = False def random_host(length=None): if length is None: length = random.randint(8, 16) rand = ''.join(random.choice(string.ascii_lowercase) for _ in xrange(length)) return rand+".com" class ProtocolException(Exception): pass class EndOfFile(Exception): pass class MiniHTTPConnection(common.HTTPObfuscator): _http_vsn_str = 'HTTP/1.1' def __init__(self, sock): common.HTTPObfuscator.__init__(self, sock) self.host = random_host() self.key = os.urandom(16) logging.debug("Selected host '%s'"%self.host) self.request("GET", "/", None, {'Cookie': base64.b32encode(self.key)}) version, status, reason, headers, body = self.getresponse() logging.debug("Completed handshake") ## Based on _read_status() from httplib.py def read_status(self): line = self.rfile.readline() if not line: logging.debug("Reached end-of-file on read") raise EndOfFile() try: [version, status, reason] = line.split(None, 2) except ValueError: try: [version, status] = line.split(None, 1) reason = "" except ValueError: raise ProtocolException("Bad status line: %s"%line) if not version.startswith('HTTP/'): raise ProtocolException("Bad version: %s"%version) # The status code is a three-digit number try: status = int(status) if status < 100 or status > 999: raise ProtocolException("Bad status: %03d"%status) except ValueError: raise ProtocolException("Bad status: %s"%status) reason = reason.strip() logging.debug("Read HTTP Response %s %s %s"%(version, status, reason)) return version, status, reason ## Based on HTTPResponse.begin() from httplib.py def getresponse(self): # read until we get a non-100 response while True: version, status, reason = self.read_status() if status != 100: break # skip the header from the 100 response logging.debug("Skipping response %s %s %s"%(version, status, reason)) while True: skip = self.rfile.readline().strip() if not skip: break headers = mimetools.Message(self.rfile, 0) try: length = headers.getheader('Content-Length') if length is None: raise ProtocolException("Missing Content-Length header") length = int(length) except ValueError: raise ProtocolException("Invalid Content-Length header: %s"%length) body = self.rfile.read(length) if LOG_PAYLOAD: logged_body = repr(body) else: logged_body = 'skipped' logging.debug("Got response %s %s %s (%s): %s"%(version, status, reason, common.format_headers(headers), logged_body)) return version, status, reason, headers, body ## Based on _send_request() and putrequest from httplib.py def request(self, method, url, body=None, headers={}): # honour explicitly requested Host: and Accept-Encoding headers header_names = dict.fromkeys([k.lower() for k in headers]) #self.putrequest(method, url, **skips) self.send('%s %s %s\r\n' % (method, url, self._http_vsn_str)) self.send_header('Host', self.host) self.send_header('Accept-Encoding', 'identity') if body: length = len(body) self.send_header('Content-Length', length) for hdr, value in headers.iteritems(): self.send_header(hdr, value) self.end_headers() if body: self.send(body) self.flush() def send_http(self, data): self.request("POST", "/", self.obfuscate(data)) def read_http(self, len): try: version, status, reason, headers, body = self.getresponse() except EndOfFile, e: return '' data = self.deobfuscate(body) return data def forward_http(client_sock, server_sock): octets_in, octets_out = 0, 0 try: sockslist = [client_sock, server_sock.sock] while True: readables, writeables, exceptions = select.select( sockslist, [], [], INACTIVITY_TIMEOUT) if (exceptions): return "exception on fd" if (readables, writeables, exceptions) == ([], [], []): return "timeout" for readable_sock in readables: if readable_sock == client_sock: write_socket = server_sock read_socket = client_sock else: write_socket = client_sock read_socket = server_sock if isinstance(read_socket, MiniHTTPConnection): data = read_socket.read_http(DATA_BUF_SIZE) else: data = read_socket.recv(DATA_BUF_SIZE) if data: if isinstance(write_socket, MiniHTTPConnection): write_socket.send_http(data) else: write_socket.send(data) # This is only for future logging/stats. if readable_sock == client_sock: octets_out += len(data) else: octets_in += len(data) else: # The sock is readable but nothing can be read. # This means a poorly detected connection close. return "readable, but no data" except socket.error, e: return "exception: %s"%e finally: logging.info("%s: %s octets in and %s octets out"%(thread.get_ident(), octets_in, octets_out)) class HTTorHandler: def __init__(self): pass def handle_connect(self, socks_handler, req, addr): logging.info("Re-writing address from %s to %s"%( req['address'], addr)) http_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) try: logging.info("%s: Connecting to %s"%(thread.get_ident(), addr)) try: http_socket.connect(addr) except socket.error: raise Remote_Connection_Failed socks_handler.answer_granted() result = forward_http(socks_handler.request, MiniHTTPConnection(http_socket)) logging.info("Connection closed (%s)"%result) finally: http_socket.close() if __name__ == "__main__": server = ThreadingSocks4Proxy(ReceiveSocksReq, sys.argv[1:], HTTorHandler()) server.serve_forever()