PyCat A small implementation of netcat in Python3.x
up vote
7
down vote
favorite
Intro
netcat is an all-round tool used with many applicable features
I was playing around with sockets and openssl, and decided I should build my own.
It works as expected, but my code feels a bit smelly.
Features
- ssl
- persistent shell
- cd
- exit
Example
server
$ python pycat.py -lvp 8080 --ssl
[*] Incoming connection from 127.0.0.1:53391
username@hostame PyCat C:devPycat
> echo hooooooi
hooooooi
username@hostame PyCat C:devPyCat
> cd ../
username@hostame PyCat C:dev
> exit
client
python pycat.py -i localhost -p 8080 --ssl
Code
import subprocess
import tempfile
import datetime
import socket
import ssl
import sys
import os
import re
import argparse
from cryptography import x509
from cryptography.x509.oid import NameOID
from cryptography.hazmat.primitives import serialization, hashes
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.backends import default_backend
class PyCat():
def __init__(self, host, port, listen, verbose, _ssl):
self.buffer = b""
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.port = port
self.host = host if host else '0.0.0.0'
self.listen = listen
self.verbose = verbose
self.ssl = _ssl
if self.ssl:
self.context = ssl.create_default_context()
if self.listen:
self.key_file, self.cert_file = self.generate_temp_cert()
self.main_func = self.nc_listen if self.listen else self.nc_connect
self.main()
def generate_temp_cert(self):
key, key_path = tempfile.mkstemp()
cert, cert_path = tempfile.mkstemp()
name_attributes = [
x509.NameAttribute(NameOID.COUNTRY_NAME, "OK"),
x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "OK"),
x509.NameAttribute(NameOID.LOCALITY_NAME, "OK"),
x509.NameAttribute(NameOID.ORGANIZATION_NAME, "OK"),
x509.NameAttribute(NameOID.COMMON_NAME, "PyCat")
]
key = rsa.generate_private_key(
public_exponent=65537,
key_size=2048,
backend=default_backend()
)
with open(key_path, "wb") as f:
f.write(
key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.TraditionalOpenSSL,
encryption_algorithm=serialization.NoEncryption()
)
)
subject = issuer = x509.Name(name_attributes)
cert = x509.CertificateBuilder()
.subject_name(subject)
.issuer_name(issuer)
.public_key(key.public_key())
.serial_number(x509.random_serial_number())
.not_valid_before(datetime.datetime.utcnow())
.not_valid_after(datetime.datetime.utcnow() + datetime.timedelta(days=365))
cert = cert.sign(key, hashes.SHA256(), default_backend())
with open(cert_path, "wb") as f:
f.write(
cert.public_bytes(serialization.Encoding.PEM)
)
return key_path, cert_path
def main(self):
self.main_func()
def exit(self):
self.socket.close()
sys.exit(0)
def read(self, socket_conn, length=1024):
data, response = "starting", b""
while data:
data = socket_conn.recv(length)
response += data
if len(data) < length:
break
return response.decode("utf-8").rstrip("n")
def handle_command(self, cmd):
response = b" "
cd = re.match(r'cd(?:s+|$)(.*)', cmd)
if cmd == "exit":
self.exit()
elif cd and cd.group(1):
try:
os.chdir(cd.group(1))
except FileNotFoundError:
pass
else:
response = self.exec_command(cmd)
return response
def exec_command(self, command):
try:
output = subprocess.check_output(command, stderr=subprocess.STDOUT, shell=True)
except Exception as e:
output = str(e).encode("utf-8")
return output
def nc_connect(self):
self.socket.connect((self.host, self.port))
if self.ssl:
self.context.check_hostname = False
self.context.verify_mode = ssl.CERT_NONE
self.socket = self.context.wrap_socket(self.socket)
while True:
cmd = self.read(self.socket)
response = self.handle_command(cmd)
self.socket.send(response)
def create_prompt_string(self, client_socket):
client_socket.send(b"cd")
pwd = self.read(client_socket)
client_socket.send(b"whoami")
whoami = self.read(client_socket)
client_socket.send(b"hostname")
hostname = self.read(client_socket)
return f"{whoami}@{hostname} PyCat {pwd}n> "
def client_handler(self, client_socket):
while True:
prompt_string = self.create_prompt_string(client_socket)
buf = input(f"{prompt_string}")
client_socket.send(buf.encode("utf-8"))
if buf == "exit":
self.exit()
if self.ssl:
os.remove(self.cert_file)
os.remove(self.key_file)
print(self.read(client_socket))
def nc_listen(self):
self.socket.bind((self.host, self.port))
self.socket.listen(0)
if self.ssl:
self.socket = ssl.wrap_socket(
self.socket,
server_side=True,
certfile=self.cert_file,
keyfile=self.key_file
)
client_socket, addr = self.socket.accept()
if self.verbose:
ip, port = addr
print(f"[*] Incoming connection from {ip}:{port}")
self.client_handler(client_socket)
def parse_arguments():
parser = argparse.ArgumentParser(usage='%(prog)s [options]',
description='PyCat @Ludisposed',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog='Examples:npython3 pycat.py -lvp 443npython3 pycat.py -i localhost -p 443')
parser.add_argument('-l', '--listen', action="store_true", help='Listen')
parser.add_argument('-v', '--verbose', action="store_true", help='Verbose output')
parser.add_argument('-s', '--ssl', action="store_true", help='Encrypt connection')
parser.add_argument('-p', '--port', type=int, help='Port to listen on')
parser.add_argument('-i', '--ip', type=str, help='Ip to connect to')
args = parser.parse_args()
if (args.listen or args.ip) and not args.port:
parser.error('Specify which port to connect to')
elif not args.listen and not args.ip:
parser.error('Specify --listen or --ip')
return args.ip, args.port, args.listen, args.verbose, args.ssl
if __name__ == '__main__':
PyCat(*parse_arguments())
python python-3.x networking
add a comment |
up vote
7
down vote
favorite
Intro
netcat is an all-round tool used with many applicable features
I was playing around with sockets and openssl, and decided I should build my own.
It works as expected, but my code feels a bit smelly.
Features
- ssl
- persistent shell
- cd
- exit
Example
server
$ python pycat.py -lvp 8080 --ssl
[*] Incoming connection from 127.0.0.1:53391
username@hostame PyCat C:devPycat
> echo hooooooi
hooooooi
username@hostame PyCat C:devPyCat
> cd ../
username@hostame PyCat C:dev
> exit
client
python pycat.py -i localhost -p 8080 --ssl
Code
import subprocess
import tempfile
import datetime
import socket
import ssl
import sys
import os
import re
import argparse
from cryptography import x509
from cryptography.x509.oid import NameOID
from cryptography.hazmat.primitives import serialization, hashes
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.backends import default_backend
class PyCat():
def __init__(self, host, port, listen, verbose, _ssl):
self.buffer = b""
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.port = port
self.host = host if host else '0.0.0.0'
self.listen = listen
self.verbose = verbose
self.ssl = _ssl
if self.ssl:
self.context = ssl.create_default_context()
if self.listen:
self.key_file, self.cert_file = self.generate_temp_cert()
self.main_func = self.nc_listen if self.listen else self.nc_connect
self.main()
def generate_temp_cert(self):
key, key_path = tempfile.mkstemp()
cert, cert_path = tempfile.mkstemp()
name_attributes = [
x509.NameAttribute(NameOID.COUNTRY_NAME, "OK"),
x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "OK"),
x509.NameAttribute(NameOID.LOCALITY_NAME, "OK"),
x509.NameAttribute(NameOID.ORGANIZATION_NAME, "OK"),
x509.NameAttribute(NameOID.COMMON_NAME, "PyCat")
]
key = rsa.generate_private_key(
public_exponent=65537,
key_size=2048,
backend=default_backend()
)
with open(key_path, "wb") as f:
f.write(
key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.TraditionalOpenSSL,
encryption_algorithm=serialization.NoEncryption()
)
)
subject = issuer = x509.Name(name_attributes)
cert = x509.CertificateBuilder()
.subject_name(subject)
.issuer_name(issuer)
.public_key(key.public_key())
.serial_number(x509.random_serial_number())
.not_valid_before(datetime.datetime.utcnow())
.not_valid_after(datetime.datetime.utcnow() + datetime.timedelta(days=365))
cert = cert.sign(key, hashes.SHA256(), default_backend())
with open(cert_path, "wb") as f:
f.write(
cert.public_bytes(serialization.Encoding.PEM)
)
return key_path, cert_path
def main(self):
self.main_func()
def exit(self):
self.socket.close()
sys.exit(0)
def read(self, socket_conn, length=1024):
data, response = "starting", b""
while data:
data = socket_conn.recv(length)
response += data
if len(data) < length:
break
return response.decode("utf-8").rstrip("n")
def handle_command(self, cmd):
response = b" "
cd = re.match(r'cd(?:s+|$)(.*)', cmd)
if cmd == "exit":
self.exit()
elif cd and cd.group(1):
try:
os.chdir(cd.group(1))
except FileNotFoundError:
pass
else:
response = self.exec_command(cmd)
return response
def exec_command(self, command):
try:
output = subprocess.check_output(command, stderr=subprocess.STDOUT, shell=True)
except Exception as e:
output = str(e).encode("utf-8")
return output
def nc_connect(self):
self.socket.connect((self.host, self.port))
if self.ssl:
self.context.check_hostname = False
self.context.verify_mode = ssl.CERT_NONE
self.socket = self.context.wrap_socket(self.socket)
while True:
cmd = self.read(self.socket)
response = self.handle_command(cmd)
self.socket.send(response)
def create_prompt_string(self, client_socket):
client_socket.send(b"cd")
pwd = self.read(client_socket)
client_socket.send(b"whoami")
whoami = self.read(client_socket)
client_socket.send(b"hostname")
hostname = self.read(client_socket)
return f"{whoami}@{hostname} PyCat {pwd}n> "
def client_handler(self, client_socket):
while True:
prompt_string = self.create_prompt_string(client_socket)
buf = input(f"{prompt_string}")
client_socket.send(buf.encode("utf-8"))
if buf == "exit":
self.exit()
if self.ssl:
os.remove(self.cert_file)
os.remove(self.key_file)
print(self.read(client_socket))
def nc_listen(self):
self.socket.bind((self.host, self.port))
self.socket.listen(0)
if self.ssl:
self.socket = ssl.wrap_socket(
self.socket,
server_side=True,
certfile=self.cert_file,
keyfile=self.key_file
)
client_socket, addr = self.socket.accept()
if self.verbose:
ip, port = addr
print(f"[*] Incoming connection from {ip}:{port}")
self.client_handler(client_socket)
def parse_arguments():
parser = argparse.ArgumentParser(usage='%(prog)s [options]',
description='PyCat @Ludisposed',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog='Examples:npython3 pycat.py -lvp 443npython3 pycat.py -i localhost -p 443')
parser.add_argument('-l', '--listen', action="store_true", help='Listen')
parser.add_argument('-v', '--verbose', action="store_true", help='Verbose output')
parser.add_argument('-s', '--ssl', action="store_true", help='Encrypt connection')
parser.add_argument('-p', '--port', type=int, help='Port to listen on')
parser.add_argument('-i', '--ip', type=str, help='Ip to connect to')
args = parser.parse_args()
if (args.listen or args.ip) and not args.port:
parser.error('Specify which port to connect to')
elif not args.listen and not args.ip:
parser.error('Specify --listen or --ip')
return args.ip, args.port, args.listen, args.verbose, args.ssl
if __name__ == '__main__':
PyCat(*parse_arguments())
python python-3.x networking
add a comment |
up vote
7
down vote
favorite
up vote
7
down vote
favorite
Intro
netcat is an all-round tool used with many applicable features
I was playing around with sockets and openssl, and decided I should build my own.
It works as expected, but my code feels a bit smelly.
Features
- ssl
- persistent shell
- cd
- exit
Example
server
$ python pycat.py -lvp 8080 --ssl
[*] Incoming connection from 127.0.0.1:53391
username@hostame PyCat C:devPycat
> echo hooooooi
hooooooi
username@hostame PyCat C:devPyCat
> cd ../
username@hostame PyCat C:dev
> exit
client
python pycat.py -i localhost -p 8080 --ssl
Code
import subprocess
import tempfile
import datetime
import socket
import ssl
import sys
import os
import re
import argparse
from cryptography import x509
from cryptography.x509.oid import NameOID
from cryptography.hazmat.primitives import serialization, hashes
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.backends import default_backend
class PyCat():
def __init__(self, host, port, listen, verbose, _ssl):
self.buffer = b""
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.port = port
self.host = host if host else '0.0.0.0'
self.listen = listen
self.verbose = verbose
self.ssl = _ssl
if self.ssl:
self.context = ssl.create_default_context()
if self.listen:
self.key_file, self.cert_file = self.generate_temp_cert()
self.main_func = self.nc_listen if self.listen else self.nc_connect
self.main()
def generate_temp_cert(self):
key, key_path = tempfile.mkstemp()
cert, cert_path = tempfile.mkstemp()
name_attributes = [
x509.NameAttribute(NameOID.COUNTRY_NAME, "OK"),
x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "OK"),
x509.NameAttribute(NameOID.LOCALITY_NAME, "OK"),
x509.NameAttribute(NameOID.ORGANIZATION_NAME, "OK"),
x509.NameAttribute(NameOID.COMMON_NAME, "PyCat")
]
key = rsa.generate_private_key(
public_exponent=65537,
key_size=2048,
backend=default_backend()
)
with open(key_path, "wb") as f:
f.write(
key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.TraditionalOpenSSL,
encryption_algorithm=serialization.NoEncryption()
)
)
subject = issuer = x509.Name(name_attributes)
cert = x509.CertificateBuilder()
.subject_name(subject)
.issuer_name(issuer)
.public_key(key.public_key())
.serial_number(x509.random_serial_number())
.not_valid_before(datetime.datetime.utcnow())
.not_valid_after(datetime.datetime.utcnow() + datetime.timedelta(days=365))
cert = cert.sign(key, hashes.SHA256(), default_backend())
with open(cert_path, "wb") as f:
f.write(
cert.public_bytes(serialization.Encoding.PEM)
)
return key_path, cert_path
def main(self):
self.main_func()
def exit(self):
self.socket.close()
sys.exit(0)
def read(self, socket_conn, length=1024):
data, response = "starting", b""
while data:
data = socket_conn.recv(length)
response += data
if len(data) < length:
break
return response.decode("utf-8").rstrip("n")
def handle_command(self, cmd):
response = b" "
cd = re.match(r'cd(?:s+|$)(.*)', cmd)
if cmd == "exit":
self.exit()
elif cd and cd.group(1):
try:
os.chdir(cd.group(1))
except FileNotFoundError:
pass
else:
response = self.exec_command(cmd)
return response
def exec_command(self, command):
try:
output = subprocess.check_output(command, stderr=subprocess.STDOUT, shell=True)
except Exception as e:
output = str(e).encode("utf-8")
return output
def nc_connect(self):
self.socket.connect((self.host, self.port))
if self.ssl:
self.context.check_hostname = False
self.context.verify_mode = ssl.CERT_NONE
self.socket = self.context.wrap_socket(self.socket)
while True:
cmd = self.read(self.socket)
response = self.handle_command(cmd)
self.socket.send(response)
def create_prompt_string(self, client_socket):
client_socket.send(b"cd")
pwd = self.read(client_socket)
client_socket.send(b"whoami")
whoami = self.read(client_socket)
client_socket.send(b"hostname")
hostname = self.read(client_socket)
return f"{whoami}@{hostname} PyCat {pwd}n> "
def client_handler(self, client_socket):
while True:
prompt_string = self.create_prompt_string(client_socket)
buf = input(f"{prompt_string}")
client_socket.send(buf.encode("utf-8"))
if buf == "exit":
self.exit()
if self.ssl:
os.remove(self.cert_file)
os.remove(self.key_file)
print(self.read(client_socket))
def nc_listen(self):
self.socket.bind((self.host, self.port))
self.socket.listen(0)
if self.ssl:
self.socket = ssl.wrap_socket(
self.socket,
server_side=True,
certfile=self.cert_file,
keyfile=self.key_file
)
client_socket, addr = self.socket.accept()
if self.verbose:
ip, port = addr
print(f"[*] Incoming connection from {ip}:{port}")
self.client_handler(client_socket)
def parse_arguments():
parser = argparse.ArgumentParser(usage='%(prog)s [options]',
description='PyCat @Ludisposed',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog='Examples:npython3 pycat.py -lvp 443npython3 pycat.py -i localhost -p 443')
parser.add_argument('-l', '--listen', action="store_true", help='Listen')
parser.add_argument('-v', '--verbose', action="store_true", help='Verbose output')
parser.add_argument('-s', '--ssl', action="store_true", help='Encrypt connection')
parser.add_argument('-p', '--port', type=int, help='Port to listen on')
parser.add_argument('-i', '--ip', type=str, help='Ip to connect to')
args = parser.parse_args()
if (args.listen or args.ip) and not args.port:
parser.error('Specify which port to connect to')
elif not args.listen and not args.ip:
parser.error('Specify --listen or --ip')
return args.ip, args.port, args.listen, args.verbose, args.ssl
if __name__ == '__main__':
PyCat(*parse_arguments())
python python-3.x networking
Intro
netcat is an all-round tool used with many applicable features
I was playing around with sockets and openssl, and decided I should build my own.
It works as expected, but my code feels a bit smelly.
Features
- ssl
- persistent shell
- cd
- exit
Example
server
$ python pycat.py -lvp 8080 --ssl
[*] Incoming connection from 127.0.0.1:53391
username@hostame PyCat C:devPycat
> echo hooooooi
hooooooi
username@hostame PyCat C:devPyCat
> cd ../
username@hostame PyCat C:dev
> exit
client
python pycat.py -i localhost -p 8080 --ssl
Code
import subprocess
import tempfile
import datetime
import socket
import ssl
import sys
import os
import re
import argparse
from cryptography import x509
from cryptography.x509.oid import NameOID
from cryptography.hazmat.primitives import serialization, hashes
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.backends import default_backend
class PyCat():
def __init__(self, host, port, listen, verbose, _ssl):
self.buffer = b""
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.port = port
self.host = host if host else '0.0.0.0'
self.listen = listen
self.verbose = verbose
self.ssl = _ssl
if self.ssl:
self.context = ssl.create_default_context()
if self.listen:
self.key_file, self.cert_file = self.generate_temp_cert()
self.main_func = self.nc_listen if self.listen else self.nc_connect
self.main()
def generate_temp_cert(self):
key, key_path = tempfile.mkstemp()
cert, cert_path = tempfile.mkstemp()
name_attributes = [
x509.NameAttribute(NameOID.COUNTRY_NAME, "OK"),
x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "OK"),
x509.NameAttribute(NameOID.LOCALITY_NAME, "OK"),
x509.NameAttribute(NameOID.ORGANIZATION_NAME, "OK"),
x509.NameAttribute(NameOID.COMMON_NAME, "PyCat")
]
key = rsa.generate_private_key(
public_exponent=65537,
key_size=2048,
backend=default_backend()
)
with open(key_path, "wb") as f:
f.write(
key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.TraditionalOpenSSL,
encryption_algorithm=serialization.NoEncryption()
)
)
subject = issuer = x509.Name(name_attributes)
cert = x509.CertificateBuilder()
.subject_name(subject)
.issuer_name(issuer)
.public_key(key.public_key())
.serial_number(x509.random_serial_number())
.not_valid_before(datetime.datetime.utcnow())
.not_valid_after(datetime.datetime.utcnow() + datetime.timedelta(days=365))
cert = cert.sign(key, hashes.SHA256(), default_backend())
with open(cert_path, "wb") as f:
f.write(
cert.public_bytes(serialization.Encoding.PEM)
)
return key_path, cert_path
def main(self):
self.main_func()
def exit(self):
self.socket.close()
sys.exit(0)
def read(self, socket_conn, length=1024):
data, response = "starting", b""
while data:
data = socket_conn.recv(length)
response += data
if len(data) < length:
break
return response.decode("utf-8").rstrip("n")
def handle_command(self, cmd):
response = b" "
cd = re.match(r'cd(?:s+|$)(.*)', cmd)
if cmd == "exit":
self.exit()
elif cd and cd.group(1):
try:
os.chdir(cd.group(1))
except FileNotFoundError:
pass
else:
response = self.exec_command(cmd)
return response
def exec_command(self, command):
try:
output = subprocess.check_output(command, stderr=subprocess.STDOUT, shell=True)
except Exception as e:
output = str(e).encode("utf-8")
return output
def nc_connect(self):
self.socket.connect((self.host, self.port))
if self.ssl:
self.context.check_hostname = False
self.context.verify_mode = ssl.CERT_NONE
self.socket = self.context.wrap_socket(self.socket)
while True:
cmd = self.read(self.socket)
response = self.handle_command(cmd)
self.socket.send(response)
def create_prompt_string(self, client_socket):
client_socket.send(b"cd")
pwd = self.read(client_socket)
client_socket.send(b"whoami")
whoami = self.read(client_socket)
client_socket.send(b"hostname")
hostname = self.read(client_socket)
return f"{whoami}@{hostname} PyCat {pwd}n> "
def client_handler(self, client_socket):
while True:
prompt_string = self.create_prompt_string(client_socket)
buf = input(f"{prompt_string}")
client_socket.send(buf.encode("utf-8"))
if buf == "exit":
self.exit()
if self.ssl:
os.remove(self.cert_file)
os.remove(self.key_file)
print(self.read(client_socket))
def nc_listen(self):
self.socket.bind((self.host, self.port))
self.socket.listen(0)
if self.ssl:
self.socket = ssl.wrap_socket(
self.socket,
server_side=True,
certfile=self.cert_file,
keyfile=self.key_file
)
client_socket, addr = self.socket.accept()
if self.verbose:
ip, port = addr
print(f"[*] Incoming connection from {ip}:{port}")
self.client_handler(client_socket)
def parse_arguments():
parser = argparse.ArgumentParser(usage='%(prog)s [options]',
description='PyCat @Ludisposed',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog='Examples:npython3 pycat.py -lvp 443npython3 pycat.py -i localhost -p 443')
parser.add_argument('-l', '--listen', action="store_true", help='Listen')
parser.add_argument('-v', '--verbose', action="store_true", help='Verbose output')
parser.add_argument('-s', '--ssl', action="store_true", help='Encrypt connection')
parser.add_argument('-p', '--port', type=int, help='Port to listen on')
parser.add_argument('-i', '--ip', type=str, help='Ip to connect to')
args = parser.parse_args()
if (args.listen or args.ip) and not args.port:
parser.error('Specify which port to connect to')
elif not args.listen and not args.ip:
parser.error('Specify --listen or --ip')
return args.ip, args.port, args.listen, args.verbose, args.ssl
if __name__ == '__main__':
PyCat(*parse_arguments())
python python-3.x networking
python python-3.x networking
edited Dec 7 at 12:02
asked Dec 7 at 11:14
Ludisposed
6,93921959
6,93921959
add a comment |
add a comment |
2 Answers
2
active
oldest
votes
up vote
5
down vote
accepted
I/O problems
I trust that this code works on your machine, but it did not work for me (Homebrew Python 3.7.1 on macOS 10.14.2). As soon as the client connects, both sides crash. The client's stack trace:
$ python3 pycat.py -i localhost -p 8080 --ssl
Traceback (most recent call last):
File "pycat.py", line 188, in <module>
PyCat(*parse_arguments())
File "pycat.py", line 31, in __init__
self.main()
File "pycat.py", line 78, in main
self.main_func()
File "pycat.py", line 125, in nc_connect
self.socket.send(response)
File "/usr/local/Cellar/python/3.7.1/Frameworks/Python.framework/Versions/3.7/lib/python3.7/ssl.py", line 984, in send
return self._sslobj.write(data)
ssl.SSLEOFError: EOF occurred in violation of protocol (_ssl.c:2324)
If I try to launch both the server and client without --ssl
, then after the server reports that it accepted the incoming connection, both sides just sit there and do nothing, no matter what I type.
I suspect that the default I/O buffering behavior in Windows is different from Unix.
Object-oriented design
In OOP, classes are nouns. A constructor should initialize an object. The constructor should not also start actively doing things. The action happens when someone calls a method (a verb) on the object.
I don't think that the PyCat
class is a good one. Its behavior completely switches depending on self.main_func = self.nc_listen if self.listen else self.nc_connect
! Consider the calling chains for the server mode compared to the client mode:
Server mode:
PyCat.__init__()
self.generate_temp_cert()
self.main()
self.nc_listen()
self.socket.…
self.client_handler(…)
self.create_prompt_string(…)
self.read(…)
self.read(…)
Client mode:
PyCat.__init__()
self.main()
self.nc_connect()
self.socket.connect(…)
self.context.…
self.read(…)
self.handle_command(…)
self.exec_command(…)
self.socket.send(…)
There is very little in common between the two modes. There is, therefore, no advantage to cramming them into the same class. All that the two modes have in common, really, are some socket-creation parameters and the ability to read and write. That should be a class used in some composition, or perhaps a base class in some inheritance hierarchy.
Nitpicks
Imports should be listed in alphabetical order. The argparse
import belongs with the first group of imports.
Having parse_arguments()
return a tuple, whose elements are in the same order that the PyCat
constructor expects, is fragile. Rather, parse_arguments
should return a dictionary, and you can call PyCat(**parse_arguments())
. Note that what the argument parser calls an "ip" is what the PyCat
class inconsistently calls the "host". Also, the _ssl
parameter is ugly. If you're worried about name collisions, you can rename the parameter to use_ssl
, or change the import statement in one of these ways:
from ssl import CERT_NONE, create_default_context, wrap_socket
import ssl as ssllib
A shorter way to write
self.host = host if host else '0.0.0.0'
would be
self.host = host or '0.0.0.0'
I wonder why it doesn't work on *nix, I will test that myself. You gave me much to think about for my next iteration, lots of improvements to be made.
– Ludisposed
Dec 7 at 20:41
Regarding the imports I think it should be (1. standerd libs, 2. 3rd party libs, 3 local libs) reference So argparse should be the first of the second grouped imports.
– Ludisposed
Dec 11 at 13:03
@Ludisposed Isargparse
not in the standard library?
– Graipher
2 days ago
1
@Graipher I didn't think it was.. but apperently it is. :)
– Ludisposed
2 days ago
add a comment |
up vote
2
down vote
self.host = host if host else '0.0.0.0'
Since this seems to be optional, perhaps you should given the corresponding __init__
argument a default value (None?). Same with _ssl
, if it's an object. If it's a boolean, rename it to _use_ssl
or something; otherwise its purpose is not clear.
This:
self.main_func = self.nc_listen if self.listen else self.nc_connect
self.main()
# ...
def main(self):
self.main_func()
can be simplified to:
self.main = self.nc_listen if self.listen else self.nc_connect
self.main()
However, I advise that you don't call main()
from the constructor. Init is for init. The entire app shouldn't run from the constructor. Call main()
from the top level after construction.
One nicety would be to write a static method PyCat.from_command_line
which returns a new instance of PyCat
based on arg parsing, which would also be a static method. That way, the class contains more of its business logic and is more useful on its own.
generate_temp_cert
can - and should - be a @staticmethod
, as well. It doesn't reference self
.
You should turn the class into a context manager, since you have a socket that needs to be closed. For more information do some reading here - https://docs.python.org/3/library/stdtypes.html#typecontextmanager - and then use a with
at the top level.
Your Python file, since it's executable, is missing a shebang at the top:
#!/usr/bin/env python3
Your little hack to initialize data
to starting
to work around the first loop iteration is not advisable. There's also a bug where you don't actually check data
directly after your recv
, so you could get back an unexpected None
. Instead, do something like:
response = b''
while True:
data = socket_conn.recv(length)
if not data:
break
# ...
The concatenation you're doing on response
might also be problematic. If you're in Python 2 it's still (strangely) the best thing to do; however, if you're in Python 3, this kind of concatenation can become very inefficient, and you should be using a BytesIO
instead.
You should check if cmd == "exit"
before doing your regex, because you might just throw the results away anyway.
subprocess.check_output(command, stderr=subprocess.STDOUT, shell=True)
Be very careful about this. shell=True
on its own is a security risk. Allowing a network-connected, unauthenticated application to run arbitrary shell commands is a gaping security hole.
That security risk is by design ;) But I wholeheartedly agree with the rest of your points, and really should've implemented a context manager!
– Ludisposed
Dec 7 at 19:51
add a comment |
Your Answer
StackExchange.ifUsing("editor", function () {
return StackExchange.using("mathjaxEditing", function () {
StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix) {
StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
});
});
}, "mathjax-editing");
StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "196"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
convertImagesToLinks: false,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: null,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f209204%2fpycat-a-small-implementation-of-netcat-in-python3-x%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
2 Answers
2
active
oldest
votes
2 Answers
2
active
oldest
votes
active
oldest
votes
active
oldest
votes
up vote
5
down vote
accepted
I/O problems
I trust that this code works on your machine, but it did not work for me (Homebrew Python 3.7.1 on macOS 10.14.2). As soon as the client connects, both sides crash. The client's stack trace:
$ python3 pycat.py -i localhost -p 8080 --ssl
Traceback (most recent call last):
File "pycat.py", line 188, in <module>
PyCat(*parse_arguments())
File "pycat.py", line 31, in __init__
self.main()
File "pycat.py", line 78, in main
self.main_func()
File "pycat.py", line 125, in nc_connect
self.socket.send(response)
File "/usr/local/Cellar/python/3.7.1/Frameworks/Python.framework/Versions/3.7/lib/python3.7/ssl.py", line 984, in send
return self._sslobj.write(data)
ssl.SSLEOFError: EOF occurred in violation of protocol (_ssl.c:2324)
If I try to launch both the server and client without --ssl
, then after the server reports that it accepted the incoming connection, both sides just sit there and do nothing, no matter what I type.
I suspect that the default I/O buffering behavior in Windows is different from Unix.
Object-oriented design
In OOP, classes are nouns. A constructor should initialize an object. The constructor should not also start actively doing things. The action happens when someone calls a method (a verb) on the object.
I don't think that the PyCat
class is a good one. Its behavior completely switches depending on self.main_func = self.nc_listen if self.listen else self.nc_connect
! Consider the calling chains for the server mode compared to the client mode:
Server mode:
PyCat.__init__()
self.generate_temp_cert()
self.main()
self.nc_listen()
self.socket.…
self.client_handler(…)
self.create_prompt_string(…)
self.read(…)
self.read(…)
Client mode:
PyCat.__init__()
self.main()
self.nc_connect()
self.socket.connect(…)
self.context.…
self.read(…)
self.handle_command(…)
self.exec_command(…)
self.socket.send(…)
There is very little in common between the two modes. There is, therefore, no advantage to cramming them into the same class. All that the two modes have in common, really, are some socket-creation parameters and the ability to read and write. That should be a class used in some composition, or perhaps a base class in some inheritance hierarchy.
Nitpicks
Imports should be listed in alphabetical order. The argparse
import belongs with the first group of imports.
Having parse_arguments()
return a tuple, whose elements are in the same order that the PyCat
constructor expects, is fragile. Rather, parse_arguments
should return a dictionary, and you can call PyCat(**parse_arguments())
. Note that what the argument parser calls an "ip" is what the PyCat
class inconsistently calls the "host". Also, the _ssl
parameter is ugly. If you're worried about name collisions, you can rename the parameter to use_ssl
, or change the import statement in one of these ways:
from ssl import CERT_NONE, create_default_context, wrap_socket
import ssl as ssllib
A shorter way to write
self.host = host if host else '0.0.0.0'
would be
self.host = host or '0.0.0.0'
I wonder why it doesn't work on *nix, I will test that myself. You gave me much to think about for my next iteration, lots of improvements to be made.
– Ludisposed
Dec 7 at 20:41
Regarding the imports I think it should be (1. standerd libs, 2. 3rd party libs, 3 local libs) reference So argparse should be the first of the second grouped imports.
– Ludisposed
Dec 11 at 13:03
@Ludisposed Isargparse
not in the standard library?
– Graipher
2 days ago
1
@Graipher I didn't think it was.. but apperently it is. :)
– Ludisposed
2 days ago
add a comment |
up vote
5
down vote
accepted
I/O problems
I trust that this code works on your machine, but it did not work for me (Homebrew Python 3.7.1 on macOS 10.14.2). As soon as the client connects, both sides crash. The client's stack trace:
$ python3 pycat.py -i localhost -p 8080 --ssl
Traceback (most recent call last):
File "pycat.py", line 188, in <module>
PyCat(*parse_arguments())
File "pycat.py", line 31, in __init__
self.main()
File "pycat.py", line 78, in main
self.main_func()
File "pycat.py", line 125, in nc_connect
self.socket.send(response)
File "/usr/local/Cellar/python/3.7.1/Frameworks/Python.framework/Versions/3.7/lib/python3.7/ssl.py", line 984, in send
return self._sslobj.write(data)
ssl.SSLEOFError: EOF occurred in violation of protocol (_ssl.c:2324)
If I try to launch both the server and client without --ssl
, then after the server reports that it accepted the incoming connection, both sides just sit there and do nothing, no matter what I type.
I suspect that the default I/O buffering behavior in Windows is different from Unix.
Object-oriented design
In OOP, classes are nouns. A constructor should initialize an object. The constructor should not also start actively doing things. The action happens when someone calls a method (a verb) on the object.
I don't think that the PyCat
class is a good one. Its behavior completely switches depending on self.main_func = self.nc_listen if self.listen else self.nc_connect
! Consider the calling chains for the server mode compared to the client mode:
Server mode:
PyCat.__init__()
self.generate_temp_cert()
self.main()
self.nc_listen()
self.socket.…
self.client_handler(…)
self.create_prompt_string(…)
self.read(…)
self.read(…)
Client mode:
PyCat.__init__()
self.main()
self.nc_connect()
self.socket.connect(…)
self.context.…
self.read(…)
self.handle_command(…)
self.exec_command(…)
self.socket.send(…)
There is very little in common between the two modes. There is, therefore, no advantage to cramming them into the same class. All that the two modes have in common, really, are some socket-creation parameters and the ability to read and write. That should be a class used in some composition, or perhaps a base class in some inheritance hierarchy.
Nitpicks
Imports should be listed in alphabetical order. The argparse
import belongs with the first group of imports.
Having parse_arguments()
return a tuple, whose elements are in the same order that the PyCat
constructor expects, is fragile. Rather, parse_arguments
should return a dictionary, and you can call PyCat(**parse_arguments())
. Note that what the argument parser calls an "ip" is what the PyCat
class inconsistently calls the "host". Also, the _ssl
parameter is ugly. If you're worried about name collisions, you can rename the parameter to use_ssl
, or change the import statement in one of these ways:
from ssl import CERT_NONE, create_default_context, wrap_socket
import ssl as ssllib
A shorter way to write
self.host = host if host else '0.0.0.0'
would be
self.host = host or '0.0.0.0'
I wonder why it doesn't work on *nix, I will test that myself. You gave me much to think about for my next iteration, lots of improvements to be made.
– Ludisposed
Dec 7 at 20:41
Regarding the imports I think it should be (1. standerd libs, 2. 3rd party libs, 3 local libs) reference So argparse should be the first of the second grouped imports.
– Ludisposed
Dec 11 at 13:03
@Ludisposed Isargparse
not in the standard library?
– Graipher
2 days ago
1
@Graipher I didn't think it was.. but apperently it is. :)
– Ludisposed
2 days ago
add a comment |
up vote
5
down vote
accepted
up vote
5
down vote
accepted
I/O problems
I trust that this code works on your machine, but it did not work for me (Homebrew Python 3.7.1 on macOS 10.14.2). As soon as the client connects, both sides crash. The client's stack trace:
$ python3 pycat.py -i localhost -p 8080 --ssl
Traceback (most recent call last):
File "pycat.py", line 188, in <module>
PyCat(*parse_arguments())
File "pycat.py", line 31, in __init__
self.main()
File "pycat.py", line 78, in main
self.main_func()
File "pycat.py", line 125, in nc_connect
self.socket.send(response)
File "/usr/local/Cellar/python/3.7.1/Frameworks/Python.framework/Versions/3.7/lib/python3.7/ssl.py", line 984, in send
return self._sslobj.write(data)
ssl.SSLEOFError: EOF occurred in violation of protocol (_ssl.c:2324)
If I try to launch both the server and client without --ssl
, then after the server reports that it accepted the incoming connection, both sides just sit there and do nothing, no matter what I type.
I suspect that the default I/O buffering behavior in Windows is different from Unix.
Object-oriented design
In OOP, classes are nouns. A constructor should initialize an object. The constructor should not also start actively doing things. The action happens when someone calls a method (a verb) on the object.
I don't think that the PyCat
class is a good one. Its behavior completely switches depending on self.main_func = self.nc_listen if self.listen else self.nc_connect
! Consider the calling chains for the server mode compared to the client mode:
Server mode:
PyCat.__init__()
self.generate_temp_cert()
self.main()
self.nc_listen()
self.socket.…
self.client_handler(…)
self.create_prompt_string(…)
self.read(…)
self.read(…)
Client mode:
PyCat.__init__()
self.main()
self.nc_connect()
self.socket.connect(…)
self.context.…
self.read(…)
self.handle_command(…)
self.exec_command(…)
self.socket.send(…)
There is very little in common between the two modes. There is, therefore, no advantage to cramming them into the same class. All that the two modes have in common, really, are some socket-creation parameters and the ability to read and write. That should be a class used in some composition, or perhaps a base class in some inheritance hierarchy.
Nitpicks
Imports should be listed in alphabetical order. The argparse
import belongs with the first group of imports.
Having parse_arguments()
return a tuple, whose elements are in the same order that the PyCat
constructor expects, is fragile. Rather, parse_arguments
should return a dictionary, and you can call PyCat(**parse_arguments())
. Note that what the argument parser calls an "ip" is what the PyCat
class inconsistently calls the "host". Also, the _ssl
parameter is ugly. If you're worried about name collisions, you can rename the parameter to use_ssl
, or change the import statement in one of these ways:
from ssl import CERT_NONE, create_default_context, wrap_socket
import ssl as ssllib
A shorter way to write
self.host = host if host else '0.0.0.0'
would be
self.host = host or '0.0.0.0'
I/O problems
I trust that this code works on your machine, but it did not work for me (Homebrew Python 3.7.1 on macOS 10.14.2). As soon as the client connects, both sides crash. The client's stack trace:
$ python3 pycat.py -i localhost -p 8080 --ssl
Traceback (most recent call last):
File "pycat.py", line 188, in <module>
PyCat(*parse_arguments())
File "pycat.py", line 31, in __init__
self.main()
File "pycat.py", line 78, in main
self.main_func()
File "pycat.py", line 125, in nc_connect
self.socket.send(response)
File "/usr/local/Cellar/python/3.7.1/Frameworks/Python.framework/Versions/3.7/lib/python3.7/ssl.py", line 984, in send
return self._sslobj.write(data)
ssl.SSLEOFError: EOF occurred in violation of protocol (_ssl.c:2324)
If I try to launch both the server and client without --ssl
, then after the server reports that it accepted the incoming connection, both sides just sit there and do nothing, no matter what I type.
I suspect that the default I/O buffering behavior in Windows is different from Unix.
Object-oriented design
In OOP, classes are nouns. A constructor should initialize an object. The constructor should not also start actively doing things. The action happens when someone calls a method (a verb) on the object.
I don't think that the PyCat
class is a good one. Its behavior completely switches depending on self.main_func = self.nc_listen if self.listen else self.nc_connect
! Consider the calling chains for the server mode compared to the client mode:
Server mode:
PyCat.__init__()
self.generate_temp_cert()
self.main()
self.nc_listen()
self.socket.…
self.client_handler(…)
self.create_prompt_string(…)
self.read(…)
self.read(…)
Client mode:
PyCat.__init__()
self.main()
self.nc_connect()
self.socket.connect(…)
self.context.…
self.read(…)
self.handle_command(…)
self.exec_command(…)
self.socket.send(…)
There is very little in common between the two modes. There is, therefore, no advantage to cramming them into the same class. All that the two modes have in common, really, are some socket-creation parameters and the ability to read and write. That should be a class used in some composition, or perhaps a base class in some inheritance hierarchy.
Nitpicks
Imports should be listed in alphabetical order. The argparse
import belongs with the first group of imports.
Having parse_arguments()
return a tuple, whose elements are in the same order that the PyCat
constructor expects, is fragile. Rather, parse_arguments
should return a dictionary, and you can call PyCat(**parse_arguments())
. Note that what the argument parser calls an "ip" is what the PyCat
class inconsistently calls the "host". Also, the _ssl
parameter is ugly. If you're worried about name collisions, you can rename the parameter to use_ssl
, or change the import statement in one of these ways:
from ssl import CERT_NONE, create_default_context, wrap_socket
import ssl as ssllib
A shorter way to write
self.host = host if host else '0.0.0.0'
would be
self.host = host or '0.0.0.0'
answered Dec 7 at 20:15
200_success
128k15149412
128k15149412
I wonder why it doesn't work on *nix, I will test that myself. You gave me much to think about for my next iteration, lots of improvements to be made.
– Ludisposed
Dec 7 at 20:41
Regarding the imports I think it should be (1. standerd libs, 2. 3rd party libs, 3 local libs) reference So argparse should be the first of the second grouped imports.
– Ludisposed
Dec 11 at 13:03
@Ludisposed Isargparse
not in the standard library?
– Graipher
2 days ago
1
@Graipher I didn't think it was.. but apperently it is. :)
– Ludisposed
2 days ago
add a comment |
I wonder why it doesn't work on *nix, I will test that myself. You gave me much to think about for my next iteration, lots of improvements to be made.
– Ludisposed
Dec 7 at 20:41
Regarding the imports I think it should be (1. standerd libs, 2. 3rd party libs, 3 local libs) reference So argparse should be the first of the second grouped imports.
– Ludisposed
Dec 11 at 13:03
@Ludisposed Isargparse
not in the standard library?
– Graipher
2 days ago
1
@Graipher I didn't think it was.. but apperently it is. :)
– Ludisposed
2 days ago
I wonder why it doesn't work on *nix, I will test that myself. You gave me much to think about for my next iteration, lots of improvements to be made.
– Ludisposed
Dec 7 at 20:41
I wonder why it doesn't work on *nix, I will test that myself. You gave me much to think about for my next iteration, lots of improvements to be made.
– Ludisposed
Dec 7 at 20:41
Regarding the imports I think it should be (1. standerd libs, 2. 3rd party libs, 3 local libs) reference So argparse should be the first of the second grouped imports.
– Ludisposed
Dec 11 at 13:03
Regarding the imports I think it should be (1. standerd libs, 2. 3rd party libs, 3 local libs) reference So argparse should be the first of the second grouped imports.
– Ludisposed
Dec 11 at 13:03
@Ludisposed Is
argparse
not in the standard library?– Graipher
2 days ago
@Ludisposed Is
argparse
not in the standard library?– Graipher
2 days ago
1
1
@Graipher I didn't think it was.. but apperently it is. :)
– Ludisposed
2 days ago
@Graipher I didn't think it was.. but apperently it is. :)
– Ludisposed
2 days ago
add a comment |
up vote
2
down vote
self.host = host if host else '0.0.0.0'
Since this seems to be optional, perhaps you should given the corresponding __init__
argument a default value (None?). Same with _ssl
, if it's an object. If it's a boolean, rename it to _use_ssl
or something; otherwise its purpose is not clear.
This:
self.main_func = self.nc_listen if self.listen else self.nc_connect
self.main()
# ...
def main(self):
self.main_func()
can be simplified to:
self.main = self.nc_listen if self.listen else self.nc_connect
self.main()
However, I advise that you don't call main()
from the constructor. Init is for init. The entire app shouldn't run from the constructor. Call main()
from the top level after construction.
One nicety would be to write a static method PyCat.from_command_line
which returns a new instance of PyCat
based on arg parsing, which would also be a static method. That way, the class contains more of its business logic and is more useful on its own.
generate_temp_cert
can - and should - be a @staticmethod
, as well. It doesn't reference self
.
You should turn the class into a context manager, since you have a socket that needs to be closed. For more information do some reading here - https://docs.python.org/3/library/stdtypes.html#typecontextmanager - and then use a with
at the top level.
Your Python file, since it's executable, is missing a shebang at the top:
#!/usr/bin/env python3
Your little hack to initialize data
to starting
to work around the first loop iteration is not advisable. There's also a bug where you don't actually check data
directly after your recv
, so you could get back an unexpected None
. Instead, do something like:
response = b''
while True:
data = socket_conn.recv(length)
if not data:
break
# ...
The concatenation you're doing on response
might also be problematic. If you're in Python 2 it's still (strangely) the best thing to do; however, if you're in Python 3, this kind of concatenation can become very inefficient, and you should be using a BytesIO
instead.
You should check if cmd == "exit"
before doing your regex, because you might just throw the results away anyway.
subprocess.check_output(command, stderr=subprocess.STDOUT, shell=True)
Be very careful about this. shell=True
on its own is a security risk. Allowing a network-connected, unauthenticated application to run arbitrary shell commands is a gaping security hole.
That security risk is by design ;) But I wholeheartedly agree with the rest of your points, and really should've implemented a context manager!
– Ludisposed
Dec 7 at 19:51
add a comment |
up vote
2
down vote
self.host = host if host else '0.0.0.0'
Since this seems to be optional, perhaps you should given the corresponding __init__
argument a default value (None?). Same with _ssl
, if it's an object. If it's a boolean, rename it to _use_ssl
or something; otherwise its purpose is not clear.
This:
self.main_func = self.nc_listen if self.listen else self.nc_connect
self.main()
# ...
def main(self):
self.main_func()
can be simplified to:
self.main = self.nc_listen if self.listen else self.nc_connect
self.main()
However, I advise that you don't call main()
from the constructor. Init is for init. The entire app shouldn't run from the constructor. Call main()
from the top level after construction.
One nicety would be to write a static method PyCat.from_command_line
which returns a new instance of PyCat
based on arg parsing, which would also be a static method. That way, the class contains more of its business logic and is more useful on its own.
generate_temp_cert
can - and should - be a @staticmethod
, as well. It doesn't reference self
.
You should turn the class into a context manager, since you have a socket that needs to be closed. For more information do some reading here - https://docs.python.org/3/library/stdtypes.html#typecontextmanager - and then use a with
at the top level.
Your Python file, since it's executable, is missing a shebang at the top:
#!/usr/bin/env python3
Your little hack to initialize data
to starting
to work around the first loop iteration is not advisable. There's also a bug where you don't actually check data
directly after your recv
, so you could get back an unexpected None
. Instead, do something like:
response = b''
while True:
data = socket_conn.recv(length)
if not data:
break
# ...
The concatenation you're doing on response
might also be problematic. If you're in Python 2 it's still (strangely) the best thing to do; however, if you're in Python 3, this kind of concatenation can become very inefficient, and you should be using a BytesIO
instead.
You should check if cmd == "exit"
before doing your regex, because you might just throw the results away anyway.
subprocess.check_output(command, stderr=subprocess.STDOUT, shell=True)
Be very careful about this. shell=True
on its own is a security risk. Allowing a network-connected, unauthenticated application to run arbitrary shell commands is a gaping security hole.
That security risk is by design ;) But I wholeheartedly agree with the rest of your points, and really should've implemented a context manager!
– Ludisposed
Dec 7 at 19:51
add a comment |
up vote
2
down vote
up vote
2
down vote
self.host = host if host else '0.0.0.0'
Since this seems to be optional, perhaps you should given the corresponding __init__
argument a default value (None?). Same with _ssl
, if it's an object. If it's a boolean, rename it to _use_ssl
or something; otherwise its purpose is not clear.
This:
self.main_func = self.nc_listen if self.listen else self.nc_connect
self.main()
# ...
def main(self):
self.main_func()
can be simplified to:
self.main = self.nc_listen if self.listen else self.nc_connect
self.main()
However, I advise that you don't call main()
from the constructor. Init is for init. The entire app shouldn't run from the constructor. Call main()
from the top level after construction.
One nicety would be to write a static method PyCat.from_command_line
which returns a new instance of PyCat
based on arg parsing, which would also be a static method. That way, the class contains more of its business logic and is more useful on its own.
generate_temp_cert
can - and should - be a @staticmethod
, as well. It doesn't reference self
.
You should turn the class into a context manager, since you have a socket that needs to be closed. For more information do some reading here - https://docs.python.org/3/library/stdtypes.html#typecontextmanager - and then use a with
at the top level.
Your Python file, since it's executable, is missing a shebang at the top:
#!/usr/bin/env python3
Your little hack to initialize data
to starting
to work around the first loop iteration is not advisable. There's also a bug where you don't actually check data
directly after your recv
, so you could get back an unexpected None
. Instead, do something like:
response = b''
while True:
data = socket_conn.recv(length)
if not data:
break
# ...
The concatenation you're doing on response
might also be problematic. If you're in Python 2 it's still (strangely) the best thing to do; however, if you're in Python 3, this kind of concatenation can become very inefficient, and you should be using a BytesIO
instead.
You should check if cmd == "exit"
before doing your regex, because you might just throw the results away anyway.
subprocess.check_output(command, stderr=subprocess.STDOUT, shell=True)
Be very careful about this. shell=True
on its own is a security risk. Allowing a network-connected, unauthenticated application to run arbitrary shell commands is a gaping security hole.
self.host = host if host else '0.0.0.0'
Since this seems to be optional, perhaps you should given the corresponding __init__
argument a default value (None?). Same with _ssl
, if it's an object. If it's a boolean, rename it to _use_ssl
or something; otherwise its purpose is not clear.
This:
self.main_func = self.nc_listen if self.listen else self.nc_connect
self.main()
# ...
def main(self):
self.main_func()
can be simplified to:
self.main = self.nc_listen if self.listen else self.nc_connect
self.main()
However, I advise that you don't call main()
from the constructor. Init is for init. The entire app shouldn't run from the constructor. Call main()
from the top level after construction.
One nicety would be to write a static method PyCat.from_command_line
which returns a new instance of PyCat
based on arg parsing, which would also be a static method. That way, the class contains more of its business logic and is more useful on its own.
generate_temp_cert
can - and should - be a @staticmethod
, as well. It doesn't reference self
.
You should turn the class into a context manager, since you have a socket that needs to be closed. For more information do some reading here - https://docs.python.org/3/library/stdtypes.html#typecontextmanager - and then use a with
at the top level.
Your Python file, since it's executable, is missing a shebang at the top:
#!/usr/bin/env python3
Your little hack to initialize data
to starting
to work around the first loop iteration is not advisable. There's also a bug where you don't actually check data
directly after your recv
, so you could get back an unexpected None
. Instead, do something like:
response = b''
while True:
data = socket_conn.recv(length)
if not data:
break
# ...
The concatenation you're doing on response
might also be problematic. If you're in Python 2 it's still (strangely) the best thing to do; however, if you're in Python 3, this kind of concatenation can become very inefficient, and you should be using a BytesIO
instead.
You should check if cmd == "exit"
before doing your regex, because you might just throw the results away anyway.
subprocess.check_output(command, stderr=subprocess.STDOUT, shell=True)
Be very careful about this. shell=True
on its own is a security risk. Allowing a network-connected, unauthenticated application to run arbitrary shell commands is a gaping security hole.
answered Dec 7 at 18:57
Reinderien
1,984616
1,984616
That security risk is by design ;) But I wholeheartedly agree with the rest of your points, and really should've implemented a context manager!
– Ludisposed
Dec 7 at 19:51
add a comment |
That security risk is by design ;) But I wholeheartedly agree with the rest of your points, and really should've implemented a context manager!
– Ludisposed
Dec 7 at 19:51
That security risk is by design ;) But I wholeheartedly agree with the rest of your points, and really should've implemented a context manager!
– Ludisposed
Dec 7 at 19:51
That security risk is by design ;) But I wholeheartedly agree with the rest of your points, and really should've implemented a context manager!
– Ludisposed
Dec 7 at 19:51
add a comment |
Thanks for contributing an answer to Code Review Stack Exchange!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
Use MathJax to format equations. MathJax reference.
To learn more, see our tips on writing great answers.
Some of your past answers have not been well-received, and you're in danger of being blocked from answering.
Please pay close attention to the following guidance:
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f209204%2fpycat-a-small-implementation-of-netcat-in-python3-x%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown