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