Make ssh-keys.tar.gz readable only by the user.
[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 the account is locked, disable incoming mail
521       if (GetAttr(x,"userPassword").find("*LK*") != -1):
522          if GetAttr(x,"uid") == "luther":
523             continue
524          else:
525             Reason = "user account locked"
526       else:
527          if x[1].has_key("mailDisableMessage"):
528             Reason = GetAttr(x,"mailDisableMessage")
529          else:
530             continue
531
532       # Must be in the Debian group (yuk, hard coded for now)
533       if GetAttr(x,"gidNumber") != "800":
534          continue;
535
536       try:
537          Line = "%s: %s"%(GetAttr(x,"uid"),Reason);
538          Line = Sanitize(Line) + "\n";
539          F.write(Line);
540       except:
541          pass;
542       
543   # Oops, something unspeakable happened.
544   except:
545    Die(File,F,None);
546    raise;
547   Done(File,F,None);
548
549 # Generate a list of uids that should have boolean affects applied
550 def GenMailBool(l,File,Key):
551   F = None;
552   try:
553    F = open(File + ".tmp","w");
554
555    # Fetch all the users
556    global PasswdAttrs;
557    if PasswdAttrs == None:
558       raise "No Users";
559
560    for x in PasswdAttrs:
561       Reason = None
562       
563       if x[1].has_key(Key) == 0:
564          continue
565
566       # Must be in the Debian group (yuk, hard coded for now)
567       if GetAttr(x,"gidNumber") != "800":
568          continue
569
570       if GetAttr(x,Key) != "TRUE":
571          continue
572
573       try:
574          Line = "%s"%(GetAttr(x,"uid"));
575          Line = Sanitize(Line) + "\n";
576          F.write(Line);
577       except:
578          pass;
579       
580   # Oops, something unspeakable happened.
581   except:
582    Die(File,F,None);
583    raise;
584   Done(File,F,None);
585
586 # Generate a list of hosts for RBL or whitelist purposes.
587 def GenMailList(l,File,Key):
588   F = None;
589   try:
590    F = open(File + ".tmp","w");
591
592    # Fetch all the users
593    global PasswdAttrs;
594    if PasswdAttrs == None:
595       raise "No Users";
596
597    for x in PasswdAttrs:
598       Reason = None
599       
600       if x[1].has_key(Key) == 0:
601          continue
602
603       # Must be in the Debian group (yuk, hard coded for now)
604       if GetAttr(x,"gidNumber") != "800":
605          continue
606
607       try:
608          found = 0
609          Line = None
610          for z in x[1][Key]:
611              if Key == "mailWhitelist":
612                  if re.match('^[-\w.]+(/[\d]+)?$',z) == None:
613                      continue
614              else:
615                  if re.match('^[-\w.]+$',z) == None:
616                      continue
617              if found == 0:
618                  found = 1
619                  Line = GetAttr(x,"uid")
620              else:
621                  Line += " "
622              Line += ": " + z
623              if Key == "mailRHSBL":
624                  Line += "/$sender_address_domain"
625
626          if Line != None:
627              Line = Sanitize(Line) + "\n";
628              F.write(Line);
629       except:
630          pass;
631       
632   # Oops, something unspeakable happened.
633   except:
634    Die(File,F,None);
635    raise;
636   Done(File,F,None);
637
638 # Generate the DNS Zone file
639 def GenDNS(l,File,HomePrefix):
640   F = None;
641   try:
642    F = open(File + ".tmp","w");
643    
644    # Fetch all the users
645    global PasswdAttrs;
646    if PasswdAttrs == None:
647       raise "No Users";
648
649    # Write out the zone file entry for each user
650    for x in PasswdAttrs:
651       if x[1].has_key("dnsZoneEntry") == 0:
652          continue;
653
654       # If the account has no PGP key, do not write it
655       if x[1].has_key("keyFingerPrint") == 0:
656          continue;
657       try:
658          F.write("; %s\n"%(EmailAddress(x)));
659          for z in x[1]["dnsZoneEntry"]:
660             Split = z.lower().split()
661             if Split[1].lower() == 'in':
662                for y in range(0,len(Split)):
663                   if Split[y] == "$":
664                      Split[y] = "\n\t";
665                Line = " ".join(Split) + "\n";
666                F.write(Line);
667                
668                Host = Split[0] + DNSZone;
669                if BSMTPCheck.match(Line) != None:
670                    F.write("; Has BSMTP\n");
671                                
672                # Write some identification information
673                if Split[2].lower() == "a":
674                   Line = "%s IN TXT \"%s\"\n"%(Split[0],EmailAddress(x));
675                   for y in x[1]["keyFingerPrint"]:
676                      Line = Line + "%s IN TXT \"PGP %s\"\n"%(Split[0],FormatPGPKey(y));
677                   F.write(Line);
678             else:
679                Line = "; Err %s"%(str(Split));
680                F.write(Line);
681
682          F.write("\n");
683       except:
684          F.write("; Errors\n");
685          pass;
686       
687   # Oops, something unspeakable happened.
688   except:
689    Die(File,F,None);
690    raise;
691   Done(File,F,None);
692
693 # Generate the DNS SSHFP records
694 def GenSSHFP(l,File,HomePrefix):
695   F = None
696   try:
697    F = open(File + ".tmp","w")
698    
699    # Fetch all the hosts
700    global HostAttrs
701    if HostAttrs == None:
702       raise "No Hosts"
703
704    for x in HostAttrs:
705       if x[1].has_key("hostname") == 0 or \
706          x[1].has_key("sshRSAHostKey") == 0:
707          continue
708       Host = GetAttr(x,"hostname");
709       Algorithm = None
710       for I in x[1]["sshRSAHostKey"]:
711          Split = I.split()
712          if Split[0] == 'ssh-rsa':
713             Algorithm = 1
714          if Split[0] == 'ssh-dss':
715             Algorithm = 2
716          if Algorithm == None:
717             continue
718          Fingerprint = sha.new(base64.decodestring(Split[1])).hexdigest()
719          Line = "%s. IN SSHFP %u 1 %s" % (Host,Algorithm,Fingerprint)
720          Line = Sanitize(Line) + "\n"
721          F.write(Line)
722   # Oops, something unspeakable happened.
723   except:
724    Die(File,F,None)
725    raise;
726   Done(File,F,None)
727
728 # Generate the BSMTP file
729 def GenBSMTP(l,File,HomePrefix):
730   F = None;
731   try:
732    F = open(File + ".tmp","w");
733    
734    # Fetch all the users
735    global PasswdAttrs;
736    if PasswdAttrs == None:
737       raise "No Users";
738
739    # Write out the zone file entry for each user
740    for x in PasswdAttrs:
741       if x[1].has_key("dnsZoneEntry") == 0:
742          continue;
743
744       # If the account has no PGP key, do not write it
745       if x[1].has_key("keyFingerPrint") == 0:
746          continue;
747       try:
748          for z in x[1]["dnsZoneEntry"]:
749             Split = z.lower().split()
750             if Split[1].lower() == 'in':
751                for y in range(0,len(Split)):
752                   if Split[y] == "$":
753                      Split[y] = "\n\t";
754                Line = " ".join(Split) + "\n";
755                
756                Host = Split[0] + DNSZone;
757                if BSMTPCheck.match(Line) != None:
758                    F.write("%s: user=%s group=Debian file=%s%s/bsmtp/%s\n"%(Host,
759                                GetAttr(x,"uid"),HomePrefix,GetAttr(x,"uid"),Host));
760                                
761       except:
762          F.write("; Errors\n");
763          pass;
764       
765   # Oops, something unspeakable happened.
766   except:
767    Die(File,F,None);
768    raise;
769   Done(File,F,None);
770
771 # cache IP adresses
772 HostToIPCache = {}
773 def HostToIP(Host):
774     global HostToIPCache
775     if not Host in HostToIPCache:
776         IPAdressesT = None
777         try:
778             IPAdressesT = list(set([ (a[0],a[4][0]) for a in socket.getaddrinfo(Host, None)]))
779         except socket.gaierror, (code):
780             if code[0] != -2: raise
781         IPAdresses = []
782         for addr in IPAdressesT:
783             if addr[0] == socket.AF_INET: IPAdresses += [addr[1], "::ffff:"+addr[1]]
784             else: IPAdresses += [addr[1]]
785         HostToIPCache[Host] = IPAdresses
786     return HostToIPCache[Host]
787
788
789 # Generate the ssh known hosts file
790 def GenSSHKnown(l,File,mode=None):
791   F = None;
792   try:
793    OldMask = os.umask(0022);
794    F = open(File + ".tmp","w",0644);
795    os.umask(OldMask);
796
797    global HostAttrs
798    if HostAttrs == None:
799       raise "No Hosts";
800    
801    for x in HostAttrs:
802       if x[1].has_key("hostname") == 0 or \
803          x[1].has_key("sshRSAHostKey") == 0:
804          continue;
805       Host = GetAttr(x,"hostname");
806       HostNames = [ Host ]
807       SHost = Host.find(".")
808       if SHost != None: HostNames += [Host[0:SHost]]
809
810       for I in x[1]["sshRSAHostKey"]:
811          if mode and mode == 'authorized_keys':
812             #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)
813             Line = 'command="rsync --server --sender -pr . /var/cache/userdir-ldap/hosts/%s",no-port-forwarding,no-X11-forwarding,no-agent-forwarding %s' % (Host,I)
814          else:
815             Line = "%s %s" %(",".join(HostNames + HostToIP(Host)), I);
816          Line = Sanitize(Line) + "\n";
817          F.write(Line);
818   # Oops, something unspeakable happened.
819   except:
820    Die(File,F,None);
821    raise;
822   Done(File,F,None);
823
824 # Generate the debianhosts file (list of all IP addresses)
825 def GenHosts(l,File):
826   F = None;
827   try:
828    OldMask = os.umask(0022);
829    F = open(File + ".tmp","w",0644);
830    os.umask(OldMask);
831
832    # Fetch all the hosts
833    HostNames = l.search_s(HostBaseDn,ldap.SCOPE_ONELEVEL,"hostname=*",\
834                 ["hostname"]);
835    
836    if HostNames == None:
837       raise "No Hosts";
838
839    for x in HostNames:
840       if x[1].has_key("hostname") == 0:
841          continue;
842       Host = GetAttr(x,"hostname");
843       try:
844         Addr = socket.gethostbyname(Host);
845         F.write(Addr + "\n");
846       except:
847         pass
848   # Oops, something unspeakable happened.
849   except:
850    Die(File,F,None);
851    raise;
852   Done(File,F,None);
853
854 def GenKeyrings(l,OutDir):
855   for k in Keyrings:
856     shutil.copy(k, OutDir)
857
858 # Connect to the ldap server
859 l = ldap.open(LDAPServer);
860 F = open(PassDir+"/pass-"+pwd.getpwuid(os.getuid())[0],"r");
861 Pass = F.readline().strip().split(" ")
862 F.close();
863 l.simple_bind_s("uid="+Pass[0]+","+BaseDn,Pass[1]);
864
865 # Fetch all the groups
866 GroupIDMap = {};
867 Attrs = l.search_s(BaseDn,ldap.SCOPE_ONELEVEL,"gid=*",\
868                   ["gid","gidNumber"]);
869
870 # Generate the GroupMap and GroupIDMap
871 for x in Attrs:
872    if x[1].has_key("gidNumber") == 0:
873       continue;
874    GroupIDMap[x[1]["gid"][0]] = int(x[1]["gidNumber"][0]);
875
876 # Fetch all the users
877 PasswdAttrs = l.search_s(BaseDn,ldap.SCOPE_ONELEVEL,"uid=*",\
878                 ["uid","uidNumber","gidNumber","supplementaryGid",\
879                  "gecos","loginShell","userPassword","shadowLastChange",\
880                  "shadowMin","shadowMax","shadowWarning","shadowinactive",
881                  "shadowexpire","emailForward","latitude","longitude",\
882                  "allowedHost","sshRSAAuthKey","dnsZoneEntry","cn","sn",\
883                  "keyFingerPrint","privateSub","mailDisableMessage",\
884                  "mailGreylisting","mailCallout","mailRBL","mailRHSBL",\
885                  "mailWhitelist"]);
886 # Fetch all the hosts
887 HostAttrs    = l.search_s(HostBaseDn,ldap.SCOPE_ONELEVEL,"sshRSAHostKey=*",\
888                 ["hostname","sshRSAHostKey"]);
889
890 # Open the control file
891 if len(sys.argv) == 1:
892    F = open(GenerateConf,"r");
893 else:
894    F = open(sys.argv[1],"r")
895
896 # Generate global things
897 GlobalDir = GenerateDir+"/";
898 SSHGlobal, SSHFiles = GenSSHShadow(l,GlobalDir+"ssh-rsa-shadow");
899 GenAllForward(l,GlobalDir+"mail-forward.cdb");
900 GenMarkers(l,GlobalDir+"markers");
901 GenPrivate(l,GlobalDir+"debian-private");
902 GenDisabledAccounts(l,GlobalDir+"disabled-accounts");
903 GenSSHKnown(l,GlobalDir+"ssh_known_hosts");
904 #GenSSHKnown(l,GlobalDir+"authorized_keys", 'authorized_keys');
905 GenHosts(l,GlobalDir+"debianhosts");
906 GenMailDisable(l,GlobalDir+"mail-disable");
907 GenMailBool(l,GlobalDir+"mail-greylist","mailGreylisting");
908 GenMailBool(l,GlobalDir+"mail-callout","mailCallout");
909 GenMailList(l,GlobalDir+"mail-rbl","mailRBL");
910 GenMailList(l,GlobalDir+"mail-rhsbl","mailRHSBL");
911 GenMailList(l,GlobalDir+"mail-whitelist","mailWhitelist");
912 GenKeyrings(l,GlobalDir);
913
914 # Compatibility.
915 GenForward(l,GlobalDir+"forward-alias");
916
917 while(1):
918    Line = F.readline();
919    if Line == "":
920       break;
921    Line = Line.strip()
922    if Line == "":
923       continue;
924    if Line[0] == '#':
925       continue;
926
927    Split = Line.split(" ")
928    OutDir = GenerateDir + '/' + Split[0] + '/';
929    try: os.mkdir(OutDir);
930    except: pass;
931
932    # Get the group list and convert any named groups to numerics
933    GroupList = {};
934    ExtraList = {};
935    for I in Split[2:]:
936       if I[0] == '[':
937          ExtraList[I] = None;
938          continue;
939       GroupList[I] = None;
940       if GroupIDMap.has_key(I):
941          GroupList[str(GroupIDMap[I])] = None;
942
943    Allowed = GroupList;
944    if Allowed == {}:
945      Allowed = None
946    CurrentHost = Split[0];
947
948    # If we're using a single SSH file, deal with it
949    if SSHGlobal is not None:
950       DoLink(GlobalDir, OutDir, SSHGlobal)
951
952    DoLink(GlobalDir,OutDir,"debianhosts");
953    DoLink(GlobalDir,OutDir,"ssh_known_hosts");
954    DoLink(GlobalDir,OutDir,"disabled-accounts")
955
956    sys.stdout.flush();
957    if ExtraList.has_key("[NOPASSWD]"):
958       userlist = GenPasswd(l,OutDir+"passwd",Split[1], "*");
959    else:
960       userlist = GenPasswd(l,OutDir+"passwd",Split[1], "x");
961    sys.stdout.flush();
962    grouprevmap = GenGroup(l,OutDir+"group");
963    if ExtraList.has_key("[UNTRUSTED]"):
964         continue;
965    if not ExtraList.has_key("[NOPASSWD]"):
966      GenShadow(l,OutDir+"shadow");
967
968    # Now we know who we're allowing on the machine, export
969    # the relevant ssh keys
970    if MultipleSSHFiles:
971       OldMask = os.umask(0077);
972       tf = tarfile.open(name=os.path.join(GlobalDir, 'ssh-keys-%s.tar.gz' % CurrentHost), mode='w:gz')
973       os.umask(OldMask);
974       for f in userlist.keys():
975         if f not in SSHFiles:
976             continue
977         # If we're not exporting their primary group, don't export 
978         # the key and warn
979         grname = None
980         if userlist[f] in grouprevmap.keys():
981             grname = grouprevmap[userlist[f]]
982         else:
983             try:
984                 if int(userlist[f]) <= 100:
985                     # In these cases, look it up in the normal way so we
986                     # deal with cases where, for instance, users are in group
987                     # users as their primary group.
988                     grname = grp.getgrgid(userlist[f])[0]
989             except Exception, e:
990                 pass
991
992         if grname is None:
993             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])
994             continue
995
996         to = tf.gettarinfo(os.path.join(GlobalDir, 'userkeys', f), f)
997         # These will only be used where the username doesn't
998         # exist on the target system for some reason; hence,
999         # in those cases, the safest thing is for the file to
1000         # be owned by root but group nobody.  This deals with
1001         # the bloody obscure case where the group fails to exist
1002         # whilst the user does (in which case we want to avoid
1003         # ending up with a file which is owned user:root to avoid
1004         # a fairly obvious attack vector)
1005         to.uid = 0
1006         to.gid = 65534
1007         # Using the username / groupname fields avoids any need
1008         # to give a shit^W^W^Wcare about the UIDoffset stuff.
1009         to.uname = f
1010         to.gname = grname
1011         to.mode  = 0600
1012         tf.addfile(to, file(os.path.join(GlobalDir, 'userkeys', f)))
1013
1014       tf.close()
1015       os.rename(os.path.join(GlobalDir, 'ssh-keys-%s.tar.gz' % CurrentHost),
1016                 os.path.join(OutDir, 'ssh-keys.tar.gz'))
1017
1018    # Link in global things   
1019    DoLink(GlobalDir,OutDir,"markers");
1020    DoLink(GlobalDir,OutDir,"mail-forward.cdb");
1021    DoLink(GlobalDir,OutDir,"mail-disable");
1022    DoLink(GlobalDir,OutDir,"mail-greylist");
1023    DoLink(GlobalDir,OutDir,"mail-callout");
1024    DoLink(GlobalDir,OutDir,"mail-rbl");
1025    DoLink(GlobalDir,OutDir,"mail-rhsbl");
1026    DoLink(GlobalDir,OutDir,"mail-whitelist");
1027
1028    # Compatibility.
1029    DoLink(GlobalDir,OutDir,"forward-alias");
1030
1031    if ExtraList.has_key("[DNS]"):
1032       GenDNS(l,OutDir+"dns-zone",Split[1]);
1033       GenSSHFP(l,OutDir+"dns-sshfp",Split[1])
1034       
1035    if ExtraList.has_key("[BSMTP]"):
1036       GenBSMTP(l,OutDir+"bsmtp",Split[1])
1037
1038    if ExtraList.has_key("[PRIVATE]"):
1039       DoLink(GlobalDir,OutDir,"debian-private")
1040
1041    if ExtraList.has_key("[KEYRING]"):
1042       for k in Keyrings:
1043         DoLink(GlobalDir,OutDir,os.path.basename(k))
1044    else:
1045      for k in Keyrings:
1046        try: posix.remove(OutDir+os.path.basename(k));
1047        except: pass;