#!/usr/bin/env python # CCS Computer Science # Admin # # Douglas Thrift # # $Id$ import common import fnmatch import ldap import optparse import os import psycopg2 import re import shutil import subprocess import sys import warnings with warnings.catch_warnings(): warnings.filterwarnings('ignore', 'the sets module is deprecated', DeprecationWarning) import MySQLdb MASTER = 'zweihander.ccs.ucsb.edu' SLAVE = 'wireless.ccs.ucsb.edu' MASTER_URI = 'ldaps://' + MASTER SLAVE_URI = 'ldaps://' + SLAVE BASE = 'dc=ccs,dc=ucsb,dc=edu' PEOPLE = 'ou=People,' + BASE GROUP = 'ou=Group,' + BASE SECRET = '/ccs/etc/secret' SHELLS = map(lambda system: 'ucsbCcs' + system.capitalize(), common.SYSTEMS) SAMBA_SID = 'S-1-5-21-3739982181-3886045993-82308153-%u' USER = re.compile('^[a-z0-9]{1,16}$') NAME = re.compile('^[^:]+$') INVALID_USER = 'Invalid user name: "%s"' INVALID_NAME = 'Invalid full name: "%s"' ldap.set_option(ldap.OPT_PROTOCOL_VERSION, 3) ldap.set_option(ldap.OPT_X_TLS_CACERTFILE, '/ccs/ssl/ccscert.pem') ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_DEMAND) def _user(user): return 'uid=%s,%s' % (user, PEOPLE) def _group(group): return 'cn=%s,%s' % (group, GROUP) def _program(): return os.path.splitext(os.path.basename(sys.argv[0]))[0] def ldap_connection(root = True): connection = ldap.initialize(MASTER_URI if fnmatch.fnmatch(common.HOST, '*.ccs.ucsb.edu') else MASTER_URI.replace('ldaps:', 'ldap:')) if root: with open(SECRET, 'rb') as secret: connection.simple_bind_s(_user('root'), secret.read()) else: connection.simple_bind_s() return connection def master(): return common.HOST == MASTER def run(errors): if errors: for host, error in errors.iteritems(): sys.stderr.write('%s: %s\n' % (host, error)) sys.exit(1) def error(error): sys.exit('%s: %s' % (_program(), error)) def eof(): print sys.exit(130) def parser(**kwargs): kwargs['prog'] = _program() parser = optparse.OptionParser(**kwargs) def version(option, opt_str, value, parser, *args, **kwargs): parser.version = '%%prog %s (CCS CS Lab)' % subprocess.Popen('svnversion', stdout = subprocess.PIPE, cwd = os.path.dirname(__file__)).communicate()[0].strip() if opt_str != '--version-short': name = os.uname() parser.version += ' - %s %s %s' % (name[0], name[2], name[4]) parser.print_version() parser.exit() parser.add_option('-V', '--version', action = 'callback', callback = version, help = 'show version information and exit') parser.add_option('--version-short', action = 'callback', callback = version, help = optparse.SUPPRESS_HELP) return parser def user(options): user = os.environ['USER'] if user == 'root': user = options.user while USER.match(user) is None: if options.user: error(INVALID_USER % user) elif user: warn(INVALID_USER % user) user = raw_input('User: ') return user def warn(error): sys.stderr.write('%s: %s\n' % (_program(), error)) def adduser(user, name, password): connection = ldap_connection() uid = max(map(lambda user: int(user[1]['uidNumber'][0]), connection.search_s(PEOPLE, ldap.SCOPE_ONELEVEL, '(&(uid=*)(!(uid=root)))', ('uidNumber',)))) + 1 gid = uid samba_gid = gid + 1000 home = os.path.join('/home', user) connection.add_s(_user(user), [ ('objectclass', ['top', 'account', 'posixAccount', 'shadowAccount', 'ucsbCcsLoginShells', 'sambaSamAccount']), ('cn', name), ('uid', user), ('uidNumber', str(uid)), ('gidNumber', str(gid)), ('homeDirectory', home), ('loginShell', 'bash'), ] + zip(SHELLS, dict(common.SHELLS)['bash']) + [ ('sambaAcctFlags', '[U ]'), ('sambaSID', SAMBA_SID % uid), ('sambaPrimaryGroupSID', SAMBA_SID % samba_gid), ]) connection.add_s(_group(user), [ ('objectclass', ['top', 'posixGroup', 'sambaGroupMapping']), ('cn', user), ('gidNumber', str(gid)), ('sambaSID', SAMBA_SID % samba_gid), ('sambaGroupType', '4'), ]) for group in ('wheel', 'fuse', 'operator'): connection.modify_s(_group(group), [(ldap.MOD_ADD, 'memberUid', user)]) connection.unbind_s() os.umask(0022) os.mkdir(home) os.chown(home, uid, gid) for skel in ('/usr/share/skel', '/ccs/skel'): for source, directories, files in os.walk(skel): destination = os.path.join(home, source[len(skel):]) for directory in directories: target = os.path.join(destination, directory[3:] if directory.startswith('dot') else directory) os.mkdir(target) shutil.copymode(os.path.join(source, directory), target) os.chown(target, uid, gid) for file in files: target = os.path.join(destination, file[3:] if file.startswith('dot') else file) shutil.copy(os.path.join(source, file), target) os.chown(target, uid, gid) db = psycopg2.connect(database = 'postgres') cursor = db.cursor() cursor.execute('create user %s with createdb' % user) db.commit() passwd(user, None, password) def chfn(user, name): connection = ldap_connection() connection.modify_s(_user(user), [(ldap.MOD_REPLACE, 'cn', name)]) connection.unbind_s() def chsh(user, shell, shells): if shell != 'custom': shells = dict(common.SHELLS)[shell] else: for _shell, _shells in common.SHELLS[:-1]: if shells == _shells: shell = _shell connection = ldap_connection() connection.modify_s(_user(user), map(lambda (key, value): (ldap.MOD_REPLACE, key, value), [('loginShell', shell)] + zip(SHELLS, shells))) connection.unbind_s() def passwd(user, old_password, new_password): connection = ldap_connection() connection.passwd_s(_user(user), old_password, new_password) connection.unbind_s() with open(SECRET, 'rb') as secret: db = MySQLdb.connect(passwd = secret.read(), db = 'mysql') cursor = db.cursor() cursor.execute('select count(User) from user where User = %s', (user,)) if cursor.fetchone()[0]: cursor.execute('update user set Password = PASSWORD(%s) where User = %s', (new_password, user)) cursor.execute('flush privileges'); else: cursor.executemany('grant all on `' + db.escape_string(user) + r'\_%%`.* to %s@%s identified by %s', map(lambda host: (user, host, new_password), ('localhost', '%'))) if __name__ == '__main__': parser = parser(usage = '%prog [options] [VARIABLE...]') parser.add_option('-l', '--list', action = 'store_true', default = False, help = 'list the available variables and their values') options, args = parser.parse_args() variables = ('MASTER', 'SLAVE', 'MASTER_URI', 'SLAVE_URI', 'BASE', 'PEOPLE', 'GROUP') if options.list: for variable in variables: exec "print '%%10s: %%s' %% (variable, %s)" % variable parser.exit() if not args: parser.error('no variable specified') for variable in args: if variable not in variables: parser.error('unknown variable: "%s"' % variable) for variable in args: exec 'print %s' % variable # vim: noexpandtab tabstop=4