From 0c58273e69f08d17e69d9af6b04734bdce302532 Mon Sep 17 00:00:00 2001 From: Mark Wooding Date: Mon, 22 Dec 2014 20:32:58 +0000 Subject: [PATCH 01/16] agpl.py: Python 2.5 compatibility. --- agpl.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/agpl.py b/agpl.py index a35ea3f..0929e78 100644 --- a/agpl.py +++ b/agpl.py @@ -23,6 +23,8 @@ ### License along with Chopwood; if not, see ### . +from __future__ import with_statement + import contextlib as CTX import grp as GR import os as OS -- 2.11.0 From 2ec2b38ffe12eb14c57dc0053cdf70a93736a283 Mon Sep 17 00:00:00 2001 From: Mark Wooding Date: Mon, 22 Dec 2014 20:32:58 +0000 Subject: [PATCH 02/16] cgi.py: Set the default static URL prefix from user's `SCRIPT_NAME'. Otherwise you have to set them both, and that's just annoying. --- cgi.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/cgi.py b/cgi.py index 8009eaf..6ad9e75 100644 --- a/cgi.py +++ b/cgi.py @@ -51,7 +51,11 @@ CONF.DEFAULTS.update( ## A (maybe relative) URL for static content. By default this comes from ## the main script, but we hope that user agents cache it. - STATIC = _script_name + '/static') + STATIC = None) + +@CONF.hook +def set_static(): + if CFG.STATIC is None: CFG.STATIC = CFG.SCRIPT_NAME + '/static' ###-------------------------------------------------------------------------- ### Escaping and encoding. -- 2.11.0 From 5d1f4e278fa37b9b7f23fcdffc49d3028a4b178e Mon Sep 17 00:00:00 2001 From: Mark Wooding Date: Mon, 22 Dec 2014 20:32:58 +0000 Subject: [PATCH 03/16] operation.py: Fix stupid typo in commentary. --- operation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/operation.py b/operation.py index 50d7952..bf17b05 100644 --- a/operation.py +++ b/operation.py @@ -179,7 +179,7 @@ class BaseRequest (object): It provides an empty `INFO' map; a simple `check' method which checks the operation name (in the class attribute `OP') against the configured policy - `CFG'ALLOWOP'; and the obvious `perform' method which assumes that the + `CFG.ALLOWOP'; and the obvious `perform' method which assumes that the `ops' list has already been constructed. """ -- 2.11.0 From d9ca01b99b8476719e958e0c6ec0a9fcda348e51 Mon Sep 17 00:00:00 2001 From: Mark Wooding Date: Mon, 22 Dec 2014 20:32:58 +0000 Subject: [PATCH 04/16] backend.py: Change default lock directory. All of the other state things end up under the working tree, so this should too. --- backend.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend.py b/backend.py index d6fac78..576486c 100644 --- a/backend.py +++ b/backend.py @@ -37,7 +37,7 @@ import util as U CONF.DEFAULTS.update( ## A directory in which we can create lockfiles. - LOCKDIR = OS.path.join(ENV['HOME'], 'var', 'lock', 'chpwd')) + LOCKDIR = OS.path.join(HOME, 'lock')) ###-------------------------------------------------------------------------- ### Utilities. -- 2.11.0 From e32b221f0be6c37d2a4e6c9dce616e1eb11fdc6d Mon Sep 17 00:00:00 2001 From: Mark Wooding Date: Mon, 22 Dec 2014 20:32:58 +0000 Subject: [PATCH 05/16] backend.py: Introduce protocol for alternative locking schemes. It sounds fancier than it is. There's now a method for FlatFileBackend subclasses to override if they want to apply different locking semantics. --- backend.py | 33 ++++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/backend.py b/backend.py index 576486c..fda32e0 100644 --- a/backend.py +++ b/backend.py @@ -25,6 +25,8 @@ from __future__ import with_statement +from auto import HOME +import errno as E import itertools as I import os as OS; ENV = OS.environ @@ -206,10 +208,13 @@ class FlatFileBackend (object): specified by the DELIM constructor argument. The file is updated by writing a new version alongside, as `FILE.new', and - renaming it over the old version. If a LOCK file is named then an - exclusive fcntl(2)-style lock is taken out on `LOCKDIR/LOCK' (creating the - file if necessary) during the update operation. Use of a lockfile is - strongly recommended. + renaming it over the old version. If a LOCK is provided then this is done + while holding a lock. By default, an exclusive fcntl(2)-style lock is + taken out on `LOCKDIR/LOCK' (creating the file if necessary) during the + update operation, but subclasses can override the `dolocked' method to + provide alternative locking behaviour; the LOCK parameter is not + interpreted by any other methods. Use of a lockfile is strongly + recommended. The DELIM constructor argument specifies the delimiter character used when splitting lines into fields. The USER and PASSWD arguments give the field @@ -329,11 +334,21 @@ class FlatFileBackend (object): ## If there's a lockfile, then acquire it around the meat of this ## function; otherwise just do the job. - if me._lock is None: - doit() - else: - with U.lockfile(OS.path.join(CFG.LOCKDIR, me._lock), 5): - doit() + if me._lock is None: doit() + else: me.dolocked(me._lock, doit) + + def dolocked(me, lock, func): + """ + Call FUNC with the LOCK held. + + Subclasses can override this method in order to provide alternative + locking functionality. + """ + try: OS.mkdir(CFG.LOCKDIR) + except OSError, e: + if e.errno != E.EEXIST: raise + with U.lockfile(OS.path.join(CFG.LOCKDIR, lock), 5): + func() def _parse(me, line): """Convenience function for constructing a record.""" -- 2.11.0 From c5412a5f53e8887d37e6ade9de528e404279288a Mon Sep 17 00:00:00 2001 From: Mark Wooding Date: Mon, 22 Dec 2014 20:32:58 +0000 Subject: [PATCH 06/16] chpwd: Publish command-line options to the `CFG' module. They need to go somewhere and this seemed like the least bad choice. --- chpwd | 1 + 1 file changed, 1 insertion(+) diff --git a/chpwd b/chpwd index dc054b4..0e0de39 100755 --- a/chpwd +++ b/chpwd @@ -91,6 +91,7 @@ def parse_options(): 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 -- 2.11.0 From 46b48b43e0b666bcedaea0d65183687f540b9dfd Mon Sep 17 00:00:00 2001 From: Mark Wooding Date: Mon, 22 Dec 2014 20:32:58 +0000 Subject: [PATCH 07/16] chpwd, operation.py: Allow administrative override of policy. --- chpwd | 3 +++ operation.py | 17 +++++++++-------- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/chpwd b/chpwd index 0e0de39..2d6c75c 100755 --- a/chpwd +++ b/chpwd @@ -76,6 +76,9 @@ for short, long, props in [ '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' }), diff --git a/operation.py b/operation.py index bf17b05..fccd172 100644 --- a/operation.py +++ b/operation.py @@ -343,14 +343,15 @@ def operate(op, accts, *args, **kw): """ rq = getattr(CFG.RQCLASS, op)(accts, *args, **kw) desc = rq.describe() - try: - rq.check() - except U.ExpectedError, e: - L.syslog('REFUSE %s %s: %s' % - (desc, - ', '.join(['%s@%s' % (o.user, o.svc.name) for o in rq.ops]), - e)) - raise + if not CFG.OPTS.ignpol: + try: + rq.check() + except U.ExpectedError, e: + L.syslog('REFUSE %s %s: %s' % + (desc, + ', '.join(['%s@%s' % (o.user, o.svc.name) for o in rq.ops]), + e)) + raise ops = rq.perform() nwin = nlose = 0 for o in ops: -- 2.11.0 From b87bee6c4260bb87735628d5ec6980e0d9d0b3aa Mon Sep 17 00:00:00 2001 From: Mark Wooding Date: Fri, 24 Apr 2015 10:52:15 +0100 Subject: [PATCH 08/16] sshsvc.conf: Configuration file for `sshsvc-mkauthkeys'. --- sshsvc.conf | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 sshsvc.conf diff --git a/sshsvc.conf b/sshsvc.conf new file mode 100644 index 0000000..d6cc1ea --- /dev/null +++ b/sshsvc.conf @@ -0,0 +1,12 @@ +### -*-sh-*- + +cmd="./chpwd" + +make_key_line () { + user=$1 + case $user in + chpwd@*) var=CHPWD_SSH_MASTER user=${user#*@} ;; + *) var=CHPWD_SSH_USER ;; + esac + echo "environment=\"$var=$user\"" +} -- 2.11.0 From 79d5cac822039966d79017f92346652b765bad85 Mon Sep 17 00:00:00 2001 From: Mark Wooding Date: Sat, 4 Jul 2015 17:52:35 +0100 Subject: [PATCH 09/16] userv.rc: Fix stupid configuration bug. --- userv.rc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/userv.rc b/userv.rc index 6a7500f..e258c7e 100644 --- a/userv.rc +++ b/userv.rc @@ -1,6 +1,6 @@ ### -*-conf-*- -if ( grep service-user-shell /etc/shells +if ( grep calling-user-shell /etc/shells & ! glob service www-cgi ) no-suppress-args -- 2.11.0 From 13409f0092c75159b1ca87af1b940ade76f47cbd Mon Sep 17 00:00:00 2001 From: Mark Wooding Date: Mon, 10 Apr 2017 14:28:14 +0100 Subject: [PATCH 10/16] format.py: Document `#' as a format parameter. It's standard Common Lisp, but was unaccountably left out of the documentation. --- format.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/format.py b/format.py index d6eb896..af76c59 100644 --- a/format.py +++ b/format.py @@ -798,7 +798,7 @@ def compile(control): PARAMS ::= PARAM [`,' PARAMS] - PARAM ::= EMPTY | INT | `'' CHAR | `v' | `!' ARG + PARAM ::= EMPTY | INT | `#' | `'' CHAR | `v' | `!' ARG FLAGS ::= [[ `@' | `:' ]]* @@ -806,11 +806,11 @@ def compile(control): items drawn from the listed alternatives, each appearing at most once. See the function `parse_arg' for the syntax of ARG.) - An empty PARAM is equivalent to omitting the parameter; `!ARG' reads the - parameter value from the argument; `v' is equivalent to `!+', as a - convenient abbreviation and for Common Lisp compatibility. The `=ARG' - notation indicates which argument(s) should be processed by the operation: - the default is `=+'. + An empty PARAM is equivalent to omitting the parameter; `#' is the number + of remaining positional arguments; `!ARG' reads the parameter value from + the argument; `v' is equivalent to `!+', as a convenient abbreviation and + for Common Lisp compatibility. The `=ARG' notation indicates which + argument(s) should be processed by the operation: the default is `=+' """ if not isinstance(control, basestring): return control pp = [] -- 2.11.0 From b91d3f0725ba4cda985ba91fb2fd41b7e3a3d54d Mon Sep 17 00:00:00 2001 From: Mark Wooding Date: Tue, 10 Sep 2019 20:42:49 +0100 Subject: [PATCH 11/16] format.py: Fix some commentary typos. --- format.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/format.py b/format.py index af76c59..2b90cb9 100644 --- a/format.py +++ b/format.py @@ -296,7 +296,7 @@ class NextArg (BaseArg): else: return None NEXTARG = NextArg() -## Because a `NextArg' collectors are used so commonly, and they're all the +## Because `NextArg' collectors are used so commonly, and they're all the ## same, we make a distinguished one and try to use that instead. Nothing ## goes badly wrong if you don't use this, but you'll use more memory than ## strictly necessary. @@ -528,7 +528,7 @@ VARNEXT = VariableParameter(NEXTARG) ## whether the `@' and `:' modifiers were set in the control string. ## GETARG is the collector for the operation's argument(s). The PARAMS ## are a list of parameter collectors. Finally, CHAR is the directive -## character (so directives with siilar behaviour can use the same +## character (so directives with similar behaviour can use the same ## class). class FormatLiteral (object): -- 2.11.0 From 99ed4b3f1e1c687f915a98aa01bcf9703d30d641 Mon Sep 17 00:00:00 2001 From: Mark Wooding Date: Fri, 15 May 2020 17:59:48 +0100 Subject: [PATCH 12/16] cookies.fhtml: Fix a stupid typo. --- cookies.fhtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cookies.fhtml b/cookies.fhtml index 121b2e4..36302f7 100644 --- a/cookies.fhtml +++ b/cookies.fhtml @@ -41,7 +41,7 @@ carry this token about, but that causes other trouble.

For example, if we used GET requests then the token would appear as part of a URL, where it would end up being written in the location bar of -many browsers, stored in history databases, many even sent to random cloud +many browsers, stored in history databases, maybe even sent to random cloud services; this obviously has an adverse effect on security. Also, the token is kind of long and ugly. -- 2.11.0 From 245db33989f02513293b162695fac2114141bdc0 Mon Sep 17 00:00:00 2001 From: Mark Wooding Date: Fri, 15 May 2020 18:00:06 +0100 Subject: [PATCH 13/16] chpwd.js: Only update DOM properties if they're actually going to change. This might have a significant effect on the background-friendliness of the validation machinery. Certainly, if I open Firefox's developer tools, I used to see the various `whinge' elements highlighted as changing all the time, which was rather distracting if nothing else, and probably meant that DOM change-handling machinery was being engaged in order to do nothing of any use. Introduce a new function `update' which changes an object property only if its value would actually change, and use this in the `check' function. --- chpwd.js | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/chpwd.js b/chpwd.js index a271db4..f771d65 100644 --- a/chpwd.js +++ b/chpwd.js @@ -64,10 +64,15 @@ var FORMS = {}; * submitting a form with invalid data. */ +function update(obj, slot, value) { + /* Update an object slot only if we're gping to change its value. */ + if (obj[slot] !== value) obj[slot] = value; +} + function check() { /* Check through the various forms to make sure they're filled in OK. If - * not, set the `F-whinge' elements, and disable `F-submit'. - */ + * not, set the `F-whinge' elements, and disable `F-submit'. + */ var f, form, whinge; for (f in FORMS) { @@ -75,10 +80,10 @@ function check() { we = elt(f + '-whinge'); sb = elt(f + '-submit'); whinge = form.check(); - if (sb !== null) sb.disabled = (whinge !== null); + if (sb !== null) update(sb, 'disabled', whinge !== null); if (we !== null) { - we.textContent = whinge || 'OK'; - we.className = whinge === null ? 'whinge' : 'whinge wrong'; + update(we, 'textContent', whinge || 'OK'); + update(we, 'className', whinge === null ? 'whinge' : 'whinge wrong'); } } -- 2.11.0 From 9140b588b71496b427f3723faf9713bb43b147bf Mon Sep 17 00:00:00 2001 From: Mark Wooding Date: Fri, 15 May 2020 18:04:47 +0100 Subject: [PATCH 14/16] chpwd.css: Use tabs for indentation. --- chpwd.css | 56 ++++++++++++++++++++++++++++---------------------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/chpwd.css b/chpwd.css index 02759f4..2ca07d4 100644 --- a/chpwd.css +++ b/chpwd.css @@ -27,31 +27,31 @@ /*----- General typesetting and layout -----------------------------------*/ h1 { - border-bottom-style: solid; - border-bottom-width: medium; - padding-bottom: 1ex; + border-bottom-style: solid; + border-bottom-width: medium; + padding-bottom: 1ex; } h2 { - border-top-style: solid; - border-top-width: thin; - padding-top: 1ex; - margin-top: 4ex; + border-top-style: solid; + border-top-width: thin; + padding-top: 1ex; + margin-top: 4ex; } h1 + h2, h2:first-child { - border-top-style: hidden; - margin-top: inherit; + border-top-style: hidden; + margin-top: inherit; } div.credits { - border-top-style: solid; - border-top-width: thin; - padding-top: 0.5ex; - margin-top: 2ex; - text-align: right; - font-size: small; - font-style: italic; + border-top-style: solid; + border-top-width: thin; + padding-top: 0.5ex; + margin-top: 2ex; + text-align: right; + font-size: small; + font-style: italic; } div.warn { @@ -66,8 +66,8 @@ div.warn { /* Common form validation styling. */ .whinge { - font-size: smaller; - visibility: hidden; + font-size: smaller; + visibility: hidden; } .login-whinge { @@ -75,8 +75,8 @@ div.warn { } .wrong { - color: red; - visibility: visible; + color: red; + visibility: visible; } /* Specific forms. */ @@ -86,26 +86,26 @@ td.label { text-align: right; } .expand { height: 100%; } div.expand-outer { position: relative; } div.expand-inner { - position: absolute; - width: 50%; - height: 100%; + position: absolute; + width: 50%; + height: 100%; } div.expand-reference { - margin-left: 50%; + margin-left: 50%; } table.expand { width: 95%; } table.expand, table.expand tbody, table.expand tr { - border-collapse: collapse; - border-spacing: 0; + border-collapse: collapse; + border-spacing: 0; } table.expand td { padding: 0; } #acct-list { - width: 100%; - height: 100%; + width: 100%; + height: 100%; } /*----- That's all, folks -------------------------------------------------*/ -- 2.11.0 From d67ea869401e2c8ba56cf9d253397d94973fbf49 Mon Sep 17 00:00:00 2001 From: Mark Wooding Date: Fri, 15 May 2020 18:10:28 +0100 Subject: [PATCH 15/16] chpwd.css: Use the abbreviated border-setting notation. --- chpwd.css | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/chpwd.css b/chpwd.css index 2ca07d4..1cf87a5 100644 --- a/chpwd.css +++ b/chpwd.css @@ -27,14 +27,12 @@ /*----- General typesetting and layout -----------------------------------*/ h1 { - border-bottom-style: solid; - border-bottom-width: medium; + border-bottom: medium solid; padding-bottom: 1ex; } h2 { - border-top-style: solid; - border-top-width: thin; + border-top: thin solid; padding-top: 1ex; margin-top: 4ex; } @@ -45,8 +43,7 @@ h1 + h2, h2:first-child { } div.credits { - border-top-style: solid; - border-top-width: thin; + border-top: thin solid; padding-top: 0.5ex; margin-top: 2ex; text-align: right; -- 2.11.0 From efe96413119f689752c6395c68531b17b70c2743 Mon Sep 17 00:00:00 2001 From: Mark Wooding Date: Sat, 16 May 2020 10:46:41 +0100 Subject: [PATCH 16/16] chpwd.css: Make style match `distorted.org.uk' general house style. Which is rather minimal, and hopefully not especially offensive. (Note that, in particular, it doesn't override the body text face or size.) --- chpwd.css | 42 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 36 insertions(+), 6 deletions(-) diff --git a/chpwd.css b/chpwd.css index 1cf87a5..9710665 100644 --- a/chpwd.css +++ b/chpwd.css @@ -26,26 +26,39 @@ /*----- General typesetting and layout -----------------------------------*/ +body { + background: white; + color: black; + margin: 1ex 2em; +} + h1 { - border-bottom: medium solid; - padding-bottom: 1ex; + border-bottom: thick solid; + padding: 8px 0; + margin-bottom: 24px; } h2 { border-top: thin solid; padding-top: 1ex; - margin-top: 4ex; + margin-top: 3ex; } h1 + h2, h2:first-child { - border-top-style: hidden; + border-top: none; + padding-top: 0; margin-top: inherit; } +h1, h2 { + font-family: sans-serif; + font-weight: bold; +} + div.credits { border-top: thin solid; - padding-top: 0.5ex; - margin-top: 2ex; + padding-top: 1ex; + margin-top: 3.43px; text-align: right; font-size: small; font-style: italic; @@ -58,6 +71,23 @@ div.warn { background: red; } +a { text-decoration: none; } +a:link { color: blue; } +a:link:active, a:visited { color: darkblue; } +a:link:hover, a:visited:hover { background: #ccc; } + +dl { display: block; } +dt { + font-weight: bold; + width: 9em; + clear: left; + float: left; +} +dd { + margin: 1.5ex 0 1.5ex 10em; + display: block; +} + /*----- Form layout -------------------------------------------------------*/ /* Common form validation styling. */ -- 2.11.0