Lowercase UIN
[mirror/userdir-ldap.git] / ud-generate
1 #!/usr/bin/env python
2 # -*- mode: python -*-
3 # Generates passwd, shadow and group files from the ldap directory.
4
5 import string, re, time, ldap, getopt, sys, os, pwd, posix;
6 from userdir_ldap import *;
7
8 PasswdAttrs = None;
9 GroupIDMap = {};
10 Allowed = None;
11 CurrentHost = "";
12
13 def Sanitize(Str):
14   return string.translate(Str,string.maketrans("\n\r\t","$$$"));
15
16 def DoLink(From,To,File):
17    try: posix.remove(To+File);
18    except: pass;
19    posix.link(From+File,To+File);
20
21 # See if this user is in the group list
22 def IsInGroup(DnRecord):
23   global Allowed,CurrentHost;
24   if Allowed == None:
25      return 1;
26
27   # See if the primary group is in the list
28   if Allowed.has_key(GetAttr(DnRecord,"gidnumber")) != 0:
29      return 1;
30
31   # Check the host based ACL
32   if DnRecord[1].has_key("allowedhosts") != 0:
33      for I in DnRecord[1]["allowedhosts"]:
34         if CurrentHost == I:
35            return 1;
36
37   # See if there are supplementary groups
38   if DnRecord[1].has_key("supplementarygid") == 0:
39      return 0;
40
41   # Check the supplementary groups
42   for I in DnRecord[1]["supplementarygid"]:
43      if Allowed.has_key(I):
44         return 1;
45   return 0;
46
47 def Die(File,F,Fdb):
48    if F != None:
49       F.close();
50    if Fdb != None:
51       Fdb.close();
52    try: os.remove(File + ".tmp");
53    except: pass;
54    try: os.remove(File + ".tdb.tmp");
55    except: pass;
56
57 def Done(File,F,Fdb):
58   if F != None:
59     F.close();
60     os.rename(File + ".tmp",File);
61   if Fdb != None:
62     Fdb.close();
63     os.rename(File + ".tdb.tmp",File+".tdb");
64   
65 # Generate the password list
66 def GenPasswd(l,File,HomePrefix):
67   F = None;
68   Fdb = None;
69   try:
70    F = open(File + ".tmp","w");
71    Fdb = open(File + ".tdb.tmp","w");
72
73    # Fetch all the users
74    global PasswdAttrs;
75    if PasswdAttrs == None:
76       raise "No Users";
77
78    I = 0;
79    for x in PasswdAttrs:
80       if x[1].has_key("uidnumber") == 0 or IsInGroup(x) == 0:
81          continue;
82
83       # Do not let people try to buffer overflow some busted passwd parser.
84       if len(GetAttr(x,"gecos")) > 100 or len(GetAttr(x,"loginshell")) > 50:
85          continue;
86
87       Line = "%s:x:%s:%s:%s:%s%s:%s\n" % (GetAttr(x,"uid"),\
88               GetAttr(x,"uidnumber"),GetAttr(x,"gidnumber"),\
89               GetAttr(x,"gecos"),HomePrefix,GetAttr(x,"uid"),\
90               GetAttr(x,"loginshell"));
91       F.write(Line);
92       Fdb.write("0%u %s" % (I,Line));
93       Fdb.write(".%s %s" % (GetAttr(x,"uid"),Line));
94       Fdb.write("=%s %s" % (GetAttr(x,"uidnumber"),Line));
95       I = I + 1;
96
97   # Oops, something unspeakable happened.
98   except:
99    Die(File,F,Fdb);
100    raise;
101   Done(File,F,Fdb);
102
103 # Generate the shadow list
104 def GenShadow(l,File):
105   F = None;
106   Fdb = None;
107   try:
108    OldMask = os.umask(0077);
109    F = open(File + ".tmp","w",0600);
110    Fdb = open(File + ".tdb.tmp","w",0600);
111    os.umask(OldMask);
112
113    # Fetch all the users
114    global PasswdAttrs;
115    if PasswdAttrs == None:
116       raise "No Users";
117
118    I = 0;
119    for x in PasswdAttrs:
120       if x[1].has_key("uidnumber") == 0 or IsInGroup(x) == 0:
121          continue;
122          
123       Pass = GetAttr(x,"userpassword");
124       if Pass[0:7] != "{crypt}" or len(Pass) > 50:
125          Pass = '*';
126       else:
127          Pass = Pass[7:];
128       Line = "%s:%s:%s:%s:%s:%s:%s:%s:" % (GetAttr(x,"uid"),\
129               Pass,GetAttr(x,"shadowlastchange"),\
130               GetAttr(x,"shadowmin"),GetAttr(x,"shadowmax"),\
131               GetAttr(x,"shadowwarning"),GetAttr(x,"shadowinactive"),\
132               GetAttr(x,"shadowexpire"));
133       Line = Sanitize(Line) + "\n";
134       F.write(Line);
135       Fdb.write("0%u %s" % (I,Line));
136       Fdb.write(".%s %s" % (GetAttr(x,"uid"),Line));
137       I = I + 1;
138
139   # Oops, something unspeakable happened.
140   except:
141    Die(File,F,Fdb);
142    raise;
143   Done(File,F,Fdb);
144
145 # Generate the shadow list
146 def GenSSHShadow(l,File):
147   F = None;
148   Fdb = None;
149   try:
150    OldMask = os.umask(0077);
151    F = open(File + ".tmp","w",0600);
152    Fdb = os.popen("cdbmake %s.cdb %s.cdb.tmp"%(File,File),"w");
153    os.umask(OldMask);
154
155    # Fetch all the users
156    global PasswdAttrs;
157    if PasswdAttrs == None:
158       raise "No Users";
159
160    I = 0;
161    for x in PasswdAttrs:
162       if x[1].has_key("uidnumber") == 0 or \
163          x[1].has_key("sshrsaauthkey") == 0:
164          continue;
165       for I in x[1]["sshrsaauthkey"]:
166          User = GetAttr(x,"uid");
167          Line = "%s: %s" %(User,I);
168          Line = Sanitize(Line) + "\n";
169          F.write(Line);
170          Fdb.write("+%d,%d:%s->%s\n"%(len(User),len(I),User,I));
171    Fdb.write("\n");
172   # Oops, something unspeakable happened.
173   except:
174    Die(File,F,Fdb);
175    raise;
176   if Fdb.close() != None:
177      raise "cdbmake gave an error";
178   Done(File,F,None);
179
180 # Generate the group list
181 def GenGroup(l,File):
182   F = None;
183   Fdb = None;
184   try:
185    F = open(File + ".tmp","w");
186    Fdb = open(File + ".tdb.tmp","w");
187
188    # Generate the GroupMap
189    GroupMap = {};
190    for x in GroupIDMap.keys():
191       GroupMap[x] = [];
192       
193    # Fetch all the users
194    global PasswdAttrs;
195    if PasswdAttrs == None:
196       raise "No Users";
197
198    # Sort them into a list of groups having a set of users
199    for x in PasswdAttrs:
200       if x[1].has_key("uidnumber") == 0 or IsInGroup(x) == 0:
201          continue;
202       if x[1].has_key("supplementarygid") == 0:
203          continue;
204          
205       for I in x[1]["supplementarygid"]:
206          if GroupMap.has_key(I):
207             GroupMap[I].append(GetAttr(x,"uid"));
208          else:
209             print "Group does not exist ",I,"but",GetAttr(x,"uid"),"is in it";
210             
211    # Output the group file.
212    Counter = 0; 
213    for x in GroupMap.keys():
214       if GroupIDMap.has_key(x) == 0:
215          continue;
216       Line = "%s:x:%u:" % (x,GroupIDMap[x]);
217       Comma = '';
218       for I in GroupMap[x]:
219         Line = Line + ("%s%s" % (Comma,I));
220         Comma = ',';
221       Line = Sanitize(Line) + "\n";
222       F.write(Line);
223       Fdb.write("0%u %s" % (Counter,Line));
224       Fdb.write(".%s %s" % (x,Line));
225       Fdb.write("=%u %s" % (GroupIDMap[x],Line));
226       Counter = Counter + 1;
227       
228   # Oops, something unspeakable happened.
229   except:
230    Die(File,F,Fdb);
231    raise;
232   Done(File,F,Fdb);
233
234 # Generate the email forwarding list
235 def GenForward(l,File):
236   F = None;
237   Fdb = None;
238   try:
239    OldMask = os.umask(0022);
240    F = open(File + ".tmp","w",0644);
241    os.umask(OldMask);
242
243    # Fetch all the users
244    global PasswdAttrs;
245    if PasswdAttrs == None:
246       raise "No Users";
247
248    # Write out the email address for each user
249    for x in PasswdAttrs:
250       if x[1].has_key("emailforward") == 0 or IsInGroup(x) == 0:
251          continue;
252       
253       # Do not allow people to try to buffer overflow busted parsers
254       if len(GetAttr(x,"emailforward")) > 200:
255          continue;
256
257       Line = "%s: %s" % (GetAttr(x,"uid"),GetAttr(x,"emailforward"));
258       Line = Sanitize(Line) + "\n";
259       F.write(Line);
260       
261   # Oops, something unspeakable happened.
262   except:
263    Die(File,F,Fdb);
264    raise;
265   Done(File,F,Fdb);
266
267 def GenAllForward(l,File):
268   Fdb = None;
269   try:
270    OldMask = os.umask(0022);
271    Fdb = os.popen("cdbmake %s %s.tmp"%(File,File),"w");
272    os.umask(OldMask);
273
274    # Fetch all the users
275    global PasswdAttrs;
276    if PasswdAttrs == None:
277       raise "No Users";
278
279    # Write out the email address for each user
280    for x in PasswdAttrs:
281       if x[1].has_key("emailforward") == 0:
282          continue;
283       
284       # Do not allow people to try to buffer overflow busted parsers
285       Forward = GetAttr(x,"emailforward");
286       if len(Forward) > 200:
287          continue;
288
289       User = GetAttr(x,"uid");
290       Fdb.write("+%d,%d:%s->%s\n"%(len(User),len(Forward),User,Forward));
291    Fdb.write("\n");
292   # Oops, something unspeakable happened.
293   except:
294     Fdb.close();
295     raise;
296   if Fdb.close() != None:
297     raise "cdbmake gave an error";
298
299 # Generate the anon XEarth marker file 
300 def GenMarkers(l,File):
301   F = None;
302   Fdb = None;
303   try:
304    F = open(File + ".tmp","w");
305    Fdb = None;
306
307    # Fetch all the users
308    global PasswdAttrs;
309    if PasswdAttrs == None:
310       raise "No Users";
311
312    # Write out the position for each user
313    for x in PasswdAttrs:
314       if x[1].has_key("latitude") == 0 or x[1].has_key("longitude") == 0:
315          continue;       
316       try:
317          Line = "%8s %8s \"\""%(DecDegree(GetAttr(x,"latitude"),1),DecDegree(GetAttr(x,"longitude"),1));
318          Line = Sanitize(Line) + "\n";
319          F.write(Line);
320       except:
321          pass;
322       
323   # Oops, something unspeakable happened.
324   except:
325    Die(File,F,Fdb);
326    raise;
327   Done(File,F,Fdb);
328
329 # Generate the debian-private subscription list
330 def GenPrivate(l,File):
331   F = None;
332   Fdb = None;
333   try:
334    F = open(File + ".tmp","w");
335    Fdb = None;
336
337    # Fetch all the users
338    global PasswdAttrs;
339    if PasswdAttrs == None:
340       raise "No Users";
341
342    # Write out the position for each user
343    for x in PasswdAttrs:
344       if x[1].has_key("privatesub") == 0:
345          continue;
346
347       # If the account is locked, do not write it
348       if (string.find(GetAttr(x,"userpassword"),"*LK*")  != -1):
349          continue;
350
351       # If the account has no PGP key, do not write it
352       if x[1].has_key("keyfingerprint") == 0:
353          continue;
354
355       # Must be in the Debian group (yuk, hard coded for now)
356       if GetAttr(x,"gidnumber") != "800":
357          continue;
358
359       try:
360          Line = "%s"%(GetAttr(x,"privatesub"));
361          Line = Sanitize(Line) + "\n";
362          F.write(Line);
363       except:
364          pass;
365       
366   # Oops, something unspeakable happened.
367   except:
368    Die(File,F,Fdb);
369    raise;
370   Done(File,F,Fdb);
371
372 # Generate the DNS Zone file
373 def GenDNS(l,File):
374   F = None;
375   Fdb = None;
376   try:
377    F = open(File + ".tmp","w");
378    Fdb = None;
379
380    # Fetch all the users
381    global PasswdAttrs;
382    if PasswdAttrs == None:
383       raise "No Users";
384
385    # Write out the zone file entry for each user
386    for x in PasswdAttrs:
387       if x[1].has_key("dnszoneentry") == 0:
388          continue;
389       try:
390          F.write("; %s\n"%(EmailAddress(x)));
391          for z in x[1]["dnszoneentry"]:
392             Split = string.split(string.lower(z));
393             if string.lower(Split[1]) == 'in':
394                for y in range(0,len(Split)):
395                   if Split[y] == "$":
396                      Split[y] = "\n\t";
397                Line = string.join(Split," ") + "\n";
398                F.write(Line);
399                
400                # Write some identication information
401                if string.lower(Split[2]) != "cname":
402                   Line = "%s IN TXT \"%s\"\n"%(Split[0],EmailAddress(x));
403                   for y in x[1]["keyfingerprint"]:
404                      Line = Line + "%s IN TXT \"PGP %s\"\n"%(Split[0],FormatPGPKey(y));
405                   F.write(Line);
406             else:
407                Line = "; Err %s"%(str(Split));
408                F.write(Line);
409
410          F.write("\n");
411       except:
412          pass;
413       
414   # Oops, something unspeakable happened.
415   except:
416    Die(File,F,Fdb);
417    raise;
418   Done(File,F,Fdb);
419
420 # Connect to the ldap server
421 l = ldap.open(LDAPServer);
422 F = open(PassDir+"/pass-"+pwd.getpwuid(os.getuid())[0],"r");
423 Pass = string.split(string.strip(F.readline())," ");
424 F.close();
425 l.simple_bind_s("uid="+Pass[0]+","+BaseDn,Pass[1]);
426
427 # Fetch all the groups
428 GroupIDMap = {};
429 Attrs = l.search_s(BaseDn,ldap.SCOPE_ONELEVEL,"gid=*",\
430                   ["gid","gidnumber"]);
431
432 # Generate the GroupMap and GroupIDMap
433 for x in Attrs:
434    if x[1].has_key("gidnumber") == 0:
435       continue;
436    GroupIDMap[x[1]["gid"][0]] = int(x[1]["gidnumber"][0]);
437
438 # Fetch all the users
439 PasswdAttrs = l.search_s(BaseDn,ldap.SCOPE_ONELEVEL,"uid=*",\
440                 ["uid","uidnumber","gidnumber","supplementarygid",\
441                  "gecos","loginshell","userpassword","shadowlastchange",\
442                  "shadowmin","shadowmax","shadowwarning","shadowinactive",
443                  "shadowexpire","emailforward","latitude","longitude",\
444                  "allowedhosts","sshrsaauthkey","dnszoneentry","cn","sn",\
445                  "keyfingerprint","privatesub"]);
446
447 # Open the control file
448 if len(sys.argv) == 1:
449    F = open(GenerateConf,"r");
450 else:
451    F = open(sys.argv[1],"r")
452
453 # Generate global things
454 GlobalDir = GenerateDir+"/";
455 GenSSHShadow(l,GlobalDir+"ssh-rsa-shadow");
456 GenAllForward(l,GlobalDir+"mail-forward.cdb");
457 GenMarkers(l,GlobalDir+"markers");
458 GenDNS(l,GlobalDir+"dns-zone");
459 GenPrivate(l,GlobalDir+"debian-private");
460
461 # Compatibility.
462 GenForward(l,GlobalDir+"forward-alias");
463    
464 while(1):
465    Line = F.readline();
466    if Line == "":
467       break;
468    Line = string.strip(Line);
469    if Line == "":
470       continue;
471    if Line[0] == '#':
472       continue;
473
474    Split = string.split(Line," ");
475    OutDir = GenerateDir + '/' + Split[0] + '/';
476    try: os.mkdir(OutDir);
477    except: pass;
478
479    # Get the group list and convert any named groups to numerics
480    GroupList = {};
481    ExtraList = {};
482    for I in Split[2:]:
483       if I[0] == '[':
484          ExtraList[I] = None;
485          continue;
486       GroupList[I] = None;
487       if GroupIDMap.has_key(I):
488          GroupList[str(GroupIDMap[I])] = None;
489
490    global Allowed,CurrentHost;
491    Allowed = GroupList;
492    CurrentHost = Split[0];
493
494    sys.stdout.flush();
495    GenPasswd(l,OutDir+"passwd",Split[1]);
496    sys.stdout.flush();
497    GenGroup(l,OutDir+"group");
498    GenShadow(l,OutDir+"shadow");
499         
500    # Link in global things   
501    DoLink(GlobalDir,OutDir,"ssh-rsa-shadow");
502    DoLink(GlobalDir,OutDir,"ssh-rsa-shadow.cdb");
503    DoLink(GlobalDir,OutDir,"markers");
504    DoLink(GlobalDir,OutDir,"mail-forward.cdb");
505
506    # Compatibility.
507    DoLink(GlobalDir,OutDir,"forward-alias");
508
509    if ExtraList.has_key("[DNS]"):
510       DoLink(GlobalDir,OutDir,"dns-zone");
511       
512    if ExtraList.has_key("[PRIVATE]"):
513       DoLink(GlobalDir,OutDir,"debian-private");