#!/usr/bin/env python3 """ Netcat-like tool with encryption, UDP, file transfer, IPv6, authentication, PTY, and more. Single-file implementation using only the Python standard library. rrw.it Author: Generated by AI Date: 2025 """ import os import sys import socket import threading import subprocess import argparse import time import struct import hashlib import hmac import secrets import errno import pty import tty import termios import fcntl # ============================================================================ # Funzioni crittografiche (solo libreria standard) # ============================================================================ # Parametri Diffie-Hellman da RFC 3526 (2048-bit MODP group) DH_P = int( "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" "83655D23DCA3AD961C62F356208552BB9ED529077096966D" "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9" "DE2BCBF6955817183995497CEA956AE515D2261898FA0510" "15728E5A8AACAA68FFFFFFFFFFFFFFFF", 16 ) DH_G = 2 def dh_generate_keypair(): """Genera una coppia di chiavi DH: (privata, pubblica).""" private = secrets.randbelow(DH_P - 2) + 1 public = pow(DH_G, private, DH_P) return private, public def dh_compute_shared(private, peer_public): """Calcola il segreto condiviso.""" return pow(peer_public, private, DH_P) def kdf(shared_secret): """Deriva una chiave master di 32 byte dal segreto condiviso.""" secret_bytes = shared_secret.to_bytes((DH_P.bit_length() + 7) // 8, 'big') return hashlib.sha256(secret_bytes).digest() def derive_keys(master_key, password=None): """ Deriva le chiavi per cifratura, MAC e (opzionale) autenticazione. Restituisce un dizionario con le chiavi. """ keys = {} # Chiavi per cifratura (due direzioni) keys['send_cipher'] = hmac.new(master_key, b"cipher_send", hashlib.sha256).digest() keys['recv_cipher'] = hmac.new(master_key, b"cipher_recv", hashlib.sha256).digest() # Chiavi per MAC keys['send_mac'] = hmac.new(master_key, b"mac_send", hashlib.sha256).digest() keys['recv_mac'] = hmac.new(master_key, b"mac_recv", hashlib.sha256).digest() # Se c'è una password, deriviamo una chiave per l'autenticazione DH if password: # Usiamo PBKDF2 per derivare una chiave robusta dalla password salt = b"netcat-auth-salt" auth_key = hashlib.pbkdf2_hmac('sha256', password.encode(), salt, 100000, dklen=32) keys['auth_key'] = auth_key return keys def generate_keystream(key, nonce, length): """Genera un flusso di byte di lunghezza 'length' usando HMAC-SHA256 con nonce e contatore.""" result = b'' counter = 0 while len(result) < length: counter_bytes = counter.to_bytes(8, 'big') h = hmac.new(key, nonce + counter_bytes, hashlib.sha256) result += h.digest() counter += 1 return result[:length] def encrypt_message(key, plaintext): """Cifra un messaggio: restituisce nonce (8 byte) + ciphertext.""" nonce = secrets.token_bytes(8) keystream = generate_keystream(key, nonce, len(plaintext)) ciphertext = bytes(a ^ b for a, b in zip(plaintext, keystream)) return nonce + ciphertext def decrypt_message(key, nonce_cipher): """Decifra un messaggio: riceve nonce (8 byte) + ciphertext, restituisce plaintext.""" nonce = nonce_cipher[:8] ciphertext = nonce_cipher[8:] keystream = generate_keystream(key, nonce, len(ciphertext)) return bytes(a ^ b for a, b in zip(ciphertext, keystream)) def compute_mac(key, data): """Calcola HMAC-SHA256 di data.""" return hmac.new(key, data, hashlib.sha256).digest() # ============================================================================ # Funzioni di utilità # ============================================================================ def recv_exact(sock, n): """Legge esattamente n byte dal socket. Solleva ConnectionError se la connessione viene chiusa.""" data = b'' while len(data) < n: chunk = sock.recv(n - len(data)) if not chunk: raise ConnectionError("Connessione chiusa durante la lettura") data += chunk return data def progress_bar(current, total, bar_length=40): """Stampa una barra di progresso su stderr.""" percent = current / total arrow = '=' * int(round(percent * bar_length)) spaces = ' ' * (bar_length - len(arrow)) sys.stderr.write(f'\r[{arrow}{spaces}] {percent:.1%}') sys.stderr.flush() class ProgressFileReader: """Wrapper per leggere un file e mostrare una barra di progresso.""" def __init__(self, filename, verbose=False): self.file = open(filename, 'rb') self.size = os.path.getsize(filename) self.read_so_far = 0 self.verbose = verbose if verbose: print(f"Invio {self.size} byte da {filename}", file=sys.stderr) def read(self, size=-1): data = self.file.read(size) if data: self.read_so_far += len(data) if self.verbose: progress_bar(self.read_so_far, self.size) return data def close(self): self.file.close() if self.verbose: print(file=sys.stderr) # nuova linea dopo la barra # ============================================================================ # Funzioni per la creazione di socket (supporto IPv4/IPv6) # ============================================================================ def create_client_socket(host, port, family=socket.AF_UNSPEC, udp=False, source=None, timeout=None, numeric=False): """Crea e connette un socket client (TCP o UDP) con risoluzione DNS e indirizzo di origine.""" sock_type = socket.SOCK_DGRAM if udp else socket.SOCK_STREAM flags = socket.AI_ADDRCONFIG if numeric: flags |= socket.AI_NUMERICHOST try: addrinfo = socket.getaddrinfo(host, port, family, sock_type, 0, flags) except socket.gaierror as e: print(f"Errore risoluzione host: {e}", file=sys.stderr) sys.exit(1) fam, socktype, proto, _, sa = addrinfo[0] sock = socket.socket(fam, socktype, proto) if timeout: sock.settimeout(timeout) if source: try: src_addrinfo = socket.getaddrinfo(source, 0, fam, socktype, proto, flags) src_fam, _, _, _, src_sa = src_addrinfo[0] sock.bind(src_sa) except Exception as e: print(f"Errore bind su {source}: {e}", file=sys.stderr) sys.exit(1) if udp: sock.connect(sa) # imposta l'indirizzo di destinazione predefinito else: try: sock.connect(sa) except Exception as e: print(f"Errore di connessione: {e}", file=sys.stderr) sys.exit(1) return sock, sa def create_server_socket(bind_addr, port, family=socket.AF_UNSPEC, udp=False, numeric=False): """Crea un socket server in ascolto (TCP) o pronto per ricevere (UDP).""" sock_type = socket.SOCK_DGRAM if udp else socket.SOCK_STREAM flags = socket.AI_PASSIVE if numeric: flags |= socket.AI_NUMERICHOST if bind_addr is None: bind_addr = '' try: addrinfo = socket.getaddrinfo(bind_addr, port, family, sock_type, 0, flags) except socket.gaierror as e: print(f"Errore risoluzione indirizzo di bind: {e}", file=sys.stderr) sys.exit(1) fam, socktype, proto, _, sa = addrinfo[0] sock = socket.socket(fam, socktype, proto) sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sock.bind(sa) if not udp: sock.listen(5) return sock, sa # ============================================================================ # Classe principale per la gestione di una connessione # ============================================================================ class ConnectionHandler: def __init__(self, sock, addr, verbose=False, exec_cmd=None, close_on_eof=False, timeout=None, encrypt=False, password=None, keepalive=None, file_to_send=None, zero_io=False): self.sock = sock self.addr = addr self.verbose = verbose self.exec_cmd = exec_cmd self.close_on_eof = close_on_eof self.timeout = timeout self.encrypt = encrypt self.password = password self.keepalive = keepalive self.file_to_send = file_to_send self.zero_io = zero_io self.stop_event = threading.Event() self.encrypted = False self.send_key = None self.recv_key = None self.send_mac_key = None self.recv_mac_key = None self.last_activity = time.time() if self.timeout: self.sock.settimeout(self.timeout) def log(self, msg): if self.verbose: print(f"[*] {msg}", file=sys.stderr) # ------------------------------------------------------------------------ # Handshake crittografico (DH + autenticazione opzionale con password) # ------------------------------------------------------------------------ def perform_handshake(self, is_server): """Esegue lo scambio di chiavi Diffie-Hellman e l'autenticazione con password (se richiesta).""" try: priv, pub = dh_generate_keypair() pub_bytes = pub.to_bytes((DH_P.bit_length() + 7) // 8, 'big') # Invia la propria chiave pubblica self.sock.sendall(pub_bytes) # Riceve la chiave pubblica del peer peer_pub_bytes = recv_exact(self.sock, 256) peer_pub = int.from_bytes(peer_pub_bytes, 'big') # Se è richiesta autenticazione con password if self.password: # Deriva una chiave di autenticazione dalla password (stessa derivazione per entrambi i lati) auth_key = hashlib.pbkdf2_hmac('sha256', self.password.encode(), b"netcat-auth-salt", 100000, dklen=32) # Calcola MAC delle due chiavi pubbliche (in ordine: prima la propria, poi quella del peer) # Nota: server e client devono usare lo stesso ordine. Per semplicità usiamo: propria + peer mac = hmac.new(auth_key, pub_bytes + peer_pub_bytes, hashlib.sha256).digest() # Invia il MAC self.sock.sendall(mac) # Riceve il MAC del peer peer_mac = recv_exact(self.sock, 32) # Calcola il MAC atteso (peer + propria, perché il peer ha usato il suo ordine) expected_mac = hmac.new(auth_key, peer_pub_bytes + pub_bytes, hashlib.sha256).digest() if not hmac.compare_digest(peer_mac, expected_mac): raise ValueError("Autenticazione password fallita") # Calcola il segreto condiviso shared = dh_compute_shared(priv, peer_pub) master = kdf(shared) # Deriva tutte le chiavi simmetriche keys = derive_keys(master, self.password) # Assegna le chiavi in base al ruolo (server inverte send/recv) if is_server: self.send_key = keys['recv_cipher'] self.recv_key = keys['send_cipher'] self.send_mac_key = keys['recv_mac'] self.recv_mac_key = keys['send_mac'] else: self.send_key = keys['send_cipher'] self.recv_key = keys['recv_cipher'] self.send_mac_key = keys['send_mac'] self.recv_mac_key = keys['recv_mac'] self.encrypted = True self.log("Handshake crittografico completato" + (" con autenticazione password" if self.password else "")) except Exception as e: self.log(f"Handshake fallito: {e}") self.sock.close() sys.exit(1) # ------------------------------------------------------------------------ # Invio e ricezione cifrati con MAC # ------------------------------------------------------------------------ def send_encrypted(self, data): """Invia dati cifrati con MAC. I dati vengono suddivisi in blocchi.""" if not self.encrypted: return self.sock.sendall(data) max_chunk = 4096 offset = 0 while offset < len(data): chunk = data[offset:offset+max_chunk] offset += len(chunk) # Cifra il chunk nonce_cipher = encrypt_message(self.send_key, chunk) # Calcola il MAC mac = hmac.new(self.send_mac_key, nonce_cipher, hashlib.sha256).digest() # Invia lunghezza totale + nonce_cipher + mac msg = struct.pack('!I', len(nonce_cipher) + len(mac)) + nonce_cipher + mac self.sock.sendall(msg) self.last_activity = time.time() def recv_encrypted(self): """Generatore: riceve blocchi cifrati, li verifica e restituisce il plaintext.""" if not self.encrypted: data = self.sock.recv(4096) if data: yield data return while not self.stop_event.is_set(): try: header = recv_exact(self.sock, 4) total_len = struct.unpack('!I', header)[0] data = recv_exact(self.sock, total_len) nonce_cipher = data[:-32] mac = data[-32:] expected_mac = hmac.new(self.recv_mac_key, nonce_cipher, hashlib.sha256).digest() if not hmac.compare_digest(mac, expected_mac): if self.verbose: self.log("MAC verification failed") break plain = decrypt_message(self.recv_key, nonce_cipher) yield plain self.last_activity = time.time() except socket.timeout: continue except Exception as e: if self.verbose: self.log(f"Errore ricezione: {e}") break # ------------------------------------------------------------------------ # Gestione keep-alive # ------------------------------------------------------------------------ def _keepalive_thread(self): """Invia pacchetti keep-alive se il socket è rimasto inattivo per più di keepalive secondi.""" while not self.stop_event.is_set(): time.sleep(self.keepalive) if time.time() - self.last_activity > self.keepalive: try: if self.encrypted: self.send_encrypted(b'') else: self.sock.sendall(b'') except: break # ------------------------------------------------------------------------ # Gestione normale (stdin/stdout) # ------------------------------------------------------------------------ def _handle_stdin_stdout(self): """Scambio dati tra socket e stdin/stdout.""" def receive(): if self.encrypted: recv_gen = self.recv_encrypted() for plain in recv_gen: sys.stdout.buffer.write(plain) sys.stdout.buffer.flush() self.stop_event.set() else: while not self.stop_event.is_set(): try: data = self.sock.recv(4096) if not data: break sys.stdout.buffer.write(data) sys.stdout.buffer.flush() except socket.timeout: continue except Exception: break self.stop_event.set() def send(): while not self.stop_event.is_set(): try: data = sys.stdin.buffer.read1(4096) if not data: if self.close_on_eof: self.sock.shutdown(socket.SHUT_WR) break if self.encrypted: self.send_encrypted(data) else: self.sock.sendall(data) except socket.timeout: continue except Exception: break self.stop_event.set() t_recv = threading.Thread(target=receive, daemon=True) t_send = threading.Thread(target=send, daemon=True) t_recv.start() t_send.start() try: while not self.stop_event.is_set(): self.stop_event.wait(0.5) except KeyboardInterrupt: self.log("Interruzione da tastiera") finally: self.sock.close() # In modalità client o server singolo, usciamo dal processo. # In modalità server con -k, il main continua, quindi non chiamiamo os._exit. if not (getattr(args, 'keep', False) and getattr(args, 'listen', False)): os._exit(0) # ------------------------------------------------------------------------ # Gestione esecuzione comandi con PTY (shell interattiva) # ------------------------------------------------------------------------ def _handle_exec_pty(self): """Avvia il comando in un pseudo-terminale (solo Unix).""" pid, fd = pty.fork() if pid == 0: # processo figlio # Esegue il comando os.execvp('/bin/sh', ['sh', '-c', self.exec_cmd]) else: # processo padre # Imposta il fd in modalità non bloccante? No, useremo thread. def master_to_socket(): while not self.stop_event.is_set(): try: data = os.read(fd, 4096) if not data: break if self.encrypted: self.send_encrypted(data) else: self.sock.sendall(data) except OSError as e: if e.errno != errno.EIO: break except Exception: break self.stop_event.set() def socket_to_master(): if self.encrypted: recv_gen = self.recv_encrypted() for data in recv_gen: os.write(fd, data) else: while not self.stop_event.is_set(): try: data = self.sock.recv(4096) if not data: break os.write(fd, data) except socket.timeout: continue except Exception: break self.stop_event.set() t1 = threading.Thread(target=master_to_socket, daemon=True) t2 = threading.Thread(target=socket_to_master, daemon=True) t1.start() t2.start() try: while not self.stop_event.is_set(): self.stop_event.wait(0.5) except KeyboardInterrupt: self.log("Interruzione") finally: os.close(fd) self.sock.close() os._exit(0) # ------------------------------------------------------------------------ # Gestione esecuzione comandi con pipe (fallback non interattivo) # ------------------------------------------------------------------------ def _handle_exec_pipe(self): """Avvia il comando con pipe semplici (non interattivo).""" try: proc = subprocess.Popen( self.exec_cmd, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT ) except Exception as e: self.log(f"Impossibile avviare il comando: {e}") return def socket_to_process(): if self.encrypted: recv_gen = self.recv_encrypted() for data in recv_gen: proc.stdin.write(data) proc.stdin.flush() proc.stdin.close() else: while not self.stop_event.is_set(): try: data = self.sock.recv(4096) if not data: break proc.stdin.write(data) proc.stdin.flush() except socket.timeout: continue except Exception: break proc.stdin.close() self.stop_event.set() def process_to_socket(): while not self.stop_event.is_set(): try: data = proc.stdout.read(4096) if not data: break if self.encrypted: self.send_encrypted(data) else: self.sock.sendall(data) except socket.timeout: continue except Exception: break proc.terminate() self.stop_event.set() t_s2p = threading.Thread(target=socket_to_process, daemon=True) t_p2s = threading.Thread(target=process_to_socket, daemon=True) t_s2p.start() t_p2s.start() try: while not self.stop_event.is_set(): self.stop_event.wait(0.5) except KeyboardInterrupt: self.log("Interruzione da tastiera") finally: proc.terminate() self.sock.close() if not (getattr(args, 'keep', False) and getattr(args, 'listen', False)): os._exit(0) # ------------------------------------------------------------------------ # Gestione invio file con barra di progresso # ------------------------------------------------------------------------ def _handle_file_send(self): """Invia un file al peer e poi chiude la connessione.""" reader = ProgressFileReader(self.file_to_send, self.verbose) try: while True: data = reader.read(4096) if not data: break if self.encrypted: self.send_encrypted(data) else: self.sock.sendall(data) except Exception as e: self.log(f"Errore durante l'invio del file: {e}") finally: reader.close() self.sock.close() # ------------------------------------------------------------------------ # Metodo principale di avvio # ------------------------------------------------------------------------ def start(self, is_server): """Avvia la gestione della connessione in base alle opzioni.""" if self.encrypt: self.perform_handshake(is_server) if self.keepalive: threading.Thread(target=self._keepalive_thread, daemon=True).start() if self.zero_io: # Modalità zero I/O: dopo l'handshake, chiudi e basta self.sock.close() return if self.file_to_send: self._handle_file_send() return if self.exec_cmd: if hasattr(pty, 'fork') and os.name == 'posix': self._handle_exec_pty() else: self._handle_exec_pipe() else: self._handle_stdin_stdout() # ============================================================================ # Funzione principale con parsing degli argomenti # ============================================================================ def main(): parser = argparse.ArgumentParser( description="Netcat con crittografia, UDP, file transfer, IPv6, autenticazione, PTY e molto altro.", epilog=""" Esempi: # Server con shell remota cifrata (richiede password) %(prog)s -l -p 4444 -x -k miapassword -e /bin/bash -v # Client cifrato %(prog)s -x -k miapassword -v example.com 4444 # Trasferimento file cifrato con barra di progresso %(prog)s -l -p 1234 -x > file_ricevuto # server %(prog)s -x -f file_da_inviare server 1234 # client # Port scanning con -z for port in $(seq 1 1000); do %(prog)s -z -w 1 target.com $port && echo "porta $port aperta" done # Server UDP cifrato %(prog)s -l -u -x -k miapassword -p 5555 -v """, formatter_class=argparse.RawDescriptionHelpFormatter ) # Opzioni di base parser.add_argument("-l", "--listen", action="store_true", help="Modalità ascolto (server)") parser.add_argument("-p", "--port", type=int, required=True, help="Porta") parser.add_argument("-u", "--udp", action="store_true", help="Usa UDP invece di TCP") parser.add_argument("-x", "--encrypt", action="store_true", help="Abilita crittografia (richiede -k per autenticazione)") parser.add_argument("-k", "--password", metavar="PASSWORD", help="Password per autenticare lo scambio di chiavi (MITM protection)") parser.add_argument("-e", "--execute", metavar="COMANDO", help="Esegue il comando e collega I/O al socket") parser.add_argument("-f", "--file", metavar="FILE", help="Invia il file (client) e mostra progresso") parser.add_argument("-z", "--zero", action="store_true", help="Modalità zero I/O: si connette e chiude (utile per scan porte)") parser.add_argument("-v", "--verbose", action="store_true", help="Output verboso") # Opzioni di rete parser.add_argument("-4", "--ipv4", action="store_true", help="Forza IPv4") parser.add_argument("-6", "--ipv6", action="store_true", help="Forza IPv6") parser.add_argument("-n", "--numeric", action="store_true", help="Non risolvere i nomi (solo numerico)") parser.add_argument("-s", "--source", metavar="INDIRIZZO", help="Indirizzo di origine (client) o di bind (server)") parser.add_argument("-w", "--timeout", type=float, metavar="SEC", help="Timeout per connessione e I/O") parser.add_argument("-c", "--close-on-eof", action="store_true", help="Chiudi connessione quando stdin finisce (solo client)") parser.add_argument("-K", "--keepalive", type=float, metavar="SEC", help="Invia keep-alive ogni SEC secondi quando inattivo") # Opzioni server parser.add_argument("--keep", action="store_true", help="Accetta più connessioni (solo TCP)") parser.add_argument("host", nargs="?", help="Host remoto (obbligatorio in modalità client)") args = parser.parse_args() # Controlli di coerenza if args.encrypt and args.udp and not args.password: parser.error("UDP cifrato richiede una password (-k) per autenticare i datagrammi iniziali") if args.file and args.listen: parser.error("-f è solo per il client (invia file)") if args.zero and args.file: parser.error("-z e -f sono mutuamente esclusivi") if args.zero and args.execute: parser.error("-z e -e sono mutuamente esclusivi") if args.keep and args.udp: parser.error("--keep non supportato con UDP") # Famiglia indirizzi if args.ipv4 and args.ipv6: parser.error("Non puoi usare -4 e -6 insieme") family = socket.AF_INET6 if args.ipv6 else socket.AF_INET if args.ipv4 else socket.AF_UNSPEC if args.listen: # Modalità server if args.host: parser.error("In modalità server non si specifica host (usa -s per il bind)") if args.close_on_eof: parser.error("-c solo in modalità client") sock, sa = create_server_socket(args.source, args.port, family, args.udp, args.numeric) if args.verbose: proto = "UDP" if args.udp else "TCP" print(f"[*] In ascolto {proto} su {sa[0]}:{sa[1]}", file=sys.stderr) if args.udp: # UDP: ricevi il primo datagramma per individuare il client data, client_addr = sock.recvfrom(4096) if args.verbose: print(f"[*] Primo datagramma da {client_addr[0]}:{client_addr[1]}", file=sys.stderr) sock.connect(client_addr) # Wrapper per iniettare il primo datagramma nel flusso di ricezione class UDPSocketWithFirst: def __init__(self, sock, first_data): self.sock = sock self.first_data = first_data self.first_sent = False def recv(self, bufsize): if not self.first_sent and self.first_data is not None: self.first_sent = True return self.first_data return self.sock.recv(bufsize) def sendall(self, data): return self.sock.send(data) def close(self): self.sock.close() wrapped = UDPSocketWithFirst(sock, data) handler = ConnectionHandler(wrapped, client_addr, verbose=args.verbose, exec_cmd=args.execute, timeout=args.timeout, encrypt=args.encrypt, password=args.password, keepalive=args.keepalive, file_to_send=args.file, zero_io=args.zero) handler.start(is_server=True) else: # TCP if args.keep: while True: conn, client_addr = sock.accept() if args.verbose: print(f"[*] Connessione da {client_addr[0]}:{client_addr[1]}", file=sys.stderr) handler = ConnectionHandler(conn, client_addr, verbose=args.verbose, exec_cmd=args.execute, timeout=args.timeout, encrypt=args.encrypt, password=args.password, keepalive=args.keepalive, file_to_send=args.file, zero_io=args.zero) t = threading.Thread(target=handler.start, args=(True,), daemon=True) t.start() else: conn, client_addr = sock.accept() if args.verbose: print(f"[*] Connessione da {client_addr[0]}:{client_addr[1]}", file=sys.stderr) handler = ConnectionHandler(conn, client_addr, verbose=args.verbose, exec_cmd=args.execute, timeout=args.timeout, encrypt=args.encrypt, password=args.password, keepalive=args.keepalive, file_to_send=args.file, zero_io=args.zero) handler.start(is_server=True) else: # Modalità client if not args.host: parser.error("In modalità client è richiesto l'host") if args.encrypt and args.udp and not args.password: parser.error("UDP cifrato richiede una password (-k) per autenticare i datagrammi iniziali") sock, sa = create_client_socket(args.host, args.port, family, args.udp, args.source, args.timeout, args.numeric) if args.verbose: proto = "UDP" if args.udp else "TCP" print(f"[*] Connesso {proto} a {sa[0]}:{sa[1]}", file=sys.stderr) handler = ConnectionHandler(sock, sa, verbose=args.verbose, exec_cmd=args.execute, close_on_eof=args.close_on_eof, timeout=args.timeout, encrypt=args.encrypt, password=args.password, keepalive=args.keepalive, file_to_send=args.file, zero_io=args.zero) handler.start(is_server=False) if __name__ == "__main__": main()