aboutsummaryrefslogtreecommitdiffstats
path: root/pathod/libpathod/pathoc.py
diff options
context:
space:
mode:
Diffstat (limited to 'pathod/libpathod/pathoc.py')
-rw-r--r--pathod/libpathod/pathoc.py534
1 files changed, 0 insertions, 534 deletions
diff --git a/pathod/libpathod/pathoc.py b/pathod/libpathod/pathoc.py
deleted file mode 100644
index c0a33b62..00000000
--- a/pathod/libpathod/pathoc.py
+++ /dev/null
@@ -1,534 +0,0 @@
-import contextlib
-import sys
-import os
-import itertools
-import hashlib
-import Queue
-import random
-import select
-import time
-import threading
-
-import OpenSSL.crypto
-import six
-
-from netlib import tcp, http, certutils, websockets, socks
-from netlib.exceptions import HttpException, TcpDisconnect, TcpTimeout, TlsException, TcpException, \
- NetlibException
-from netlib.http import http1, http2
-
-import language.http
-import language.websockets
-from . import utils, log
-
-import logging
-from netlib.tutils import treq
-
-logging.getLogger("hpack").setLevel(logging.WARNING)
-
-
-class PathocError(Exception):
- pass
-
-
-class SSLInfo(object):
-
- def __init__(self, certchain, cipher, alp):
- self.certchain, self.cipher, self.alp = certchain, cipher, alp
-
- def __str__(self):
- parts = [
- "Application Layer Protocol: %s" % self.alp,
- "Cipher: %s, %s bit, %s" % self.cipher,
- "SSL certificate chain:"
- ]
- for i in self.certchain:
- parts.append("\tSubject: ")
- for cn in i.get_subject().get_components():
- parts.append("\t\t%s=%s" % cn)
- parts.append("\tIssuer: ")
- for cn in i.get_issuer().get_components():
- parts.append("\t\t%s=%s" % cn)
- parts.extend(
- [
- "\tVersion: %s" % i.get_version(),
- "\tValidity: %s - %s" % (
- i.get_notBefore(), i.get_notAfter()
- ),
- "\tSerial: %s" % i.get_serial_number(),
- "\tAlgorithm: %s" % i.get_signature_algorithm()
- ]
- )
- pk = i.get_pubkey()
- types = {
- OpenSSL.crypto.TYPE_RSA: "RSA",
- OpenSSL.crypto.TYPE_DSA: "DSA"
- }
- t = types.get(pk.type(), "Uknown")
- parts.append("\tPubkey: %s bit %s" % (pk.bits(), t))
- s = certutils.SSLCert(i)
- if s.altnames:
- parts.append("\tSANs: %s" % " ".join(s.altnames))
- return "\n".join(parts)
-
-
-
-class WebsocketFrameReader(threading.Thread):
-
- def __init__(
- self,
- rfile,
- logfp,
- showresp,
- hexdump,
- ws_read_limit,
- timeout
- ):
- threading.Thread.__init__(self)
- self.timeout = timeout
- self.ws_read_limit = ws_read_limit
- self.logfp = logfp
- self.showresp = showresp
- self.hexdump = hexdump
- self.rfile = rfile
- self.terminate = Queue.Queue()
- self.frames_queue = Queue.Queue()
- self.logger = log.ConnectionLogger(
- self.logfp,
- self.hexdump,
- rfile if showresp else None,
- None
- )
-
- @contextlib.contextmanager
- def terminator(self):
- yield
- self.frames_queue.put(None)
-
- def run(self):
- starttime = time.time()
- with self.terminator():
- while True:
- if self.ws_read_limit == 0:
- return
- r, _, _ = select.select([self.rfile], [], [], 0.05)
- delta = time.time() - starttime
- if not r and self.timeout and delta > self.timeout:
- return
- try:
- self.terminate.get_nowait()
- return
- except Queue.Empty:
- pass
- for rfile in r:
- with self.logger.ctx() as log:
- try:
- frm = websockets.Frame.from_file(self.rfile)
- except TcpDisconnect:
- return
- self.frames_queue.put(frm)
- log("<< %s" % frm.header.human_readable())
- if self.ws_read_limit is not None:
- self.ws_read_limit -= 1
- starttime = time.time()
-
-
-class Pathoc(tcp.TCPClient):
-
- def __init__(
- self,
- address,
-
- # SSL
- ssl=None,
- sni=None,
- ssl_version=tcp.SSL_DEFAULT_METHOD,
- ssl_options=tcp.SSL_DEFAULT_OPTIONS,
- clientcert=None,
- ciphers=None,
-
- # HTTP/2
- use_http2=False,
- http2_skip_connection_preface=False,
- http2_framedump=False,
-
- # Websockets
- ws_read_limit=None,
-
- # Network
- timeout=None,
-
- # Output control
- showreq=False,
- showresp=False,
- explain=False,
- hexdump=False,
- ignorecodes=(),
- ignoretimeout=False,
- showsummary=False,
- fp=sys.stdout
- ):
- """
- spec: A request specification
- showreq: Print requests
- showresp: Print responses
- explain: Print request explanation
- showssl: Print info on SSL connection
- hexdump: When printing requests or responses, use hex dump output
- showsummary: Show a summary of requests
- ignorecodes: Sequence of return codes to ignore
- """
- tcp.TCPClient.__init__(self, address)
-
- self.ssl, self.sni = ssl, sni
- self.clientcert = clientcert
- self.ssl_version = ssl_version
- self.ssl_options = ssl_options
- self.ciphers = ciphers
- self.sslinfo = None
-
- self.use_http2 = use_http2
- self.http2_skip_connection_preface = http2_skip_connection_preface
- self.http2_framedump = http2_framedump
-
- self.ws_read_limit = ws_read_limit
-
- self.timeout = timeout
-
- self.showreq = showreq
- self.showresp = showresp
- self.explain = explain
- self.hexdump = hexdump
- self.ignorecodes = ignorecodes
- self.ignoretimeout = ignoretimeout
- self.showsummary = showsummary
- self.fp = fp
-
- self.ws_framereader = None
-
- if self.use_http2:
- if not tcp.HAS_ALPN: # pragma: nocover
- log.write_raw(
- self.fp,
- "HTTP/2 requires ALPN support. "
- "Please use OpenSSL >= 1.0.2. "
- "Pathoc might not be working as expected without ALPN."
- )
- self.protocol = http2.HTTP2Protocol(self, dump_frames=self.http2_framedump)
- else:
- self.protocol = http1
-
- self.settings = language.Settings(
- is_client=True,
- staticdir=os.getcwd(),
- unconstrained_file_access=True,
- request_host=self.address.host,
- protocol=self.protocol,
- )
-
- def http_connect(self, connect_to):
- self.wfile.write(
- 'CONNECT %s:%s HTTP/1.1\r\n' % tuple(connect_to) +
- '\r\n'
- )
- self.wfile.flush()
- try:
- resp = self.protocol.read_response(self.rfile, treq(method="CONNECT"))
- if resp.status_code != 200:
- raise HttpException("Unexpected status code: %s" % resp.status_code)
- except HttpException as e:
- six.reraise(PathocError, PathocError(
- "Proxy CONNECT failed: %s" % repr(e)
- ))
-
- def socks_connect(self, connect_to):
- try:
- client_greet = socks.ClientGreeting(socks.VERSION.SOCKS5, [socks.METHOD.NO_AUTHENTICATION_REQUIRED])
- client_greet.to_file(self.wfile)
- self.wfile.flush()
-
- server_greet = socks.ServerGreeting.from_file(self.rfile)
- server_greet.assert_socks5()
- if server_greet.method != socks.METHOD.NO_AUTHENTICATION_REQUIRED:
- raise socks.SocksError(
- socks.METHOD.NO_ACCEPTABLE_METHODS,
- "pathoc only supports SOCKS without authentication"
- )
-
- connect_request = socks.Message(
- socks.VERSION.SOCKS5,
- socks.CMD.CONNECT,
- socks.ATYP.DOMAINNAME,
- tcp.Address.wrap(connect_to)
- )
- connect_request.to_file(self.wfile)
- self.wfile.flush()
-
- connect_reply = socks.Message.from_file(self.rfile)
- connect_reply.assert_socks5()
- if connect_reply.msg != socks.REP.SUCCEEDED:
- raise socks.SocksError(
- connect_reply.msg,
- "SOCKS server error"
- )
- except (socks.SocksError, TcpDisconnect) as e:
- raise PathocError(str(e))
-
- def connect(self, connect_to=None, showssl=False, fp=sys.stdout):
- """
- connect_to: A (host, port) tuple, which will be connected to with
- an HTTP CONNECT request.
- """
- if self.use_http2 and not self.ssl:
- raise NotImplementedError("HTTP2 without SSL is not supported.")
-
- tcp.TCPClient.connect(self)
-
- if connect_to:
- self.http_connect(connect_to)
-
- self.sslinfo = None
- if self.ssl:
- try:
- alpn_protos = [b'http/1.1']
- if self.use_http2:
- alpn_protos.append(b'h2')
-
- self.convert_to_ssl(
- sni=self.sni,
- cert=self.clientcert,
- method=self.ssl_version,
- options=self.ssl_options,
- cipher_list=self.ciphers,
- alpn_protos=alpn_protos
- )
- except TlsException as v:
- raise PathocError(str(v))
-
- self.sslinfo = SSLInfo(
- self.connection.get_peer_cert_chain(),
- self.get_current_cipher(),
- self.get_alpn_proto_negotiated()
- )
- if showssl:
- print >> fp, str(self.sslinfo)
-
- if self.use_http2:
- self.protocol.check_alpn()
- if not self.http2_skip_connection_preface:
- self.protocol.perform_client_connection_preface()
-
- if self.timeout:
- self.settimeout(self.timeout)
-
- def stop(self):
- if self.ws_framereader:
- self.ws_framereader.terminate.put(None)
-
- def wait(self, timeout=0.01, finish=True):
- """
- A generator that yields frames until Pathoc terminates.
-
- timeout: If specified None may be yielded instead if timeout is
- reached. If timeout is None, wait forever. If timeout is 0, return
- immedately if nothing is on the queue.
-
- finish: If true, consume messages until the reader shuts down.
- Otherwise, return None on timeout.
- """
- if self.ws_framereader:
- while True:
- try:
- frm = self.ws_framereader.frames_queue.get(
- timeout=timeout,
- block=True if timeout != 0 else False
- )
- except Queue.Empty:
- if finish:
- continue
- else:
- return
- if frm is None:
- self.ws_framereader.join()
- return
- yield frm
-
- def websocket_send_frame(self, r):
- """
- Sends a single websocket frame.
- """
- logger = log.ConnectionLogger(
- self.fp,
- self.hexdump,
- None,
- self.wfile if self.showreq else None,
- )
- with logger.ctx() as lg:
- lg(">> %s" % r)
- language.serve(r, self.wfile, self.settings)
- self.wfile.flush()
-
- def websocket_start(self, r):
- """
- Performs an HTTP request, and attempts to drop into websocket
- connection.
- """
- resp = self.http(r)
- if resp.status_code == 101:
- self.ws_framereader = WebsocketFrameReader(
- self.rfile,
- self.fp,
- self.showresp,
- self.hexdump,
- self.ws_read_limit,
- self.timeout
- )
- self.ws_framereader.start()
- return resp
-
- def http(self, r):
- """
- Performs a single request.
-
- r: A language.http.Request object, or a string representing one
- request.
-
- Returns Response if we have a non-ignored response.
-
- May raise a NetlibException
- """
- logger = log.ConnectionLogger(
- self.fp,
- self.hexdump,
- self.rfile if self.showresp else None,
- self.wfile if self.showreq else None,
- )
- with logger.ctx() as lg:
- lg(">> %s" % r)
- resp, req = None, None
- try:
- req = language.serve(r, self.wfile, self.settings)
- self.wfile.flush()
-
- resp = self.protocol.read_response(self.rfile, treq(method=req["method"].encode()))
- resp.sslinfo = self.sslinfo
- except HttpException as v:
- lg("Invalid server response: %s" % v)
- raise
- except TcpTimeout:
- if self.ignoretimeout:
- lg("Timeout (ignored)")
- return None
- lg("Timeout")
- raise
- finally:
- if resp:
- lg("<< %s %s: %s bytes" % (
- resp.status_code, utils.xrepr(resp.msg), len(resp.content)
- ))
- if resp.status_code in self.ignorecodes:
- lg.suppress()
- return resp
-
- def request(self, r):
- """
- Performs a single request.
-
- r: A language.message.Messsage object, or a string representing
- one.
-
- Returns Response if we have a non-ignored response.
-
- May raise a NetlibException
- """
- if isinstance(r, basestring):
- r = language.parse_pathoc(r, self.use_http2).next()
-
- if isinstance(r, language.http.Request):
- if r.ws:
- return self.websocket_start(r)
- else:
- return self.http(r)
- elif isinstance(r, language.websockets.WebsocketFrame):
- self.websocket_send_frame(r)
- elif isinstance(r, language.http2.Request):
- return self.http(r)
- # elif isinstance(r, language.http2.Frame):
- # TODO: do something
-
-
-def main(args): # pragma: nocover
- memo = set([])
- trycount = 0
- p = None
- try:
- cnt = 0
- while True:
- if cnt == args.repeat and args.repeat != 0:
- break
- if args.wait and cnt != 0:
- time.sleep(args.wait)
-
- cnt += 1
- playlist = itertools.chain(*args.requests)
- if args.random:
- playlist = random.choice(args.requests)
- p = Pathoc(
- (args.host, args.port),
- ssl=args.ssl,
- sni=args.sni,
- ssl_version=args.ssl_version,
- ssl_options=args.ssl_options,
- clientcert=args.clientcert,
- ciphers=args.ciphers,
- use_http2=args.use_http2,
- http2_skip_connection_preface=args.http2_skip_connection_preface,
- http2_framedump=args.http2_framedump,
- showreq=args.showreq,
- showresp=args.showresp,
- explain=args.explain,
- hexdump=args.hexdump,
- ignorecodes=args.ignorecodes,
- timeout=args.timeout,
- ignoretimeout=args.ignoretimeout,
- showsummary=True
- )
- trycount = 0
- try:
- p.connect(args.connect_to, args.showssl)
- except TcpException as v:
- print >> sys.stderr, str(v)
- continue
- except PathocError as v:
- print >> sys.stderr, str(v)
- sys.exit(1)
- for spec in playlist:
- if args.explain or args.memo:
- spec = spec.freeze(p.settings)
- if args.memo:
- h = hashlib.sha256(spec.spec()).digest()
- if h not in memo:
- trycount = 0
- memo.add(h)
- else:
- trycount += 1
- if trycount > args.memolimit:
- print >> sys.stderr, "Memo limit exceeded..."
- return
- else:
- continue
- try:
- ret = p.request(spec)
- if ret and args.oneshot:
- return
- # We consume the queue when we can, so it doesn't build up.
- for i_ in p.wait(timeout=0, finish=False):
- pass
- except NetlibException:
- break
- for i_ in p.wait(timeout=0.01, finish=True):
- pass
- except KeyboardInterrupt:
- pass
- if p:
- p.stop()