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