2020-05-03 22:48:05 +02:00
#!/usr/bin/env python
# coding: utf-8
from __future__ import print_function , unicode_literals
2020-08-23 22:32:44 +00:00
import re , os , sys , time , shutil , signal , tarfile , hashlib , platform , tempfile
2020-05-03 22:48:05 +02:00
import subprocess as sp
"""
run me with any version of python , i will unpack and run copyparty
( but please don ' t edit this file with a text editor
since that would probably corrupt the binary stuff at the end )
2020-05-06 00:39:21 +02:00
there ' s zero binaries! just plaintext python scripts all the way down
2020-05-03 22:48:05 +02:00
so you can easily unpack the archive and inspect it for shady stuff
the archive data is attached after the b " \n # eof \n " archive marker ,
b " \n #n " decodes to b " \n "
b " \n #r " decodes to b " \r "
b " \n # " decodes to b " "
"""
2020-05-06 00:39:21 +02:00
# set by make-sfx.sh
2020-05-03 22:48:05 +02:00
VER = None
SIZE = None
CKSUM = None
STAMP = None
2020-05-06 00:39:21 +02:00
PY2 = sys . version_info [ 0 ] == 2
2020-05-03 22:48:05 +02:00
sys . dont_write_bytecode = True
me = os . path . abspath ( os . path . realpath ( __file__ ) )
2020-08-23 22:32:44 +00:00
cpp = None
2020-05-03 22:48:05 +02:00
def eprint ( * args , * * kwargs ) :
kwargs [ " file " ] = sys . stderr
print ( * args , * * kwargs )
def msg ( * args , * * kwargs ) :
if args :
2020-05-06 00:39:21 +02:00
args = [ " [SFX] " , args [ 0 ] ] + list ( args [ 1 : ] )
2020-05-03 22:48:05 +02:00
eprint ( * args , * * kwargs )
# skip 1
def testptn1 ( ) :
""" test: creates a test-pattern for encode() """
import struct
buf = b " "
for c in range ( 256 ) :
buf + = struct . pack ( " B " , c )
yield buf
def testptn2 ( ) :
import struct
for a in range ( 256 ) :
if a % 16 == 0 :
msg ( a )
for b in range ( 256 ) :
buf = b " "
for c in range ( 256 ) :
buf + = struct . pack ( " BBBB " , a , b , c , b )
yield buf
def testptn3 ( ) :
with open ( " C:/Users/ed/Downloads/python-3.8.1-amd64.exe " , " rb " , 512 * 1024 ) as f :
while True :
buf = f . read ( 512 * 1024 )
if not buf :
break
yield buf
testptn = testptn2
def testchk ( cdata ) :
""" test: verifies that `data` yields testptn """
import struct
cbuf = b " "
mbuf = b " "
checked = 0
t0 = time . time ( )
mdata = testptn ( )
while True :
if not mbuf :
try :
mbuf + = next ( mdata )
except :
break
if not cbuf :
try :
cbuf + = next ( cdata )
except :
expect = mbuf [ : 8 ]
expect = " " . join (
" {:02x} " . format ( x )
for x in struct . unpack ( " B " * len ( expect ) , expect )
)
raise Exception (
" truncated at {} , expected {} " . format ( checked + len ( cbuf ) , expect )
)
ncmp = min ( len ( cbuf ) , len ( mbuf ) )
# msg("checking {:x}H bytes, {:x}H ok so far".format(ncmp, checked))
for n in range ( ncmp ) :
checked + = 1
if cbuf [ n ] != mbuf [ n ] :
expect = mbuf [ n : n + 8 ]
expect = " " . join (
" {:02x} " . format ( x )
for x in struct . unpack ( " B " * len ( expect ) , expect )
)
cc = struct . unpack ( b " B " , cbuf [ n : n + 1 ] ) [ 0 ]
raise Exception (
" byte {:x} H bad, got {:02x} , expected {} " . format ( checked , cc , expect )
)
cbuf = cbuf [ ncmp : ]
mbuf = mbuf [ ncmp : ]
td = time . time ( ) - t0
txt = " all {} d bytes OK in {:.3f} sec, {:.3f} MB/s " . format (
checked , td , ( checked / ( 1024 * 1024.0 ) ) / td
)
msg ( txt )
2020-05-06 00:39:21 +02:00
def encode ( data , size , cksum , ver , ts ) :
2020-05-03 22:48:05 +02:00
""" creates a new sfx; `data` should yield bufs to attach """
nin = 0
nout = 0
skip = False
with open ( me , " rb " ) as fi :
unpk = " "
src = fi . read ( ) . replace ( b " \r " , b " " ) . rstrip ( b " \n " ) . decode ( " utf-8 " )
for ln in src . split ( " \n " ) :
if ln . endswith ( " # skip 0 " ) :
skip = False
continue
if ln . endswith ( " # skip 1 " ) or skip :
skip = True
continue
unpk + = ln + " \n "
for k , v in [
[ " VER " , ' " ' + ver + ' " ' ] ,
[ " SIZE " , size ] ,
[ " CKSUM " , ' " ' + cksum + ' " ' ] ,
2020-05-06 00:39:21 +02:00
[ " STAMP " , ts ] ,
2020-05-03 22:48:05 +02:00
] :
v1 = " \n {} = None \n " . format ( k )
v2 = " \n {} = {} \n " . format ( k , v )
unpk = unpk . replace ( v1 , v2 )
unpk = unpk . replace ( " \n " , " \n \t " )
for _ in range ( 16 ) :
unpk = unpk . replace ( " \t " , " \t \t " )
with open ( " sfx.out " , " wb " ) as f :
f . write ( unpk . encode ( " utf-8 " ) + b " \n \n # eof \n # " )
for buf in data :
ebuf = buf . replace ( b " \n " , b " \n #n " ) . replace ( b " \r " , b " \n #r " )
f . write ( ebuf )
nin + = len ( buf )
nout + = len ( ebuf )
msg ( " wrote {:x} H bytes ( {:x} H after encode) " . format ( nin , nout ) )
2020-05-06 00:39:21 +02:00
def makesfx ( tar_src , ver , ts ) :
2020-05-03 22:48:05 +02:00
sz = os . path . getsize ( tar_src )
cksum = hashfile ( tar_src )
2020-05-06 00:39:21 +02:00
encode ( yieldfile ( tar_src ) , sz , cksum , ver , ts )
2020-05-03 22:48:05 +02:00
# skip 0
2020-08-18 19:23:17 +00:00
def u8 ( gen ) :
try :
for s in gen :
yield s . decode ( " utf-8 " , " ignore " )
except :
yield s
for s in gen :
yield s
2020-05-03 22:48:05 +02:00
def get_py_win ( ret ) :
tops = [ ]
p = str ( os . getenv ( " LocalAppdata " ) )
if p :
tops . append ( os . path . join ( p , " Programs " , " Python " ) )
progfiles = { }
for p in [ " ProgramFiles " , " ProgramFiles(x86) " ] :
p = str ( os . getenv ( p ) )
if p :
progfiles [ p ] = 1
# 32bit apps get x86 for both
if p . endswith ( " (x86) " ) :
progfiles [ p [ : - 6 ] ] = 1
tops + = list ( progfiles . keys ( ) )
for sysroot in [ me , sys . executable ] :
sysroot = sysroot [ : 3 ] . upper ( )
if sysroot [ 1 ] == " : " and sysroot not in tops :
tops . append ( sysroot )
# $WIRESHARK_SLOGAN
for top in tops :
try :
2020-08-18 19:23:17 +00:00
for name1 in u8 ( sorted ( os . listdir ( top ) , reverse = True ) ) :
2020-05-03 22:48:05 +02:00
if name1 . lower ( ) . startswith ( " python " ) :
path1 = os . path . join ( top , name1 )
try :
2020-08-18 19:23:17 +00:00
for name2 in u8 ( os . listdir ( path1 ) ) :
2020-05-03 22:48:05 +02:00
if name2 . lower ( ) == " python.exe " :
path2 = os . path . join ( path1 , name2 )
ret [ path2 . lower ( ) ] = path2
except :
pass
except :
pass
def get_py_nix ( ret ) :
ptn = re . compile ( r " ^(python|pypy)[0-9 \ .-]*$ " )
for bindir in os . getenv ( " PATH " ) . split ( " : " ) :
if not bindir :
next
try :
2020-08-18 19:23:17 +00:00
for fn in u8 ( os . listdir ( bindir ) ) :
2020-05-03 22:48:05 +02:00
if ptn . match ( fn ) :
fn = os . path . join ( bindir , fn )
ret [ fn . lower ( ) ] = fn
except :
pass
def read_py ( binp ) :
cmd = [
binp ,
" -c " ,
" import sys; sys.stdout.write( ' ' .join(str(x) for x in sys.version_info)); import jinja2 " ,
]
p = sp . Popen ( cmd , stdout = sp . PIPE , stderr = sp . PIPE )
ver , _ = p . communicate ( )
2020-05-06 00:39:21 +02:00
ver = ver . decode ( " utf-8 " ) . split ( " " ) [ : 3 ]
ver = [ int ( x ) if x . isdigit ( ) else 0 for x in ver ]
return ver , p . returncode == 0
2020-05-03 22:48:05 +02:00
def get_pys ( ) :
ver , chk = read_py ( sys . executable )
2020-05-12 00:26:40 +02:00
if chk or PY2 :
2020-05-03 22:48:05 +02:00
return [ [ chk , ver , sys . executable ] ]
hits = { sys . executable . lower ( ) : sys . executable }
if platform . system ( ) == " Windows " :
get_py_win ( hits )
else :
get_py_nix ( hits )
ret = [ ]
for binp in hits . values ( ) :
ver , chk = read_py ( binp )
ret . append ( [ chk , ver , binp ] )
2020-05-06 00:39:21 +02:00
msg ( " \t " . join ( str ( x ) for x in ret [ - 1 ] ) )
2020-05-03 22:48:05 +02:00
return ret
def yieldfile ( fn ) :
with open ( fn , " rb " ) as f :
for block in iter ( lambda : f . read ( 64 * 1024 ) , b " " ) :
yield block
def hashfile ( fn ) :
hasher = hashlib . md5 ( )
for block in yieldfile ( fn ) :
hasher . update ( block )
return hasher . hexdigest ( )
def unpack ( ) :
""" unpacks the tar yielded by `data` """
2020-05-06 00:39:21 +02:00
name = " pe-copyparty "
2020-08-23 22:32:44 +00:00
tag = " v " + str ( STAMP )
2020-05-06 00:39:21 +02:00
withpid = " {} . {} " . format ( name , os . getpid ( ) )
top = tempfile . gettempdir ( )
final = os . path . join ( top , name )
mine = os . path . join ( top , withpid )
tar = os . path . join ( mine , " tar " )
2020-08-23 22:32:44 +00:00
try :
if tag in os . listdir ( final ) :
msg ( " found early " )
return final
except :
pass
2020-05-03 22:48:05 +02:00
nwrite = 0
2020-05-06 00:39:21 +02:00
os . mkdir ( mine )
2020-05-03 22:48:05 +02:00
with open ( tar , " wb " ) as f :
for buf in get_payload ( ) :
nwrite + = len ( buf )
f . write ( buf )
if nwrite != SIZE :
t = " \n \n bad file: \n expected {} bytes, got {} \n " . format ( SIZE , nwrite )
raise Exception ( t )
cksum = hashfile ( tar )
if cksum != CKSUM :
t = " \n \n bad file: \n {} expected, \n {} obtained \n " . format ( CKSUM , cksum )
raise Exception ( t )
with tarfile . open ( tar , " r:bz2 " ) as tf :
2020-05-06 00:39:21 +02:00
tf . extractall ( mine )
2020-05-03 22:48:05 +02:00
os . remove ( tar )
2020-08-23 22:32:44 +00:00
with open ( os . path . join ( mine , tag ) , " wb " ) as f :
2020-05-06 00:39:21 +02:00
f . write ( b " h \n " )
2020-08-23 22:32:44 +00:00
try :
if tag in os . listdir ( final ) :
msg ( " found late " )
return final
except :
pass
2020-05-06 00:39:21 +02:00
try :
if os . path . islink ( final ) :
os . remove ( final )
else :
shutil . rmtree ( final )
except :
2020-05-03 22:48:05 +02:00
pass
2020-05-06 00:39:21 +02:00
try :
os . symlink ( mine , final )
except :
try :
os . rename ( mine , final )
except :
msg ( " reloc fail, " , mine )
return mine
2020-08-18 19:23:17 +00:00
for fn in u8 ( os . listdir ( top ) ) :
2020-05-06 00:39:21 +02:00
if fn . startswith ( name ) and fn not in [ name , withpid ] :
try :
old = os . path . join ( top , fn )
if time . time ( ) - os . path . getmtime ( old ) > 10 :
shutil . rmtree ( old )
except :
pass
return final
2020-05-03 22:48:05 +02:00
def get_payload ( ) :
""" yields the binary data attached to script """
with open ( me , " rb " ) as f :
ptn = b " \n # eof \n # "
buf = b " "
for n in range ( 64 ) :
buf + = f . read ( 4096 )
ofs = buf . find ( ptn )
if ofs > = 0 :
break
if ofs < 0 :
raise Exception ( " could not find archive marker " )
# start reading from the final b"\n"
fpos = ofs + len ( ptn ) - 3
# msg("tar found at", fpos)
f . seek ( fpos )
dpos = 0
leftovers = b " "
while True :
rbuf = f . read ( 1024 * 32 )
if rbuf :
buf = leftovers + rbuf
ofs = buf . rfind ( b " \n " )
if len ( buf ) < = 4 :
leftovers = buf
continue
if ofs > = len ( buf ) - 4 :
leftovers = buf [ ofs : ]
buf = buf [ : ofs ]
else :
leftovers = b " \n # "
else :
buf = leftovers
fpos + = len ( buf ) + 1
buf = (
buf . replace ( b " \n # " , b " " )
. replace ( b " \n #r " , b " \r " )
. replace ( b " \n #n " , b " \n " )
)
dpos + = len ( buf ) - 1
yield buf
if not rbuf :
break
def confirm ( ) :
msg ( )
msg ( " *** hit enter to exit *** " )
2020-08-23 22:32:44 +00:00
try :
raw_input ( ) if PY2 else input ( )
except :
pass
2020-05-03 22:48:05 +02:00
def run ( tmp , py ) :
2020-08-23 22:32:44 +00:00
global cpp
2020-05-03 22:48:05 +02:00
msg ( " OK " )
msg ( " will use: " , py )
2020-05-06 00:39:21 +02:00
msg ( " bound to: " , tmp )
2020-05-03 22:48:05 +02:00
2020-06-24 23:53:23 +00:00
# "systemd-tmpfiles-clean.timer"?? HOW do you even come up with this shit
try :
import fcntl
fd = os . open ( tmp , os . O_RDONLY )
fcntl . flock ( fd , fcntl . LOCK_EX | fcntl . LOCK_NB )
tmp = os . readlink ( tmp ) # can't flock a symlink, even with O_NOFOLLOW
except :
pass
2020-05-03 22:48:05 +02:00
fp_py = os . path . join ( tmp , " py " )
2020-08-23 22:32:44 +00:00
try :
with open ( fp_py , " wb " ) as f :
f . write ( py . encode ( " utf-8 " ) + b " \n " )
except :
pass
2020-05-03 22:48:05 +02:00
2020-05-06 00:39:21 +02:00
# avoid loading ./copyparty.py
cmd = [
py ,
" -c " ,
' import sys, runpy; sys.path.insert(0, r " '
+ tmp
+ ' " ); runpy.run_module( " copyparty " , run_name= " __main__ " ) ' ,
] + list ( sys . argv [ 1 : ] )
2020-05-03 22:48:05 +02:00
2020-05-06 00:39:21 +02:00
msg ( " \n " , cmd , " \n " )
2020-08-23 22:32:44 +00:00
cpp = sp . Popen ( str ( x ) for x in cmd )
2020-05-03 22:48:05 +02:00
try :
2020-08-23 22:32:44 +00:00
cpp . wait ( )
2020-05-03 22:48:05 +02:00
except :
2020-08-23 22:32:44 +00:00
cpp . wait ( )
2020-05-03 22:48:05 +02:00
2020-08-23 22:32:44 +00:00
if cpp . returncode != 0 :
2020-05-03 22:48:05 +02:00
confirm ( )
2020-08-23 22:32:44 +00:00
sys . exit ( cpp . returncode )
def bye ( sig , frame ) :
if cpp is not None :
cpp . terminate ( )
2020-05-03 22:48:05 +02:00
def main ( ) :
sysver = str ( sys . version ) . replace ( " \n " , " \n " + " " * 18 )
2020-05-06 00:39:21 +02:00
pktime = time . strftime ( " % Y- % m- %d , % H: % M: % S " , time . gmtime ( STAMP ) )
os . system ( " " )
2020-05-03 22:48:05 +02:00
msg ( )
msg ( " this is: copyparty " , VER )
2020-05-06 00:39:21 +02:00
msg ( " packed at: " , pktime , " UTC, " , STAMP )
2020-05-03 22:48:05 +02:00
msg ( " archive is: " , me )
msg ( " python bin: " , sys . executable )
msg ( " python ver: " , platform . python_implementation ( ) , sysver )
msg ( )
arg = " "
try :
arg = sys . argv [ 1 ]
except :
pass
# skip 1
if arg == " --sfx-testgen " :
2020-05-06 00:39:21 +02:00
return encode ( testptn ( ) , 1 , " x " , " x " , 1 )
2020-05-03 22:48:05 +02:00
if arg == " --sfx-testchk " :
return testchk ( get_payload ( ) )
if arg == " --sfx-make " :
2020-05-06 00:39:21 +02:00
tar , ver , ts = sys . argv [ 2 : ]
return makesfx ( tar , ver , ts )
2020-05-03 22:48:05 +02:00
# skip 0
2020-08-23 22:32:44 +00:00
signal . signal ( signal . SIGTERM , bye )
2020-05-03 22:48:05 +02:00
tmp = unpack ( )
fp_py = os . path . join ( tmp , " py " )
if os . path . exists ( fp_py ) :
with open ( fp_py , " rb " ) as f :
py = f . read ( ) . decode ( " utf-8 " ) . rstrip ( )
2020-05-06 00:39:21 +02:00
return run ( tmp , py )
2020-05-03 22:48:05 +02:00
pys = get_pys ( )
pys . sort ( reverse = True )
j2 , ver , py = pys [ 0 ]
if j2 :
2020-05-06 00:39:21 +02:00
try :
os . rename ( os . path . join ( tmp , " jinja2 " ) , os . path . join ( tmp , " x.jinja2 " ) )
except :
pass
2020-05-03 22:48:05 +02:00
return run ( tmp , py )
msg ( " \n could not find jinja2; will use py2 + the bundled version \n " )
for _ , ver , py in pys :
if ver > [ 2 , 7 ] and ver < [ 3 , 0 ] :
return run ( tmp , py )
m = " \033 [1;31m \n \n \n could not find a python with jinja2 installed; please do one of these: \n \n pip install --user jinja2 \n \n install python2 \n \n \033 [0m "
msg ( m )
confirm ( )
sys . exit ( 1 )
if __name__ == " __main__ " :
main ( )
# skip 1
# python sfx.py --sfx-testgen && python test.py --sfx-testchk
# c:\Python27\python.exe sfx.py --sfx-testgen && c:\Python27\python.exe test.py --sfx-testchk