X-Git-Url: https://git.distorted.org.uk/~mdw/chopwood/blobdiff_plain/bb623e8fca6fd67635eac42e26c11abcc45e46a5..HEAD:/chpwd diff --git a/chpwd b/chpwd index 1e4248b..2d6c75c 100755 --- a/chpwd +++ b/chpwd @@ -30,6 +30,7 @@ import optparse as OP import os as OS; ENV = OS.environ import shlex as SL import sys as SYS +import syslog as L from auto import HOME, VERSION import cgi as CGI @@ -38,6 +39,7 @@ import config as CONF; CFG = CONF.CFG import dbmaint as D import httpauth as HA import output as O; OUT = O.OUT +import service as S import subcommand as SC import util as U @@ -71,8 +73,12 @@ for short, long, props in [ 'help': 'run commands with the given CONTEXT' }), ('-f', '--config-file', { 'metavar': 'FILE', 'dest': 'config', - 'default': OS.path.join(HOME, 'chpwd.conf'), + 'default': ENV.get('CHPWD_CONFIG', + OS.path.join(HOME, 'chpwd.conf')), 'help': 'read configuration from FILE.' }), + ('-i', '--ignore-policy', { + 'dest': 'ignpol', 'default': False, 'action': 'store_true', + 'help': 'ignore the operation policy (for administrators)' }), ('-s', '--ssl', { 'dest': 'sslp', 'action': 'store_true', 'help': 'pretend CGI connection is carried over SSL/TLS' }), @@ -81,21 +87,35 @@ for short, long, props in [ 'help': "impersonate USER, and default context to `userv'." })]: OPTPARSE.add_option(short, long, **props) +def parse_options(): + """ + Parse the main command-line options, returning the positional arguments. + """ + global OPTS + OPTS, args = OPTPARSE.parse_args() + OPTPARSE.show_global_opts = False + CFG.OPTS = OPTS + ## It's tempting to load the configuration here. Don't do that. Some + ## contexts will want to check that the command line was handled properly + ## upstream before believing it for anything, such as executing arbitrary + ## Python code. + return args + ###-------------------------------------------------------------------------- ### CGI dispatch. ## The special variables, to be picked out by `cgiparse'. CGI.SPECIAL['%act'] = None CGI.SPECIAL['%nonce'] = None +CGI.SPECIAL['%user'] = None ## We don't want to parse arguments until we've settled on a context; but ## issuing redirects in the early setup phase fails because we don't know ## the script name. So package the setup here. def cgi_setup(ctx = 'cgi-noauth'): - global OPTS if OPTS: return OPTPARSE.context = ctx - OPTS, args = OPTPARSE.parse_args() + args = parse_options() if args: raise U.ExpectedError, (500, 'Unexpected arguments to CGI') CONF.loadconfig(OPTS.config) D.opendb() @@ -143,6 +163,14 @@ def dispatch_cgi(): ## `cgi-noauth'. if ctx != 'cgi-noauth': + ## The next part of the URL should be the user name, so that caches don't + ## cross things over. + expuser = CGI.SPECIAL['%user'] + if expuser is None: + if i >= np: raise U.ExpectedError, (404, 'Missing user name') + expuser = CGI.PATH[i] + i += 1 + ## If there's no token cookie, then we have to bail. try: token = CGI.COOKIE['chpwd-token'] except KeyError: @@ -166,6 +194,8 @@ def dispatch_cgi(): except HA.AuthenticationFailed, e: CGI.redirect(CGI.action('login', why = e.why)) return + if CU.USER != expuser: raise U.ExpectedError, (401, 'User mismatch') + CGI.STATE.kw['user'] = CU.USER ## Invoke the subcommand handler. c.cgi(CGI.PARAM, CGI.PATH[i:]) @@ -187,24 +217,30 @@ def cli_errors(): if __name__ == '__main__': + L.openlog(OS.path.basename(SYS.argv[0]), 0, L.LOG_AUTH) + if 'REQUEST_METHOD' in ENV: ## This looks like a CGI request. The heavy lifting for authentication ## over HTTP is done in `dispatch_cgi'. with OUT.redirect_to(CGI.HTTPOutput()): - with CGI.cgi_errors(cgi_setup): dispatch_cgi() + with U.Escape() as CGI.HEADER_DONE: + with CGI.cgi_errors(cgi_setup): + dispatch_cgi() elif 'USERV_SERVICE' in ENV: ## This is a Userv request. The caller's user name is helpfully in the ## `USERV_USER' environment variable. with cli_errors(): - OPTS, args = OPTPARSE.parse_args() - CONF.loadconfig(OPTS.config) - try: CU.set_user(ENV['USERV_USER']) - except KeyError: raise ExpectedError, (500, 'USERV_USER unset') with OUT.redirect_to(O.FileOutput()): - OPTPARSE.dispatch('userv', [ENV['USERV_SERVICE']] + args) + args = parse_options() + if not args or args[0] != 'userv': + raise U.ExpectedError, (500, 'missing userv token') + CONF.loadconfig(OPTS.config) + try: CU.set_user(ENV['USERV_USER']) + except KeyError: raise ExpectedError, (500, 'USERV_USER unset') + OPTPARSE.dispatch('userv', [ENV['USERV_SERVICE']] + args[1:]) elif 'SSH_ORIGINAL_COMMAND' in ENV: ## This looks like an SSH request; but we present two different @@ -213,11 +249,10 @@ if __name__ == '__main__': def ssh_setup(): """Extract and parse the client's request from where SSH left it.""" - global OPTS - OPTS, args = OPTPARSE.parse_args() + args = parse_options() CONF.loadconfig(OPTS.config) cmd = SL.split(ENV['SSH_ORIGINAL_COMMAND']) - if args: raise ExpectedError, (500, 'Unexpected arguments via SSH') + if args: raise U.ExpectedError, (500, 'Unexpected arguments via SSH') return cmd if 'CHPWD_SSH_USER' in ENV: @@ -225,10 +260,9 @@ if __name__ == '__main__': ## of telling us that this is a user request, so treat it like Userv. with cli_errors(): - cmd = ssh_setup() - CU.set_user(ENV['CHPWD_SSH_USER']) - SERVICES['master'].find(USER) with OUT.redirect_to(O.FileOutput()): + cmd = ssh_setup() + CU.set_user(ENV['CHPWD_SSH_USER']) OPTPARSE.dispatch('userv', cmd) elif 'CHPWD_SSH_MASTER' in ENV: @@ -238,10 +272,10 @@ if __name__ == '__main__': ## a user. try: - cmd = ssh_setup() with OUT.redirect_to(O.RemoteOutput()): - OPTPARSE.dispatch('remote', map(urldecode, cmd)) - except ExpectedError, e: + cmd = ssh_setup() + OPTPARSE.dispatch('remote', map(CGI.urldecode, cmd)) + except U.ExpectedError, e: print 'ERR', e.code, e.msg else: print 'OK' @@ -251,7 +285,7 @@ if __name__ == '__main__': ## file, but we can't do much about it from here. with cli_errors(): - raise ExpectedError, (400, "Unabled to determine SSH context") + raise U.ExpectedError, (400, "Unabled to determine SSH context") else: ## Plain old command line, apparently. We default to administration @@ -260,17 +294,20 @@ if __name__ == '__main__': ## as we are. with cli_errors(): - OPTS, args = OPTPARSE.parse_args() - CONF.loadconfig(OPTS.config) - CGI.SSLP = OPTS.sslp - ctx = OPTS.context - if OPTS.user: - CU.set_user(OPTS.user) - if ctx is None: ctx = 'userv' - else: - D.opendb() - if ctx is None: ctx = 'admin' with OUT.redirect_to(O.FileOutput()): + args = parse_options() + CONF.loadconfig(OPTS.config) + CGI.SSLP = OPTS.sslp + ctx = OPTS.context + if OPTS.user: + CU.set_user(OPTS.user) + CGI.STATE.kw['user'] = OPTS.user + if ctx is None: ctx = 'userv' + else: + D.opendb() + if ctx is None: + ctx = 'admin' + OPTPARSE.show_global_opts = True OPTPARSE.dispatch(ctx, args) ###----- That's all, folks --------------------------------------------------