3 # Generates passwd, shadow and group files from the ldap directory.
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>
11 # This program is free software; you can redistribute it and/or modify
12 # it under the terms of the GNU General Public License as published by
13 # the Free Software Foundation; either version 2 of the License, or
14 # (at your option) any later version.
16 # This program is distributed in the hope that it will be useful,
17 # but WITHOUT ANY WARRANTY; without even the implied warranty of
18 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 # GNU General Public License for more details.
21 # You should have received a copy of the GNU General Public License
22 # along with this program; if not, write to the Free Software
23 # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
25 import string, re, time, ldap, getopt, sys, os, pwd, posix, socket, base64, sha, shutil, errno, tarfile
26 from userdir_ldap import *;
36 EmailCheck = re.compile("^([^ <>@]+@[^ ,<>@]+)?$");
37 BSMTPCheck = re.compile(".*mx 0 (gluck)\.debian\.org\..*",re.DOTALL);
38 DNSZone = ".debian.net"
39 Keyrings = [ "/org/keyring.debian.org/keyrings/debian-keyring.gpg",
40 "/org/keyring.debian.org/keyrings/debian-keyring.pgp" ]
42 def safe_makedirs(dir):
46 if e.errno == errno.EEXIST:
55 if e.errno == errno.ENOENT:
61 return Str.translate(string.maketrans("\n\r\t","$$$"))
63 def DoLink(From,To,File):
64 try: posix.remove(To+File);
66 posix.link(From+File,To+File);
68 # See if this user is in the group list
69 def IsInGroup(DnRecord):
73 # See if the primary group is in the list
74 if Allowed.has_key(GetAttr(DnRecord,"gidNumber")) != 0:
77 # Check the host based ACL
78 if DnRecord[1].has_key("allowedHost") != 0:
79 for I in DnRecord[1]["allowedHost"]:
83 # See if there are supplementary groups
84 if DnRecord[1].has_key("supplementaryGid") == 0:
87 # Check the supplementary groups
88 for I in DnRecord[1]["supplementaryGid"]:
89 if Allowed.has_key(I):
98 try: os.remove(File + ".tmp");
100 try: os.remove(File + ".tdb.tmp");
103 def Done(File,F,Fdb):
106 os.rename(File + ".tmp",File);
109 os.rename(File + ".tdb.tmp",File+".tdb");
111 # Generate the password list
112 def GenPasswd(l,File,HomePrefix,PwdMarker):
115 F = open(File + ".tdb.tmp","w");
118 # Fetch all the users
120 if PasswdAttrs == None:
124 for x in PasswdAttrs:
125 if x[1].has_key("uidNumber") == 0 or IsInGroup(x) == 0:
128 # Do not let people try to buffer overflow some busted passwd parser.
129 if len(GetAttr(x,"gecos")) > 100 or len(GetAttr(x,"loginShell")) > 50:
132 userlist.append(GetAttr(x, "uid"))
133 Line = "%s:%s:%s:%s:%s:%s%s:%s" % (GetAttr(x,"uid"),\
135 GetAttr(x,"uidNumber"),GetAttr(x,"gidNumber"),\
136 GetAttr(x,"gecos"),HomePrefix,GetAttr(x,"uid"),\
137 GetAttr(x,"loginShell"));
139 Line = Sanitize(Line) + "\n";
140 F.write("0%u %s" % (I,Line));
141 F.write(".%s %s" % (GetAttr(x,"uid"),Line));
142 F.write("=%s %s" % (GetAttr(x,"uidNumber"),Line));
145 # Oops, something unspeakable happened.
151 # Return the list of users so we know which keys to export
154 # Generate the shadow list
155 def GenShadow(l,File):
158 OldMask = os.umask(0077);
159 F = open(File + ".tdb.tmp","w",0600);
162 # Fetch all the users
164 if PasswdAttrs == None:
168 for x in PasswdAttrs:
169 if x[1].has_key("uidNumber") == 0 or IsInGroup(x) == 0:
172 Pass = GetAttr(x,"userPassword");
173 if Pass[0:7] != "{crypt}" or len(Pass) > 50:
178 # If the account is locked, mark it as such in shadow
179 # See Debian Bug #308229 for why we set it to 1 instead of 0
180 if (GetAttr(x,"userPassword").find("*LK*") != -1) \
181 or GetAttr(x,"userPassword").startswith("!"):
184 ShadowExpire = GetAttr(x,"shadowexpire")
186 Line = "%s:%s:%s:%s:%s:%s:%s:%s:" % (GetAttr(x,"uid"),\
187 Pass,GetAttr(x,"shadowLastChange"),\
188 GetAttr(x,"shadowMin"),GetAttr(x,"shadowMax"),\
189 GetAttr(x,"shadowWarning"),GetAttr(x,"shadowinactive"),\
191 Line = Sanitize(Line) + "\n";
192 F.write("0%u %s" % (I,Line));
193 F.write(".%s %s" % (GetAttr(x,"uid"),Line));
196 # Oops, something unspeakable happened.
202 # Generate the shadow list
203 def GenSSHShadow(l,masterFileName):
204 # Fetch all the users
207 # Depending on config, we write out either a single file,
208 # multiple files, or both
211 OldMask = os.umask(0077);
212 masterFile = open(masterFileName + ".tmp","w",0600);
215 Die(masterFileName,masterFile,None)
219 if PasswdAttrs == None:
222 # If we're going to be dealing with multiple keys, empty the
223 # directory before we start to avoid old keys hanging around
225 safe_rmtree(os.path.join(GlobalDir, 'userkeys'))
226 safe_makedirs(os.path.join(GlobalDir, 'userkeys'))
228 for x in PasswdAttrs:
229 # If the account is locked, do not write it.
230 # This is a partial stop-gap. The ssh also needs to change this
231 # to ignore ~/.ssh/authorized* files.
232 if (GetAttr(x,"userPassword").find("*LK*") != -1) \
233 or GetAttr(x,"userPassword").startswith("!"):
236 if x[1].has_key("uidNumber") == 0 or \
237 x[1].has_key("sshRSAAuthKey") == 0:
239 User = GetAttr(x,"uid");
244 OldMask = os.umask(0077);
245 File = os.path.join(GlobalDir, 'userkeys', User)
246 F = open(File + ".tmp","w",0600);
249 for I in x[1]["sshRSAAuthKey"]:
251 MultipleLine = "%s" % I
252 MultipleLine = Sanitize(MultipleLine) + "\n"
253 F.write(MultipleLine)
255 SingleLine = "%s: %s" % (User, I)
256 SingleLine = Sanitize(SingleLine) + "\n"
257 masterFile.write(SingleLine)
261 userfiles.append(os.path.basename(File))
263 # Oops, something unspeakable happened.
266 Die(masterFileName,masterFile,None)
270 Done(masterFileName,masterFile,None)
271 singlefile = os.path.basename(masterFileName)
273 return singlefile, userfiles
275 # Generate the group list
276 def GenGroup(l,File):
279 F = open(File + ".tdb.tmp","w");
281 # Generate the GroupMap
283 for x in GroupIDMap.keys():
286 # Fetch all the users
288 if PasswdAttrs == None:
291 # Sort them into a list of groups having a set of users
292 for x in PasswdAttrs:
293 if x[1].has_key("uidNumber") == 0 or IsInGroup(x) == 0:
295 if x[1].has_key("supplementaryGid") == 0:
298 for I in x[1]["supplementaryGid"]:
299 if GroupMap.has_key(I):
300 GroupMap[I].append(GetAttr(x,"uid"));
302 print "Group does not exist ",I,"but",GetAttr(x,"uid"),"is in it";
304 # Output the group file.
306 for x in GroupMap.keys():
307 if GroupIDMap.has_key(x) == 0:
309 Line = "%s:x:%u:" % (x,GroupIDMap[x]);
311 for I in GroupMap[x]:
312 Line = Line + ("%s%s" % (Comma,I));
314 Line = Sanitize(Line) + "\n";
315 F.write("0%u %s" % (J,Line));
316 F.write(".%s %s" % (x,Line));
317 F.write("=%u %s" % (GroupIDMap[x],Line));
320 # Oops, something unspeakable happened.
326 # Generate the email forwarding list
327 def GenForward(l,File):
330 OldMask = os.umask(0022);
331 F = open(File + ".tmp","w",0644);
334 # Fetch all the users
336 if PasswdAttrs == None:
339 # Write out the email address for each user
340 for x in PasswdAttrs:
341 if x[1].has_key("emailForward") == 0 or IsInGroup(x) == 0:
344 # Do not allow people to try to buffer overflow busted parsers
345 if len(GetAttr(x,"emailForward")) > 200:
348 # Check the forwarding address
349 if EmailCheck.match(GetAttr(x,"emailForward")) == None:
351 Line = "%s: %s" % (GetAttr(x,"uid"),GetAttr(x,"emailForward"));
352 Line = Sanitize(Line) + "\n";
355 # Oops, something unspeakable happened.
361 def GenAllForward(l,File):
364 OldMask = os.umask(0022);
365 Fdb = os.popen("cdbmake %s %s.tmp"%(File,File),"w");
368 # Fetch all the users
370 if PasswdAttrs == None:
373 # Write out the email address for each user
374 for x in PasswdAttrs:
375 if x[1].has_key("emailForward") == 0:
378 # Do not allow people to try to buffer overflow busted parsers
379 Forward = GetAttr(x,"emailForward");
380 if len(Forward) > 200:
383 # Check the forwarding address
384 if EmailCheck.match(Forward) == None:
387 User = GetAttr(x,"uid");
388 Fdb.write("+%d,%d:%s->%s\n"%(len(User),len(Forward),User,Forward));
390 # Oops, something unspeakable happened.
394 if Fdb.close() != None:
395 raise "cdbmake gave an error";
397 # Generate the anon XEarth marker file
398 def GenMarkers(l,File):
401 F = open(File + ".tmp","w");
403 # Fetch all the users
405 if PasswdAttrs == None:
408 # Write out the position for each user
409 for x in PasswdAttrs:
410 if x[1].has_key("latitude") == 0 or x[1].has_key("longitude") == 0:
413 Line = "%8s %8s \"\""%(DecDegree(GetAttr(x,"latitude"),1),DecDegree(GetAttr(x,"longitude"),1));
414 Line = Sanitize(Line) + "\n";
419 # Oops, something unspeakable happened.
425 # Generate the debian-private subscription list
426 def GenPrivate(l,File):
429 F = open(File + ".tmp","w");
431 # Fetch all the users
433 if PasswdAttrs == None:
436 # Write out the position for each user
437 for x in PasswdAttrs:
438 if x[1].has_key("privateSub") == 0:
441 # If the account is locked, do not write it
442 if (GetAttr(x,"userPassword").find("*LK*") != -1) \
443 or GetAttr(x,"userPassword").startswith("!"):
446 # If the account has no PGP key, do not write it
447 if x[1].has_key("keyFingerPrint") == 0:
450 # Must be in the Debian group (yuk, hard coded for now)
451 if GetAttr(x,"gidNumber") != "800":
455 Line = "%s"%(GetAttr(x,"privateSub"));
456 Line = Sanitize(Line) + "\n";
461 # Oops, something unspeakable happened.
467 # Generate a list of locked accounts
468 def GenDisabledAccounts(l,File):
471 F = open(File + ".tmp","w");
473 # Fetch all the users
475 if PasswdAttrs == None:
479 for x in PasswdAttrs:
480 if x[1].has_key("uidNumber") == 0:
483 Pass = GetAttr(x,"userPassword");
485 # *LK* is the reference value for a locked account
486 # password starting with ! is also a locked account
487 if Pass.find("*LK*") != -1 or Pass.startswith("!"):
488 # Format is <login>:<reason>
489 Line = "%s:%s" % (GetAttr(x,"uid"), "Account is locked")
492 F.write(Sanitize(Line) + "\n")
494 # Oops, something unspeakable happened.
500 # Generate the list of local addresses that refuse all mail
501 def GenMailDisable(l,File):
504 F = open(File + ".tmp","w");
506 # Fetch all the users
508 if PasswdAttrs == None:
511 for x in PasswdAttrs:
514 # If the account is locked, disable incoming mail
515 if (GetAttr(x,"userPassword").find("*LK*") != -1):
516 if GetAttr(x,"uid") == "luther":
519 Reason = "user account locked"
521 if x[1].has_key("mailDisableMessage"):
522 Reason = GetAttr(x,"mailDisableMessage")
526 # Must be in the Debian group (yuk, hard coded for now)
527 if GetAttr(x,"gidNumber") != "800":
531 Line = "%s: %s"%(GetAttr(x,"uid"),Reason);
532 Line = Sanitize(Line) + "\n";
537 # Oops, something unspeakable happened.
543 # Generate a list of uids that should have boolean affects applied
544 def GenMailBool(l,File,Key):
547 F = open(File + ".tmp","w");
549 # Fetch all the users
551 if PasswdAttrs == None:
554 for x in PasswdAttrs:
557 if x[1].has_key(Key) == 0:
560 # Must be in the Debian group (yuk, hard coded for now)
561 if GetAttr(x,"gidNumber") != "800":
564 if GetAttr(x,Key) != "TRUE":
568 Line = "%s"%(GetAttr(x,"uid"));
569 Line = Sanitize(Line) + "\n";
574 # Oops, something unspeakable happened.
580 # Generate a list of hosts for RBL or whitelist purposes.
581 def GenMailList(l,File,Key):
584 F = open(File + ".tmp","w");
586 # Fetch all the users
588 if PasswdAttrs == None:
591 for x in PasswdAttrs:
594 if x[1].has_key(Key) == 0:
597 # Must be in the Debian group (yuk, hard coded for now)
598 if GetAttr(x,"gidNumber") != "800":
605 if Key == "mailWhitelist":
606 if re.match('^[-\w.]+(/[\d]+)?$',z) == None:
609 if re.match('^[-\w.]+$',z) == None:
613 Line = GetAttr(x,"uid")
617 if Key == "mailRHSBL":
618 Line += "/$sender_address_domain"
621 Line = Sanitize(Line) + "\n";
626 # Oops, something unspeakable happened.
632 # Generate the DNS Zone file
633 def GenDNS(l,File,HomePrefix):
636 F = open(File + ".tmp","w");
638 # Fetch all the users
640 if PasswdAttrs == None:
643 # Write out the zone file entry for each user
644 for x in PasswdAttrs:
645 if x[1].has_key("dnsZoneEntry") == 0:
648 # If the account has no PGP key, do not write it
649 if x[1].has_key("keyFingerPrint") == 0:
652 F.write("; %s\n"%(EmailAddress(x)));
653 for z in x[1]["dnsZoneEntry"]:
654 Split = z.lower().split()
655 if Split[1].lower() == 'in':
656 for y in range(0,len(Split)):
659 Line = " ".join(Split) + "\n";
662 Host = Split[0] + DNSZone;
663 if BSMTPCheck.match(Line) != None:
664 F.write("; Has BSMTP\n");
666 # Write some identification information
667 if Split[2].lower() == "a":
668 Line = "%s IN TXT \"%s\"\n"%(Split[0],EmailAddress(x));
669 for y in x[1]["keyFingerPrint"]:
670 Line = Line + "%s IN TXT \"PGP %s\"\n"%(Split[0],FormatPGPKey(y));
673 Line = "; Err %s"%(str(Split));
678 F.write("; Errors\n");
681 # Oops, something unspeakable happened.
687 # Generate the DNS SSHFP records
688 def GenSSHFP(l,File,HomePrefix):
691 F = open(File + ".tmp","w")
693 # Fetch all the hosts
695 if HostAttrs == None:
699 if x[1].has_key("hostname") == 0 or \
700 x[1].has_key("sshRSAHostKey") == 0:
702 Host = GetAttr(x,"hostname");
704 for I in x[1]["sshRSAHostKey"]:
706 if Split[0] == 'ssh-rsa':
708 if Split[0] == 'ssh-dss':
710 if Algorithm == None:
712 Fingerprint = sha.new(base64.decodestring(Split[1])).hexdigest()
713 Line = "%s. IN SSHFP %u 1 %s" % (Host,Algorithm,Fingerprint)
714 Line = Sanitize(Line) + "\n"
716 # Oops, something unspeakable happened.
722 # Generate the BSMTP file
723 def GenBSMTP(l,File,HomePrefix):
726 F = open(File + ".tmp","w");
728 # Fetch all the users
730 if PasswdAttrs == None:
733 # Write out the zone file entry for each user
734 for x in PasswdAttrs:
735 if x[1].has_key("dnsZoneEntry") == 0:
738 # If the account has no PGP key, do not write it
739 if x[1].has_key("keyFingerPrint") == 0:
742 for z in x[1]["dnsZoneEntry"]:
743 Split = z.lower().split()
744 if Split[1].lower() == 'in':
745 for y in range(0,len(Split)):
748 Line = " ".join(Split) + "\n";
750 Host = Split[0] + DNSZone;
751 if BSMTPCheck.match(Line) != None:
752 F.write("%s: user=%s group=Debian file=%s%s/bsmtp/%s\n"%(Host,
753 GetAttr(x,"uid"),HomePrefix,GetAttr(x,"uid"),Host));
756 F.write("; Errors\n");
759 # Oops, something unspeakable happened.
765 # Generate the ssh known hosts file
766 def GenSSHKnown(l,File):
769 OldMask = os.umask(0022);
770 F = open(File + ".tmp","w",0644);
774 if HostAttrs == None:
778 if x[1].has_key("hostname") == 0 or \
779 x[1].has_key("sshRSAHostKey") == 0:
781 Host = GetAttr(x,"hostname");
783 SHost = Host.find(".")
784 if SHost != None: HostNames += [Host[0:SHost]]
788 # get IP adresses back as "proto adress" to distinguish between v4 and v6
790 IPAdressesT = set([ (a[0],a[4][0]) for a in socket.getaddrinfo(Host, None)])
792 if code[0] != -2: raise
793 for addr in IPAdressesT:
794 if addr[0] == socket.AF_INET: IPAdresses += [addr[1], "::ffff:"+addr[1]]
795 else: IPAdresses += [addr[1]]
797 for I in x[1]["sshRSAHostKey"]:
798 Line = "%s %s" %(",".join(HostNames + IPAdresses), I);
799 Line = Sanitize(Line) + "\n";
801 # Oops, something unspeakable happened.
807 # Generate the debianhosts file (list of all IP addresses)
808 def GenHosts(l,File):
811 OldMask = os.umask(0022);
812 F = open(File + ".tmp","w",0644);
815 # Fetch all the hosts
816 HostNames = l.search_s(HostBaseDn,ldap.SCOPE_ONELEVEL,"hostname=*",\
819 if HostNames == None:
823 if x[1].has_key("hostname") == 0:
825 Host = GetAttr(x,"hostname");
827 Addr = socket.gethostbyname(Host);
828 F.write(Addr + "\n");
831 # Oops, something unspeakable happened.
837 def GenKeyrings(l,OutDir):
839 shutil.copy(k, OutDir)
841 # Connect to the ldap server
842 l = ldap.open(LDAPServer);
843 F = open(PassDir+"/pass-"+pwd.getpwuid(os.getuid())[0],"r");
844 Pass = F.readline().strip().split(" ")
846 l.simple_bind_s("uid="+Pass[0]+","+BaseDn,Pass[1]);
848 # Fetch all the groups
850 Attrs = l.search_s(BaseDn,ldap.SCOPE_ONELEVEL,"gid=*",\
851 ["gid","gidNumber"]);
853 # Generate the GroupMap and GroupIDMap
855 if x[1].has_key("gidNumber") == 0:
857 GroupIDMap[x[1]["gid"][0]] = int(x[1]["gidNumber"][0]);
859 # Fetch all the users
860 PasswdAttrs = l.search_s(BaseDn,ldap.SCOPE_ONELEVEL,"uid=*",\
861 ["uid","uidNumber","gidNumber","supplementaryGid",\
862 "gecos","loginShell","userPassword","shadowLastChange",\
863 "shadowMin","shadowMax","shadowWarning","shadowinactive",
864 "shadowexpire","emailForward","latitude","longitude",\
865 "allowedHost","sshRSAAuthKey","dnsZoneEntry","cn","sn",\
866 "keyFingerPrint","privateSub","mailDisableMessage",\
867 "mailGreylisting","mailCallout","mailRBL","mailRHSBL",\
869 # Fetch all the hosts
870 HostAttrs = l.search_s(HostBaseDn,ldap.SCOPE_ONELEVEL,"sshRSAHostKey=*",\
871 ["hostname","sshRSAHostKey"]);
873 # Open the control file
874 if len(sys.argv) == 1:
875 F = open(GenerateConf,"r");
877 F = open(sys.argv[1],"r")
879 # Generate global things
880 GlobalDir = GenerateDir+"/";
881 SSHGlobal, SSHFiles = GenSSHShadow(l,GlobalDir+"ssh-rsa-shadow");
882 GenAllForward(l,GlobalDir+"mail-forward.cdb");
883 GenMarkers(l,GlobalDir+"markers");
884 GenPrivate(l,GlobalDir+"debian-private");
885 GenDisabledAccounts(l,GlobalDir+"disabled-accounts");
886 GenSSHKnown(l,GlobalDir+"ssh_known_hosts");
887 GenHosts(l,GlobalDir+"debianhosts");
888 GenMailDisable(l,GlobalDir+"mail-disable");
889 GenMailBool(l,GlobalDir+"mail-greylist","mailGreylisting");
890 GenMailBool(l,GlobalDir+"mail-callout","mailCallout");
891 GenMailList(l,GlobalDir+"mail-rbl","mailRBL");
892 GenMailList(l,GlobalDir+"mail-rhsbl","mailRHSBL");
893 GenMailList(l,GlobalDir+"mail-whitelist","mailWhitelist");
894 GenKeyrings(l,GlobalDir);
897 GenForward(l,GlobalDir+"forward-alias");
909 Split = Line.split(" ")
910 OutDir = GenerateDir + '/' + Split[0] + '/';
911 try: os.mkdir(OutDir);
914 # Get the group list and convert any named groups to numerics
922 if GroupIDMap.has_key(I):
923 GroupList[str(GroupIDMap[I])] = None;
928 CurrentHost = Split[0];
930 # If we're using a single SSH file, deal with it
931 if SSHGlobal is not None:
932 DoLink(GlobalDir, OutDir, SSHGlobal)
934 DoLink(GlobalDir,OutDir,"debianhosts");
935 DoLink(GlobalDir,OutDir,"ssh_known_hosts");
936 DoLink(GlobalDir,OutDir,"disabled-accounts")
939 if ExtraList.has_key("[NOPASSWD]"):
940 userlist = GenPasswd(l,OutDir+"passwd",Split[1], "*");
942 userlist = GenPasswd(l,OutDir+"passwd",Split[1], "x");
944 GenGroup(l,OutDir+"group");
945 if ExtraList.has_key("[UNTRUSTED]"):
947 if not ExtraList.has_key("[NOPASSWD]"):
948 GenShadow(l,OutDir+"shadow");
950 # Now we know who we're allowing on the machine, export
951 # the relevant ssh keys
953 tf = tarfile.open(name=os.path.join(GlobalDir, 'ssh-keys-%s.tar.gz' % CurrentHost), mode='w:gz')
955 if f not in SSHFiles:
957 to = tf.gettarinfo(os.path.join(GlobalDir, 'userkeys', f), f)
958 # These will only be used where the username doesn't
959 # exist on the target system for some reason; hence,
960 # in those cases, the safest thing is for the file to
964 # Using the username / groupname fields avoids any need
965 # to give a shit^W^W^Wcare about the UIDoffset stuff.
969 tf.addfile(to, file(os.path.join(GlobalDir, 'userkeys', f)))
972 os.rename(os.path.join(GlobalDir, 'ssh-keys-%s.tar.gz' % CurrentHost),
973 os.path.join(OutDir, 'ssh-keys.tar.gz'))
975 # Link in global things
976 DoLink(GlobalDir,OutDir,"markers");
977 DoLink(GlobalDir,OutDir,"mail-forward.cdb");
978 DoLink(GlobalDir,OutDir,"mail-disable");
979 DoLink(GlobalDir,OutDir,"mail-greylist");
980 DoLink(GlobalDir,OutDir,"mail-callout");
981 DoLink(GlobalDir,OutDir,"mail-rbl");
982 DoLink(GlobalDir,OutDir,"mail-rhsbl");
983 DoLink(GlobalDir,OutDir,"mail-whitelist");
986 DoLink(GlobalDir,OutDir,"forward-alias");
988 if ExtraList.has_key("[DNS]"):
989 GenDNS(l,OutDir+"dns-zone",Split[1]);
990 GenSSHFP(l,OutDir+"dns-sshfp",Split[1])
992 if ExtraList.has_key("[BSMTP]"):
993 GenBSMTP(l,OutDir+"bsmtp",Split[1])
995 if ExtraList.has_key("[PRIVATE]"):
996 DoLink(GlobalDir,OutDir,"debian-private")
998 if ExtraList.has_key("[KEYRING]"):
1000 DoLink(GlobalDir,OutDir,os.path.basename(k))
1003 try: posix.remove(OutDir+os.path.basename(k));