From 5bcde852ad76319a400bdd956407cb28b5e33737 Mon Sep 17 00:00:00 2001 From: richard Date: Sun, 22 Jul 2001 11:15:45 +0000 Subject: [PATCH] More Grande Splite stuff git-svn-id: http://svn.roundup-tracker.org/svnroot/roundup/trunk@27 57a73879-2fb5-44c3-a270-3262357dd7e2 --- bin/roundup | 271 +++++++++++++++++++++++++++++++++++++++++++++ bin/roundup-mailgw | 34 ++++++ bin/roundup-server | 223 +++++++++++++++++++++++++++++++++++++ 3 files changed, 528 insertions(+) create mode 100755 bin/roundup create mode 100755 bin/roundup-mailgw create mode 100755 bin/roundup-server diff --git a/bin/roundup b/bin/roundup new file mode 100755 index 0000000..0ca6554 --- /dev/null +++ b/bin/roundup @@ -0,0 +1,271 @@ +#! /usr/bin/python + +# $Id: roundup,v 1.1 2001-07-22 11:15:45 richard Exp $ + +import sys +if int(sys.version[0]) < 2: + print 'Roundup requires python 2.0 or later.' + sys.exit(1) + +import string, os, getpass +from roundup import date, roundupdb, init + +def determineLogin(instance, argv, n = 2): + name = password = '' + if argv[2] == '-u': + l = argv[3].split(':') + name = l[0] + if len(l) > 1: + password = l[1] + n = 4 + elif os.environ.has_key('ROUNDUP_LOGIN'): + l = os.environ['ROUNDUP_LOGIN'].split(':') + name = l[0] + if len(l) > 1: + password = l[1] + while not name: + name = raw_input('Login name: ') + while not password: + password = getpass.getpass(' password: ') + # TODO use the password... + return n, instance.open(name) + +def usage(message=''): + if message: message = 'Problem: '+message+'\n' + print '''%sUsage: + + roundup [-i instance] init template + -- initialise the database + roundup [-i instance] spec classname + -- show the properties for a classname + roundup [-i instance] create [-u login] classname propname=value ... + -- create a new entry of a given class + roundup [-i instance] list [-c] classname + -- list the instances of a class + roundup [-i instance] history [-c] designator + -- show the history entries of a designator + roundup [-i instance] get [-c] designator[,designator,...] propname + -- get the given property of one or more designator(s) + roundup [-i instance] set [-u login] designator[,designator,...] propname=value ... + -- set the given property of one or more designator(s) + roundup [-i instance] find [-c] classname propname=value ... + -- find the class instances with a given property + roundup [-i instance] retire designator[,designator,...] + -- "retire" a designator + roundup help + -- this help + roundup morehelp + -- even more detailed help +'''%message + +def moreusage(message=''): + usage(message) + print ''' +All commands (except help) require an instance specifier. This is just the path +to the roundup instance you're working with. It may be specified in the environment +variable ROUNDUP_INSTANCE or on the command line as "-i instance". + +A designator is a classname and a nodeid concatenated, eg. bug1, user10, ... + +Property values are represented as strings in command arguments and in the +printed results: + . Strings are, well, strings. + . Date values are printed in the full date format in the local time zone, and + accepted in the full format or any of the partial formats explained below. + . Link values are printed as node designators. When given as an argument, + node designators and key strings are both accepted. + . Multilink values are printed as lists of node designators joined by commas. + When given as an argument, node designators and key strings are both + accepted; an empty string, a single node, or a list of nodes joined by + commas is accepted. + +When multiple nodes are specified to the roundup get or roundup set +commands, the specified properties are retrieved or set on all the listed +nodes. + +When multiple results are returned by the roundup get or roundup find +commands, they are printed one per line (default) or joined by commas (with +the -c) option. + +Where the command changes data, a login name/password is required. The +login may be specified as either "name" or "name:password". + . ROUNDUP_LOGIN environment variable + . the -u command-line option +If either the name or password is not supplied, they are obtained from the +command-line. + +Date format examples: + "2000-04-17.03:45" means + "2000-04-17" means + "01-25" means + "08-13.22:13" means + "11-07.09:32:43" means + "14:25" means + "8:47:11" means + "." means "right now" +''' + +def main(): + argv = sys.argv + + if len(argv) == 1: + usage('No command specified') + return 1 + + # handle help now + if argv[1] == 'help': + usage() + return 0 + if argv[1] == 'morehelp': + moreusage() + return 0 + + # figure the instance home + n = 1 + if argv[1] == '-i': + if len(argv) == 2: + usage() + return 1 + instance_home = argv[2] + n = 3 + else: + instance_home = os.environ.get('ROUNDUP_INSTANCE', '') + if not instance_home: + usage('No instance home specified') + return 1 + + # now figure the command + command = argv[n] + n = n + 1 + + if command == 'init': + adminpw = '' + confirm = 'x' + while adminpw != confirm: + adminpw = getpass.getpass('Admin Password:') + confirm = getpass.getpass(' Confirm:') + init.init(instance_home, argv[n], adminpw) + return 0 + + # get the instance + path, instance = os.path.split(instance_home) + sys.path.insert(0, path) + try: + instance = __import__(instance) + finally: + del sys.path[0] + + if command == 'get': + db = instance.open() + designators = string.split(argv[n], ',') + propname = argv[n+1] + # TODO: handle the -c option + for designator in designators: + classname, nodeid = roundupdb.splitDesignator(designator) + print db.getclass(classname).get(nodeid, propname) + + elif command == 'set': + n, db = determineLogin(instance, argv, n) + designators = string.split(argv[n], ',') + props = {} + for prop in argv[n+1:]: + key, value = prop.split('=') + props[key] = value + for designator in designators: + classname, nodeid = roundupdb.splitDesignator(designator) + cl = db.getclass(classname) + properties = cl.getprops() + for key, value in props.items(): + type = properties[key] + if type.isStringType: + continue + elif type.isDateType: + props[key] = date.Date(value) + elif type.isIntervalType: + props[key] = date.Interval(value) + elif type.isLinkType: + props[key] = value + elif type.isMultilinkType: + props[key] = value.split(',') + apply(cl.set, (nodeid, ), props) + + elif command == 'find': + db = instance.open() + classname = argv[n] + cl = db.getclass(classname) + + # look up the linked-to class and get the nodeid that has the value + propname, value = argv[n+1:].split('=') + propcl = cl[propname].classname + nodeid = propcl.lookup(value) + + # now do the find + # TODO: handle the -c option + print cl.find(propname, nodeid) + + elif command == 'spec': + db = instance.open() + classname = argv[n] + cl = db.getclass(classname) + for key, value in cl.properties.items(): + print '%s: %s'%(key, value) + + elif command == 'create': + n, db = determineLogin(instance, argv, n) + classname = argv[n] + cl = db.getclass(classname) + props = {} + properties = cl.getprops() + for prop in argv[n+1:]: + key, value = prop.split('=') + type = properties[key] + if type.isStringType: + props[key] = value + elif type.isDateType: + props[key] = date.Date(value) + elif type.isIntervalType: + props[key] = date.Interval(value) + elif type.isLinkType: + props[key] = value + elif type.isMultilinkType: + props[key] = value.split(',') + print apply(cl.create, (), props) + + elif command == 'list': + db = instance.open() + classname = argv[n] + cl = db.getclass(classname) + key = cl.getkey() or cl.properties.keys()[0] + # TODO: handle the -c option + for nodeid in cl.list(): + value = cl.get(nodeid, key) + print "%4s: %s"%(nodeid, value) + + elif command == 'history': + db = instance.open() + classname, nodeid = roundupdb.splitDesignator(argv[n]) + # TODO: handle the -c option + print db.getclass(classname).history(nodeid) + + elif command == 'retire': + n, db = determineLogin(instance, argv, n) + designators = string.split(argv[n], ',') + for designator in designators: + classname, nodeid = roundupdb.splitDesignator(designator) + db.getclass(classname).retire(nodeid) + + else: + print "Unknown command '%s'"%command + usage() + return 1 + + db.close() + return 0 + +if __name__ == '__main__': + sys.exit(main()) + +# +# $Log: not supported by cvs2svn $ +# + diff --git a/bin/roundup-mailgw b/bin/roundup-mailgw new file mode 100755 index 0000000..4add812 --- /dev/null +++ b/bin/roundup-mailgw @@ -0,0 +1,34 @@ +#! /usr/bin/python + +# $ID: $ + +import sys +if int(sys.version[0]) < 2: + print "Roundup requires Python 2.0 or newer." + sys.exit(1) + +# figure the instance home +import os +if len(sys.argv) > 1: + instance_home = sys.argv[1] +else: + instance_home = os.environ.get('ROUNDUP_INSTANCE', '') +if not instance_home: + print 'No instance home specified' + sys.exit(1) + +# get the instance +path, instance = os.path.split(instance_home) +sys.path.insert(0, path) +instance = __import__(instance) +sys.path[0] + +# invokde the mail handler +db = instance.open('admin') +handler = instance.MailGW(db) +handler.main(sys.stdin) + +# +# $Log: not supported by cvs2svn $ +# + diff --git a/bin/roundup-server b/bin/roundup-server new file mode 100755 index 0000000..c1e12c1 --- /dev/null +++ b/bin/roundup-server @@ -0,0 +1,223 @@ +#!/usr/bin/python +""" HTTP Server that serves roundup. + +Stolen from CGIHTTPServer + +$Id: roundup-server,v 1.1 2001-07-22 11:15:45 richard Exp $ + +""" +import sys +if int(sys.version[0]) < 2: + print "Content-Type: text/plain\n" + print "Roundup requires Python 2.0 or newer." + +__version__ = "0.1" + +__all__ = ["RoundupRequestHandler"] + +import os, urllib, StringIO, traceback, cgi, binascii, string +import BaseHTTPServer +import SimpleHTTPServer + +# Roundup modules of use here +from roundup import cgitb, cgi_client + +# These are here temporarily until I get a real reload system in place +from roundup import date, hyperdb, hyper_bsddb, roundupdb, htmltemplate + +# +## Configuration +# + +# This indicates where the Roundup instance lives +ROUNDUPS = { + 'test': '/tmp/roundup_test', +} + +# Where to log debugging information to. Use an instance of DevNull if you +# don't want to log anywhere. +# TODO: actually use this stuff +#class DevNull: +# def write(self, info): +# pass +#LOG = open('/var/log/roundup.cgi.log', 'a') +#LOG = DevNull() + +# +## end configuration +# + + +class RoundupRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): + def send_head(self): + """Version of send_head that support CGI scripts""" + # TODO: actually do the HEAD ... + return self.run_cgi() + + def run_cgi(self): + """ Execute the CGI command. Wrap an innner call in an error + handler so all errors can be caught. + """ + save_stdin = sys.stdin + sys.stdin = self.rfile + try: + self.inner_run_cgi() + except cgi_client.Unauthorised: + self.wfile.write('Content-Type: text/html\n') + self.wfile.write('Status: 403\n') + self.wfile.write('Unauthorised') + except: + try: + reload(cgitb) + self.wfile.write("Content-Type: text/html\n\n") + self.wfile.write(cgitb.breaker()) + self.wfile.write(cgitb.html()) + except: + self.wfile.write("Content-Type: text/html\n\n") + self.wfile.write("
")
+                s = StringIO.StringIO()
+                traceback.print_exc(None, s)
+                self.wfile.write(cgi.escape(s.getvalue()))
+                self.wfile.write("
\n") + sys.stdin = save_stdin + + def inner_run_cgi(self): + ''' This is the inner part of the CGI handling + ''' + + rest = self.path + i = rest.rfind('?') + if i >= 0: + rest, query = rest[:i], rest[i+1:] + else: + query = '' + + # figure the instance + if rest == '/': + raise ValueError, 'No instance specified' + l_path = string.split(rest, '/') + instance = urllib.unquote(l_path[1]) + if ROUNDUPS.has_key(instance): + instance_home = ROUNDUPS[instance] + module_path, instance = os.path.split(instance_home) + sys.path.insert(0, module_path) + try: + instance = __import__(instance) + finally: + del sys.path[0] + else: + raise ValueError, 'No such instance "%s"'%instance + + # figure out what the rest of the path is + if len(l_path) > 2: + rest = '/'.join(l_path[2:]) + else: + rest = '/' + + # Set up the CGI environment + env = {} + env['REQUEST_METHOD'] = self.command + env['PATH_INFO'] = urllib.unquote(rest) + if query: + env['QUERY_STRING'] = query + host = self.address_string() + if self.headers.typeheader is None: + env['CONTENT_TYPE'] = self.headers.type + else: + env['CONTENT_TYPE'] = self.headers.typeheader + length = self.headers.getheader('content-length') + if length: + env['CONTENT_LENGTH'] = length + co = filter(None, self.headers.getheaders('cookie')) + if co: + env['HTTP_COOKIE'] = ', '.join(co) + env['SCRIPT_NAME'] = '' + env['SERVER_NAME'] = self.server.server_name + env['SERVER_PORT'] = str(self.server.server_port) + + decoded_query = query.replace('+', ' ') + + # if root, setuid to nobody + # TODO why isn't this done much earlier? - say, in main()? + if not os.getuid(): + nobody = nobody_uid() + os.setuid(nobody) + + # reload all modules + # TODO check for file timestamp changes and dependencies + reload(date) + reload(hyperdb) + reload(roundupdb) + reload(htmltemplate) + reload(cgi_client) + sys.path.insert(0, module_path) + try: + reload(instance) + finally: + del sys.path[0] + + # initialise the roundupdb, check for auth + db = instance.open('admin') + message = 'Unauthorised' + auth = self.headers.getheader('authorization') + if auth: + l = binascii.a2b_base64(auth.split(' ')[1]).split(':') + user = l[0] + password = None + if len(l) > 1: + password = l[1] + try: + uid = db.user.lookup(user) + except KeyError: + auth = None + message = 'Username not recognised' + else: + if password != db.user.get(uid, 'password'): + message = 'Incorrect password' + auth = None + db.close() + del db + if not auth: + self.send_response(401) + self.send_header('Content-Type', 'text/html') + self.send_header('WWW-Authenticate', 'basic realm="Roundup"') + self.end_headers() + self.wfile.write(message) + return + + self.send_response(200, "Script output follows") + + # do the roundup thang + db = instance.open(user) + client = instance.Client(self.wfile, db, env, user) + client.main() + do_POST = run_cgi + +nobody = None + +def nobody_uid(): + """Internal routine to get nobody's uid""" + global nobody + if nobody: + return nobody + try: + import pwd + except ImportError: + return -1 + try: + nobody = pwd.getpwnam('nobody')[2] + except KeyError: + nobody = 1 + max(map(lambda x: x[2], pwd.getpwall())) + return nobody + +if __name__ == '__main__': + # TODO make this configurable again? command-line seems ok to me... + address = ('', 8080) + httpd = BaseHTTPServer.HTTPServer(address, RoundupRequestHandler) + print 'Roundup server started on', address + httpd.serve_forever() + +# +# $Log: not supported by cvs2svn $ +# + -- 2.30.2