Source code for pync.netcat

# -*- coding: utf-8 -*-

"""
pync - arbitrary TCP and UDP connections and listens (Netcat for Python).
"""

from __future__ import unicode_literals
import argparse
import errno
import io
import itertools
import multiprocessing
import os
import random
import select
import shlex
import socket
import subprocess
import sys
import threading
import time

try:
    import msvcrt
    _mswindows = True
except ImportError:
    _mswindows = False

try:
    # py2
    import Queue as queue
except ImportError:
    # py3
    import queue

import socks

from .argparsing import GroupingArgumentParser
from . import compat
from .conin import NonBlockingConsoleInput as NetcatConsoleInput
from .process import (
        NonBlockingPopen, ProcessTerminated,
        PythonProcess, PythonStdinWriter, PythonStdoutReader,
)


# For handling Process error.
try:
    # py3
    FileNotFoundError
except NameError:
    # py2
    FileNotFoundError = IOError


TOSKEYWORDS = dict(
        af11=0x28,
        af12=0x30,
        af21=0x38,
        af22=0x50,
        af23=0x58,
        af31=0x68,
        af32=0x70,
        af33=0x78,
        af41=0x88,
        af42=0x90,
        af43=0x98,
        critical=0xa0,
        cs0=0x00,
        cs1=0x20,
        cs2=0x40,
        cs3=0x60,
        cs4=0x80,
        cs5=0xa0,
        cs6=0xc0,
        cs7=0xe0,
        ef=0xb8,
        inetcontrol=0xc0,
        lowcost=0x02,
        lowdelay=0x10,
        netcontrol=0xe0,
        reliability=0x04,
        throughput=0x08,
)


PIPE = subprocess.PIPE
STDOUT = subprocess.STDOUT
QUEUE = -3


class NetcatError(Exception):
    
    def __init__(self, msg, *args):
        super(NetcatError, self).__init__(msg, *args)
        self.msg = msg

    def __str__(self):
        return str(self.msg)


class NetcatSocketError(NetcatError):
    
    def __init__(self, socket_err, *args):
        msg = str(socket_err)
        super(NetcatSocketError, self).__init__(msg, socket_err, *args)
        self.socket_err = socket_err


class NetcatProxyError(NetcatError):
    
    def __init__(self, proxy_err, *args):
        msg = str(proxy_err)
        if proxy_err.socket_err is not None:
            msg = str(proxy_err.socket_err)
        super(NetcatProxyError, self).__init__(msg, proxy_err, *args)
        self.proxy_err =  proxy_err


class NetcatIOBase(object):

    def readable(self):
        return False

    def writable(self):
        return False

    def seekable(self):
        return False

    def closed(self):
        return False

    def read(self, size=None):
        raise io.UnsupportedOperation

    def write(self, data):
        raise io.UnsupportedOperation

    def flush(self):
        pass


class NetcatIO(NetcatIOBase):
    Reader = None
    Writer = None

    def __init__(self, reader=None, writer=None):
        self.reader = reader
        self.writer = writer
        if self.reader is None and self.Reader is not None:
            self.reader = self.Reader()
        if self.writer is None and self.Writer is not None:
            self.writer = self.Writer()

    def read(self, size=None):
        return self.reader.read(size)

    def write(self, data):
        return self.writer.write(data)

    def flush(self):
        return self.writer.flush()


class NetcatStdinReader(NetcatIOBase):

    def __getattr__(self, name):
        return getattr(sys.stdin, name)

    def __eq__(self, other):
        return other == sys.stdin

    def read(self, size=None):
        return sys.stdin.read(size)


class NetcatStdoutWriter(NetcatIOBase):

    def __getattr__(self, name):
        return getattr(sys.stdout, name)

    def __eq__(self, other):
        return other == sys.stdout

    def write(self, data):
        return sys.stdout.write(data)


class NetcatStderrWriter(NetcatIOBase):

    def __getattr__(self, name):
        return getattr(sys.stderr, name)

    def __eq__(self, other):
        return other == sys.stderr

    def write(self, data):
        return sys.stderr.write(data)


class NetcatPipeIOBase(NetcatIOBase):

    def __init__(self, conn):
        self.connection = conn
        self._fileno = self.connection.fileno()
        if _mswindows:
            self._fileno = msvcrt.open_osfhandle(self._fileno, os.O_TEXT)
        super(NetcatPipeIOBase, self).__init__()

    def fileno(self):
        return self._fileno
    
    def poll(self):
        raise io.UnsupportedOperation

    def send_bytes(self, data):
        raise io.UnsupportedOperation

    def recv_bytes(self):
        raise io.UnsupportedOperation

    def close(self):
        self.connection.close()


class NetcatPipeReader(NetcatPipeIOBase):

    def readable(self):
        return True

    def poll(self):
        return self.connection.poll()

    def recv_bytes(self):
        return self.connection.recv_bytes()

    def read(self, size=None):
        try:
            if self.poll():
                return self.recv_bytes()
        except BrokenPipeError:
            return b''


class NetcatPipeWriter(NetcatPipeIOBase):

    def writable(self):
        return True

    def send_bytes(self, data):
        return self.connection.send_bytes(data)

    def write(self, data):
        self.send_bytes(data)
        if not data:
            self.close()

    def flush(self):
        pass


class NetcatPipeIO(NetcatIO):
    Reader = NetcatPipeReader
    Writer = NetcatPipeWriter

    def __init__(self):
        recv_conn, send_conn = multiprocessing.Pipe(False)
        reader = self.Reader(recv_conn)
        writer = self.Writer(send_conn)
        super(NetcatPipeIO, self).__init__(reader, writer)


class NetcatQueueIOBase(NetcatIOBase):

    def __init__(self, q):
        self.queue = q
        super(NetcatQueueIOBase, self).__init__()
    
    def get_nowait(self):
        raise io.UnsupportedOperation

    def put(self, data):
        raise io.UnsupportedOperation


class NetcatQueueReader(NetcatQueueIOBase):

    def get_nowait(self):
        return self.queue.get_nowait()

    def read(self, size=None):
        try:
            return self.get_nowait()
        except queue.Empty:
            pass


class NetcatQueueWriter(NetcatQueueIOBase):

    def put(self, data):
        return self.queue.put(data)

    def write(self, data):
        self.put(data)


class NetcatQueueIO(NetcatIO):
    Reader = NetcatQueueReader
    Writer = NetcatQueueWriter

    def __init__(self):
        q = multiprocessing.Queue()
        reader = self.Reader(q)
        writer = self.Writer(q)
        super(NetcatQueueIO, self).__init__(reader, writer)


class NetcatFileIOBase(NetcatIOBase):
    
    def __init__(self, f):
        self.file = f
        try:
            self._fileno = self.fileno()
        except (AttributeError, io.UnsupportedOperation):
            self._fileno = None
        super(NetcatFileIOBase, self).__init__()

    def fileno(self):
        return self.file.fileno()

    def poll(self):
        raise io.UnsupportedOperation


class NetcatFileReader(NetcatFileIOBase):

    def __init__(self, f):
        super(NetcatFileReader, self).__init__(f)
        self.__poll_fileno = True
        self.__read_fileno = True

    def read(self, size=None):
        if self.poll():
            if self.__read_fileno:
                try:
                    return self._read_fileno(size)
                except OSError as e:
                    if e.errno != errno.EBADF:
                        raise
                except TypeError:
                    pass
                self.__read_fileno = False
            return self._read_file(size)

    def poll(self):
        if self.__poll_fileno:
            try:
                return self._poll_fileno()
            except (OSError, TypeError):
                self.__poll_fileno = False
        return self._poll_file()

    def _read_fileno(self, size):
        return os.read(self._fileno, size)

    def _read_file(self, size):
        return self.file.read(size)

    def _poll_file(self):
        return True

    def _poll_fileno(self):
        readables, _, _ = select.select([self._fileno], [], [], 0)
        if self._fileno in readables:
            return True
        return False


class NetcatFileWriter(NetcatFileIOBase):

    def __init__(self, f):
        super(NetcatFileWriter, self).__init__(f)
        self.__write_fileno = True

    def write(self, data):
        if self.__write_fileno:
            try:
                self._write_fileno(data)
            except OSError as e:
                if e.errno != errno.EBADF:
                    raise
            except TypeError:
                pass
            else:
                self.flush()
                return
            self.__write_fileno = False
        self._write_file(data)
        self.flush()

    def _write_file(self, data):
        self.file.write(data)

    def _write_fileno(self, data):
        os.write(self._fileno, data)

    def flush(self):
        self.file.flush()


class NetcatFileIO(NetcatIO):
    Reader = NetcatFileReader
    Writer = NetcatFileWriter

    def __init__(self, *args, **kwargs):
        raise NotImplementedError


class NetcatConsoleWriter(NetcatFileWriter):

    def __init__(self):
        super(NetcatConsoleWriter, self).__init__(sys.stdout)


class NetcatContext(object):
    D = False
    v = False
    stdin = sys.stdin
    stdout = sys.stdout
    stderr = sys.stderr

    def __init__(self,
            D=None,
            v=None,
            stdin=None, stdout=None, stderr=None, **kwargs):

        if D is not None:
            self.D = D
        if v is not None:
            self.v = v

        self.stdin = stdin or self.stdin
        self.stdout = stdout or self.stdout
        self.stderr = stderr or self.stderr

        if isinstance(self.stdin, NetcatIO):
            self._stdin = self.stdin.reader
            self.stdin = self.stdin.writer
        elif isinstance(self.stdin, NetcatIOBase):
            self._stdin = self.stdin
            self.stdin = None
        elif self.stdin is sys.stdin:
            if self.stdin.isatty():
                self._stdin = NetcatConsoleInput()
            else:
                self._stdin = NetcatFileReader(NetcatStdinReader())
            self.stdin = None
        elif self.stdin == PIPE:
            pipe = NetcatPipeIO()
            self._stdin = pipe.reader
            self.stdin = pipe.writer
        elif self.stdin == QUEUE:
            q = NetcatQueueIO()
            self._stdin = q.reader
            self.stdin = q.writer
        else:
            self._stdin = NetcatFileReader(self.stdin)
            self.stdin = None

        if isinstance(self.stdout, NetcatIO):
            self._stdout = self.stdout.writer
            self.stdout = self.stdout.reader
        elif isinstance(self.stdout, NetcatIOBase):
            self._stdout = self.stdout
            self.stdout = None
        elif self.stdout is sys.stdout:
            self._stdout = NetcatFileWriter(NetcatStdoutWriter())
            self.stdout = None
        elif self.stdout == PIPE:
            pipe = NetcatPipeIO()
            self._stdout = pipe.writer
            self.stdout = pipe.reader
        elif self.stdout == QUEUE:
            q = NetcatQueueIO()
            self._stdout = q.writer
            self.stdout = q.reader
        else:
            self._stdout = NetcatFileWriter(self.stdout)
            self.stdout = None

        if isinstance(self.stderr, NetcatIO):
            self._stderr = self.stderr.writer
            self.stderr = self.stderr.reader
        elif isinstance(self.stderr, NetcatIOBase):
            self._stderr = self.stderr
            self.stderr = None
        elif self.stderr is sys.stderr:
            self._stderr = NetcatFileWriter(NetcatStderrWriter())
            self.stderr = None
        elif self.stderr == PIPE:
            pipe = NetcatPipeIO()
            self._stderr = pipe.writer
            self.stderr = pipe.reader
        elif self.stderr == QUEUE:
            q = NetcatQueueIO()
            self._stderr = q.writer
            self.stderr = q.reader
        elif self.stderr == STDOUT:
            self._stderr = self._stdout
            self.stderr = self.stdout
        else:
            self._stderr = NetcatFileWriter(self.stderr)
            self.stderr = None

        self._init_kwargs(**kwargs)

    def _init_kwargs(self, **kwargs):
        """
        Override this to parse and initialize
        any unknown keyword arguments
        """
        if kwargs:
            raise ValueError(kwargs)

    def __enter__(self):
        return self

    def __exit__(self, *args, **kwargs):
        self.close()

    def run(self):
        try:
            self.readwrite()
        finally:
            self.close()

    def communicate(self, input=None):
        if input is not None and self.stdin:
            self.stdin.write(input)
            self.stdin.close()

        stdout = None
        if self.stdout:
            stdout = io.BytesIO()
            self._stdout = stdout

        stderr = None
        if self.stderr:
            stderr = io.StringIO()
            self._stderr = stderr
        
        self.readwrite()

        if stdout:
            stdout.seek(0)
            stdout = stdout.read()
        
        if stderr:
            stderr.seek(0)
            stderr = stderr.read()

        return stdout, stderr

    def __start(self, daemon=False):
        raise NotImplementedError
        try:
            return self.start_process(daemon=daemon)
        except:
            return self.start_thread(daemon=daemon)

    def start_process(self, daemon=False):
        p = multiprocessing.Process(
                target=self.run,
                daemon=daemon,
        )
        p.start()
        if isinstance(self._stdout, NetcatPipeWriter):
            self._stdout.close()
        return p

    def start_thread(self, daemon=False):
        t = threading.Thread(
                target=self.run,
                daemon=daemon,
        )
        t.start()
        return t

    def close(self):
        """
        Override to add any cleanup code.
        """
        pass

    def _print_message(self, message, file=None):
        if message:
            if file is None:
                file = self._stderr
            try:
                file.write(message+'\n')
            except TypeError:
                file.write(message.encode()+b'\n')
            file.flush()

    def print_verbose(self, message):
        if self.v:
            self._print_message(message, file=self._stderr)

    def print_debug(self, message):
        if self.D:
            self._print_message(message, file=self._stderr)


[docs] class NetcatConnection(NetcatContext): """ Wraps a socket object to provide Netcat-like functionality. :param q: Quit the readwrite loop after EOF on stdin and delay of secs. :type q: int, optional You can use sub-classes of this class as a context manager using the "with" statement: .. code-block:: python with NetcatConnection(...) as nc: nc.readwrite() If you choose not to use the "with" statement, please make sure to use the close() method after use: .. code-block:: python nc = NetcatConnection(...) nc.readwrite() nc.close() """ C = False d = False i = 0 q = 0 w = None plen = 2048 def __init__(self, net, C=None, d=None, i=None, q=None, w=None, **kwargs): super(NetcatConnection, self).__init__(**kwargs) self.net = net self.dest, self.port = self._getpeername(net) if C is not None: self.C = C if d is not None: self.d = d if i is not None: self.i = i if q is not None: self.q = q if w is not None: self.w = w
[docs] @classmethod def connect(cls, dest, port, **kwargs): """ Factory method to connect to a server and return a NetcatConnection instance. This method should be implemented by a sub-class. :param dest: The destination hostname or IP address to connect to. :type dest: str :param port: The port number to connect to. :type port: int :param kwargs: Any other keyword arguments get passed to __init__. :returns: Returns a subclass of :class:`pync.NetcatConnection` once a connection has been established. :rtype: :class:`pync.NetcatConnection` :Example: .. code-block:: python with NetcatConnection.connect('localhost', 8000) as conn: conn.readwrite() """ raise NotImplementedError
[docs] @classmethod def listen(cls, dest, port, **kwargs): """ Factory method to listen for a connection and return a NetcatConnection instance. This method should be implemented by a sub-class. :param dest: The hostname or IP address to bind to. :type dest: str :param port: The port number to bind to. :type port: int :param kwargs: Any other keyword arguments get passed to __init__. :returns: Returns a subclass of :class:`pync.NetcatConnection` once a connection has been established. :rtype: :class:`pync.NetcatConnection` :Example: .. code-block:: python with NetcatConnection.listen('localhost', 8000) as conn: conn.readwrite() """ raise NotImplementedError
@property def w_timeout(self): return self.w def _getpeername(self, sock): try: # IPv4 dest, port = sock.getpeername() except ValueError: # IPv6 dest, port, _, _ = sock.getpeername() return dest, port def recv(self, n, blocking=True): if blocking: return self.net.recv(n) try: can_read, _, _ = select.select([self.net], [], [], 0) except ValueError: return self.net.recv(n) if self.net in can_read: return self.net.recv(n) def send(self, data): self.net.sendall(data)
[docs] def close(self): super(NetcatConnection, self).close() self.net.close()
def shutdown(self, how): try: return self.net.shutdown(how) except (socket.error, OSError): pass def shutdown_rd(self): self.shutdown(socket.SHUT_RD) def shutdown_wr(self): self.shutdown(socket.SHUT_WR)
[docs] def readwrite(self): """ The main loop to read and write i_o. Read from stdin and send to network. Receive from network and write to stdout. Write verbose/debug/error messages to stderr. This loop is based on the netcat-openbsd 1.105-7 ubuntu version. :Example: .. code-block:: python with NetcatConnection(sock) as nc: nc.readwrite() """ netin_eof, stdin_eof = False, None time_now, time_sleep = time.time, time.sleep last_io, plen = time_now(), self.plen carriage_return, quit_eof = self.C, self.q i, timeout = self.i, self.w_timeout net_send, net_recv = self.send, self.recv net_shutdown_rd, net_shutdown_wr = self.shutdown_rd, self.shutdown_wr stdin_detach = self.d stdin_read = self._stdin.read stdout_write = self._stdout.write #stdout_flush = self._stdout.flush try: while not netin_eof: sleep = True if i: time_sleep(i) # netin try: net_data = net_recv(plen, blocking=False) except (socket.error, OSError): return if net_data: # stdout stdout_write(net_data) #stdout_flush() last_io, sleep = time_now(), False elif net_data is not None: # netin EOF stdout_write(b'') net_shutdown_rd() netin_eof = True # stdin if not stdin_detach: try: stdin_data = stdin_read(plen) except EOFError: stdin_data = b'' # netout if stdin_data: if carriage_return: stdin_data = stdin_data.replace(b'\n', b'\r\n') try: net_send(stdin_data) except socket.error as e: if e.errno != errno.EPIPE: # Not a broken pipe. raise # Broken pipe. # netin connection lost return last_io, sleep = time_now(), False elif stdin_data is not None: # stdin EOF if not stdin_eof: stdin_eof = time_now() # If the user asked to exit on EOF, do it if quit_eof == 0: net_shutdown_wr() #self._stdin.close() # If the user asked to die after a while, arrange for it if quit_eof > 0: stdin_eof_elapsed = time_now() - stdin_eof if stdin_eof_elapsed >= quit_eof: return if timeout is not None: idle_time_elapsed = time_now() - last_io if idle_time_elapsed >= timeout: return if sleep: time_sleep(.001) except NetcatStopReadWrite: # IO has requested to stop the readwrite loop. pass
[docs] class NetcatTCPConnection(NetcatConnection): """ Wraps a TCP socket to provide Netcat-like functionality. """
[docs] @classmethod def connect(cls, dest, port, **kwargs): """ Factory method to connect to a TCP server and return a :class:`pync.NetcatTCPConnection` object. :param dest: The destination hostname or IP address to connect to. :type dest: str :param port: The port number to connect to. :type port: int :param kwargs: Any other keyword arguments get passed to __init__. :rtype: :class:`pync.NetcatTCPConnection` :Example: .. code-block:: python from pync import NetcatTCPConnection with NetcatTCPConnection.connect('localhost', 8000) as conn: conn.readwrite() """ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.connect((dest, port)) return cls(sock, **kwargs)
[docs] @classmethod def listen(cls, dest, port, **kwargs): """ Factory method to listen for an incoming TCP connection and return a :class:`pync.NetcatTCPConnection` object. :param dest: The destination hostname or IP address to bind to. :type dest: str :param port: The port number to bind to. :type port: int :param kwargs: Any other keyword arguments get passed to __init__. :rtype: :class:`pync.NetcatTCPConnection` :Example: .. code-block:: python from pync import NetcatTCPConnection with NetcatTCPConnection.listen('localhost', 8000) as conn: conn.readwrite() """ server_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) server_sock.bind((dest, port)) server_sock.listen(1) # ctrl-c interrupt doesn't seem to break out of server accept. # So using select for non-blocking server accept. while True: readables, _, _ = select.select([server_sock], [], [], .002) if server_sock in readables: sock, _ = server_sock.accept() break server_sock.close() return cls(sock, **kwargs)
[docs] class NetcatUDPConnection(NetcatConnection): """ Wraps a UDP socket object to provide Netcat-like functionality. """
[docs] @classmethod def connect(cls, dest, port, **kwargs): """ :TODO: """ # TODO raise NotImplementedError
[docs] @classmethod def listen(cls, dest, port, **kwargs): """ :TODO: """ raise NotImplementedError
def recv(self, *args, **kwargs): try: return super(NetcatUDPConnection, self).recv(*args, **kwargs) except (socket.error, compat.ConnectionRefusedError): raise NetcatStopReadWrite
class ConnectionRefused(Exception): ''' Same as ConnectionRefusedError but passes back the dest and port of the refused connection. ''' def __init__(self, dest, port): self.dest = dest self.port = port class NetcatIterator(NetcatContext): ''' Base class for Netcat clients and servers. NetcatClients can iterate through one or more ports and NetcatServers can accept one or more connections. ''' Connection = None c = None e = None T = None y = None Y = None allow_reuse_port = True def __init__(self, c=None, e=None, T=None, y=None, Y=None, *args, **kwargs): super(NetcatIterator, self).__init__(*args, **kwargs) if c is not None: self.c = c if e is not None: self.e = e if T is not None: self.T = T if y is not None: self.Y = Y self._proc = None def _init_kwargs(self, **kwargs): self._conn_kwargs = kwargs def _init_connection(self, sock): inout = dict( stdin=NetcatIO(reader=self._stdin, writer=self.stdin), stdout=NetcatIO(reader=self.stdout, writer=self._stdout), stderr=NetcatIO(reader=self.stderr, writer=self._stderr), ) proc = None if self.c: cmd = self.c sh = True try: proc = NetcatPopen(cmd, shell=sh, stdin=PIPE, stdout=PIPE, stderr=STDOUT, ) except (FileNotFoundError, OSError) as e: raise NetcatError(str(e)) elif self.e: cmd = shlex.split(self.e) sh = False try: proc = NetcatPopen(cmd, shell=sh, stdin=PIPE, stdout=PIPE, stderr=STDOUT, ) except (FileNotFoundError, OSError) as e: raise NetcatError(str(e)) elif self.y: code = self.y proc = NetcatPythonProcess(code) elif self.Y: filename = self.Y proc = NetcatPythonProcess.from_file(filename) if proc is not None: inout.update(stdin=proc.stdout, stdout=proc.stdin) self._proc = proc self._conn_kwargs.update(inout) return self.Connection(sock, **self._conn_kwargs) def __iter__(self): return self.iter_connections() def __next__(self): return self.next_connection() @property def T_keyword(self): ''' Returns IP TOS integer value. ''' T = self.T if T in TOSKEYWORDS: T = TOSKEYWORDS[T] return int(T) def iter_connections(self): ''' Override in subclass Iterate through and yield each connection. Close each connection before moving on to the next. ''' raise NotImplementedError def next_connection(self): ''' Override in subclass Return the next NetcatConnection. ''' raise NotImplementedError def readwrite(self): for conn in self: conn.readwrite() def _set_common_sockopts(self, sock): if self.allow_reuse_port: sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) if self.b: sock.setsockopt(socket.IPPROTO_TCP, socket.SO_BROADCAST, 1) if self.D: sock.setsockopt(socket.SOL_SOCKET, socket.SO_DEBUG, 1) if self.T: sock.setsockopt(socket.IPPROTO_IP, socket.IP_TOS, self.T_keyword) if self.I: sock.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, self.I) if self.O: sock.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, self.O) def _getaddrinfo(self, addr, port): # Used to raise socket error on bad address. try: return socket.getaddrinfo( addr, port, self.address_family, 0, 0, self.flags, ) except socket.error as e: raise NetcatSocketError(e) def close(self): super(NetcatIterator, self).close() if self._proc is not None: self._proc.close()
[docs] class NetcatClient(NetcatIterator): """ A Netcat client is iterable. You can pass one or more ports and iterate through each :class:`pync.NetcatConnection`. :param dest: The destination hostname or IP address to connect to. :type dest: str :param port: The port number(s) to connect to. :type port: int, list(int) :param e: Execute a command upon connection. :type e: str, optional :param z: Set to True to turn Zero i_o on (connect then close). Useful for simple port scanning. :type z: bool, optional You can use sub-classes of this class as a context manager using the "with" statement: .. code-block:: python with NetcatClient(...) as nc: nc.readwrite() If you choose not to use the "with" statement, please make sure to use the close() method after use: .. code-block:: python nc = NetcatClient(...) nc.readwrite() nc.close() :Example: .. code-block:: python :caption: We can connect to multiple ports one after another by passing a list of ports. with NetcatClient('localhost', [8000, 8001]) as nc: for connection in nc: connection.readwrite() .. code-block:: python :caption: Using the "z" and "v" options, we can perform a simple port scan. with NetcatClient('localhost', [8000, 8002], z=True, v=True) as nc: nc.readwrite() """ protocol_name = '' address_family = socket.AF_INET socket_type = None v_conn_succeeded = 'Connection to {dest} {port} port [{proto_name}/{proto}] succeeded!' v_conn_refused = 'connect to {dest} port {port} ({proto_name}) failed: Connection refused' _4 = True _6 = False b = False c = None D = False e = None I = None n = False O = None P = None p = 0 r = False s = '' w = None X = '5' x = None y = None Y = None z = False def __init__(self, dest, port, _4=None, _6=None, b=None, c=None, D=None, e=None, I=None, n=None, O=None, P=None, p=None, r=None, s=None, w=None, X=None, x=None, y=None, Y=None, z=None, **kwargs): super(NetcatClient, self).__init__(**kwargs) self.dest, self.port = dest, port if _4 is not None: self._4 = _4 if _6 is not None: self._6 = _6 if b is not None: self.b = b if c is not None: self.c = c if D is not None: self.D = D if e is not None: self.e = e if I is not None: self.I = I if n is not None: self.n = n if O is not None: self.O = O if p is not None: self.p = p if r is not None: self.r = r if s is not None: self.s = s if w is not None: self.w = w if X is not None: self.X = X if x is not None: self.x = x if y is not None: self.y = y if Y is not None: self.Y = Y if z is not None: self.z = z self._conn_kwargs['w'] = self.w if isinstance(self.port, int): # Only one port passed, wrap it in a list # for the __iter__ function. self.port = [self.port] if self.r: self.port = list(self.port) random.shuffle(self.port) self._iterports = iter(self.port) if self._6: self.address_family = socket.AF_INET6 self.flags = 0 if self.n: self.flags = socket.AI_NUMERICHOST @property def X_proto(self): protocols = { '5': socks.SOCKS5, '4': socks.SOCKS4, 'connect': socks.HTTP } return protocols[self.X] @property def x_addr(self): return self.x.split(':', 1)[0] @property def x_port(self): defaults = { '5': 1080, '4': 1080, 'connect': 3128, } try: port = self.x.split(':', 1)[1] except IndexError: port = defaults[self.X] try: port = int(port) except (TypeError, ValueError): port = repr(port) return port @property def w_timeout(self): return self.w
[docs] def iter_connections(self): while True: try: nc_conn = self.next_connection() except StopIteration: # No more ports to connect to. # Exit loop return except ConnectionRefused: # Move onto next connection if any errors. continue try: if not self.z: yield nc_conn finally: nc_conn.close()
def _conn_refused(self, port, dest=None): if dest is None: dest = self.dest self.print_verbose( self.v_conn_refused.format( dest=dest, port=port, proto_name=self.protocol_name, ), ) raise ConnectionRefused(self.dest, port)
[docs] def next_connection(self): # This will raise StopIteration when no more ports. port = next(self._iterports) try: nc_conn = self._create_connection((self.dest, port)) except compat.ConnectionRefusedError: self._conn_refused(port) except socks.ProxyError as e: if e.socket_err and e.socket_err.errno == errno.ECONNREFUSED: self._conn_refused(self.x_port, dest=self.x_addr, ) raise NetcatProxyError(e) except socket.error as e: if e.errno != errno.ECONNREFUSED: raise NetcatSocketError(e) self._conn_refused(port) else: self._conn_succeeded(port) if self.z: # If zero io mode, close the connection. nc_conn.close() return nc_conn
def _conn_succeeded(self, port, dest=None): if dest is None: dest = self.dest proto = '*' if not self.n: try: proto = socket.getservbyport(port, self.protocol_name) except (socket.error, OSError): pass self.print_verbose( self.v_conn_succeeded.format( dest=dest, port=port, proto_name=self.protocol_name, proto=proto, ), ) def _create_connection(self, addr): dest, port = addr addrinfo = self._getaddrinfo(dest, port) sock = self._client_init() self._client_bind(sock) self._client_connect(sock, addr) nc_conn = self._init_connection(sock) return nc_conn def _client_init(self): if self.x: # proxy socket addrinfo = self._getaddrinfo(self.x_addr, self.x_port) s = socks.socksocket(self.address_family, self.socket_type) s.set_proxy( proxy_type=self.X_proto, addr=self.x_addr, port=self.x_port, username=self.P, ) return s return socket.socket(self.address_family, self.socket_type) def _client_bind(self, sock): self._set_common_sockopts(sock) if self.s or self.p: source = self.s or None port = self.p or None addrinfo = self._getaddrinfo(source, port) sock.bind((self.s, self.p)) def _client_connect(self, sock, addr): if self.w_timeout: sock.settimeout(self.w_timeout) sock.connect(addr) sock.settimeout(None)
[docs] class NetcatTCPClient(NetcatClient): """ A :class:`pync.NetcatClient` for the Transmission Control Protocol. """ protocol_name = 'tcp' Connection = NetcatTCPConnection socket_type = socket.SOCK_STREAM
[docs] class NetcatUDPClient(NetcatClient): """ A :class:`pync.NetcatClient` for the User Datagram Protocol. """ protocol_name = 'udp' Connection = NetcatUDPConnection socket_type = socket.SOCK_DGRAM udp_scan_timeout = 3 def _client_connect(self, sock, addr): super(NetcatUDPClient, self)._client_connect(sock, addr) self._udptest(sock) def _udptest(self, sock): for i in compat.range(2): sock.sendall(b'X') timeout = self.w_timeout if timeout is None: timeout = self.udp_scan_timeout # Give the remote host some time to reply. for i in compat.range(0, timeout): time.sleep(1) sock.sendall(b'X')
[docs] class NetcatServer(NetcatIterator): """ A Netcat server is iterable. You can iterate through each incoming connection. :param port: The port number to bind the server to. :type port: int :param dest: The hostname or IP address to bind the server to. :type dest: str, optional :param e: Execute a command upon connection. :type e: str, optional :param k: Set to True to keep the server open between connections. :type k: bool, optional :param kwargs: Any other keyword arguments get passed to each connection. You can use sub-classes of this class as a context manager using the "with" statement: .. code-block:: python with NetcatServer(...) as nc: nc.readwrite() If you don't use the "with" statement, please make sure to use the close() method after use: .. code-block:: python nc = NetcatServer(...) nc.readwrite() nc.close() :Example: .. code-block:: python :caption: Use the "k" option to keep the server open and iterate through each :class:`pync.NetcatConnection`. with NetcatServer(8000, dest='localhost', k=True) as nc: for connection in nc: connection.readwrite() """ protocol_name = '' address_family = socket.AF_INET socket_type = None v_listening = 'Listening on [{dest}] (family {family}, port {port})' v_conn_accepted = 'Connection from [{dest}] port {port} [{proto_name}/{proto}] accepted (family {family}, sport {sport})' v_listening_again = 'Connection closed, listening again.' _4 = True _6 = False b = False c = None D = False e = None I = None k = False n = False O = None y = None Y = None def __init__(self, port, dest='', _4=None, _6=None, b=None, c=None, D=None, e=None, I=None, k=None, n=None, O=None, y=None, Y=None, **kwargs): super(NetcatServer, self).__init__(**kwargs) self.dest = dest if dest == '': # getaddrinfo doesn't accept an empty string. # set to 0.0.0.0 to listen on all interfaces. self.dest = '0.0.0.0' self.port = port if not isinstance(port, int) and not isinstance(port, str): # port is not an int or a string. # getaddrinfo expects an int or string. # All objects have __repr__ so call repr to get string. self.port = repr(port) if _4 is not None: self._4 = _4 if _6 is not None: self._6 = _6 if b is not None: self.b = b if c is not None: self.c = c if D is not None: self.D = D if e is not None: self.e = e if I is not None: self.I = I if k is not None: self.k = k if n is not None: self.n = n if O is not None: self.O = O if y is not None: self.y = y if Y is not None: self.Y = Y if _6: self.address_family = socket.AF_INET6 self.flags = 0 if self.n: self.flags = socket.AI_NUMERICHOST self._sock = socket.socket(self.address_family, self.socket_type) bind_and_activate = True if bind_and_activate: try: self._server_bind() self._server_activate() except: self._server_close() raise def _listening(self): self.print_verbose(self.v_listening.format( dest=self.dest, family=self.address_family, port=self.port, )) def _listening_again(self): self.print_verbose(self.v_listening_again)
[docs] def iter_connections(self): self._listening() try: nc_conn = self.next_connection() except StopIteration: return try: yield nc_conn finally: self._close_request(nc_conn) if self.k: while True: self._listening_again() try: nc_conn = self.next_connection() except StopIteration: return try: yield nc_conn finally: self._close_request(nc_conn)
def _conn_accepted(self, cli_dest, cli_port): proto = '*' if not self.n: try: proto = socket.getservbyport(self.port, self.protocol_name) except (socket.error, OSError): pass self.print_verbose(self.v_conn_accepted.format( dest=cli_dest, port=self.port, proto_name=self.protocol_name, proto=proto, family=self.address_family, sport=cli_port, ))
[docs] def next_connection(self): while True: try: can_read, _, _ = select.select([self._sock], [], [], .002) except (ValueError, socket.error): # Bad / closed socket. # This can occur when the server is closed. raise StopIteration if self._sock in can_read: cli_sock, cli_addr = self._get_request() try: # IPv4 cli_dest, cli_port = cli_addr except ValueError: # IPv6 cli_dest, cli_port, _, _ = cli_addr nc_conn = self._init_connection(cli_sock) break self._conn_accepted(cli_dest, cli_port) return nc_conn
def _server_bind(self): addrinfo = self._getaddrinfo(self.dest, self.port) self._set_common_sockopts(self._sock) try: self._sock.bind((self.dest, self.port)) except socket.error as e: raise NetcatSocketError(e) def _server_activate(self): pass def _server_close(self): self._sock.close() def _get_request(self): ''' Override in subclass Accept connection. Return (socket, addr) tuple. ''' raise NotImplementedError def _close_request(self, request): request.close()
[docs] def close(self): """ Close the server. """ super(NetcatServer, self).close() self._server_close()
[docs] class NetcatTCPServer(NetcatServer): """ A :class:`pync.NetcatServer` for the Transmission Control Protocol. """ protocol_name = 'tcp' Connection = NetcatTCPConnection address_family = socket.AF_INET socket_type = socket.SOCK_STREAM request_queue_size = 1
[docs] def next_connection(self): nc_conn = super(NetcatTCPServer, self).next_connection() if not self.k: self._server_close() return nc_conn
def _server_activate(self): self._sock.listen(self.request_queue_size) def _get_request(self): return self._sock.accept()
[docs] class NetcatUDPServer(NetcatServer): """ A :class:`pync.NetcatServer` for the User Datagram Protocol. """ protocol_name = 'udp' Connection = NetcatUDPConnection address_family = socket.AF_INET socket_type = socket.SOCK_DGRAM max_packet_size = 8192 def _get_request(self): data, addr = self._sock.recvfrom(self.max_packet_size) try: # py3 self._stdout.buffer.write(data) except AttributeError: # py2 self._stdout.write(data) self._sock.connect(addr) return self._sock, addr def _close_request(self, request): if not self.k: request.close()
class NetcatStopReadWrite(Exception): """ Exception to stop the readwrite loop. """ class NetcatPythonStdinWriter(PythonStdinWriter, NetcatIOBase): def write(self, *args, **kwargs): try: return super(NetcatPythonStdinWriter, self).write(*args, **kwargs) except OSError: raise NetcatStopReadWrite class NetcatPythonStdoutReader(PythonStdoutReader, NetcatIOBase): def read(self, *args, **kwargs): try: return super(NetcatPythonStdoutReader, self).read(*args, **kwargs) except ProcessTerminated: raise NetcatStopReadWrite class NetcatPythonProcess(PythonProcess): StdinWriter = NetcatPythonStdinWriter StdoutReader = NetcatPythonStdoutReader class NetcatPopen(NonBlockingPopen): """ A non-blocking process to be used with Netcat classes. Use this instead of <subprocess.Popen>. """ def __init__(self, *args, **kwargs): super(NetcatPopen, self).__init__(*args, **kwargs) self.stdin = NetcatProcessWriter(self.stdin) self.stdout = NetcatProcessReader(self.stdout) class NetcatProcessIOBase(NetcatIOBase): def __init__(self, f): self.file = f super(NetcatProcessIOBase, self).__init__() class NetcatProcessWriter(NetcatProcessIOBase): def write(self, data): try: self.file.write(data) except OSError: raise NetcatStopReadWrite self.flush() def flush(self): self.file.flush() class NetcatProcessReader(NetcatProcessIOBase): def read(self, n): try: return self.file.read(n) except ProcessTerminated: raise NetcatStopReadWrite class NetcatPortAction(argparse.Action): def __call__(self, parser, namespace, values, option_string=None): # If one port is given on the command line, set that as value. # If more that one is given, sort and chain as one iterator. if not values: return if len(values) == 1 and values[0].start == (values[0].stop - 1): # Only one port given. setattr(namespace, self.dest, values[0].start) return # sort the list of port ranges. sorted_values = sorted(values, key=lambda r: r.start) # chain the port ranges into one iter. chained_values = itertools.chain(*sorted_values) setattr(namespace, self.dest, chained_values) return class NetcatArgumentParser(GroupingArgumentParser): prog = 'Netcat' usage = ("%(prog)s [-46bCDdhklnruvz] [-c string] [-e filename] [-I length]" "\n\t [-i interval] [-O length] [-P proxyuser] [-p source_port]" "\n\t [-q seconds] [-s source] [-T keyword] [-w timeout]" "\n\t [-X proto] [-x addr[:port]]" "\n\t [-Y pyfile] [-y pycode] [dest] [port]" ) description = 'arbitrary TCP and UDP connections and listens (Netcat for Python).' add_help = False PortAction = NetcatPortAction def __init__(self, *args, **kwargs): super(NetcatArgumentParser, self).__init__(*args, **kwargs) self.add_argument('-4', help='Use IPv4', action='store_true', dest='_4', ) self.add_argument('-6', help='Use IPv6', action='store_true', dest='_6', ) self.add_argument('-b', help='Allow broadcast', action='store_true', ) self.add_argument('-c', help='specify shell commands to exec after connect (use with caution).', metavar='string', ) self.add_argument('-C', help='Send CRLF as line-ending', action='store_true', ) self.add_argument('-D', help='Enable the debug socket option', action='store_true', ) self.add_argument('-d', help='Detach from stdin', action='store_true', ) self.add_argument('-e', help='specify filename to exec after connect (use with caution).', metavar='filename', ) self.add_argument('-h', '--help', help='show this help message and exit.', action='help', ) self.add_argument('-I', help='TCP receive buffer length', metavar='length', type=int, ) self.add_argument('-i', help='Delay interval for lines sent, ports scanned', type=int, metavar='secs', ) self.add_argument('-k', group='server arguments', help='Keep inbound sockets open for multiple connects', action='store_true', ) self.add_argument('-l', group='server arguments', help='Listen mode, for inbound connects', action='store_true', ) self.add_argument('-n', help='Suppress name/port resolutions', action='store_true', ) self.add_argument('-O', help='TCP send buffer length', metavar='length', type=int, ) self.add_argument('-P', group='client arguments', help='Username for proxy authentication', metavar='proxyuser', ) self.add_argument('-p', help='Specify local port for remote connects', metavar='source_port', type=self.source_port, ) self.add_argument('-q', help='quit after EOF on stdin and delay of seconds', metavar='seconds', default=0, type=int, ) self.add_argument('-r', group='client arguments', help='Randomize remote ports', action='store_true', ) self.add_argument('-s', group='client arguments', help='Local source address', metavar='source', ) self.add_argument('-T', help='Set IP Type of Service', metavar='keyword', type=self.toskeyword, ) self.add_argument('-u', help='UDP mode [default: TCP]', action='store_true', ) self.add_argument('-v', help='Verbose', action='store_true', ) self.add_argument('-w', help='Timeout for connects and final net reads', metavar='secs', type=self.timeout, ) self.add_argument('-X', group='client arguments', help='Proxy protocol: "4", "5" (SOCKS) or "connect"', metavar='proto', choices=['5', '4', 'connect'], default='5', ) self.add_argument('-x', group='client arguments', help='Specify proxy address and port', metavar='addr[:port]', ) self.add_argument('-Y', help='specify python file to exec after connect (use with caution).', metavar='pyfile', ) self.add_argument('-y', help='specify python code to exec after connect (use with caution).', metavar='pycode', ) self.add_argument('-z', group='client arguments', help='Zero-I/O mode [used for scanning]', action='store_true', ) self.add_argument('dest', help='The destination host name or ip to connect or bind to', nargs='?', default='', metavar='dest', ) self.add_argument('port', help='The port number to connect or bind to', type=self.port, metavar='port', nargs='*', action=self.PortAction, ) def _valid_port(self, value): return 1 <= int(value) <= 65535 def timeout(self, value): value = int(value) if value < 0: raise ValueError('timeout too small') return value def toskeyword(self, value): if value in TOSKEYWORDS: return TOSKEYWORDS[value] try: value = int(value) except ValueError: # value might be a hex number. value = int(value, 16) if 0 <= value <= 255: return value raise ValueError('illegal tos value {}'.format(value)) def source_port(self, value): msg = 'invalid source_port value: {}' if not self._valid_port(value): raise ValueError(msg.format(value)) return int(value) def port(self, value): # This should always return a range of ports. # Even if only one port is given. # # The PortAction then turns it into a single port # if one port is given or a chain of sorted port # ranges if more than one port is given. msg = 'invalid port value: {}' try: # assume port value is a range. # e.g 8000-8005 start_port, end_port = [int(x) for x in value.split('-')] except ValueError: # port value is not a range. value = int(value) if not self._valid_port(value): raise ValueError(msg.format(value)) return compat.range(value, value+1) if start_port > end_port: start_port, end_port = end_port, start_port for p in [start_port, end_port]: if not self._valid_port(p): raise ValueError(msg.format(p)) return compat.range(start_port, end_port+1) def parse_args(self, args): # First, modify the args to allow a negative number # to be passed to the -q option. # The argparse module doesn't seem to provide a # solution to this problem so modifying the args # directly before parsing them seems to be the # only option. # # For a negative number to work, I just need to # remove the space between -q and it's argument # then argparse won't complain with an error. # # pync -q -1 localhost 8000 -> pync -q-1 localhost 8000 _args = list() skip = False for i, a in enumerate(args): if skip: skip = False continue if a.startswith('-') and a.endswith('q'): try: a += args[i+1] except IndexError: pass else: skip = True _args.append(a) args = _args grouped_args = self.group_parse_args(args) args = grouped_args['general arguments'] client_args = grouped_args['client arguments'] server_args = grouped_args['server arguments'] if server_args.l: # Server mode. if args.dest and args.port and not args.p: # pync -l localhost 8000 pass elif args.dest and not args.port and not args.p: # pync -l 8000 # Get the port from args.dest. # This will need feeding through the parser # again to detect any port number errors. args.port = args.dest args.dest = '' test_args = ['dest', args.port] test_args = self.group_parse_args(test_args)['general arguments'] args.port = test_args.port elif not args.dest and not args.port and args.p: # pync -lp 8000 pass elif args.dest and not args.port and args.p: # pync -lp 8000 localhost pass elif args.dest and args.port and args.p: # pync -lp 8000 localhost 8001 pass else: self.print_usage(file=self.stderr) self.exit() else: # Client mode. if args.dest and args.port: # pync localhost 8000 pass elif args.dest and args.port and args.p: # pync -p 1234 localhost 8000 pass else: self.print_usage(file=self.stderr) self.exit() kwargs = dict() kwargs.update(vars(args)) if server_args.l: kwargs.update(vars(server_args)) else: kwargs.update(vars(client_args)) return argparse.Namespace(**kwargs)
[docs] class Netcat(object): """ Factory class that returns the correct Netcat object based on the arguments given. :param dest: The IP address or hostname to connect or bind to depending on the "l" parameter. :type dest: str, optional :param port: The port number to connect or bind to depending on the "l" parameter. :type port: int, list(int) :param l: Set to True to create a server and listen for incoming connections. :type l: bool, optional :param u: Set to True to use UDP for transport instead of the default TCP. :type u: bool, optional :param p: The source port number to bind to. :type p: int, optional :param kwargs: All other keyword arguments get passed to the underlying Netcat class. You can use this class as a context manager using the "with" statement: .. code-block:: python with Netcat(...) as nc: nc.readwrite() If you use it without the "with" statement, please make sure to use the close method after use: .. code-block:: python nc = Netcat(...) nc.readwrite() nc.close() :Examples: .. code-block:: python :caption: Use the "l" option to create a :class:`pync.NetcatTCPServer` object. from pync import Netcat with Netcat(dest='localhost', port=8000, l=True) as nc: nc.readwrite() .. code-block:: python :caption: By default, without the "l" option, Netcat will return a :class:`pync.NetcatTCPClient` object. from pync import Netcat with Netcat(dest='localhost', port=8000) as nc: nc.readwrite() .. code-block:: python :caption: Create a :class:`pync.NetcatUDPServer` with the "u" and "l" options. from pync import Netcat with Netcat(dest='localhost', port=8000, l=True, u=True) as nc: nc.readwrite() .. code-block:: python :caption: And a :class:`pync.NetcatUDPClient` using only the "u" option. from pync import Netcat with Netcat(dest='localhost', port=8000, u=True) as nc: nc.readwrite() .. code-block:: python :caption: Any other keyword arguments get passed to the underlying Netcat class. from pync import Netcat # Use the "k" option to keep the server open between connections. with Netcat(dest='localhost', port=8000, l=True, k=True) as nc: nc.readwrite() .. code-block:: python :caption: Pass a list of ports to connect to one after the other. # Simple port scan example. from pync import Netcat # Use the "z" option to turn Zero i_o on (connect then close). # Use the "v" option to turn verbose output on to see connection success or failure. ports = [8000, 8003, 8002] with Netcat(dest='localhost', port=ports, z=True, v=True) as nc: nc.readwrite() """ ArgumentParser = NetcatArgumentParser TCPClient = NetcatTCPClient TCPServer = NetcatTCPServer UDPClient = NetcatUDPClient UDPServer = NetcatUDPServer stdin = sys.stdin stdout = sys.stdout stderr = sys.stderr #stdin = None #stdout = None #stderr = None def __new__(cls, dest='', port=None, l=False, u=False, p=None, stdin=None, stdout=None, stderr=None, **kwargs): stdin = stdin or cls.stdin stdout = stdout or cls.stdout stderr = stderr or cls.stderr kwargs.update(dict( stdin=stdin, stdout=stdout, stderr=stderr)) if l: if p is not None: port = p if u: return cls.UDPServer(port, dest=dest, **kwargs) else: return cls.TCPServer(port, dest=dest, **kwargs) else: if u: return cls.UDPClient(dest, port, p=p, **kwargs) else: return cls.TCPClient(dest, port, p=p, **kwargs)
[docs] @classmethod def from_args(cls, args, stdin=None, stdout=None, stderr=None): """ Create a Netcat object from command-line arguments instead of keyword arguments. :param args: A string containing the command-line arguments to create the Netcat instance with. :type args: str :param stdin: A file-like object to read outgoing network data from. :type stdin: file, optional :param stdout: A file-like object to write incoming network data to. :type stdout: file, optional :param stderr: A file-like object to write verbose/debug/error messages to. :type stderr: file, optional :Example: .. code-block:: python from pync import Netcat with Netcat.from_args('-l localhost 8000') as nc: nc.readwrite() """ stdin = stdin or cls.stdin stdout = stdout or cls.stdout stderr = stderr or cls.stderr _stdin, _stdout, _stderr = stdin, stdout, stderr if stdout == PIPE: _stdout = NetcatPipeIO() stdout = _stdout.writer elif stdout == QUEUE: _stdout = NetcatQueueIO() stdout = _stdout.writer if stderr == PIPE: _stderr = NetcatPipeIO() stderr = _stderr.writer elif stderr == QUEUE: _stderr = NetcatQueueIO() stderr = _stderr.writer try: # Assume args is a string and try to split it. args = shlex.split(args) except AttributeError: # args is not a string, assume it's a list. pass parser = cls.ArgumentParser(stdout=stdout, stderr=stderr) args = parser.parse_args(args) kwargs = dict() kwargs.update(vars(args)) kwargs.update(dict( stdin=_stdin, stdout=_stdout, stderr=_stderr, )) return cls(**kwargs)
class CompletedNetcat(argparse.Namespace): pass