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