From 46eb5a384c3e7882e9d5339021c998c2f7d4d9b2 Mon Sep 17 00:00:00 2001 Message-Id: <46eb5a384c3e7882e9d5339021c998c2f7d4d9b2.1715477583.git.mdw@distorted.org.uk> From: Mark Wooding Date: Sat, 24 May 2014 14:00:03 +0100 Subject: [PATCH] service.py: Incompatible changes to CommandRemoteService. Organization: Straylight/Edgeware From: Mark Wooding Rather than having a constructor argument for each possible remote command, we now have a more complex and flexible system. Firstly, there's a `default' command prefix, to which the remaining remote-command arguments are appended. Secondly, there's a dictionary mapping command names to full command lists with placeholders (like the old system). --- service.py | 64 ++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 45 insertions(+), 19 deletions(-) diff --git a/service.py b/service.py index 9ba9fca..aa77388 100644 --- a/service.py +++ b/service.py @@ -306,32 +306,39 @@ class CommandRemoteService (BasicRemoteService): """ A remote service transported over a standard Unix command. - This is left rather generic. We need to know some command lists SET and - CLEAR containing the relevant service names and arguments. These are - simply executed, after simple placeholder substitution. - - The SET command should read a password as its first line on stdin, and set - that as the user's new password. The CLEAR command should simply prevent - the user from logging in with a password. On success, the commands should - print a line `OK' to standard output, and on any kind of anticipated - failure, they should print `ERR' followed by an HTTP status code and a - message; in either case, the program should exit with status zero. In - disastrous cases, it's acceptable to print an error message to stderr - and/or exit with a nonzero status. - - The placeholders are as follows. + This is left rather generic. Two strategies are available (and can be + combined using appropriate configuration). A DEFAULT command list can be + specified, and will be invoked as `DEFAULT OP ARGS...', where OP ARGS form + a Chopwood remote command. Additionally, an OPMAP dictionary can be + provided, mapping OP names (remote command names) to command lists + containing `%' placeholders, as follows: `%u' the user's name `%%' a single `%' character + + On success, the commands should print a line `OK' to standard output, and + on any kind of anticipated failure, they should print `ERR' followed by an + HTTP status code and a message; in either case, the program should exit + with status zero. In disastrous cases, it's acceptable to print an error + message to stderr and/or exit with a nonzero status. + + Configuration hint: if you can only handle some subset of the available + commands, then your easy approach is to set commands for the operations you + can handle in the OPMAP, and set the DEFAULT to something like + + ['echo', 'ERR 500', 'unsupported command:'] + + to reject other commands. """ R_PAT = RX.compile('%(.)') - def __init__(me, set, clear, *args, **kw): + def __init__(me, default = ['ERR', '500', 'unimplemented command:'], + opmap = {}, *args, **kw): """Initialize the command remote service.""" super(CommandRemoteService, me).__init__(*args, **kw) - me._set = set - me._clear = clear + me._default = default + me._opmap = opmap def _describe(me): """Description of the remote service.""" @@ -343,16 +350,35 @@ class CommandRemoteService (BasicRemoteService): def _mkcmd(me, cmd, map): """Construct the command to be executed, by substituting placeholders.""" + if map is None: return cmd return [me.R_PAT.sub(lambda m: me._subst(m.group(1), map), arg) for arg in cmd] + def _dispatch(me, func, op, args, input = None): + """ + Work out how to invoke a particular command. + + Invoke FUNC, which works like `_run', with appropriate arguments. The OP + is a remote command name; ARGS is a sequence of (C, ARG) pairs, where C + is a placeholder character and ARG is a string value; INPUT is the text + to provide to the command on standard input. + """ + try: + cmd = me._opmap[op] + except KeyError: + cmd = me._default + [op] + [v for k, v in args] + map = None + else: + map = dict(args) + return func(cmd, input = input, state = map) + def setpasswd(me, user, passwd): """Service protocol: set the USER's password to PASSWD.""" - me._run_noout(me._set, passwd + '\n', state = dict(u = user)) + me._dispatch(me._run_noout, 'set', [('u', user)], passwd + '\n') def clearpasswd(me, user): """Service protocol: clear the USER's password.""" - me._run_noout(me._clear, state = dict(u = user)) + me._dispatch(me._run_noout, 'clear', [('u', user)]) CONF.export('CommandRemoteService') -- [mdw]