1 |
douglas |
600 |
#!/usr/bin/env python |
2 |
douglas |
585 |
# CCS Computer Science |
3 |
douglas |
590 |
# Admin |
4 |
douglas |
585 |
# |
5 |
|
|
# Douglas Thrift |
6 |
|
|
# |
7 |
|
|
# $Id$ |
8 |
|
|
|
9 |
douglas |
591 |
import common |
10 |
douglas |
692 |
import fnmatch |
11 |
douglas |
585 |
import ldap |
12 |
douglas |
687 |
import optparse |
13 |
douglas |
598 |
import os |
14 |
|
|
import psycopg2 |
15 |
douglas |
681 |
import re |
16 |
douglas |
598 |
import shutil |
17 |
douglas |
681 |
import subprocess |
18 |
douglas |
592 |
import sys |
19 |
douglas |
680 |
import warnings |
20 |
douglas |
585 |
|
21 |
douglas |
680 |
with warnings.catch_warnings(): |
22 |
|
|
warnings.filterwarnings('ignore', 'the sets module is deprecated', DeprecationWarning) |
23 |
douglas |
598 |
|
24 |
|
|
import MySQLdb |
25 |
|
|
|
26 |
douglas |
585 |
MASTER = 'zweihander.ccs.ucsb.edu' |
27 |
douglas |
600 |
SLAVE = 'wireless.ccs.ucsb.edu' |
28 |
|
|
MASTER_URI = 'ldaps://' + MASTER |
29 |
|
|
SLAVE_URI = 'ldaps://' + SLAVE |
30 |
douglas |
585 |
BASE = 'dc=ccs,dc=ucsb,dc=edu' |
31 |
douglas |
598 |
PEOPLE = 'ou=People,' + BASE |
32 |
|
|
GROUP = 'ou=Group,' + BASE |
33 |
douglas |
606 |
SECRET = '/ccs/etc/secret' |
34 |
douglas |
592 |
SHELLS = map(lambda system: 'ucsbCcs' + system.capitalize(), common.SYSTEMS) |
35 |
douglas |
598 |
SAMBA_SID = 'S-1-5-21-3739982181-3886045993-82308153-%u' |
36 |
douglas |
681 |
USER = re.compile('^[a-z0-9]{1,16}$') |
37 |
|
|
NAME = re.compile('^[^:]+$') |
38 |
|
|
INVALID_USER = 'Invalid user name: "%s"' |
39 |
|
|
INVALID_NAME = 'Invalid full name: "%s"' |
40 |
douglas |
585 |
|
41 |
|
|
ldap.set_option(ldap.OPT_PROTOCOL_VERSION, 3) |
42 |
|
|
ldap.set_option(ldap.OPT_X_TLS_CACERTFILE, '/ccs/ssl/ccscert.pem') |
43 |
|
|
ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_DEMAND) |
44 |
|
|
|
45 |
|
|
def _user(user): |
46 |
douglas |
598 |
return 'uid=%s,%s' % (user, PEOPLE) |
47 |
douglas |
585 |
|
48 |
douglas |
598 |
def _group(group): |
49 |
|
|
return 'cn=%s,%s' % (group, GROUP) |
50 |
|
|
|
51 |
douglas |
681 |
def _program(): |
52 |
|
|
return os.path.splitext(os.path.basename(sys.argv[0]))[0] |
53 |
|
|
|
54 |
|
|
def ldap_connection(root = True): |
55 |
douglas |
692 |
connection = ldap.initialize(MASTER_URI if fnmatch.fnmatch(common.HOST, '*.ccs.ucsb.edu') else MASTER_URI.replace('ldaps:', 'ldap:')) |
56 |
douglas |
585 |
|
57 |
douglas |
681 |
if root: |
58 |
|
|
with open(SECRET, 'rb') as secret: |
59 |
|
|
connection.simple_bind_s(_user('root'), secret.read()) |
60 |
|
|
else: |
61 |
|
|
connection.simple_bind_s() |
62 |
douglas |
585 |
|
63 |
|
|
return connection |
64 |
|
|
|
65 |
douglas |
591 |
def master(): |
66 |
douglas |
592 |
return common.HOST == MASTER |
67 |
douglas |
591 |
|
68 |
douglas |
593 |
def run(errors): |
69 |
|
|
if errors: |
70 |
|
|
for host, error in errors.iteritems(): |
71 |
|
|
sys.stderr.write('%s: %s\n' % (host, error)) |
72 |
douglas |
592 |
|
73 |
|
|
sys.exit(1) |
74 |
|
|
|
75 |
douglas |
593 |
def error(error): |
76 |
douglas |
681 |
sys.exit('%s: %s' % (_program(), error)) |
77 |
douglas |
593 |
|
78 |
douglas |
592 |
def eof(): |
79 |
|
|
print |
80 |
|
|
|
81 |
|
|
sys.exit(130) |
82 |
|
|
|
83 |
douglas |
681 |
def parser(**kwargs): |
84 |
|
|
kwargs['prog'] = _program() |
85 |
douglas |
687 |
parser = optparse.OptionParser(**kwargs) |
86 |
douglas |
681 |
|
87 |
douglas |
687 |
def version(option, opt_str, value, parser, *args, **kwargs): |
88 |
|
|
parser.version = '%%prog %s (CCS CS Lab)' % subprocess.Popen('svnversion', stdout = subprocess.PIPE, cwd = os.path.dirname(__file__)).communicate()[0].strip() |
89 |
douglas |
681 |
|
90 |
douglas |
687 |
if opt_str != '--version-short': |
91 |
|
|
name = os.uname() |
92 |
|
|
parser.version += ' - %s %s %s' % (name[0], name[2], name[4]) |
93 |
|
|
|
94 |
|
|
parser.print_version() |
95 |
|
|
parser.exit() |
96 |
|
|
|
97 |
|
|
parser.add_option('-V', '--version', action = 'callback', callback = version, help = 'show version information and exit') |
98 |
|
|
parser.add_option('--version-short', action = 'callback', callback = version, help = optparse.SUPPRESS_HELP) |
99 |
|
|
|
100 |
|
|
return parser |
101 |
|
|
|
102 |
|
|
def user(options): |
103 |
douglas |
681 |
user = os.environ['USER'] |
104 |
|
|
|
105 |
|
|
if user == 'root': |
106 |
douglas |
687 |
user = options.user |
107 |
douglas |
681 |
|
108 |
|
|
while USER.match(user) is None: |
109 |
douglas |
687 |
if options.user: |
110 |
douglas |
681 |
error(INVALID_USER % user) |
111 |
|
|
elif user: |
112 |
|
|
warn(INVALID_USER % user) |
113 |
|
|
|
114 |
|
|
user = raw_input('User: ') |
115 |
|
|
|
116 |
|
|
return user |
117 |
|
|
|
118 |
|
|
def warn(error): |
119 |
|
|
sys.stderr.write('%s: %s\n' % (_program(), error)) |
120 |
|
|
|
121 |
douglas |
598 |
def adduser(user, name, password): |
122 |
|
|
connection = ldap_connection() |
123 |
|
|
uid = max(map(lambda user: int(user[1]['uidNumber'][0]), connection.search_s(PEOPLE, ldap.SCOPE_ONELEVEL, '(&(uid=*)(!(uid=root)))', ('uidNumber',)))) + 1 |
124 |
|
|
gid = uid |
125 |
|
|
samba_gid = gid + 1000 |
126 |
|
|
home = os.path.join('/home', user) |
127 |
|
|
|
128 |
|
|
connection.add_s(_user(user), [ |
129 |
|
|
('objectclass', ['top', 'account', 'posixAccount', 'shadowAccount', 'ucsbCcsLoginShells', 'sambaSamAccount']), |
130 |
|
|
('cn', name), |
131 |
|
|
('uid', user), |
132 |
|
|
('uidNumber', str(uid)), |
133 |
|
|
('gidNumber', str(gid)), |
134 |
|
|
('homeDirectory', home), |
135 |
|
|
('loginShell', 'bash'), |
136 |
|
|
] + zip(SHELLS, dict(common.SHELLS)['bash']) + [ |
137 |
|
|
('sambaAcctFlags', '[U ]'), |
138 |
|
|
('sambaSID', SAMBA_SID % uid), |
139 |
|
|
('sambaPrimaryGroupSID', SAMBA_SID % samba_gid), |
140 |
|
|
]) |
141 |
|
|
connection.add_s(_group(user), [ |
142 |
|
|
('objectclass', ['top', 'posixGroup', 'sambaGroupMapping']), |
143 |
|
|
('cn', user), |
144 |
|
|
('gidNumber', str(gid)), |
145 |
|
|
('sambaSID', SAMBA_SID % samba_gid), |
146 |
|
|
('sambaGroupType', '4'), |
147 |
|
|
]) |
148 |
|
|
|
149 |
|
|
for group in ('wheel', 'fuse', 'operator'): |
150 |
|
|
connection.modify_s(_group(group), [(ldap.MOD_ADD, 'memberUid', user)]) |
151 |
|
|
|
152 |
|
|
connection.unbind_s() |
153 |
|
|
os.umask(0022) |
154 |
|
|
os.mkdir(home) |
155 |
|
|
os.chown(home, uid, gid) |
156 |
|
|
|
157 |
|
|
for skel in ('/usr/share/skel', '/ccs/skel'): |
158 |
|
|
for source, directories, files in os.walk(skel): |
159 |
|
|
destination = os.path.join(home, source[len(skel):]) |
160 |
|
|
|
161 |
|
|
for directory in directories: |
162 |
|
|
target = os.path.join(destination, directory[3:] if directory.startswith('dot') else directory) |
163 |
|
|
|
164 |
|
|
os.mkdir(target) |
165 |
|
|
shutil.copymode(os.path.join(source, directory), target) |
166 |
|
|
os.chown(target, uid, gid) |
167 |
|
|
|
168 |
|
|
for file in files: |
169 |
|
|
target = os.path.join(destination, file[3:] if file.startswith('dot') else file) |
170 |
|
|
|
171 |
|
|
shutil.copy(os.path.join(source, file), target) |
172 |
|
|
os.chown(target, uid, gid) |
173 |
|
|
|
174 |
|
|
db = psycopg2.connect(database = 'postgres') |
175 |
|
|
cursor = db.cursor() |
176 |
|
|
|
177 |
|
|
cursor.execute('create user %s with createdb' % user) |
178 |
|
|
db.commit() |
179 |
|
|
|
180 |
|
|
passwd(user, None, password) |
181 |
|
|
|
182 |
douglas |
592 |
def chfn(user, name): |
183 |
|
|
connection = ldap_connection() |
184 |
|
|
|
185 |
|
|
connection.modify_s(_user(user), [(ldap.MOD_REPLACE, 'cn', name)]) |
186 |
|
|
connection.unbind_s() |
187 |
|
|
|
188 |
douglas |
591 |
def chsh(user, shell, shells): |
189 |
|
|
if shell != 'custom': |
190 |
|
|
shells = dict(common.SHELLS)[shell] |
191 |
|
|
else: |
192 |
|
|
for _shell, _shells in common.SHELLS[:-1]: |
193 |
|
|
if shells == _shells: |
194 |
|
|
shell = _shell |
195 |
|
|
|
196 |
|
|
connection = ldap_connection() |
197 |
|
|
|
198 |
|
|
connection.modify_s(_user(user), map(lambda (key, value): (ldap.MOD_REPLACE, key, value), [('loginShell', shell)] + zip(SHELLS, shells))) |
199 |
|
|
connection.unbind_s() |
200 |
|
|
|
201 |
douglas |
585 |
def passwd(user, old_password, new_password): |
202 |
|
|
connection = ldap_connection() |
203 |
|
|
|
204 |
|
|
connection.passwd_s(_user(user), old_password, new_password) |
205 |
|
|
connection.unbind_s() |
206 |
|
|
|
207 |
douglas |
606 |
with open(SECRET, 'rb') as secret: |
208 |
douglas |
585 |
db = MySQLdb.connect(passwd = secret.read(), db = 'mysql') |
209 |
|
|
|
210 |
|
|
cursor = db.cursor() |
211 |
|
|
|
212 |
|
|
cursor.execute('select count(User) from user where User = %s', (user,)) |
213 |
|
|
|
214 |
|
|
if cursor.fetchone()[0]: |
215 |
|
|
cursor.execute('update user set Password = PASSWORD(%s) where User = %s', (new_password, user)) |
216 |
|
|
cursor.execute('flush privileges'); |
217 |
|
|
else: |
218 |
|
|
cursor.executemany('grant all on `' + db.escape_string(user) + r'\_%%`.* to %s@%s identified by %s', map(lambda host: (user, host, new_password), ('localhost', '%'))) |
219 |
douglas |
600 |
|
220 |
|
|
if __name__ == '__main__': |
221 |
douglas |
687 |
parser = parser(usage = '%prog [options] [VARIABLE...]') |
222 |
|
|
|
223 |
|
|
parser.add_option('-l', '--list', action = 'store_true', default = False, help = 'list the available variables and their values') |
224 |
|
|
|
225 |
|
|
options, args = parser.parse_args() |
226 |
douglas |
681 |
variables = ('MASTER', 'SLAVE', 'MASTER_URI', 'SLAVE_URI', 'BASE', 'PEOPLE', 'GROUP') |
227 |
douglas |
600 |
|
228 |
douglas |
687 |
if options.list: |
229 |
|
|
for variable in variables: |
230 |
|
|
exec "print '%%10s: %%s' %% (variable, %s)" % variable |
231 |
douglas |
681 |
|
232 |
douglas |
687 |
parser.exit() |
233 |
|
|
|
234 |
|
|
if not args: |
235 |
|
|
parser.error('no variable specified') |
236 |
|
|
|
237 |
|
|
|
238 |
|
|
for variable in args: |
239 |
|
|
if variable not in variables: |
240 |
|
|
parser.error('unknown variable: "%s"' % variable) |
241 |
|
|
|
242 |
|
|
for variable in args: |
243 |
douglas |
681 |
exec 'print %s' % variable |