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:
//
"python.pythonPath": "/usr/bin/python3",
"python.formatting.blackArgs": [
"-t",
"py27"
],
//"python.linting.enabled": true,
}

View File

@@ -8,7 +8,9 @@ __copyright__ = 2019
__license__ = "MIT"
__url__ = "https://github.com/9001/copyparty/"
import re
import os
import sys
import time
import shutil
import filecmp
@@ -19,7 +21,13 @@ from textwrap import dedent
from .__init__ import E, WINDOWS, VT100
from .__version__ import S_VERSION, S_BUILD_DT, CODENAME
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):
@@ -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
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():
time.strptime("19970815", "%Y%m%d") # python#7980
if WINDOWS:
@@ -96,7 +172,8 @@ def main():
print(f.format(S_VERSION, CODENAME, S_BUILD_DT, desc))
ensure_locale()
ensure_cert()
if HAVE_SSL:
ensure_cert()
ap = argparse.ArgumentParser(
formatter_class=RiceFormatter,
@@ -133,6 +210,10 @@ def main():
"save,get" dumps to file and returns the page like a GET
"print,get" prints the data in the log and returns GET
(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("--no-sendfile", action="store_true", help="disable sendfile")
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()
# fmt: on
@@ -168,6 +257,15 @@ def main():
except:
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()

View File

@@ -1,8 +1,8 @@
# coding: utf-8
VERSION = (0, 7, 4)
VERSION = (0, 7, 5)
CODENAME = "keeping track"
BUILD_DT = (2021, 2, 4)
BUILD_DT = (2021, 2, 12)
S_VERSION = ".".join(map(str, VERSION))
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["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:
if self.mode in ["GET", "HEAD"]:
return self.handle_get() and self.keepalive
@@ -211,7 +221,7 @@ class HttpCli(object):
logmsg += " [\033[36m" + rval + "\033[0m]"
self.log(logmsg)
self.log(logmsg + self.ssl_suf)
# "embedded" resources
if self.vpath.startswith(".cpr"):
@@ -245,7 +255,7 @@ class HttpCli(object):
return self.tx_browser()
def handle_options(self):
self.log("OPTIONS " + self.req)
self.log("OPTIONS " + self.req + self.ssl_suf)
self.send_headers(
None,
204,
@@ -258,7 +268,7 @@ class HttpCli(object):
return True
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":
try:
@@ -269,7 +279,7 @@ class HttpCli(object):
return self.handle_stash()
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":
try:
@@ -927,8 +937,11 @@ class HttpCli(object):
open_func = open
# 512 kB is optimal for huge files, use 64k
open_args = [fsenc(fs_path), "rb", 64 * 1024]
if hasattr(os, "sendfile"):
use_sendfile = not self.args.no_sendfile
use_sendfile = (
not self.ssl_suf
and not self.args.no_sendfile
and hasattr(os, "sendfile")
)
#
# send reply

View File

@@ -3,10 +3,15 @@ from __future__ import print_function, unicode_literals
import os
import sys
import ssl
import time
import socket
HAVE_SSL = True
try:
import ssl
except:
HAVE_SSL = False
try:
import jinja2
except ImportError:
@@ -75,9 +80,8 @@ class HttpConn(object):
def log(self, msg):
self.log_func(self.log_src, msg)
def run(self):
def _detect_https(self):
method = None
self.sr = None
if self.cert_path:
try:
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"))
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:
self.log("\033[1;31mTODO: cannot do https in jython\033[0m")
return
self.log_src = self.log_src.replace("[36m", "[35m")
try:
self.s = ssl.wrap_socket(
self.s, server_side=True, certfile=self.cert_path
)
ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
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:
em = str(ex)

View File

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

View File

@@ -108,7 +108,7 @@ def ren_open(fname, *args, **kwargs):
with open(fname, *args, **kwargs) as f:
yield {"orz": [f, fname]}
return
orig_name = fname
bname = fname
ext = ""
@@ -632,10 +632,10 @@ def sendfile_kern(lower, upper, f, s):
except Exception as ex:
# print("sendfile: " + repr(ex))
n = 0
if n <= 0:
return upper - ofs
ofs += n
# 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):
def __init__(self, code, msg=None):
super(Pebkac, self).__init__(msg or HTTPCODE[code])