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