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 if not '__author__' in json.__dict__:
23 sys.stderr.write("Warning: This is probably the wrong json module. We want python 2.6's json\n")
24 sys.stderr.write("module, or simplejson on python 2.5. Let's see if/how stuff blows up.\n")
25 import simplejson as json
33 def append(self, name, idnumber, owner=None, creator=None):
34 if name in self.by_name: raise Exception("Name '%s' is not unique."%(name))
35 if idnumber in self.by_id: raise Exception("ID '%d' is not unique."%(idnumber))
37 h = { 'name': name, 'id': idnumber, 'owner': owner, 'creator': creator }
38 self.entries.append( h )
39 self.by_name[name] = h
40 self.by_id[idnumber] = h
45 del self.by_name[h['name']]
46 self.entries.remove(h)
48 def del_name(self, n):
49 self.del_id(self.by_name[n]['id'])
52 accountsfile = '/var/lib/misc/thishost/all-accounts.json'
54 if not os.path.isfile(accountsfile):
55 print >> sys.stderr, "Accountsfile %s not found."%(accountsfile)
56 accounts_json = open(accountsfile, 'r').read()
57 accounts = json.loads(accounts_json)
59 entries = UserEntries()
62 entries.append(a['uid'], a['uidNumber'])
66 entries = UserEntries()
67 l = subprocess.Popen(('pts', 'listentries', '-users'), stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=None)
69 l.stdout.readline() # headers
70 # Name ID Owner Creator
73 m = re.match('([0-9a-z.-]+) +(\d+) +(-?\d+) +(-?\d+)$', line)
75 raise Exception("Cannot parse pts listentries line '%s'."%(line))
76 (name, afsid, owner, creator) = m.groups()
77 entries.append(name, int(afsid), int(owner), int(creator))
79 exitcode = l.returncode
81 raise Exception("pts listentries -users exited with non-zero exit code %d."%(exitcode))
85 def __init__(self, keytab, principal):
86 (fd_dummy, self.ccachefile) = tempfile.mkstemp(prefix='krb5cc')
87 os.environ['KRB5CCNAME'] = self.ccachefile
88 self.kinit(keytab, principal)
90 def kinit(self, keytab, principal):
91 subprocess.check_call( ('kinit', '-t', keytab, principal) )
93 subprocess.check_call( ('klist') )
95 if os.path.exists(self.ccachefile):
96 subprocess.check_call( ('kdestroy') )
97 if os.path.exists(self.ccachefile):
98 os.unlink(self.ccachefile)
100 def filter_common(a, b):
104 if a.by_id[i]['name'] == b.by_id[i]['name']:
105 #print "Common: %s (%d)"%(a.by_id[i]['name'], i)
109 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']))
112 # just make sure there are not same names on both sides
113 # but with differend uids:
116 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']))
120 # removing from the list means we keep the account and
121 # do not delete it later on.
122 names = a.by_name.keys()
124 if n == 'anonymous': # keep account, so remove from the have list
127 m = re.match('[0-9a-z]+$', n)
128 if not m: # weird name, probably has dots like weasel.admin etc.
132 def remove_extra(have, ifownedby):
133 for name in have.by_name:
134 if have.by_name[name]['creator'] == ifownedby:
135 subprocess.check_call( ('pts', 'delete', name) )
136 print "Deleted user %s(%d)."%(name, have.by_name[name]['id'])
138 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'])
141 #for name in want.by_name:
142 # subprocess.check_call( ('pts', 'createuser', '-name', name, '-id', '%d'%(want.by_name[name]['id'])) )
143 # print "Added user %s(%d)."%(name, want.by_name[name]['id'])
146 for name in want.by_name:
148 ids.append('%d'%(want.by_name[name]['id']))
150 if len(names) == 0: return
152 args = ['pts', 'createuser']
159 subprocess.check_call(args)
163 want = load_expected()
164 have = load_existing()
166 if not options.user in have.by_name:
167 print >> sys.stderr, "Cannot find our user, '%s', in pts listentries"%(options.user)
169 me = have.by_name[options.user]
171 filter_common(have, want)
173 # just for the sake of it, make sure 'want' does not have weird names either.
174 # this gets rid of a few accounts with underscores in them, like buildd_$ARCH
175 # but we might not care about them in AFS anyway
178 remove_extra(have, me['id'])
181 created_some = len(want.by_id) > 0
184 def do_scratchdir(d):
185 have = load_existing()
188 if not os.path.isdir(d):
189 print >> sys.stderr, "Path '%s' is not a directory"%(d)
191 for n in have.by_name:
192 tree = ( n[0], n[0:2] )
196 p = os.path.join(p, t)
197 if not os.path.exists(p): os.mkdir(p)
199 p = os.path.join(p, n)
200 if os.path.exists(p): continue
202 print "Making directory %s"%(p)
204 subprocess.check_call(('fs', 'sa', '-dir', p, '-acl', n, 'all'))
207 parser = optparse.OptionParser()
208 parser.add_option("-p", "--principal", dest="principal", metavar="name",
209 help="Principal to auth as")
210 parser.add_option("-k", "--keytab", dest="keytab", metavar="file",
211 help="keytab file location")
212 parser.add_option("-P", "--PAGed", action="store_true",
213 help="already running in own PAG")
214 parser.add_option("-s", "--self", dest="user", metavar="ownafsuser",
215 help="This principal's AFS user")
216 parser.add_option("-d", "--dir", dest="scratchdir", action="append",
217 help="scratchdir to create directories in.")
218 parser.add_option("-D", "--dircheck-force", dest="dircheck", action="store_true", default=False,
219 help="Check if all user scratch dirs exist even if no new users were created")
221 (options, args) = parser.parse_args()
226 if not options.PAGed:
227 #print >> sys.stderr, "running self in new PAG"
228 os.execlp('pagsh', 'pagsh', '-c', ' '.join(sys.argv)+" -P")
230 if options.principal is None:
231 options.principal = "%s/admin"%( pwd.getpwuid(os.getuid())[0] )
232 if options.keytab is None:
233 options.keytab = "/etc/userdir-ldap/keytab.%s"%(pwd.getpwuid(os.getuid())[0] )
234 if options.user is None:
235 options.user = options.principal.replace('/', '.')
236 if options.scratchdir is None:
237 options.scratchdir = ['/afs/debian.org/scratch/eu/grnet']
241 k = Krb(options.keytab, options.principal)
242 subprocess.check_call( ('aklog') )
244 #subprocess.check_call( ('tokens') )
246 created_some = do_accounts()
247 if created_some or options.dircheck:
248 for d in options.scratchdir:
252 subprocess.check_call( ('unlog') )
254 print >> sys.stderr, "During unlog: %s"%(e)
256 if k is not None: k.kdestroy()
261 # vim:set shiftwidth=3: