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