3 # Copyright (c) 2010 Peter Palfrader
5 # reads a list of active accounts from /var/lib/misc/thishost/all-accounts.json
6 # and creates those accounts in AFS's protection database.
7 # Furthermore it creates per-user scratch directories in
8 # /afs/debian.org/scratch/eu/grnet (or whatever path is specified in a command
9 # line option), owned by that user.
22 import simplejson as json
24 import json # this better be pthon 2.6's json..
32 def append(self, name, idnumber, owner=None, creator=None):
33 if name in self.by_name: raise Exception("Name '%s' is not unique."%(name))
34 if idnumber in self.by_id: raise Exception("ID '%d' is not unique."%(idnumber))
36 h = { 'name': name, 'id': idnumber, 'owner': owner, 'creator': creator }
37 self.entries.append( h )
38 self.by_name[name] = h
39 self.by_id[idnumber] = h
44 del self.by_name[h['name']]
45 self.entries.remove(h)
47 def del_name(self, n):
48 self.del_id(self.by_name[n]['id'])
51 accountsfile = '/var/lib/misc/thishost/all-accounts.json'
53 if not os.path.isfile(accountsfile):
54 print >> sys.stderr, "Accountsfile %s not found."%(accountsfile)
55 accounts_json = open(accountsfile, 'r').read()
56 accounts = json.loads(accounts_json)
58 entries = UserEntries()
61 entries.append(a['uid'], a['uidNumber'])
65 entries = UserEntries()
66 l = subprocess.Popen(('pts', 'listentries', '-users'), stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=None)
68 l.stdout.readline() # headers
69 # Name ID Owner Creator
72 m = re.match('([0-9a-z.-]+) +(\d+) +(-?\d+) +(-?\d+)$', line)
74 raise Exception("Cannot parse pts listentries line '%s'."%(line))
75 (name, afsid, owner, creator) = m.groups()
76 entries.append(name, int(afsid), int(owner), int(creator))
78 exitcode = l.returncode
80 raise Exception("pts listentries -users exited with non-zero exit code %d."%(exitcode))
84 def __init__(self, keytab, principal):
85 (fd_dummy, self.ccachefile) = tempfile.mkstemp(prefix='krb5cc')
86 os.environ['KRB5CCNAME'] = self.ccachefile
87 self.kinit(keytab, principal)
89 def kinit(self, keytab, principal):
90 subprocess.check_call( ('kinit', '-t', keytab, principal) )
92 subprocess.check_call( ('klist') )
94 if os.path.exists(self.ccachefile):
95 subprocess.check_call( ('kdestroy') )
96 if os.path.exists(self.ccachefile):
97 os.unlink(self.ccachefile)
99 def filter_common(a, b):
103 if a.by_id[i]['name'] == b.by_id[i]['name']:
104 #print "Common: %s (%d)"%(a.by_id[i]['name'], i)
108 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']))
111 # just make sure there are not same names on both sides
112 # but with differend uids:
115 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']))
119 # removing from the list means we keep the account and
120 # do not delete it later on.
121 names = a.by_name.keys()
123 if n == 'anonymous': # keep account, so remove from the have list
126 m = re.match('[0-9a-z-]+$', n)
127 if not m: # weird name, probably has dots like weasel.admin etc.
131 def remove_extra(have, ifownedby):
132 for name in have.by_name:
133 if have.by_name[name]['creator'] == ifownedby:
134 subprocess.check_call( ('pts', 'delete', name) )
135 print "Deleted user %s(%d)."%(name, have.by_name[name]['id'])
137 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'])
140 #for name in want.by_name:
141 # subprocess.check_call( ('pts', 'createuser', '-name', name, '-id', '%d'%(want.by_name[name]['id'])) )
142 # print "Added user %s(%d)."%(name, want.by_name[name]['id'])
145 for name in want.by_name:
147 ids.append('%d'%(want.by_name[name]['id']))
149 if len(names) == 0: return
151 args = ['pts', 'createuser']
158 subprocess.check_call(args)
162 want = load_expected()
163 have = load_existing()
165 if not options.user in have.by_name:
166 print >> sys.stderr, "Cannot find our user, '%s', in pts listentries"%(options.user)
168 me = have.by_name[options.user]
170 filter_common(have, want)
172 # just for the sake of it, make sure 'want' does not have weird names either.
173 # this gets rid of a few accounts with underscores in them, like buildd_$ARCH
174 # but we might not care about them in AFS anyway
177 remove_extra(have, me['id'])
180 created_some = len(want.by_id) > 0
183 def do_scratchdir(d):
184 have = load_existing()
187 if not os.path.isdir(d):
188 print >> sys.stderr, "Path '%s' is not a directory"%(d)
190 for n in have.by_name:
191 tree = ( n[0], n[0:2] )
195 p = os.path.join(p, t)
196 if not os.path.exists(p): os.mkdir(p)
198 p = os.path.join(p, n)
199 if os.path.exists(p): continue
201 print "Making directory %s"%(p)
203 subprocess.check_call(('fs', 'sa', '-dir', p, '-acl', n, 'all'))
206 parser = optparse.OptionParser()
207 parser.add_option("-p", "--principal", dest="principal", metavar="name",
208 help="Principal to auth as")
209 parser.add_option("-k", "--keytab", dest="keytab", metavar="file",
210 help="keytab file location")
211 parser.add_option("-P", "--PAGed", action="store_true",
212 help="already running in own PAG")
213 parser.add_option("-s", "--self", dest="user", metavar="ownafsuser",
214 help="This principal's AFS user")
215 parser.add_option("-d", "--dir", dest="scratchdir", action="append",
216 help="scratchdir to create directories in.")
217 parser.add_option("-D", "--dircheck-force", dest="dircheck", action="store_true", default=False,
218 help="Check if all user scratch dirs exist even if no new users were created")
220 (options, args) = parser.parse_args()
225 if not options.PAGed:
226 #print >> sys.stderr, "running self in new PAG"
227 os.execlp('pagsh', 'pagsh', '-c', ' '.join(sys.argv)+" -P")
229 if options.principal is None:
230 options.principal = "%s/admin"%( pwd.getpwuid(os.getuid())[0] )
231 if options.keytab is None:
232 options.keytab = "/etc/userdir-ldap/keytab.%s"%(pwd.getpwuid(os.getuid())[0] )
233 if options.user is None:
234 options.user = options.principal.replace('/', '.')
235 if options.scratchdir is None:
236 options.scratchdir = ['/afs/debian.org/scratch/eu/grnet']
240 k = Krb(options.keytab, options.principal)
241 subprocess.check_call( ('aklog') )
243 #subprocess.check_call( ('tokens') )
245 created_some = do_accounts()
246 if created_some or options.dircheck:
247 for d in options.scratchdir:
251 subprocess.check_call( ('unlog') )
253 print >> sys.stderr, "During unlog: %s"%(e)
255 if k is not None: k.kdestroy()
260 # vim:set shiftwidth=3: