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