Do not disable mail just because the account is locked.
[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 #   Copyright (c) 2000-2001  Jason Gunthorpe <jgg@debian.org>
6 #   Copyright (c) 2003-2004  James Troup <troup@debian.org>
7 #   Copyright (c) 2004-2005,7  Joey Schulze <joey@infodrom.org>
8 #   Copyright (c) 2001-2007  Ryan Murray <rmurray@debian.org>
9 #   Copyright (c) 2008 Peter Palfrader <peter@palfrader.org>
10 #   Copyright (c) 2008 Andreas Barth <aba@not.so.argh.org>
11 #   Copyright (c) 2008 Mark Hymers <mhy@debian.org>
12 #
13 #   This program is free software; you can redistribute it and/or modify
14 #   it under the terms of the GNU General Public License as published by
15 #   the Free Software Foundation; either version 2 of the License, or
16 #   (at your option) any later version.
17 #
18 #   This program is distributed in the hope that it will be useful,
19 #   but WITHOUT ANY WARRANTY; without even the implied warranty of
20 #   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21 #   GNU General Public License for more details.
22 #
23 #   You should have received a copy of the GNU General Public License
24 #   along with this program; if not, write to the Free Software
25 #   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
26
27 import string, re, time, ldap, getopt, sys, os, pwd, posix, socket, base64, sha, shutil, errno, tarfile, grp
28 from userdir_ldap import *;
29
30 global Allowed;
31 global CurrentHost;
32
33 PasswdAttrs = None;
34 GroupIDMap = {};
35 Allowed = None;
36 CurrentHost = "";
37
38 EmailCheck = re.compile("^([^ <>@]+@[^ ,<>@]+)?$");
39 BSMTPCheck = re.compile(".*mx 0 (gluck)\.debian\.org\..*",re.DOTALL);
40 DNSZone = ".debian.net"
41 Keyrings = [ "/org/keyring.debian.org/keyrings/debian-keyring.gpg",
42              "/org/keyring.debian.org/keyrings/debian-keyring.pgp" ]
43
44 def safe_makedirs(dir):
45     try:
46         os.makedirs(dir)
47     except OSError, e:
48         if e.errno == errno.EEXIST:
49             pass
50         else:
51             raise e
52
53 def safe_rmtree(dir):
54     try:
55         shutil.rmtree(dir)
56     except OSError, e:
57         if e.errno == errno.ENOENT:
58             pass
59         else:
60             raise e
61
62 def Sanitize(Str):
63   return Str.translate(string.maketrans("\n\r\t","$$$"))
64
65 def DoLink(From,To,File):
66    try: posix.remove(To+File);
67    except: pass;
68    posix.link(From+File,To+File);
69
70 # See if this user is in the group list
71 def IsInGroup(DnRecord):
72   if Allowed == None:
73      return 1;
74
75   # See if the primary group is in the list
76   if Allowed.has_key(GetAttr(DnRecord,"gidNumber")) != 0:
77      return 1;
78
79   # Check the host based ACL
80   if DnRecord[1].has_key("allowedHost") != 0:
81      for I in DnRecord[1]["allowedHost"]:
82         if CurrentHost == I:
83            return 1;
84
85   # See if there are supplementary groups
86   if DnRecord[1].has_key("supplementaryGid") == 0:
87      return 0;
88
89   # Check the supplementary groups
90   for I in DnRecord[1]["supplementaryGid"]:
91      if Allowed.has_key(I):
92         return 1;
93   return 0;
94
95 def Die(File,F,Fdb):
96    if F != None:
97       F.close();
98    if Fdb != None:
99       Fdb.close();
100    try: os.remove(File + ".tmp");
101    except: pass;
102    try: os.remove(File + ".tdb.tmp");
103    except: pass;
104
105 def Done(File,F,Fdb):
106   if F != None:
107     F.close();
108     os.rename(File + ".tmp",File);
109   if Fdb != None:
110     Fdb.close();
111     os.rename(File + ".tdb.tmp",File+".tdb");
112   
113 # Generate the password list
114 def GenPasswd(l,File,HomePrefix,PwdMarker):
115   F = None;
116   try:
117    F = open(File + ".tdb.tmp","w");
118
119    userlist = {}
120    # Fetch all the users
121    global PasswdAttrs;
122    if PasswdAttrs == None:
123       raise "No Users";
124
125    I = 0;
126    for x in PasswdAttrs:
127       if x[1].has_key("uidNumber") == 0 or IsInGroup(x) == 0:
128          continue;
129
130       # Do not let people try to buffer overflow some busted passwd parser.
131       if len(GetAttr(x,"gecos")) > 100 or len(GetAttr(x,"loginShell")) > 50:
132          continue;
133
134       userlist[GetAttr(x, "uid")] = int(GetAttr(x, "gidNumber"))
135       Line = "%s:%s:%s:%s:%s:%s%s:%s" % (GetAttr(x,"uid"),\
136               PwdMarker,\
137               GetAttr(x,"uidNumber"),GetAttr(x,"gidNumber"),\
138               GetAttr(x,"gecos"),HomePrefix,GetAttr(x,"uid"),\
139               GetAttr(x,"loginShell"));
140
141       Line = Sanitize(Line) + "\n";
142       F.write("0%u %s" % (I,Line));
143       F.write(".%s %s" % (GetAttr(x,"uid"),Line));
144       F.write("=%s %s" % (GetAttr(x,"uidNumber"),Line));
145       I = I + 1;
146
147   # Oops, something unspeakable happened.
148   except:
149    Die(File,None,F);
150    raise;
151   Done(File,None,F);
152
153   # Return the list of users so we know which keys to export
154   return userlist
155
156 # Generate the shadow list
157 def GenShadow(l,File):
158   F = None;
159   try:
160    OldMask = os.umask(0077);
161    F = open(File + ".tdb.tmp","w",0600);
162    os.umask(OldMask);
163
164    # Fetch all the users
165    global PasswdAttrs;
166    if PasswdAttrs == None:
167       raise "No Users";
168
169    I = 0;
170    for x in PasswdAttrs:
171       if x[1].has_key("uidNumber") == 0 or IsInGroup(x) == 0:
172          continue;
173          
174       Pass = GetAttr(x,"userPassword");
175       if Pass[0:7] != "{crypt}" or len(Pass) > 50:
176          Pass = '*';
177       else:
178          Pass = Pass[7:];
179
180       # If the account is locked, mark it as such in shadow
181       # See Debian Bug #308229 for why we set it to 1 instead of 0
182       if (GetAttr(x,"userPassword").find("*LK*") != -1) \
183           or GetAttr(x,"userPassword").startswith("!"):
184          ShadowExpire = '1'
185       else:
186          ShadowExpire = GetAttr(x,"shadowexpire")
187
188       Line = "%s:%s:%s:%s:%s:%s:%s:%s:" % (GetAttr(x,"uid"),\
189               Pass,GetAttr(x,"shadowLastChange"),\
190               GetAttr(x,"shadowMin"),GetAttr(x,"shadowMax"),\
191               GetAttr(x,"shadowWarning"),GetAttr(x,"shadowinactive"),\
192               ShadowExpire);
193       Line = Sanitize(Line) + "\n";
194       F.write("0%u %s" % (I,Line));
195       F.write(".%s %s" % (GetAttr(x,"uid"),Line));
196       I = I + 1;
197
198   # Oops, something unspeakable happened.
199   except:
200    Die(File,None,F);
201    raise;
202   Done(File,None,F);
203
204 # Generate the shadow list
205 def GenSSHShadow(l,masterFileName):
206    # Fetch all the users
207    singlefile = None
208    userfiles = []
209    # Depending on config, we write out either a single file,
210    # multiple files, or both
211    if SingleSSHFile:
212        try:
213            OldMask = os.umask(0077);
214            masterFile = open(masterFileName + ".tmp","w",0600);
215            os.umask(OldMask);
216        except IOError:
217            Die(masterFileName,masterFile,None)
218            raise
219
220    global PasswdAttrs;
221    if PasswdAttrs == None:
222       raise "No Users";
223
224    # If we're going to be dealing with multiple keys, empty the
225    # directory before we start to avoid old keys hanging around
226    if MultipleSSHFiles:
227       safe_rmtree(os.path.join(GlobalDir, 'userkeys'))
228       safe_makedirs(os.path.join(GlobalDir, 'userkeys'))
229       
230    for x in PasswdAttrs:
231       # If the account is locked, do not write it.
232       # This is a partial stop-gap. The ssh also needs to change this
233       # to ignore ~/.ssh/authorized* files.
234       if (GetAttr(x,"userPassword").find("*LK*") != -1) \
235              or GetAttr(x,"userPassword").startswith("!"):
236          continue;
237
238       if x[1].has_key("uidNumber") == 0 or \
239          x[1].has_key("sshRSAAuthKey") == 0:
240          continue;
241       User = GetAttr(x,"uid");
242       F = None;
243
244       try:
245          if MultipleSSHFiles:
246              OldMask = os.umask(0077);
247              File = os.path.join(GlobalDir, 'userkeys', User)
248              F = open(File + ".tmp","w",0600);
249              os.umask(OldMask);
250
251          for I in x[1]["sshRSAAuthKey"]:
252              if MultipleSSHFiles:
253                  MultipleLine = "%s" % I
254                  MultipleLine = Sanitize(MultipleLine) + "\n"
255                  F.write(MultipleLine)
256              if SingleSSHFile:
257                  SingleLine = "%s: %s" % (User, I)
258                  SingleLine = Sanitize(SingleLine) + "\n"
259                  masterFile.write(SingleLine)
260
261          if MultipleSSHFiles:
262              Done(File,F,None);
263              userfiles.append(os.path.basename(File))
264
265       # Oops, something unspeakable happened.
266       except IOError:
267           Die(File,F,None)
268           Die(masterFileName,masterFile,None)
269           raise;
270
271    if SingleSSHFile:
272        Done(masterFileName,masterFile,None)
273        singlefile = os.path.basename(masterFileName)
274
275    return singlefile, userfiles
276
277 # Generate the group list
278 def GenGroup(l,File):
279   grouprevmap = {}
280   F = None;
281   try:
282    F = open(File + ".tdb.tmp","w");
283
284    # Generate the GroupMap
285    GroupMap = {};
286    for x in GroupIDMap.keys():
287       GroupMap[x] = [];
288       
289    # Fetch all the users
290    global PasswdAttrs;
291    if PasswdAttrs == None:
292       raise "No Users";
293
294    # Sort them into a list of groups having a set of users
295    for x in PasswdAttrs:
296       if x[1].has_key("uidNumber") == 0 or IsInGroup(x) == 0:
297          continue;
298       if x[1].has_key("supplementaryGid") == 0:
299          continue;
300          
301       for I in x[1]["supplementaryGid"]:
302          if GroupMap.has_key(I):
303             GroupMap[I].append(GetAttr(x,"uid"));
304          else:
305             print "Group does not exist ",I,"but",GetAttr(x,"uid"),"is in it";
306             
307    # Output the group file.
308    J = 0;
309    for x in GroupMap.keys():
310       grouprevmap[GroupIDMap[x]] = x
311       if GroupIDMap.has_key(x) == 0:
312          continue;
313       Line = "%s:x:%u:" % (x,GroupIDMap[x]);
314       Comma = '';
315       for I in GroupMap[x]:
316         Line = Line + ("%s%s" % (Comma,I));
317         Comma = ',';
318       Line = Sanitize(Line) + "\n";
319       F.write("0%u %s" % (J,Line));
320       F.write(".%s %s" % (x,Line));
321       F.write("=%u %s" % (GroupIDMap[x],Line));
322       J = J + 1;
323       
324   # Oops, something unspeakable happened.
325   except:
326    Die(File,None,F);
327    raise;
328   Done(File,None,F);
329
330   return grouprevmap
331
332 # Generate the email forwarding list
333 def GenForward(l,File):
334   F = None;
335   try:
336    OldMask = os.umask(0022);
337    F = open(File + ".tmp","w",0644);
338    os.umask(OldMask);
339
340    # Fetch all the users
341    global PasswdAttrs;
342    if PasswdAttrs == None:
343       raise "No Users";
344
345    # Write out the email address for each user
346    for x in PasswdAttrs:
347       if x[1].has_key("emailForward") == 0 or IsInGroup(x) == 0:
348          continue;
349       
350       # Do not allow people to try to buffer overflow busted parsers
351       if len(GetAttr(x,"emailForward")) > 200:
352          continue;
353
354       # Check the forwarding address
355       if EmailCheck.match(GetAttr(x,"emailForward")) == None:
356          continue;
357       Line = "%s: %s" % (GetAttr(x,"uid"),GetAttr(x,"emailForward"));
358       Line = Sanitize(Line) + "\n";
359       F.write(Line);
360       
361   # Oops, something unspeakable happened.
362   except:
363    Die(File,F,None);
364    raise;
365   Done(File,F,None);
366
367 def GenAllForward(l,File):
368   Fdb = None;
369   try:
370    OldMask = os.umask(0022);
371    Fdb = os.popen("cdbmake %s %s.tmp"%(File,File),"w");
372    os.umask(OldMask);
373
374    # Fetch all the users
375    global PasswdAttrs;
376    if PasswdAttrs == None:
377       raise "No Users";
378
379    # Write out the email address for each user
380    for x in PasswdAttrs:
381       if x[1].has_key("emailForward") == 0:
382          continue;
383       
384       # Do not allow people to try to buffer overflow busted parsers
385       Forward = GetAttr(x,"emailForward");
386       if len(Forward) > 200:
387          continue;
388
389       # Check the forwarding address
390       if EmailCheck.match(Forward) == None:
391          continue;
392          
393       User = GetAttr(x,"uid");
394       Fdb.write("+%d,%d:%s->%s\n"%(len(User),len(Forward),User,Forward));
395    Fdb.write("\n");
396   # Oops, something unspeakable happened.
397   except:
398     Fdb.close();
399     raise;
400   if Fdb.close() != None:
401     raise "cdbmake gave an error";
402
403 # Generate the anon XEarth marker file 
404 def GenMarkers(l,File):
405   F = None;
406   try:
407    F = open(File + ".tmp","w");
408
409    # Fetch all the users
410    global PasswdAttrs;
411    if PasswdAttrs == None:
412       raise "No Users";
413
414    # Write out the position for each user
415    for x in PasswdAttrs:
416       if x[1].has_key("latitude") == 0 or x[1].has_key("longitude") == 0:
417          continue;       
418       try:
419          Line = "%8s %8s \"\""%(DecDegree(GetAttr(x,"latitude"),1),DecDegree(GetAttr(x,"longitude"),1));
420          Line = Sanitize(Line) + "\n";
421          F.write(Line);
422       except:
423          pass;
424       
425   # Oops, something unspeakable happened.
426   except:
427    Die(File,F,None);
428    raise;
429   Done(File,F,None);
430
431 # Generate the debian-private subscription list
432 def GenPrivate(l,File):
433   F = None;
434   try:
435    F = open(File + ".tmp","w");
436
437    # Fetch all the users
438    global PasswdAttrs;
439    if PasswdAttrs == None:
440       raise "No Users";
441
442    # Write out the position for each user
443    for x in PasswdAttrs:
444       if x[1].has_key("privateSub") == 0:
445          continue;
446
447       # If the account is locked, do not write it
448       if (GetAttr(x,"userPassword").find("*LK*") != -1) \
449              or GetAttr(x,"userPassword").startswith("!"):
450          continue;
451
452       # If the account has no PGP key, do not write it
453       if x[1].has_key("keyFingerPrint") == 0:
454          continue;
455
456       # Must be in the Debian group (yuk, hard coded for now)
457       if GetAttr(x,"gidNumber") != "800":
458          continue;
459
460       try:
461          Line = "%s"%(GetAttr(x,"privateSub"));
462          Line = Sanitize(Line) + "\n";
463          F.write(Line);
464       except:
465          pass;
466       
467   # Oops, something unspeakable happened.
468   except:
469    Die(File,F,None);
470    raise;
471   Done(File,F,None);
472
473 # Generate a list of locked accounts
474 def GenDisabledAccounts(l,File):
475   F = None;
476   try:
477    F = open(File + ".tmp","w");
478
479    # Fetch all the users
480    global PasswdAttrs;
481    if PasswdAttrs == None:
482       raise "No Users";
483
484    I = 0;
485    for x in PasswdAttrs:
486       if x[1].has_key("uidNumber") == 0:
487          continue;
488          
489       Pass = GetAttr(x,"userPassword");
490       Line = ""
491       # *LK* is the reference value for a locked account
492       # password starting with ! is also a locked account
493       if Pass.find("*LK*") != -1 or Pass.startswith("!"):
494          # Format is <login>:<reason>
495          Line = "%s:%s" % (GetAttr(x,"uid"), "Account is locked")
496
497       if Line != "":
498          F.write(Sanitize(Line) + "\n")
499
500   # Oops, something unspeakable happened.
501   except:
502    Die(File,F,None);
503    raise;
504   Done(File,F,None);
505
506 # Generate the list of local addresses that refuse all mail
507 def GenMailDisable(l,File):
508   F = None;
509   try:
510    F = open(File + ".tmp","w");
511
512    # Fetch all the users
513    global PasswdAttrs;
514    if PasswdAttrs == None:
515       raise "No Users";
516
517    for x in PasswdAttrs:
518       Reason = None
519
520       if x[1].has_key("mailDisableMessage"):
521          Reason = GetAttr(x,"mailDisableMessage")
522       else:
523          continue
524
525       # Must be in the Debian group (yuk, hard coded for now)
526       if GetAttr(x,"gidNumber") != "800":
527          continue;
528
529       try:
530          Line = "%s: %s"%(GetAttr(x,"uid"),Reason);
531          Line = Sanitize(Line) + "\n";
532          F.write(Line);
533       except:
534          pass;
535       
536   # Oops, something unspeakable happened.
537   except:
538    Die(File,F,None);
539    raise;
540   Done(File,F,None);
541
542 # Generate a list of uids that should have boolean affects applied
543 def GenMailBool(l,File,Key):
544   F = None;
545   try:
546    F = open(File + ".tmp","w");
547
548    # Fetch all the users
549    global PasswdAttrs;
550    if PasswdAttrs == None:
551       raise "No Users";
552
553    for x in PasswdAttrs:
554       Reason = None
555       
556       if x[1].has_key(Key) == 0:
557          continue
558
559       # Must be in the Debian group (yuk, hard coded for now)
560       if GetAttr(x,"gidNumber") != "800":
561          continue
562
563       if GetAttr(x,Key) != "TRUE":
564          continue
565
566       try:
567          Line = "%s"%(GetAttr(x,"uid"));
568          Line = Sanitize(Line) + "\n";
569          F.write(Line);
570       except:
571          pass;
572       
573   # Oops, something unspeakable happened.
574   except:
575    Die(File,F,None);
576    raise;
577   Done(File,F,None);
578
579 # Generate a list of hosts for RBL or whitelist purposes.
580 def GenMailList(l,File,Key):
581   F = None;
582   try:
583    F = open(File + ".tmp","w");
584
585    # Fetch all the users
586    global PasswdAttrs;
587    if PasswdAttrs == None:
588       raise "No Users";
589
590    for x in PasswdAttrs:
591       Reason = None
592       
593       if x[1].has_key(Key) == 0:
594          continue
595
596       # Must be in the Debian group (yuk, hard coded for now)
597       if GetAttr(x,"gidNumber") != "800":
598          continue
599
600       try:
601          found = 0
602          Line = None
603          for z in x[1][Key]:
604              if Key == "mailWhitelist":
605                  if re.match('^[-\w.]+(/[\d]+)?$',z) == None:
606                      continue
607              else:
608                  if re.match('^[-\w.]+$',z) == None:
609                      continue
610              if found == 0:
611                  found = 1
612                  Line = GetAttr(x,"uid")
613              else:
614                  Line += " "
615              Line += ": " + z
616              if Key == "mailRHSBL":
617                  Line += "/$sender_address_domain"
618
619          if Line != None:
620              Line = Sanitize(Line) + "\n";
621              F.write(Line);
622       except:
623          pass;
624       
625   # Oops, something unspeakable happened.
626   except:
627    Die(File,F,None);
628    raise;
629   Done(File,F,None);
630
631 # Generate the DNS Zone file
632 def GenDNS(l,File,HomePrefix):
633   F = None;
634   try:
635    F = open(File + ".tmp","w");
636    
637    # Fetch all the users
638    global PasswdAttrs;
639    if PasswdAttrs == None:
640       raise "No Users";
641
642    # Write out the zone file entry for each user
643    for x in PasswdAttrs:
644       if x[1].has_key("dnsZoneEntry") == 0:
645          continue;
646
647       # If the account has no PGP key, do not write it
648       if x[1].has_key("keyFingerPrint") == 0:
649          continue;
650       try:
651          F.write("; %s\n"%(EmailAddress(x)));
652          for z in x[1]["dnsZoneEntry"]:
653             Split = z.lower().split()
654             if Split[1].lower() == 'in':
655                for y in range(0,len(Split)):
656                   if Split[y] == "$":
657                      Split[y] = "\n\t";
658                Line = " ".join(Split) + "\n";
659                F.write(Line);
660                
661                Host = Split[0] + DNSZone;
662                if BSMTPCheck.match(Line) != None:
663                    F.write("; Has BSMTP\n");
664                                
665                # Write some identification information
666                if Split[2].lower() == "a":
667                   Line = "%s IN TXT \"%s\"\n"%(Split[0],EmailAddress(x));
668                   for y in x[1]["keyFingerPrint"]:
669                      Line = Line + "%s IN TXT \"PGP %s\"\n"%(Split[0],FormatPGPKey(y));
670                   F.write(Line);
671             else:
672                Line = "; Err %s"%(str(Split));
673                F.write(Line);
674
675          F.write("\n");
676       except:
677          F.write("; Errors\n");
678          pass;
679       
680   # Oops, something unspeakable happened.
681   except:
682    Die(File,F,None);
683    raise;
684   Done(File,F,None);
685
686 # Generate the DNS SSHFP records
687 def GenSSHFP(l,File,HomePrefix):
688   F = None
689   try:
690    F = open(File + ".tmp","w")
691    
692    # Fetch all the hosts
693    global HostAttrs
694    if HostAttrs == None:
695       raise "No Hosts"
696
697    for x in HostAttrs:
698       if x[1].has_key("hostname") == 0 or \
699          x[1].has_key("sshRSAHostKey") == 0:
700          continue
701       Host = GetAttr(x,"hostname");
702       Algorithm = None
703       for I in x[1]["sshRSAHostKey"]:
704          Split = I.split()
705          if Split[0] == 'ssh-rsa':
706             Algorithm = 1
707          if Split[0] == 'ssh-dss':
708             Algorithm = 2
709          if Algorithm == None:
710             continue
711          Fingerprint = sha.new(base64.decodestring(Split[1])).hexdigest()
712          Line = "%s. IN SSHFP %u 1 %s" % (Host,Algorithm,Fingerprint)
713          Line = Sanitize(Line) + "\n"
714          F.write(Line)
715   # Oops, something unspeakable happened.
716   except:
717    Die(File,F,None)
718    raise;
719   Done(File,F,None)
720
721 # Generate the BSMTP file
722 def GenBSMTP(l,File,HomePrefix):
723   F = None;
724   try:
725    F = open(File + ".tmp","w");
726    
727    # Fetch all the users
728    global PasswdAttrs;
729    if PasswdAttrs == None:
730       raise "No Users";
731
732    # Write out the zone file entry for each user
733    for x in PasswdAttrs:
734       if x[1].has_key("dnsZoneEntry") == 0:
735          continue;
736
737       # If the account has no PGP key, do not write it
738       if x[1].has_key("keyFingerPrint") == 0:
739          continue;
740       try:
741          for z in x[1]["dnsZoneEntry"]:
742             Split = z.lower().split()
743             if Split[1].lower() == 'in':
744                for y in range(0,len(Split)):
745                   if Split[y] == "$":
746                      Split[y] = "\n\t";
747                Line = " ".join(Split) + "\n";
748                
749                Host = Split[0] + DNSZone;
750                if BSMTPCheck.match(Line) != None:
751                    F.write("%s: user=%s group=Debian file=%s%s/bsmtp/%s\n"%(Host,
752                                GetAttr(x,"uid"),HomePrefix,GetAttr(x,"uid"),Host));
753                                
754       except:
755          F.write("; Errors\n");
756          pass;
757       
758   # Oops, something unspeakable happened.
759   except:
760    Die(File,F,None);
761    raise;
762   Done(File,F,None);
763
764 # cache IP adresses
765 HostToIPCache = {}
766 def HostToIP(Host):
767     global HostToIPCache
768     if not Host in HostToIPCache:
769         IPAdressesT = None
770         try:
771             IPAdressesT = list(set([ (a[0],a[4][0]) for a in socket.getaddrinfo(Host, None)]))
772         except socket.gaierror, (code):
773             if code[0] != -2: raise
774         IPAdresses = []
775         for addr in IPAdressesT:
776             if addr[0] == socket.AF_INET: IPAdresses += [addr[1], "::ffff:"+addr[1]]
777             else: IPAdresses += [addr[1]]
778         HostToIPCache[Host] = IPAdresses
779     return HostToIPCache[Host]
780
781
782 # Generate the ssh known hosts file
783 def GenSSHKnown(l,File,mode=None):
784   F = None;
785   try:
786    OldMask = os.umask(0022);
787    F = open(File + ".tmp","w",0644);
788    os.umask(OldMask);
789
790    global HostAttrs
791    if HostAttrs == None:
792       raise "No Hosts";
793    
794    for x in HostAttrs:
795       if x[1].has_key("hostname") == 0 or \
796          x[1].has_key("sshRSAHostKey") == 0:
797          continue;
798       Host = GetAttr(x,"hostname");
799       HostNames = [ Host ]
800       SHost = Host.find(".")
801       if SHost != None: HostNames += [Host[0:SHost]]
802
803       for I in x[1]["sshRSAHostKey"]:
804          if mode and mode == 'authorized_keys':
805             #Line = 'command="rsync --server --sender -pr . /var/cache/userdir-ldap/hosts/%s",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,from="%s" %s' % (Host, ",".join(HNames + HostToIP(Host)), I)
806             Line = 'command="rsync --server --sender -pr . /var/cache/userdir-ldap/hosts/%s",no-port-forwarding,no-X11-forwarding,no-agent-forwarding %s' % (Host,I)
807          else:
808             Line = "%s %s" %(",".join(HostNames + HostToIP(Host)), I);
809          Line = Sanitize(Line) + "\n";
810          F.write(Line);
811   # Oops, something unspeakable happened.
812   except:
813    Die(File,F,None);
814    raise;
815   Done(File,F,None);
816
817 # Generate the debianhosts file (list of all IP addresses)
818 def GenHosts(l,File):
819   F = None;
820   try:
821    OldMask = os.umask(0022);
822    F = open(File + ".tmp","w",0644);
823    os.umask(OldMask);
824
825    # Fetch all the hosts
826    HostNames = l.search_s(HostBaseDn,ldap.SCOPE_ONELEVEL,"hostname=*",\
827                 ["hostname"]);
828    
829    if HostNames == None:
830       raise "No Hosts";
831
832    for x in HostNames:
833       if x[1].has_key("hostname") == 0:
834          continue;
835       Host = GetAttr(x,"hostname");
836       try:
837         Addr = socket.gethostbyname(Host);
838         F.write(Addr + "\n");
839       except:
840         pass
841   # Oops, something unspeakable happened.
842   except:
843    Die(File,F,None);
844    raise;
845   Done(File,F,None);
846
847 def GenKeyrings(l,OutDir):
848   for k in Keyrings:
849     shutil.copy(k, OutDir)
850
851 # Connect to the ldap server
852 l = ldap.open(LDAPServer);
853 F = open(PassDir+"/pass-"+pwd.getpwuid(os.getuid())[0],"r");
854 Pass = F.readline().strip().split(" ")
855 F.close();
856 l.simple_bind_s("uid="+Pass[0]+","+BaseDn,Pass[1]);
857
858 # Fetch all the groups
859 GroupIDMap = {};
860 Attrs = l.search_s(BaseDn,ldap.SCOPE_ONELEVEL,"gid=*",\
861                   ["gid","gidNumber"]);
862
863 # Generate the GroupMap and GroupIDMap
864 for x in Attrs:
865    if x[1].has_key("gidNumber") == 0:
866       continue;
867    GroupIDMap[x[1]["gid"][0]] = int(x[1]["gidNumber"][0]);
868
869 # Fetch all the users
870 PasswdAttrs = l.search_s(BaseDn,ldap.SCOPE_ONELEVEL,"uid=*",\
871                 ["uid","uidNumber","gidNumber","supplementaryGid",\
872                  "gecos","loginShell","userPassword","shadowLastChange",\
873                  "shadowMin","shadowMax","shadowWarning","shadowinactive",
874                  "shadowexpire","emailForward","latitude","longitude",\
875                  "allowedHost","sshRSAAuthKey","dnsZoneEntry","cn","sn",\
876                  "keyFingerPrint","privateSub","mailDisableMessage",\
877                  "mailGreylisting","mailCallout","mailRBL","mailRHSBL",\
878                  "mailWhitelist"]);
879 # Fetch all the hosts
880 HostAttrs    = l.search_s(HostBaseDn,ldap.SCOPE_ONELEVEL,"sshRSAHostKey=*",\
881                 ["hostname","sshRSAHostKey"]);
882
883 # Open the control file
884 if len(sys.argv) == 1:
885    F = open(GenerateConf,"r");
886 else:
887    F = open(sys.argv[1],"r")
888
889 # Generate global things
890 GlobalDir = GenerateDir+"/";
891 SSHGlobal, SSHFiles = GenSSHShadow(l,GlobalDir+"ssh-rsa-shadow");
892 GenAllForward(l,GlobalDir+"mail-forward.cdb");
893 GenMarkers(l,GlobalDir+"markers");
894 GenPrivate(l,GlobalDir+"debian-private");
895 GenDisabledAccounts(l,GlobalDir+"disabled-accounts");
896 GenSSHKnown(l,GlobalDir+"ssh_known_hosts");
897 #GenSSHKnown(l,GlobalDir+"authorized_keys", 'authorized_keys');
898 GenHosts(l,GlobalDir+"debianhosts");
899 GenMailDisable(l,GlobalDir+"mail-disable");
900 GenMailBool(l,GlobalDir+"mail-greylist","mailGreylisting");
901 GenMailBool(l,GlobalDir+"mail-callout","mailCallout");
902 GenMailList(l,GlobalDir+"mail-rbl","mailRBL");
903 GenMailList(l,GlobalDir+"mail-rhsbl","mailRHSBL");
904 GenMailList(l,GlobalDir+"mail-whitelist","mailWhitelist");
905 GenKeyrings(l,GlobalDir);
906
907 # Compatibility.
908 GenForward(l,GlobalDir+"forward-alias");
909
910 while(1):
911    Line = F.readline();
912    if Line == "":
913       break;
914    Line = Line.strip()
915    if Line == "":
916       continue;
917    if Line[0] == '#':
918       continue;
919
920    Split = Line.split(" ")
921    OutDir = GenerateDir + '/' + Split[0] + '/';
922    try: os.mkdir(OutDir);
923    except: pass;
924
925    # Get the group list and convert any named groups to numerics
926    GroupList = {};
927    ExtraList = {};
928    for I in Split[2:]:
929       if I[0] == '[':
930          ExtraList[I] = None;
931          continue;
932       GroupList[I] = None;
933       if GroupIDMap.has_key(I):
934          GroupList[str(GroupIDMap[I])] = None;
935
936    Allowed = GroupList;
937    if Allowed == {}:
938      Allowed = None
939    CurrentHost = Split[0];
940
941    # If we're using a single SSH file, deal with it
942    if SSHGlobal is not None:
943       DoLink(GlobalDir, OutDir, SSHGlobal)
944
945    DoLink(GlobalDir,OutDir,"debianhosts");
946    DoLink(GlobalDir,OutDir,"ssh_known_hosts");
947    DoLink(GlobalDir,OutDir,"disabled-accounts")
948
949    sys.stdout.flush();
950    if ExtraList.has_key("[NOPASSWD]"):
951       userlist = GenPasswd(l,OutDir+"passwd",Split[1], "*");
952    else:
953       userlist = GenPasswd(l,OutDir+"passwd",Split[1], "x");
954    sys.stdout.flush();
955    grouprevmap = GenGroup(l,OutDir+"group");
956
957    # Now we know who we're allowing on the machine, export
958    # the relevant ssh keys
959    if MultipleSSHFiles:
960       OldMask = os.umask(0077);
961       tf = tarfile.open(name=os.path.join(GlobalDir, 'ssh-keys-%s.tar.gz' % CurrentHost), mode='w:gz')
962       os.umask(OldMask);
963       for f in userlist.keys():
964         if f not in SSHFiles:
965             continue
966         # If we're not exporting their primary group, don't export 
967         # the key and warn
968         grname = None
969         if userlist[f] in grouprevmap.keys():
970             grname = grouprevmap[userlist[f]]
971         else:
972             try:
973                 if int(userlist[f]) <= 100:
974                     # In these cases, look it up in the normal way so we
975                     # deal with cases where, for instance, users are in group
976                     # users as their primary group.
977                     grname = grp.getgrgid(userlist[f])[0]
978             except Exception, e:
979                 pass
980
981         if grname is None:
982             print "User %s is supposed to have their key exported to host %s but their primary group (gid: %d) isn't in LDAP" % (f, CurrentHost, userlist[f])
983             continue
984
985         to = tf.gettarinfo(os.path.join(GlobalDir, 'userkeys', f), f)
986         # These will only be used where the username doesn't
987         # exist on the target system for some reason; hence,
988         # in those cases, the safest thing is for the file to
989         # be owned by root but group nobody.  This deals with
990         # the bloody obscure case where the group fails to exist
991         # whilst the user does (in which case we want to avoid
992         # ending up with a file which is owned user:root to avoid
993         # a fairly obvious attack vector)
994         to.uid = 0
995         to.gid = 65534
996         # Using the username / groupname fields avoids any need
997         # to give a shit^W^W^Wcare about the UIDoffset stuff.
998         to.uname = f
999         to.gname = grname
1000         to.mode  = 0400
1001         tf.addfile(to, file(os.path.join(GlobalDir, 'userkeys', f)))
1002
1003       tf.close()
1004       os.rename(os.path.join(GlobalDir, 'ssh-keys-%s.tar.gz' % CurrentHost),
1005                 os.path.join(OutDir, 'ssh-keys.tar.gz'))
1006
1007    if ExtraList.has_key("[UNTRUSTED]"):
1008      continue;
1009    if not ExtraList.has_key("[NOPASSWD]"):
1010      GenShadow(l,OutDir+"shadow");
1011
1012    # Link in global things   
1013    DoLink(GlobalDir,OutDir,"markers");
1014    DoLink(GlobalDir,OutDir,"mail-forward.cdb");
1015    DoLink(GlobalDir,OutDir,"mail-disable");
1016    DoLink(GlobalDir,OutDir,"mail-greylist");
1017    DoLink(GlobalDir,OutDir,"mail-callout");
1018    DoLink(GlobalDir,OutDir,"mail-rbl");
1019    DoLink(GlobalDir,OutDir,"mail-rhsbl");
1020    DoLink(GlobalDir,OutDir,"mail-whitelist");
1021
1022    # Compatibility.
1023    DoLink(GlobalDir,OutDir,"forward-alias");
1024
1025    if ExtraList.has_key("[DNS]"):
1026       GenDNS(l,OutDir+"dns-zone",Split[1]);
1027       GenSSHFP(l,OutDir+"dns-sshfp",Split[1])
1028       
1029    if ExtraList.has_key("[BSMTP]"):
1030       GenBSMTP(l,OutDir+"bsmtp",Split[1])
1031
1032    if ExtraList.has_key("[PRIVATE]"):
1033       DoLink(GlobalDir,OutDir,"debian-private")
1034
1035    if ExtraList.has_key("[KEYRING]"):
1036       for k in Keyrings:
1037         DoLink(GlobalDir,OutDir,os.path.basename(k))
1038    else:
1039      for k in Keyrings:
1040        try: posix.remove(OutDir+os.path.basename(k));
1041        except: pass;