chiark / gitweb /
Found in crybaby's working tree. mdw/wip.crybaby.2016-05-05
authorMark Wooding <mdw@distorted.org.uk>
Thu, 5 May 2016 09:27:24 +0000 (10:27 +0100)
committerMark Wooding <mdw@distorted.org.uk>
Fri, 7 Jul 2017 21:58:58 +0000 (22:58 +0100)
backend.py
chpwd.rst [new file with mode: 0644]

index fda32e0b8e37d259090e71f4d6ac864bd407e069..2596306363e7c6171107bdbfa8d26b81de8f40c9 100644 (file)
@@ -44,7 +44,8 @@ CONF.DEFAULTS.update(
 ###--------------------------------------------------------------------------
 ### Utilities.
 
-def fill_in_fields(fno_user, fno_passwd, fno_map, user, passwd, args):
+def fill_in_fields(fno_user, fno_passwd, fno_map,
+                   user, passwd, args, defaults = None):
   """
   Return a vector of filled-in fields.
 
@@ -53,6 +54,10 @@ def fill_in_fields(fno_user, fno_passwd, fno_map, user, passwd, args):
   is a sequence of (NAME, POS) pairs.  The USER and PASSWD arguments give the
   actual user name and password values; ARGS are the remaining arguments,
   maybe in the form `NAME=VALUE'.
+
+  If DEFAULTS is given, it is a fully-filled-in sequence of records.  The
+  user name and password are taken from the DEFAULTS if USER and PASSWD are
+  `None' on entry; they cannot be set from ARGS.
   """
 
   ## Prepare the result vector, and set up some data structures.
@@ -63,7 +68,8 @@ def fill_in_fields(fno_user, fno_passwd, fno_map, user, passwd, args):
   if fno_user >= n or fno_passwd >= n: ok = False
   for k, i in fno_map:
     fmap[k] = i
-    rmap[i] = "`%s'" % k
+    if i in rmap: ok = False
+    else: rmap[i] = "`%s'" % k
     if i >= n: ok = False
   if not ok:
     raise U.ExpectedError, \
@@ -71,8 +77,12 @@ def fill_in_fields(fno_user, fno_passwd, fno_map, user, passwd, args):
 
   ## Prepare the new record's fields.
   f = [None]*n
-  f[fno_user] = user
-  f[fno_passwd] = passwd
+  if user is not None: f[fno_user] = user
+  elif defaults is not None: f[no_user] = defaults[no_user]
+  else: raise U.ExpectedError, (500, "No user name given")
+  if passwd is not None: f[fno_passwd] = passwd
+  elif defaults is not None: f[no_passwd] = defaults[no_passwd]
+  else: raise U.ExpectedError, (500, "No password given")
 
   for a in args:
     if '=' in a:
@@ -89,10 +99,12 @@ def fill_in_fields(fno_user, fno_passwd, fno_map, user, passwd, args):
       raise U.ExpectedError, (400, "Field %s is already set" % rmap[i])
     f[i] = v
 
-  ## Check that the vector of fields is properly set up.
+  ## Check that the vector of fields is properly set up.  Copy unset values
+  ## from the defaults if they're available.
   for i in xrange(n):
-    if f[i] is None:
-      raise U.ExpectedError, (500, "Field %s is unset" % rmap[i])
+    if f[i] is not None: pass
+    elif defaults is not None: f[i] = defaults[i]
+    else: raise U.ExpectedError, (500, "Field %s is unset" % rmap[i])
 
   ## Done.
   return f
@@ -165,7 +177,7 @@ class FlatFileRecord (BasicRecord):
   is careful to do this).
   """
 
-  def __init__(me, line, delim, fmap, *args, **kw):
+  def __init__(me, line, delim, fno_user, fno_passwd, fmap, *args, **kw):
     """
     Initialize the record, splitting the LINE into fields separated by DELIM,
     and setting attributes under control of FMAP.
@@ -174,6 +186,8 @@ class FlatFileRecord (BasicRecord):
     line = line.rstrip('\n')
     fields = line.split(delim)
     me._delim = delim
+    me._fno_user = fno_user
+    me._fno_passwd = fno_passwd
     me._fmap = fmap
     me._raw = fields
     for k, v in fmap.iteritems():
@@ -199,6 +213,14 @@ class FlatFileRecord (BasicRecord):
       fields[v] = val
     return me._delim.join(fields) + '\n'
 
+  def edit(me, args):
+    """
+    Modify the record as described by the ARGS.
+
+    Fields other than the user name and password can be modified.
+    """
+    ff = fill_in_fields(
+
 class FlatFileBackend (object):
   """
   Password storage in a flat passwd(5)-style file.
diff --git a/chpwd.rst b/chpwd.rst
new file mode 100644 (file)
index 0000000..909576b
--- /dev/null
+++ b/chpwd.rst
@@ -0,0 +1,185 @@
+=======================================
+ Chopwood: a password changing service
+=======================================
+
+:Author: Mark Wooding
+
+.. sectnum::
+       :depth: 3
+
+.. contents::
+
+Introduction
+############
+
+
+Congratulations.  You've just installed Squid, or nginx, or Exim, or
+Dovecot, or, well, something.  And it has a marvellous access control
+system with users and passwords and stuff.  Yet another little file with
+*user*\ **:**\ *password* lines, or maybe a database or something.
+Which will all be great.  Just as soon as your users can actually set
+passwords, or change them.  Chopwood is a service for letting them do
+that.
+
+Of course, you could set up some single sign-on system.  They're quite
+complicated.  Also, they probably aren't really what you wanted, because
+these various services that you've set up probably have rather different
+security levels and it's just not a good plan to feed high-value
+credentials to a low-security service.
+
+Chopwood isn't a single sign-on system: in fact it's the opposite.  It's
+a tool to let users manage a number of passwords on the same system.
+
+Chopwood has three user interfaces.
+
+  * Local users can communicate with it as a `GNU Userv`_ service.  Most
+    of the necessary plumbing is provided.  Local users don't need to do
+    authenticate explicitly via Userv, so this is simple, convenient,
+    reliable, and relatively pain free.  The only downside is that you
+    have to give users shell access to your server.
+
+  * Remote users can reach it using SSH, using OpenSSH_\ 's forced
+    command feature.  Maintaining the ``authorized_keys`` file is a bit
+    of a nuisance, especially if they need to keep changing their keys
+    [#keymgmt]_.
+
+  * Remote users can also get to it with a web browser, using good
+    old-fashioned CGI.  Unfortunately, we don't have a better approach
+    for authenticating users than more passwords.  Of course, Chopwood
+    can manage its own password, but that's not really the point.
+
+.. [#keymgmt] Maybe someone should build a similar tool for managing
+             authorized keys for SSH services.
+
+.. _GNU Userv: http://www.gnu.org/software/userv/
+
+.. _OpenSSH: http://www.openssh.org/
+
+
+
+Installation
+############
+
+
+Chopwood is written in Python, and doesn't depend on any additional
+libraries.  The program is tested with Python 2.7, but I expect that it
+works with Python 2.6, and it's intended to work with 2.5 too.
+
+You'll also need GNU Make and at least of
+
+  * GNU Userv;
+
+  * OpenSSH, or some other SSH server with a forced-command feature;
+
+  * a web server which can run CGI scripts as some other user; or
+
+  * some willingness to get your hands dirty hooking Chopwood up to some
+    other interface.
+
+Chopwood is designed to be run from a Git working tree.  You can clone
+it from
+
+  * <git://git.distorted.org.uk/~mdw/chopwood/>
+  * <http://git.distorted.org.uk/~mdw/chopwood/>
+  * <https://git.distorted.org.uk/~mdw/chopwood/>
+
+I recommend that you use the HTTPS transport if you can; unfortunately,
+at the moment, that server doesn't have a certificate from a well-known
+CA.  Please check the certificate against the TLSA record, which is
+secured using DNSsec.
+
+The working tree needs a very quick build step, which you can accomplish
+by running ::
+
+       $ make
+
+This does three things:
+
+  * it generates the ``auto.py`` file which tells Chopwood its
+    installation directory, version number, and various other
+    automatically discovered things; and
+
+  * it generates the static files for the web interface; and
+
+  * it causes Python to byte-compile the various Python modules.
+
+
+Basic configuration
+-------------------
+
+Great so far.  Now it's time to shut up the warning about not having a
+configuration file.  
+
+
+Configuration parameters
+------------------------
+
+**ALLOWOP**:
+       An object holding three boolean attributes.  If **ALLOWOP.set**
+       is true then users are allowed to set passwords to values of
+       their choosing; if **ALLOWOP.reset** is true then they're
+       allowed to ask for passwords to be reset to a value chosen by
+       Chopwood; and if **ALLOWOP.clear** is true then they're allowed
+       to clear passwords, leaving them unable to log into the
+       applicable service.
+
+**AUTHHASH**:
+       The hash function to use when making HTTP authentication
+       cookies.  This should be a function (or other callable) which
+       behaves like one of the **hashlib** hash functions.  The default
+       is **hashlib**.\ **sha256**, which is likely to be good enough.
+
+**DB**:
+       The database which Chopwood should use to store its state.  This
+       should be a pair (*module*, *modargs*), where *module* names a
+       PEP 249 database module and *modargs* is a sequence or
+       dictionary of arguments to pass to its **connect** function.
+       The default is to create a SQLite3 database in Chopwood's
+       working directory.
+
+**HASH**:
+       The password hash to use for Chopwood's own user database.  See
+       `Chopwood password hashes`_ for more information about the
+       password hashes provided and the protocol in which hash objects
+       participate.  The default is to use a **crypt**\ (3)-style hash
+       with MD5.
+
+**LOCKDIR**:
+       A string naming the directory in which Chopwood should store
+       its lockfiles.
+
+**RQCLASS** and **RQMIXIN**:
+       Classes used to determine password policies.  See `Applying
+       local policy`_ for details.  The default is to allow everything.
+
+**SCRIPT_NAME**:
+       A (possibly relative) URL referring to Chopwood's CGI script.
+       This will usually be set appropriately by the webserver, but
+       this setting exists so that you can override it if it's wrong.
+
+**SECRETFRESH**:
+       The time in seconds for which a cookie authentication key is
+       used for issuing fresh cookies.  After this, a new key is
+       generated, and the old ones kept and used to validate existing
+       cookies until their lifetime expires (see **SECRETLIFE** below).
+       The default is five minutes.
+
+**SECRETLIFE**:
+       The total lifetime, in seconds, of a key used to ensure
+       cryptographically the integrity of session cookies.  See
+       **SECRETFRESH** above.  Users' browsers are instructed to
+       discard the cookie after `**SECRETLIFE** - **SECRETFRESH**`:m:
+       seconds.  The default is half an hour.
+
+**STATIC**:
+       The URL prefix for static content: the location of static
+       resource *file* is reported to the user agent as *STATIC*\
+       **/**\ *file*.  The default is to attempt (see **SCRIPT_NAME**
+       above) to link back to Chopwood's CGI script to generate the
+       content dynamically.  You can improve responsiveness by
+       configuring Chopwood and your webserver to serve actual static
+       files instead.
+
+
+..  LocalWords:  nginx www userv CGI keymgmt https TLSA py ALLOWOP sha
+..  LocalWords:  AUTHHASH RQCLASS RQMIXIN modargs