From: Peter Palfrader Date: Wed, 15 Sep 2010 10:49:26 +0000 (+0200) Subject: Add ud-sync-accounts-to-afs, a script to sync accounts to an AFS protection database X-Git-Tag: userdir-ldap-0.3.79~26 X-Git-Url: https://git.adam-barratt.org.uk/?p=mirror%2Fuserdir-ldap.git;a=commitdiff_plain;h=e14c30b4b7a7db4a5b88f624d154781881d50228 Add ud-sync-accounts-to-afs, a script to sync accounts to an AFS protection database --- diff --git a/debian/changelog b/debian/changelog index 2157b3a..a3316b7 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,8 +1,9 @@ userdir-ldap (0.3.7X) Xnstable; urgency=low - * XX + * Add ud-sync-accounts-to-afs, a script to sync accounts to an + AFS protection database. - -- Peter Palfrader Mon, 13 Sep 2010 19:14:21 +0200 + -- Peter Palfrader Wed, 15 Sep 2010 12:48:37 +0200 userdir-ldap (0.3.78) unstable; urgency=low diff --git a/debian/install b/debian/install index 31d24f8..5800025 100644 --- a/debian/install +++ b/debian/install @@ -12,6 +12,7 @@ ud-ldapshow usr/bin ud-userimport usr/bin ud-mailgate usr/bin ud-krb-reset usr/bin +ud-sync-accounts-to-afs usr/bin ud-generate usr/bin ud-passchk usr/bin ud-useradd usr/bin diff --git a/ud-sync-accounts-to-afs b/ud-sync-accounts-to-afs new file mode 100755 index 0000000..6ce93ef --- /dev/null +++ b/ud-sync-accounts-to-afs @@ -0,0 +1,261 @@ +#!/usr/bin/python + +# Copyright (c) 2010 Peter Palfrader + +# reads a list of active accounts from /var/lib/misc/thishost/all-accounts.json +# and creates those accounts in AFS's protection database. +# Furthermore it creates per-user scratch directories in +# /afs/debian.org/scratch/eu/grnet (or whatever path is specified in a command +# line option), owned by that user. + +import optparse +import os +import os.path +import pwd +import re +import subprocess +import sys +import tempfile + + +import json +if not '__author__' in json.__dict__: + sys.stderr.write("Warning: This is probably the wrong json module. We want python 2.6's json\n") + sys.stderr.write("module, or simplejson on python 2.5. Let's see if/how stuff blows up.\n") + import simplejson as json + +class UserEntries: + def __init__(self): + self.entries = [] + self.by_name = {} + self.by_id = {} + + def append(self, name, idnumber, owner=None, creator=None): + if name in self.by_name: raise Exception("Name '%s' is not unique."%(name)) + if idnumber in self.by_id: raise Exception("ID '%d' is not unique."%(idnumber)) + + h = { 'name': name, 'id': idnumber, 'owner': owner, 'creator': creator } + self.entries.append( h ) + self.by_name[name] = h + self.by_id[idnumber] = h + + def del_id(self, i): + h = self.by_id[i] + del self.by_id[i] + del self.by_name[h['name']] + self.entries.remove(h) + + def del_name(self, n): + self.del_id(self.by_name[n]['id']) + +def load_expected(): + accountsfile = '/var/lib/misc/thishost/all-accounts.json' + + if not os.path.isfile(accountsfile): + print >> sys.stderr, "Accountsfile %s not found."%(accountsfile) + accounts_json = open(accountsfile, 'r').read() + accounts = json.loads(accounts_json) + + entries = UserEntries() + for a in accounts: + if a['active']: + entries.append(a['uid'], a['uidNumber']) + return entries + +def load_existing(): + entries = UserEntries() + l = subprocess.Popen(('pts', 'listentries', '-users'), stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=None) + l.stdin.close() + l.stdout.readline() # headers + # Name ID Owner Creator + for line in l.stdout: + line = line.strip() + m = re.match('([0-9a-z.-]+) +(\d+) +(-?\d+) +(-?\d+)$', line) + if m is None: + raise Exception("Cannot parse pts listentries line '%s'."%(line)) + (name, afsid, owner, creator) = m.groups() + entries.append(name, int(afsid), int(owner), int(creator)) + l.wait() + exitcode = l.returncode + if not exitcode == 0: + raise Exception("pts listentries -users exited with non-zero exit code %d."%(exitcode)) + return entries + +class Krb: + def __init__(self, keytab, principal): + (fd_dummy, self.ccachefile) = tempfile.mkstemp(prefix='krb5cc') + os.environ['KRB5CCNAME'] = self.ccachefile + self.kinit(keytab, principal) + + def kinit(self, keytab, principal): + subprocess.check_call( ('kinit', '-t', keytab, principal) ) + def klist(self): + subprocess.check_call( ('klist') ) + def kdestroy(self): + if os.path.exists(self.ccachefile): + subprocess.check_call( ('kdestroy') ) + if os.path.exists(self.ccachefile): + os.unlink(self.ccachefile) + +def filter_common(a, b): + ids = a.by_id.keys() + for i in ids: + if i in b.by_id: + if a.by_id[i]['name'] == b.by_id[i]['name']: + #print "Common: %s (%d)"%(a.by_id[i]['name'], i) + a.del_id(i) + b.del_id(i) + else: + print >> sys.stderr, Excetption("ID %d has different names in our two databases ('%s' vs. '%s')."%(i, a.by_id[i]['name'], b.by_id[i]['name'])) + sys.exit(1) + + # just make sure there are not same names on both sides + # but with differend uids: + for n in a.by_name: + if n in b.by_name: + print >> sys.stderr, Excetption("Name %n has different IDs in our two databases ('%d' vs. '%d')."%(n, a.by_name[n]['id'], b.by_name[n]['id'])) + sys.exit(1) + +def filter_have(a): + # removing from the list means we keep the account and + # do not delete it later on. + names = a.by_name.keys() + for n in names: + if n == 'anonymous': # keep account, so remove from the have list + a.del_name(n) + continue + m = re.match('[0-9a-z]+$', n) + if not m: # weird name, probably has dots like weasel.admin etc. + a.del_name(n) + continue + +def remove_extra(have, ifownedby): + for name in have.by_name: + if have.by_name[name]['creator'] == ifownedby: + subprocess.check_call( ('pts', 'delete', name) ) + print "Deleted user %s(%d)."%(name, have.by_name[name]['id']) + else: + print >> sys.stderr, "Did not delete %s because it was not created by me(%d) but by %d."%(name, ifownedby, have.by_name[name]['creator']) + +def add_new(want): + #for name in want.by_name: + # subprocess.check_call( ('pts', 'createuser', '-name', name, '-id', '%d'%(want.by_name[name]['id'])) ) + # print "Added user %s(%d)."%(name, want.by_name[name]['id']) + names = [] + ids = [] + for name in want.by_name: + names.append(name) + ids.append('%d'%(want.by_name[name]['id'])) + + if len(names) == 0: return + + args = ['pts', 'createuser'] + for n in names: + args.append('-name') + args.append(n) + for i in ids: + args.append('-id') + args.append(i) + subprocess.check_call(args) + + +def do_accounts(): + want = load_expected() + have = load_existing() + + if not options.user in have.by_name: + print >> sys.stderr, "Cannot find our user, '%s', in pts listentries"%(options.user) + sys.exit(1) + me = have.by_name[options.user] + + filter_common(have, want) + filter_have(have) + # just for the sake of it, make sure 'want' does not have weird names either. + # this gets rid of a few accounts with underscores in them, like buildd_$ARCH + # but we might not care about them in AFS anyway + filter_have(want) + + remove_extra(have, me['id']) + add_new(want) + + created_some = len(want.by_id) > 0 + return created_some + +def do_scratchdir(d): + have = load_existing() + filter_have(have) + + if not os.path.isdir(d): + print >> sys.stderr, "Path '%s' is not a directory"%(d) + + for n in have.by_name: + tree = ( n[0], n[0:2] ) + + p = d + for t in tree: + p = os.path.join(p, t) + if not os.path.exists(p): os.mkdir(p) + + p = os.path.join(p, n) + if os.path.exists(p): continue + + print "Making directory %s"%(p) + os.mkdir(p) + subprocess.check_call(('fs', 'sa', '-dir', p, '-acl', n, 'all')) + + +parser = optparse.OptionParser() +parser.add_option("-p", "--principal", dest="principal", metavar="name", + help="Principal to auth as") +parser.add_option("-k", "--keytab", dest="keytab", metavar="file", + help="keytab file location") +parser.add_option("-P", "--PAGed", action="store_true", + help="already running in own PAG") +parser.add_option("-s", "--self", dest="user", metavar="ownafsuser", + help="This principal's AFS user") +parser.add_option("-d", "--dir", dest="scratchdir", action="append", + help="scratchdir to create directories in.") +parser.add_option("-D", "--dircheck-force", dest="dircheck", action="store_true", default=False, + help="Check if all user scratch dirs exist even if no new users were created") + +(options, args) = parser.parse_args() +if len(args) > 0: + parser.print_help() + sys.exit(1) + +if not options.PAGed: + #print >> sys.stderr, "running self in new PAG" + os.execlp('pagsh', 'pagsh', '-c', ' '.join(sys.argv)+" -P") + +if options.principal is None: + options.principal = "%s/admin"%( pwd.getpwuid(os.getuid())[0] ) +if options.keytab is None: + options.keytab = "/etc/userdir-ldap/keytab.%s"%(pwd.getpwuid(os.getuid())[0] ) +if options.user is None: + options.user = options.principal.replace('/', '.') +if options.scratchdir is None: + options.scratchdir = ['/afs/debian.org/scratch/eu/grnet'] + +k = None +try: + k = Krb(options.keytab, options.principal) + subprocess.check_call( ('aklog') ) + #k.klist() + #subprocess.check_call( ('tokens') ) + + created_some = do_accounts() + if created_some or options.dircheck: + for d in options.scratchdir: + do_scratchdir(d) +finally: + try: + subprocess.check_call( ('unlog') ) + except Exception, e: + print >> sys.stderr, "During unlog: %s"%(e) + pass + if k is not None: k.kdestroy() + + +# vim:set et: +# vim:set ts=3: +# vim:set shiftwidth=3: