From 8ce92773c2225ebb126e39080a220f95dca76a2f Mon Sep 17 00:00:00 2001 From: david Date: Tue, 30 Oct 2012 11:08:24 +0100 Subject: [PATCH] =?UTF-8?q?/=20=E2=80=98htpasswd.py=E2=80=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- htpasswd.py | 123 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 123 insertions(+) diff --git a/htpasswd.py b/htpasswd.py index e69de29..3853f2c 100644 --- a/htpasswd.py +++ b/htpasswd.py @@ -0,0 +1,123 @@ +#!/usr/bin/python +"""Replacement for htpasswd""" +# Original author: Eli Carter + +import os +import sys +import random +from optparse import OptionParser + +# We need a crypt module, but Windows doesn't have one by default. Try to find +# one, and tell the user if we can't. +try: + import crypt +except ImportError: + try: + import fcrypt as crypt + except ImportError: + sys.stderr.write("Cannot find a crypt module. " + "Possibly http://carey.geek.nz/code/python-fcrypt/\n") + sys.exit(1) + + +def salt(): + """Returns a string of 2 randome letters""" + letters = 'abcdefghijklmnopqrstuvwxyz' \ + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' \ + '0123456789/.' + return random.choice(letters) + random.choice(letters) + + +class HtpasswdFile: + """A class for manipulating htpasswd files.""" + + def __init__(self, filename, create=False): + self.entries = [] + self.filename = filename + if not create: + if os.path.exists(self.filename): + self.load() + else: + raise Exception("%s does not exist" % self.filename) + + def load(self): + """Read the htpasswd file into memory.""" + lines = open(self.filename, 'r').readlines() + self.entries = [] + for line in lines: + username, pwhash = line.split(':') + entry = [username, pwhash.rstrip()] + self.entries.append(entry) + + def save(self): + """Write the htpasswd file to disk""" + open(self.filename, 'w').writelines(["%s:%s\n" % (entry[0], entry[1]) + for entry in self.entries]) + + def update(self, username, password): + """Replace the entry for the given user, or add it if new.""" + pwhash = crypt.crypt(password, salt()) + matching_entries = [entry for entry in self.entries + if entry[0] == username] + if matching_entries: + matching_entries[0][1] = pwhash + else: + self.entries.append([username, pwhash]) + + def delete(self, username): + """Remove the entry for the given user.""" + self.entries = [entry for entry in self.entries + if entry[0] != username] + + +def main(): + """%prog [-c] -b filename username password + Create or update an htpasswd file""" + # For now, we only care about the use cases that affect tests/functional.py + parser = OptionParser(usage=main.__doc__) + parser.add_option('-b', action='store_true', dest='batch', default=False, + help='Batch mode; password is passed on the command line IN THE CLEAR.' + ) + parser.add_option('-c', action='store_true', dest='create', default=False, + help='Create a new htpasswd file, overwriting any existing file.') + parser.add_option('-D', action='store_true', dest='delete_user', + default=False, help='Remove the given user from the password file.') + + options, args = parser.parse_args() + + def syntax_error(msg): + """Utility function for displaying fatal error messages with usage + help. + """ + sys.stderr.write("Syntax error: " + msg) + sys.stderr.write(parser.get_usage()) + sys.exit(1) + + if not options.batch: + syntax_error("Only batch mode is supported\n") + + # Non-option arguments + if len(args) < 2: + syntax_error("Insufficient number of arguments.\n") + filename, username = args[:2] + if options.delete_user: + if len(args) != 2: + syntax_error("Incorrect number of arguments.\n") + password = None + else: + if len(args) != 3: + syntax_error("Incorrect number of arguments.\n") + password = args[2] + + passwdfile = HtpasswdFile(filename, create=options.create) + + if options.delete_user: + passwdfile.delete(username) + else: + passwdfile.update(username, password) + + passwdfile.save() + + +if __name__ == '__main__': + main()