1 | #!/usr/bin/python
|
---|
2 | """Replacement for htpasswd"""
|
---|
3 | # Original author: Eli Carter
|
---|
4 |
|
---|
5 | import os
|
---|
6 | import sys
|
---|
7 | import random
|
---|
8 | from optparse import OptionParser
|
---|
9 |
|
---|
10 | # We need a crypt module, but Windows doesn't have one by default. Try to find
|
---|
11 | # one, and tell the user if we can't.
|
---|
12 | try:
|
---|
13 | import crypt
|
---|
14 | except ImportError:
|
---|
15 | try:
|
---|
16 | import fcrypt as crypt
|
---|
17 | except ImportError:
|
---|
18 | sys.stderr.write("Cannot find a crypt module. "
|
---|
19 | "Possibly http://carey.geek.nz/code/python-fcrypt/\n")
|
---|
20 | sys.exit(1)
|
---|
21 |
|
---|
22 |
|
---|
23 | def salt():
|
---|
24 | """Returns a string of 2 randome letters"""
|
---|
25 | letters = 'abcdefghijklmnopqrstuvwxyz' \
|
---|
26 | 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' \
|
---|
27 | '0123456789/.'
|
---|
28 | return random.choice(letters) + random.choice(letters)
|
---|
29 |
|
---|
30 |
|
---|
31 | class HtpasswdFile:
|
---|
32 | """A class for manipulating htpasswd files."""
|
---|
33 |
|
---|
34 | def __init__(self, filename, create=False):
|
---|
35 | self.entries = []
|
---|
36 | self.filename = filename
|
---|
37 | if not create:
|
---|
38 | if os.path.exists(self.filename):
|
---|
39 | self.load()
|
---|
40 | else:
|
---|
41 | raise Exception("%s does not exist" % self.filename)
|
---|
42 |
|
---|
43 | def load(self):
|
---|
44 | """Read the htpasswd file into memory."""
|
---|
45 | lines = open(self.filename, 'r').readlines()
|
---|
46 | self.entries = []
|
---|
47 | for line in lines:
|
---|
48 | username, pwhash = line.split(':')
|
---|
49 | entry = [username, pwhash.rstrip()]
|
---|
50 | self.entries.append(entry)
|
---|
51 |
|
---|
52 | def save(self):
|
---|
53 | """Write the htpasswd file to disk"""
|
---|
54 | open(self.filename, 'w').writelines(["%s:%s\n" % (entry[0], entry[1])
|
---|
55 | for entry in self.entries])
|
---|
56 |
|
---|
57 | def update(self, username, password):
|
---|
58 | """Replace the entry for the given user, or add it if new."""
|
---|
59 | pwhash = crypt.crypt(password, salt())
|
---|
60 | matching_entries = [entry for entry in self.entries
|
---|
61 | if entry[0] == username]
|
---|
62 | if matching_entries:
|
---|
63 | matching_entries[0][1] = pwhash
|
---|
64 | else:
|
---|
65 | self.entries.append([username, pwhash])
|
---|
66 |
|
---|
67 | def delete(self, username):
|
---|
68 | """Remove the entry for the given user."""
|
---|
69 | self.entries = [entry for entry in self.entries
|
---|
70 | if entry[0] != username]
|
---|
71 |
|
---|
72 |
|
---|
73 | def main():
|
---|
74 | """%prog [-c] -b filename username password
|
---|
75 | Create or update an htpasswd file"""
|
---|
76 | # For now, we only care about the use cases that affect tests/functional.py
|
---|
77 | parser = OptionParser(usage=main.__doc__)
|
---|
78 | parser.add_option('-b', action='store_true', dest='batch', default=False,
|
---|
79 | help='Batch mode; password is passed on the command line IN THE CLEAR.'
|
---|
80 | )
|
---|
81 | parser.add_option('-c', action='store_true', dest='create', default=False,
|
---|
82 | help='Create a new htpasswd file, overwriting any existing file.')
|
---|
83 | parser.add_option('-D', action='store_true', dest='delete_user',
|
---|
84 | default=False, help='Remove the given user from the password file.')
|
---|
85 |
|
---|
86 | options, args = parser.parse_args()
|
---|
87 |
|
---|
88 | def syntax_error(msg):
|
---|
89 | """Utility function for displaying fatal error messages with usage
|
---|
90 | help.
|
---|
91 | """
|
---|
92 | sys.stderr.write("Syntax error: " + msg)
|
---|
93 | sys.stderr.write(parser.get_usage())
|
---|
94 | sys.exit(1)
|
---|
95 |
|
---|
96 | if not options.batch:
|
---|
97 | syntax_error("Only batch mode is supported\n")
|
---|
98 |
|
---|
99 | # Non-option arguments
|
---|
100 | if len(args) < 2:
|
---|
101 | syntax_error("Insufficient number of arguments.\n")
|
---|
102 | filename, username = args[:2]
|
---|
103 | if options.delete_user:
|
---|
104 | if len(args) != 2:
|
---|
105 | syntax_error("Incorrect number of arguments.\n")
|
---|
106 | password = None
|
---|
107 | else:
|
---|
108 | if len(args) != 3:
|
---|
109 | syntax_error("Incorrect number of arguments.\n")
|
---|
110 | password = args[2]
|
---|
111 |
|
---|
112 | passwdfile = HtpasswdFile(filename, create=options.create)
|
---|
113 |
|
---|
114 | if options.delete_user:
|
---|
115 | passwdfile.delete(username)
|
---|
116 | else:
|
---|
117 | passwdfile.update(username, password)
|
---|
118 |
|
---|
119 | passwdfile.save()
|
---|
120 |
|
---|
121 |
|
---|
122 | if __name__ == '__main__':
|
---|
123 | main()
|
---|