@@ -0,0 +1,2742 @@
+#!/usr/bin/python%0A%22%22%22Replacement for htpasswd%22%22%22%0A%0Aimport os%0Aimport random%0Atry:%0A    import crypt%0Aexcept ImportError:%0A    import fcrypt as crypt%0Afrom optparse import OptionParser%0A%0A%0Adef salt():%0A    %22%22%22Returns a string of 2 randome letters%22%22%22%0A    # FIXME: Additional characters may be legal here.%0A    letters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'%0A    return random.choice(letters) + random.choice(letters)%0A%0A%0Aclass HtpasswdFile:%0A    def __init__(self, filename, create=False):%0A        self.entries = %5B%5D%0A        self.filename = filename%0A        if not create:%0A            if os.path.exists(self.filename):%0A                self.load()%0A            else:%0A                raise Exception(%22%25s does not exist%22 %25 self.filename)%0A%0A    def load(self):%0A        lines = open(self.filename, 'r').readlines()%0A        self.entries = %5B%5D%0A        for line in lines:%0A            username, hash = line.split(':')%0A            entry = %5Busername, hash.rstrip()%5D%0A            self.entries.append(entry)%0A%0A    def save(self):%0A        open(self.filename, 'w').writelines(%5B%22%25s:%25s%5Cn%22 %25 (entry%5B0%5D, entry%5B1%5D) for entry in self.entries%5D)%0A%0A    def update(self, username, password):%0A        hash = crypt.crypt(password, salt())%0A        matching_entries = %5Bentry for entry in self.entries if entry%5B0%5D == username%5D%0A        if matching_entries:%0A            matching_entries%5B0%5D%5B1%5D = hash%0A        else:%0A            self.entries.append(%5Busername, hash%5D)%0A%0A    def delete(self, username):%0A        self.entries = %5Bentry for entry in self.entries if entry%5B0%5D != username%5D%0A%0Adef main():%0A    %22%22%22%25prog %5B-c%5D -b filename username password%0A    Create or update an htpasswd file%22%22%22%0A    # For now, we only care about the use cases that affect tests/functional.py%0A    parser = OptionParser(usage=main.__doc__)%0A    parser.add_option('-b', action='store_true', dest='batch', default=False,%0A        help='Batch mode; password is passed on the command line IN THE CLEAR.')%0A    parser.add_option('-c', action='store_true', dest='create', default=False,%0A        help='Create a new htpasswd file, overwriting any existing file.')%0A    parser.add_option('-D', action='store_true', dest='delete_user', default=False,%0A        help='Remove the given user from the password file.')%0A%0A    options, args = parser.parse_args()%0A%0A    assert(options.batch) # We only support batch mode for now.%0A%0A    # Non-option arguments%0A    filename, username = args%5B:2%5D%0A    if options.delete_user:%0A        password = None%0A    else:%0A        password = args%5B2%5D%0A%0A    passwdfile = HtpasswdFile(filename, create=options.create)%0A%0A    if options.delete_user:%0A        passwdfile.delete(username)%0A    else:%0A        passwdfile.update(username, password)%0A%0A    passwdfile.save()%0A%0A%0Aif __name__ == '__main__':%0A    main()%0A

Add htpasswd.py to the contrib tree as it may be useful more generally than just for the Testing branch