seperate bsmtp from dns
[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,HomePrefix):
358   F = None;
359   try:
360    F = open(File + ".tmp","w");
361    
362    # Fetch all the users
363    global PasswdAttrs;
364    if PasswdAttrs == None:
365       raise "No Users";
366
367    # Write out the zone file entry for each user
368    for x in PasswdAttrs:
369       if x[1].has_key("dnszoneentry") == 0:
370          continue;
371       try:
372          F.write("; %s\n"%(EmailAddress(x)));
373          for z in x[1]["dnszoneentry"]:
374             Split = string.split(string.lower(z));
375             if string.lower(Split[1]) == 'in':
376                for y in range(0,len(Split)):
377                   if Split[y] == "$":
378                      Split[y] = "\n\t";
379                Line = string.join(Split," ") + "\n";
380                F.write(Line);
381                
382                Host = Split[0] + DNSZone;
383                if BSMTPCheck.match(Line) != None:
384                    F.write("; Has BSMTP\n");
385                                
386                # Write some identification information
387                if string.lower(Split[2]) == "a":
388                   Line = "%s IN TXT \"%s\"\n"%(Split[0],EmailAddress(x));
389                   for y in x[1]["keyfingerprint"]:
390                      Line = Line + "%s IN TXT \"PGP %s\"\n"%(Split[0],FormatPGPKey(y));
391                   F.write(Line);
392             else:
393                Line = "; Err %s"%(str(Split));
394                F.write(Line);
395
396          F.write("\n");
397       except:
398          F.write("; Errors\n");
399          pass;
400       
401   # Oops, something unspeakable happened.
402   except:
403    Die(File,F,None);
404    raise;
405   Done(File,F,None);
406
407 # Generate the BSMTP file
408 def GenBSMTP(l,File):
409   F = None;
410   try:
411    F = open(File + ".tmp","w");
412    
413    # Fetch all the users
414    global PasswdAttrs;
415    if PasswdAttrs == None:
416       raise "No Users";
417
418    # Write out the zone file entry for each user
419    for x in PasswdAttrs:
420       if x[1].has_key("dnszoneentry") == 0:
421          continue;
422       try:
423          for z in x[1]["dnszoneentry"]:
424             Split = string.split(string.lower(z));
425             if string.lower(Split[1]) == 'in':
426                for y in range(0,len(Split)):
427                   if Split[y] == "$":
428                      Split[y] = "\n\t";
429                Line = string.join(Split," ") + "\n";
430                
431                Host = Split[0] + DNSZone;
432                if BSMTPCheck.match(Line) != None:
433                    F.write("%s: user=%s group=Debian file=%s%s/bsmtp/%s\n"%(Host,
434                                GetAttr(x,"uid"),HomePrefix,GetAttr(x,"uid"),Host));
435                                
436          F.write("\n");
437       except:
438          F.write("; Errors\n");
439          pass;
440       
441   # Oops, something unspeakable happened.
442   except:
443    Die(File,F,None);
444    raise;
445   Done(File,F,None);
446
447 # Generate the shadow list
448 def GenSSHKnown(l,File):
449   F = None;
450   try:
451    OldMask = os.umask(0022);
452    F = open(File + ".tmp","w",0644);
453    os.umask(OldMask);
454
455    # Fetch all the hosts
456    HostKeys = l.search_s(HostBaseDn,ldap.SCOPE_ONELEVEL,"sshrsahostkey=*",\
457                 ["hostname","sshrsahostkey"]);
458    
459    if HostKeys == None:
460       raise "No Hosts";
461
462    for x in HostKeys:
463       if x[1].has_key("hostname") == 0 or \
464          x[1].has_key("sshrsahostkey") == 0:
465          continue;
466       Host = GetAttr(x,"hostname");
467       SHost = string.find(Host,".");
468       for I in x[1]["sshrsahostkey"]:
469          if SHost == None:
470             Line = "%s,%s %s" %(Host,socket.gethostbyname(Host),I);
471          else:
472             Line = "%s,%s,%s %s" %(Host,Host[0:SHost],socket.gethostbyname(Host),I);
473          Line = Sanitize(Line) + "\n";
474          F.write(Line);
475   # Oops, something unspeakable happened.
476   except:
477    Die(File,F,None);
478    raise;
479   Done(File,F,None);
480
481
482 # Connect to the ldap server
483 l = ldap.open(LDAPServer);
484 F = open(PassDir+"/pass-"+pwd.getpwuid(os.getuid())[0],"r");
485 Pass = string.split(string.strip(F.readline())," ");
486 F.close();
487 l.simple_bind_s("uid="+Pass[0]+","+BaseDn,Pass[1]);
488
489 # Fetch all the groups
490 GroupIDMap = {};
491 Attrs = l.search_s(BaseDn,ldap.SCOPE_ONELEVEL,"gid=*",\
492                   ["gid","gidnumber"]);
493
494 # Generate the GroupMap and GroupIDMap
495 for x in Attrs:
496    if x[1].has_key("gidnumber") == 0:
497       continue;
498    GroupIDMap[x[1]["gid"][0]] = int(x[1]["gidnumber"][0]);
499
500 # Fetch all the users
501 PasswdAttrs = l.search_s(BaseDn,ldap.SCOPE_ONELEVEL,"uid=*",\
502                 ["uid","uidnumber","gidnumber","supplementarygid",\
503                  "gecos","loginshell","userpassword","shadowlastchange",\
504                  "shadowmin","shadowmax","shadowwarning","shadowinactive",
505                  "shadowexpire","emailforward","latitude","longitude",\
506                  "allowedhosts","sshrsaauthkey","dnszoneentry","cn","sn",\
507                  "keyfingerprint","privatesub"]);
508
509 # Open the control file
510 if len(sys.argv) == 1:
511    F = open(GenerateConf,"r");
512 else:
513    F = open(sys.argv[1],"r")
514
515 # Generate global things
516 GlobalDir = GenerateDir+"/";
517 GenSSHShadow(l,GlobalDir+"ssh-rsa-shadow");
518 GenAllForward(l,GlobalDir+"mail-forward.cdb");
519 GenMarkers(l,GlobalDir+"markers");
520 GenPrivate(l,GlobalDir+"debian-private");
521 GenSSHKnown(l,GlobalDir+"ssh_known_hosts");
522
523 # Compatibility.
524 GenForward(l,GlobalDir+"forward-alias");
525    
526 while(1):
527    Line = F.readline();
528    if Line == "":
529       break;
530    Line = string.strip(Line);
531    if Line == "":
532       continue;
533    if Line[0] == '#':
534       continue;
535
536    Split = string.split(Line," ");
537    OutDir = GenerateDir + '/' + Split[0] + '/';
538    try: os.mkdir(OutDir);
539    except: pass;
540
541    # Get the group list and convert any named groups to numerics
542    GroupList = {};
543    ExtraList = {};
544    for I in Split[2:]:
545       if I[0] == '[':
546          ExtraList[I] = None;
547          continue;
548       GroupList[I] = None;
549       if GroupIDMap.has_key(I):
550          GroupList[str(GroupIDMap[I])] = None;
551
552    global Allowed,CurrentHost;
553    Allowed = GroupList;
554    CurrentHost = Split[0];
555
556    sys.stdout.flush();
557    GenPasswd(l,OutDir+"passwd",Split[1]);
558    sys.stdout.flush();
559    GenGroup(l,OutDir+"group");
560    GenShadow(l,OutDir+"shadow");
561         
562    # Link in global things   
563    DoLink(GlobalDir,OutDir,"ssh-rsa-shadow");
564    DoLink(GlobalDir,OutDir,"markers");
565    DoLink(GlobalDir,OutDir,"mail-forward.cdb");
566    DoLink(GlobalDir,OutDir,"ssh_known_hosts");
567
568    # Compatibility.
569    DoLink(GlobalDir,OutDir,"forward-alias");
570
571    if ExtraList.has_key("[DNS]"):
572       GenDNS(l,OutDir+"dns-zone",Split[1]);
573       
574    if ExtraList.has_key("[BSMTP]"):
575       GenBSMTP(l,OutDir+"bsmtp");
576
577    if ExtraList.has_key("[PRIVATE]"):
578       DoLink(GlobalDir,OutDir,"debian-private");
579