Compare commits

..

8 Commits

Author SHA1 Message Date
ed
9f578bfec6 v0.7.5 2021-02-12 07:06:38 +00:00
ed
1f170d7d28 up2k scanner messages less useless 2021-02-12 07:04:35 +00:00
ed
5ae14cf9be up2k scanner more better 2021-02-12 01:07:55 +00:00
ed
aaf9d53be9 more ssl options 2021-02-12 00:31:28 +00:00
ed
75c73f7ba7 add --http-only (might as well) 2021-02-11 22:54:40 +00:00
ed
b6dba8beee imagine going plaintext in the middle of a tls reply 2021-02-11 22:50:59 +00:00
ed
94521cdc1a add --https-only 2021-02-11 22:48:10 +00:00
ed
3365b1c355 add --ssl-ver (ssl/tls versions to allow) 2021-02-11 21:24:17 +00:00
7 changed files with 207 additions and 26 deletions

View File

@@ -56,5 +56,9 @@
// things you may wanna edit: // things you may wanna edit:
// //
"python.pythonPath": "/usr/bin/python3", "python.pythonPath": "/usr/bin/python3",
"python.formatting.blackArgs": [
"-t",
"py27"
],
//"python.linting.enabled": true, //"python.linting.enabled": true,
} }

View File

@@ -8,7 +8,9 @@ __copyright__ = 2019
__license__ = "MIT" __license__ = "MIT"
__url__ = "https://github.com/9001/copyparty/" __url__ = "https://github.com/9001/copyparty/"
import re
import os import os
import sys
import time import time
import shutil import shutil
import filecmp import filecmp
@@ -19,7 +21,13 @@ from textwrap import dedent
from .__init__ import E, WINDOWS, VT100 from .__init__ import E, WINDOWS, VT100
from .__version__ import S_VERSION, S_BUILD_DT, CODENAME from .__version__ import S_VERSION, S_BUILD_DT, CODENAME
from .svchub import SvcHub from .svchub import SvcHub
from .util import py_desc from .util import py_desc, align_tab
HAVE_SSL = True
try:
import ssl
except:
HAVE_SSL = False
class RiceFormatter(argparse.HelpFormatter): class RiceFormatter(argparse.HelpFormatter):
@@ -85,6 +93,74 @@ def ensure_cert():
# printf 'NO\n.\n.\n.\n.\ncopyparty-insecure\n.\n' | faketime '2000-01-01 00:00:00' openssl req -x509 -sha256 -newkey rsa:2048 -keyout insecure.pem -out insecure.pem -days $((($(printf %d 0x7fffffff)-$(date +%s --date=2000-01-01T00:00:00Z))/(60*60*24))) -nodes && ls -al insecure.pem && openssl x509 -in insecure.pem -text -noout # printf 'NO\n.\n.\n.\n.\ncopyparty-insecure\n.\n' | faketime '2000-01-01 00:00:00' openssl req -x509 -sha256 -newkey rsa:2048 -keyout insecure.pem -out insecure.pem -days $((($(printf %d 0x7fffffff)-$(date +%s --date=2000-01-01T00:00:00Z))/(60*60*24))) -nodes && ls -al insecure.pem && openssl x509 -in insecure.pem -text -noout
def configure_ssl_ver(al):
def terse_sslver(txt):
txt = txt.lower()
for c in ["_", "v", "."]:
txt = txt.replace(c, "")
return txt.replace("tls10", "tls1")
# oh man i love openssl
# check this out
# hold my beer
ptn = re.compile(r"^OP_NO_(TLS|SSL)v")
sslver = terse_sslver(al.ssl_ver).split(",")
flags = [k for k in ssl.__dict__ if ptn.match(k)]
# SSLv2 SSLv3 TLSv1 TLSv1_1 TLSv1_2 TLSv1_3
if "help" in sslver:
avail = [terse_sslver(x[6:]) for x in flags]
avail = " ".join(sorted(avail) + ["all"])
print("\navailable ssl/tls versions:\n " + avail)
sys.exit(0)
al.ssl_flags_en = 0
al.ssl_flags_de = 0
for flag in sorted(flags):
ver = terse_sslver(flag[6:])
num = getattr(ssl, flag)
if ver in sslver:
al.ssl_flags_en |= num
else:
al.ssl_flags_de |= num
if sslver == ["all"]:
x = al.ssl_flags_en
al.ssl_flags_en = al.ssl_flags_de
al.ssl_flags_de = x
for k in ["ssl_flags_en", "ssl_flags_de"]:
num = getattr(al, k)
print("{}: {:8x} ({})".format(k, num, num))
# think i need that beer now
def configure_ssl_ciphers(al):
ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
if al.ssl_ver:
ctx.options &= ~al.ssl_flags_en
ctx.options |= al.ssl_flags_de
is_help = al.ciphers == "help"
if al.ciphers:
try:
ctx.set_ciphers(al.ciphers)
except:
if not is_help:
print("\n\033[1;31mfailed to set ciphers\033[0m\n")
if not hasattr(ctx, "get_ciphers"):
print("cannot read cipher list: openssl or python too old")
else:
ciphers = [x["description"] for x in ctx.get_ciphers()]
print("\n ".join(["\nenabled ciphers:"] + align_tab(ciphers) + [""]))
if is_help:
sys.exit(0)
def main(): def main():
time.strptime("19970815", "%Y%m%d") # python#7980 time.strptime("19970815", "%Y%m%d") # python#7980
if WINDOWS: if WINDOWS:
@@ -96,7 +172,8 @@ def main():
print(f.format(S_VERSION, CODENAME, S_BUILD_DT, desc)) print(f.format(S_VERSION, CODENAME, S_BUILD_DT, desc))
ensure_locale() ensure_locale()
ensure_cert() if HAVE_SSL:
ensure_cert()
ap = argparse.ArgumentParser( ap = argparse.ArgumentParser(
formatter_class=RiceFormatter, formatter_class=RiceFormatter,
@@ -133,6 +210,10 @@ def main():
"save,get" dumps to file and returns the page like a GET "save,get" dumps to file and returns the page like a GET
"print,get" prints the data in the log and returns GET "print,get" prints the data in the log and returns GET
(leave out the ",get" to return an error instead) (leave out the ",get" to return an error instead)
see "--ciphers help" for available ssl/tls ciphers,
see "--ssl-ver help" for available ssl/tls versions,
default is what python considers safe, usually >= TLS1
""" """
), ),
) )
@@ -155,6 +236,14 @@ def main():
ap.add_argument("-nid", action="store_true", help="no info disk-usage") ap.add_argument("-nid", action="store_true", help="no info disk-usage")
ap.add_argument("--no-sendfile", action="store_true", help="disable sendfile") ap.add_argument("--no-sendfile", action="store_true", help="disable sendfile")
ap.add_argument("--urlform", type=str, default="print,get", help="how to handle url-forms") ap.add_argument("--urlform", type=str, default="print,get", help="how to handle url-forms")
ap2 = ap.add_argument_group('SSL/TLS options')
ap2.add_argument("--http-only", action="store_true", help="disable ssl/tls")
ap2.add_argument("--https-only", action="store_true", help="disable plaintext")
ap2.add_argument("--ssl-ver", type=str, help="ssl/tls versions to allow")
ap2.add_argument("--ciphers", metavar="LIST", help="set allowed ciphers")
ap2.add_argument("--ssl-dbg", action="store_true", help="dump some tls info")
ap2.add_argument("--ssl-log", metavar="PATH", help="log master secrets")
al = ap.parse_args() al = ap.parse_args()
# fmt: on # fmt: on
@@ -168,6 +257,15 @@ def main():
except: except:
raise Exception("invalid value for -p") raise Exception("invalid value for -p")
if HAVE_SSL:
if al.ssl_ver:
configure_ssl_ver(al)
if al.ciphers:
configure_ssl_ciphers(al)
else:
print("\033[33m ssl module does not exist; cannot enable https\033[0m\n")
SvcHub(al).run() SvcHub(al).run()

View File

@@ -1,8 +1,8 @@
# coding: utf-8 # coding: utf-8
VERSION = (0, 7, 4) VERSION = (0, 7, 5)
CODENAME = "keeping track" CODENAME = "keeping track"
BUILD_DT = (2021, 2, 4) BUILD_DT = (2021, 2, 12)
S_VERSION = ".".join(map(str, VERSION)) S_VERSION = ".".join(map(str, VERSION))
S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT) S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT)

View File

@@ -134,6 +134,16 @@ class HttpCli(object):
uparam["raw"] = True uparam["raw"] = True
uparam["dots"] = True uparam["dots"] = True
if hasattr(self.s, "cipher"):
self.ssl_suf = "".join(
[
" \033[3{}m{}".format(c, s)
for c, s in zip([6, 3, 6], self.s.cipher())
]
)
else:
self.ssl_suf = ""
try: try:
if self.mode in ["GET", "HEAD"]: if self.mode in ["GET", "HEAD"]:
return self.handle_get() and self.keepalive return self.handle_get() and self.keepalive
@@ -211,7 +221,7 @@ class HttpCli(object):
logmsg += " [\033[36m" + rval + "\033[0m]" logmsg += " [\033[36m" + rval + "\033[0m]"
self.log(logmsg) self.log(logmsg + self.ssl_suf)
# "embedded" resources # "embedded" resources
if self.vpath.startswith(".cpr"): if self.vpath.startswith(".cpr"):
@@ -245,7 +255,7 @@ class HttpCli(object):
return self.tx_browser() return self.tx_browser()
def handle_options(self): def handle_options(self):
self.log("OPTIONS " + self.req) self.log("OPTIONS " + self.req + self.ssl_suf)
self.send_headers( self.send_headers(
None, None,
204, 204,
@@ -258,7 +268,7 @@ class HttpCli(object):
return True return True
def handle_put(self): def handle_put(self):
self.log("PUT " + self.req) self.log("PUT " + self.req + self.ssl_suf)
if self.headers.get("expect", "").lower() == "100-continue": if self.headers.get("expect", "").lower() == "100-continue":
try: try:
@@ -269,7 +279,7 @@ class HttpCli(object):
return self.handle_stash() return self.handle_stash()
def handle_post(self): def handle_post(self):
self.log("POST " + self.req) self.log("POST " + self.req + self.ssl_suf)
if self.headers.get("expect", "").lower() == "100-continue": if self.headers.get("expect", "").lower() == "100-continue":
try: try:
@@ -927,8 +937,11 @@ class HttpCli(object):
open_func = open open_func = open
# 512 kB is optimal for huge files, use 64k # 512 kB is optimal for huge files, use 64k
open_args = [fsenc(fs_path), "rb", 64 * 1024] open_args = [fsenc(fs_path), "rb", 64 * 1024]
if hasattr(os, "sendfile"): use_sendfile = (
use_sendfile = not self.args.no_sendfile not self.ssl_suf
and not self.args.no_sendfile
and hasattr(os, "sendfile")
)
# #
# send reply # send reply

View File

@@ -3,10 +3,15 @@ from __future__ import print_function, unicode_literals
import os import os
import sys import sys
import ssl
import time import time
import socket import socket
HAVE_SSL = True
try:
import ssl
except:
HAVE_SSL = False
try: try:
import jinja2 import jinja2
except ImportError: except ImportError:
@@ -75,9 +80,8 @@ class HttpConn(object):
def log(self, msg): def log(self, msg):
self.log_func(self.log_src, msg) self.log_func(self.log_src, msg)
def run(self): def _detect_https(self):
method = None method = None
self.sr = None
if self.cert_path: if self.cert_path:
try: try:
method = self.s.recv(4, socket.MSG_PEEK) method = self.s.recv(4, socket.MSG_PEEK)
@@ -102,16 +106,52 @@ class HttpConn(object):
self.s.send(b"HTTP/1.1 400 Bad Request\r\n\r\n" + err.encode("utf-8")) self.s.send(b"HTTP/1.1 400 Bad Request\r\n\r\n" + err.encode("utf-8"))
return return
if method not in [None, b"GET ", b"HEAD", b"POST", b"PUT ", b"OPTI"]: return method not in [None, b"GET ", b"HEAD", b"POST", b"PUT ", b"OPTI"]
def run(self):
self.sr = None
if self.args.https_only:
is_https = True
elif self.args.http_only or not HAVE_SSL:
is_https = False
else:
is_https = self._detect_https()
if is_https:
if self.sr: if self.sr:
self.log("\033[1;31mTODO: cannot do https in jython\033[0m") self.log("\033[1;31mTODO: cannot do https in jython\033[0m")
return return
self.log_src = self.log_src.replace("[36m", "[35m") self.log_src = self.log_src.replace("[36m", "[35m")
try: try:
self.s = ssl.wrap_socket( ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
self.s, server_side=True, certfile=self.cert_path ctx.load_cert_chain(self.cert_path)
) if self.args.ssl_ver:
ctx.options &= ~self.args.ssl_flags_en
ctx.options |= self.args.ssl_flags_de
# print(repr(ctx.options))
if self.args.ssl_log:
try:
ctx.keylog_filename = self.args.ssl_log
except:
self.log("keylog failed; openssl or python too old")
if self.args.ciphers:
ctx.set_ciphers(self.args.ciphers)
self.s = ctx.wrap_socket(self.s, server_side=True)
if self.args.ssl_dbg and hasattr(self.s, "shared_ciphers"):
overlap = [y[::-1] for y in self.s.shared_ciphers()]
lines = [str(x) for x in (["TLS cipher overlap:"] + overlap)]
self.log("\n".join(lines))
for k, v in [
["compression", self.s.compression()],
["ALPN proto", self.s.selected_alpn_protocol()],
["NPN proto", self.s.selected_npn_protocol()],
]:
self.log("TLS {}: {}".format(k, v or "nah"))
except Exception as ex: except Exception as ex:
em = str(ex) em = str(ex)

View File

@@ -130,7 +130,7 @@ class Up2k(object):
if db: if db:
# can be symlink so don't `and d.startswith(top)`` # can be symlink so don't `and d.startswith(top)``
excl = set([d for d in tops if d != top]) excl = set([d for d in tops if d != top])
self._build_dir([db, 0], top, excl, top) self._build_dir([db, 0, time.time()], top, excl, top)
self._drop_lost(db, top) self._drop_lost(db, top)
db.commit() db.commit()
@@ -138,7 +138,7 @@ class Up2k(object):
try: try:
inodes = [fsdec(x) for x in os.listdir(fsenc(cdir))] inodes = [fsdec(x) for x in os.listdir(fsenc(cdir))]
except Exception as ex: except Exception as ex:
self.log("up2k", "listdir: " + repr(ex)) self.log("up2k", "listdir: {} @ [{}]".format(repr(ex), cdir))
return return
histdir = os.path.join(top, ".hist") histdir = os.path.join(top, ".hist")
@@ -147,7 +147,7 @@ class Up2k(object):
try: try:
inf = os.stat(fsenc(abspath)) inf = os.stat(fsenc(abspath))
except Exception as ex: except Exception as ex:
self.log("up2k", "stat: " + repr(ex)) self.log("up2k", "stat: {} @ [{}]".format(repr(ex), abspath))
continue continue
if stat.S_ISDIR(inf.st_mode): if stat.S_ISDIR(inf.st_mode):
@@ -182,15 +182,18 @@ class Up2k(object):
try: try:
hashes = self._hashlist_from_file(abspath) hashes = self._hashlist_from_file(abspath)
except Exception as ex: except Exception as ex:
self.log("up2k", "hash: " + repr(ex)) self.log("up2k", "hash: {} @ [{}]".format(repr(ex), abspath))
continue continue
wark = self._wark_from_hashlist(inf.st_size, hashes) wark = self._wark_from_hashlist(inf.st_size, hashes)
self.db_add(dbw[0], wark, rp, inf.st_mtime, inf.st_size) self.db_add(dbw[0], wark, rp, inf.st_mtime, inf.st_size)
dbw[1] += 1 dbw[1] += 1
if dbw[1] > 1024: td = time.time() - dbw[2]
if dbw[1] > 1024 or td > 60:
self.log("up2k", "commit {} files".format(dbw[1]))
dbw[0].commit() dbw[0].commit()
dbw[1] = 0 dbw[1] = 0
dbw[2] = time.time()
def _drop_lost(self, db, top): def _drop_lost(self, db, top):
rm = [] rm = []
@@ -201,7 +204,7 @@ class Up2k(object):
if not os.path.exists(fsenc(abspath)): if not os.path.exists(fsenc(abspath)):
rm.append(drp) rm.append(drp)
except Exception as ex: except Exception as ex:
self.log("up2k", "stat-rm: " + repr(ex)) self.log("up2k", "stat-rm: {} @ [{}]".format(repr(ex), abspath))
if not rm: if not rm:
return return
@@ -512,8 +515,15 @@ class Up2k(object):
fsz = os.path.getsize(path) fsz = os.path.getsize(path)
csz = self._get_chunksize(fsz) csz = self._get_chunksize(fsz)
ret = [] ret = []
last_print = time.time()
with open(path, "rb", 512 * 1024) as f: with open(path, "rb", 512 * 1024) as f:
while fsz > 0: while fsz > 0:
now = time.time()
td = now - last_print
if td >= 0.3:
last_print = now
print(" {} \n\033[A".format(fsz), end="")
hashobj = hashlib.sha512() hashobj = hashlib.sha512()
rem = min(csz, fsz) rem = min(csz, fsz)
fsz -= rem fsz -= rem

View File

@@ -108,7 +108,7 @@ def ren_open(fname, *args, **kwargs):
with open(fname, *args, **kwargs) as f: with open(fname, *args, **kwargs) as f:
yield {"orz": [f, fname]} yield {"orz": [f, fname]}
return return
orig_name = fname orig_name = fname
bname = fname bname = fname
ext = "" ext = ""
@@ -632,10 +632,10 @@ def sendfile_kern(lower, upper, f, s):
except Exception as ex: except Exception as ex:
# print("sendfile: " + repr(ex)) # print("sendfile: " + repr(ex))
n = 0 n = 0
if n <= 0: if n <= 0:
return upper - ofs return upper - ofs
ofs += n ofs += n
# print("sendfile: ok, sent {} now, {} total, {} remains".format(n, ofs - lower, upper - ofs)) # print("sendfile: ok, sent {} now, {} total, {} remains".format(n, ofs - lower, upper - ofs))
@@ -718,6 +718,22 @@ def py_desc():
) )
def align_tab(lines):
rows = []
ncols = 0
for ln in lines:
row = [x for x in ln.split(" ") if x]
ncols = max(ncols, len(row))
rows.append(row)
lens = [0] * ncols
for row in rows:
for n, col in enumerate(row):
lens[n] = max(lens[n], len(col))
return ["".join(x.ljust(y + 2) for x, y in zip(row, lens)) for row in rows]
class Pebkac(Exception): class Pebkac(Exception):
def __init__(self, code, msg=None): def __init__(self, code, msg=None):
super(Pebkac, self).__init__(msg or HTTPCODE[code]) super(Pebkac, self).__init__(msg or HTTPCODE[code])