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