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