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