diff options
-rw-r--r-- | steuermann/rexec.py | 105 | ||||
-rw-r--r-- | steuermann/rexecd.py | 242 |
2 files changed, 347 insertions, 0 deletions
diff --git a/steuermann/rexec.py b/steuermann/rexec.py new file mode 100644 index 0000000..30e58dc --- /dev/null +++ b/steuermann/rexec.py @@ -0,0 +1,105 @@ +import sys +import urllib +import urllib2 +import pandokia.helpers.easyargs as easyargs +import os.path + +# http://ss64.com/nt/ - language of cmd.exe + + +# URLs are +# http:// +# hostname +# :port +# /password/ +# directive +# args +# +# method=GET directive=run/ +# args = url encoded command line +# +# method=POST directive=upload +# no args +# post ... + +def urlbase( host, password ) : + if not ':' in host : + host = host + ':7070' + url = "http://%s/%s/"%(host,urllib.quote_plus(password)) + return url + +def run( host, cmd, password, directory ): + url = urlbase(host,password) + "run" + + data = urllib.urlencode( { 'password' : password, + 'dirname' : directory, + 'cmd' : cmd + } + ) + req = urllib2.Request(url, data) + try : + f = urllib2.urlopen(req) + except urllib2.HTTPError, e: + print "HTTP ERROR",e.code + print e.read() + return 1 + print f.read() + return 0 + +def upload( host, filename, password, directory) : + url = urlbase(host, password) + "upload" + + f = open(filename,"r") + + data = urllib.urlencode( { 'password' : password, + 'dirname' : directory, + 'filename' : os.path.basename(filename), + 'data' : f.read(), + } + ) + req = urllib2.Request(url, data) + try : + f = urllib2.urlopen(req) + except urllib2.HTTPError, e: + print "HTTP ERROR",e.code + print e.read() + return 1 + print f.read() + return 0 + +if __name__ == '__main__' : + opt, args = easyargs.get( { + '-p' : '=', # password + '-f' : '=', # password from file + '-u' : '', # upload + '-d' : '=', # upload destination directory (default .) + '-h' : '=', # host name + } ) + if '-p' in opt : + password = opt['-p'] + elif '-f' in opt : + password = open(opt['-f'],'r').readline().strip() + + if not ( '-h' in opt ) : + print "must give host name with -h" + sys.exit(2) + host = opt['-h'] + if '-d' in opt : + directory = opt['-d'] + else : + directory = "." + + if opt['-u'] : + ex = 0 + for x in args : + ex |= upload(host = host , filename=x, directory=directory, password=password) + sys.exit(ex) + else : + x = run( host=host, + cmd = ' '.join(args), + password = password, + directory = directory + ) + sys.exit(x) + + diff --git a/steuermann/rexecd.py b/steuermann/rexecd.py new file mode 100644 index 0000000..712d48f --- /dev/null +++ b/steuermann/rexecd.py @@ -0,0 +1,242 @@ +# +# steuermann remote execution daemon - use this when you can't use +# something better (like ssh) to get your commands running in the +# other machine. +# +# Copyright 2012 Association of Universities for Research in Astronomy (AURA) +# + +import os +import time +import CGIHTTPServer +import BaseHTTPServer +import SocketServer +import platform +import subprocess +import urllib +import urlparse + +os.chdir('/') +print os.getcwd() + +##### +# + +valid_client_ip = ( + '130.167.180.22', # arzach + '130.167.180.4', # ssb + '130.167.237.185', # banana + '130.167.237.55', # vxp-dukat + ) + +##### +# +# This uses the python stock http server. Here is a request handler +# that services GET requests by executing the command passed in. +# All other requests are invalid. + +password = "pass" + +class my_handler( CGIHTTPServer.CGIHTTPRequestHandler ) : + + def __init__(self, request, client_address, server) : + # init the superclass + CGIHTTPServer.CGIHTTPRequestHandler.__init__(self, request, client_address, server) + + def reject_client(self) : + print self.client_address + if not ( self.client_address[0] in valid_client_ip ) : + self.bad_client('a') + return 1 + + if self.path.startswith('/' + password + '/' ) : + self.path = self.path[len(password)+2:] + else : + self.bad_client('p') + return 1 + + return 0 + + def do_GET(self) : + # GET /password/command... + # run the command + + if self.reject_client() : + return + + path = self.path + + print "GET",path + self.send_response(200) + self.send_header('Content-type', 'text/plain') + self.end_headers() + self.wfile.write("Hello world: %s\n"%path) + self.wfile.flush() + + def bad_client(self, why) : + self.send_response(500) + self.send_header('Content-type', 'text/plain') + self.end_headers() + self.wfile.write("bad client: %s\n"%why) + self.wfile.flush() + return + + def do_INVALID(self) : + self.send_response(500) + self.send_header('Content-type', 'text/plain') + self.end_headers() + self.wfile.write("error\n") + self.wfile.flush() + return + + def do_POST(self) : + # POST password=pass&name=filename&data=filedata + # upload data to a file + + if self.reject_client() : + return + + print self.path + + + length = int(self.headers['Content-Length']) + data = self.rfile.read(length) + d = urlparse.parse_qs(data) + + for x in sorted([ x for x in d]) : + print x,d[x] + + if d['password'][0] != password : + self.bad_client('p') + return + + dirname = d['dirname'][0] + print "CD",dirname + os.chdir(dirname) + + if self.path == 'upload' : + + filename = d['filename'][0] + + mode = 'wb' + if 'mode' in d : + t = d['mode'][0] + if t == 't' or t == 'text' : + mode = 'w' + elif t == 'b' or t == 'binary' : + mode = 'wb' + else : + return self.bad_client('mode') + + f = open(filename,'wb') + f.write(d['data'][0]) + f.close() + + self.send_response(200) + self.send_header('Content-type', 'text/plain') + self.end_headers() + self.wfile.write("uploaded %s\n"%filename) + self.wfile.flush() + + elif self.path == 'run' : + cmd = d['cmd'][0] + self.send_response(200) + self.send_header('Content-type', 'text/plain') + self.end_headers() + self.wfile.write("Hello world: %s\n"%cmd) + run_child(cmd, self.wfile) + self.wfile.write("done\n") + self.wfile.flush() + else : + return self.bad_client('c') + + + def do_HEAD(self) : + self.do_invalid() + + +class MultiThreadedHTTPServer(SocketServer.ThreadingMixIn, BaseHTTPServer.HTTPServer): + pass + +#### + +windows = platform.system() == 'Windows' + +def run_child(path, wfile) : + env = os.environ + cmd = urllib.unquote_plus(path) + print "COMMAND",cmd + + # bug: implement timeouts + if windows : + status = subprocess_windows( cmd, wfile, env ) + else : + status = subprocess_unix( cmd, wfile, env ) + + # subprocess gives you weird status values + if status > 0 : + t_status="exit %d"%(status >> 8) + if status != 0 : + return_status = 1 + else : + return_status = 1 + t_status="signal %d" % ( - status ) + # subprocess does not tell you if there was a core + # dump, but there is nothing we can do about it. + + print "COMMAND EXIT:",status,t_status + + +def subprocess_windows(cmd, wfile, env ) : + # You might think that subprocess.Popen() would be portable, + # but you would be wrong. + # + # On Windows, sockets are NOT file descriptors. Since we are in a web server, wfile here is a socket. + + # print wfile.fileno() + # import msvcrt + # print msvcrt.get_osfhandle(wfile.fileno()) + p = subprocess.Popen( cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True, env = env, creationflags = subprocess.CREATE_NEW_PROCESS_GROUP ) + while 1 : + n = p.stdout.read(256) + if n == '' : + break + wfile.write(n) + return p.wait() + +def subprocess_unix( cmd, wfile, env ) : + p = subprocess.Popen( cmd, stdout=wfile, stderr=wfile, shell=True, env = env, preexec_fn=os.setpgrp ) + return p.wait() + +##### + + +def run( args = [ ] ) : + # you could parse args here if you wanted to. I don't care to spend + # the time. This is just here for people who can't (or don't want to) + # install a full featured web server just to try things out. + if len(args) > 0 : + ip = args[0] + else : + ip = platform.node() + + port = 7070 + + print "http://%s:%s/"%(str(ip),str(port)) + + httpd = MultiThreadedHTTPServer( (ip, port) , my_handler) + + sa = httpd.socket.getsockname() + print "Serving HTTP on", sa[0], "port", sa[1], "..." + while 1 : + httpd.handle_request() + +if __name__ == '__main__' : + run() + + +## +# in case we need to kill child processes: +# http://code.activestate.com/recipes/347462-terminating-a-subprocess-on-windows/ + + |