add IP address to known hosts file
[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 import string, re, time, ldap, getopt, sys, os, pwd, posix;
6 from userdir_ldap import *;
7
8 PasswdAttrs = None;
9 GroupIDMap = {};
10 Allowed = None;
11 CurrentHost = "";
12
13 EmailCheck = re.compile("^([^ <>@]+@[^ ,<>@]+)?$");
14 BSMTPCheck = re.compile(".*mx 0 klecker\.debian\.org\..*",re.DOTALL);
15 DNSZone = ".debian.net"
16
17 def Sanitize(Str):
18   return string.translate(Str,string.maketrans("\n\r\t","$$$"));
19
20 def DoLink(From,To,File):
21    try: posix.remove(To+File);
22    except: pass;
23    posix.link(From+File,To+File);
24
25 # See if this user is in the group list
26 def IsInGroup(DnRecord):
27   global Allowed,CurrentHost;
28   if Allowed == None:
29      return 1;
30
31   # See if the primary group is in the list
32   if Allowed.has_key(GetAttr(DnRecord,"gidnumber")) != 0:
33      return 1;
34
35   # Check the host based ACL
36   if DnRecord[1].has_key("allowedhosts") != 0:
37      for I in DnRecord[1]["allowedhosts"]:
38         if CurrentHost == I:
39            return 1;
40
41   # See if there are supplementary groups
42   if DnRecord[1].has_key("supplementarygid") == 0:
43      return 0;
44
45   # Check the supplementary groups
46   for I in DnRecord[1]["supplementarygid"]:
47      if Allowed.has_key(I):
48         return 1;
49   return 0;
50
51 def Die(File,F,Fdb):
52    if F != None:
53       F.close();
54    if Fdb != None:
55       Fdb.close();
56    try: os.remove(File + ".tmp");
57    except: pass;
58    try: os.remove(File + ".tdb.tmp");
59    except: pass;
60
61 def Done(File,F,Fdb):
62   if F != None:
63     F.close();
64     os.rename(File + ".tmp",File);
65   if Fdb != None:
66     Fdb.close();
67     os.rename(File + ".tdb.tmp",File+".tdb");
68   
69 # Generate the password list
70 def GenPasswd(l,File,HomePrefix):
71   F = None;
72   Fdb = None;
73   try:
74    F = open(File + ".tmp","w");
75    Fdb = open(File + ".tdb.tmp","w");
76
77    # Fetch all the users
78    global PasswdAttrs;
79    if PasswdAttrs == None:
80       raise "No Users";
81
82    I = 0;
83    for x in PasswdAttrs:
84       if x[1].has_key("uidnumber") == 0 or IsInGroup(x) == 0:
85          continue;
86
87       # Do not let people try to buffer overflow some busted passwd parser.
88       if len(GetAttr(x,"gecos")) > 100 or len(GetAttr(x,"loginshell")) > 50:
89          continue;
90
91       Line = "%s:x:%s:%s:%s:%s%s:%s\n" % (GetAttr(x,"uid"),\
92               GetAttr(x,"uidnumber"),GetAttr(x,"gidnumber"),\
93               GetAttr(x,"gecos"),HomePrefix,GetAttr(x,"uid"),\
94               GetAttr(x,"loginshell"));
95       F.write(Line);
96       Fdb.write("0%u %s" % (I,Line));
97       Fdb.write(".%s %s" % (GetAttr(x,"uid"),Line));
98       Fdb.write("=%s %s" % (GetAttr(x,"uidnumber"),Line));
99       I = I + 1;
100
101   # Oops, something unspeakable happened.
102   except:
103    Die(File,F,Fdb);
104    raise;
105   Done(File,F,Fdb);
106
107 # Generate the shadow list
108 def GenShadow(l,File):
109   F = None;
110   Fdb = None;
111   try:
112    OldMask = os.umask(0077);
113    F = open(File + ".tmp","w",0600);
114    Fdb = open(File + ".tdb.tmp","w",0600);
115    os.umask(OldMask);
116
117    # Fetch all the users
118    global PasswdAttrs;
119    if PasswdAttrs == None:
120       raise "No Users";
121
122    I = 0;
123    for x in PasswdAttrs:
124       if x[1].has_key("uidnumber") == 0 or IsInGroup(x) == 0:
125          continue;
126          
127       Pass = GetAttr(x,"userpassword");
128       if Pass[0:7] != "{crypt}" or len(Pass) > 50:
129          Pass = '*';
130       else:
131          Pass = Pass[7:];
132       Line = "%s:%s:%s:%s:%s:%s:%s:%s:" % (GetAttr(x,"uid"),\
133               Pass,GetAttr(x,"shadowlastchange"),\
134               GetAttr(x,"shadowmin"),GetAttr(x,"shadowmax"),\
135               GetAttr(x,"shadowwarning"),GetAttr(x,"shadowinactive"),\
136               GetAttr(x,"shadowexpire"));
137       Line = Sanitize(Line) + "\n";
138       F.write(Line);
139       Fdb.write("0%u %s" % (I,Line));
140       Fdb.write(".%s %s" % (GetAttr(x,"uid"),Line));
141       I = I + 1;
142
143   # Oops, something unspeakable happened.
144   except:
145    Die(File,F,Fdb);
146    raise;
147   Done(File,F,Fdb);
148
149 # Generate the shadow list
150 def GenSSHShadow(l,File):
151   F = None;
152   Fdb = None;
153   try:
154    OldMask = os.umask(0077);
155    F = open(File + ".tmp","w",0600);
156    Fdb = os.popen("cdbmake %s.cdb %s.cdb.tmp"%(File,File),"w");
157    os.umask(OldMask);
158
159    # Fetch all the users
160    global PasswdAttrs;
161    if PasswdAttrs == None:
162       raise "No Users";
163
164    I = 0;
165    for x in PasswdAttrs:
166       if x[1].has_key("uidnumber") == 0 or \
167          x[1].has_key("sshrsaauthkey") == 0:
168          continue;
169       for I in x[1]["sshrsaauthkey"]:
170          User = GetAttr(x,"uid");
171          Line = "%s: %s" %(User,I);
172          Line = Sanitize(Line) + "\n";
173          F.write(Line);
174          Fdb.write("+%d,%d:%s->%s\n"%(len(User),len(I),User,I));
175    Fdb.write("\n");
176   # Oops, something unspeakable happened.
177   except:
178    Die(File,F,Fdb);
179    raise;
180   if Fdb.close() != None:
181      raise "cdbmake gave an error";
182   Done(File,F,None);
183
184 # Generate the group list
185 def GenGroup(l,File):
186   F = None;
187   Fdb = None;
188   try:
189    F = open(File + ".tmp","w");
190    Fdb = open(File + ".tdb.tmp","w");
191
192    # Generate the GroupMap
193    GroupMap = {};
194    for x in GroupIDMap.keys():
195       GroupMap[x] = [];
196       
197    # Fetch all the users
198    global PasswdAttrs;
199    if PasswdAttrs == None:
200       raise "No Users";
201
202    # Sort them into a list of groups having a set of users
203    for x in PasswdAttrs:
204       if x[1].has_key("uidnumber") == 0 or IsInGroup(x) == 0:
205          continue;
206       if x[1].has_key("supplementarygid") == 0:
207          continue;
208          
209       for I in x[1]["supplementarygid"]:
210          if GroupMap.has_key(I):
211             GroupMap[I].append(GetAttr(x,"uid"));
212          else:
213             print "Group does not exist ",I,"but",GetAttr(x,"uid"),"is in it";
214             
215    # Output the group file.
216    Counter = 0; 
217    for x in GroupMap.keys():
218       if GroupIDMap.has_key(x) == 0:
219          continue;
220       Line = "%s:x:%u:" % (x,GroupIDMap[x]);
221       Comma = '';
222       for I in GroupMap[x]:
223         Line = Line + ("%s%s" % (Comma,I));
224         Comma = ',';
225       Line = Sanitize(Line) + "\n";
226       F.write(Line);
227       Fdb.write("0%u %s" % (Counter,Line));
228       Fdb.write(".%s %s" % (x,Line));
229       Fdb.write("=%u %s" % (GroupIDMap[x],Line));
230       Counter = Counter + 1;
231       
232   # Oops, something unspeakable happened.
233   except:
234    Die(File,F,Fdb);
235    raise;
236   Done(File,F,Fdb);
237
238 # Generate the email forwarding list
239 def GenForward(l,File):
240   F = None;
241   Fdb = None;
242   try:
243    OldMask = os.umask(0022);
244    F = open(File + ".tmp","w",0644);
245    os.umask(OldMask);
246
247    # Fetch all the users
248    global PasswdAttrs;
249    if PasswdAttrs == None:
250       raise "No Users";
251
252    # Write out the email address for each user
253    for x in PasswdAttrs:
254       if x[1].has_key("emailforward") == 0 or IsInGroup(x) == 0:
255          continue;
256       
257       # Do not allow people to try to buffer overflow busted parsers
258       if len(GetAttr(x,"emailforward")) > 200:
259          continue;
260
261       # Check the forwarding address
262       if EmailCheck.match(GetAttr(x,"emailforward")) == None:
263          continue;
264       Line = "%s: %s" % (GetAttr(x,"uid"),GetAttr(x,"emailforward"));
265       Line = Sanitize(Line) + "\n";
266       F.write(Line);
267       
268   # Oops, something unspeakable happened.
269   except:
270    Die(File,F,Fdb);
271    raise;
272   Done(File,F,Fdb);
273
274 def GenAllForward(l,File):
275   Fdb = None;
276   try:
277    OldMask = os.umask(0022);
278    Fdb = os.popen("cdbmake %s %s.tmp"%(File,File),"w");
279    os.umask(OldMask);
280
281    # Fetch all the users
282    global PasswdAttrs;
283    if PasswdAttrs == None:
284       raise "No Users";
285
286    # Write out the email address for each user
287    for x in PasswdAttrs:
288       if x[1].has_key("emailforward") == 0:
289          continue;
290       
291       # Do not allow people to try to buffer overflow busted parsers
292       Forward = GetAttr(x,"emailforward");
293       if len(Forward) > 200:
294          continue;
295
296       # Check the forwarding address
297       if EmailCheck.match(Forward) == None:
298          continue;
299          
300       User = GetAttr(x,"uid");
301       Fdb.write("+%d,%d:%s->%s\n"%(len(User),len(Forward),User,Forward));
302    Fdb.write("\n");
303   # Oops, something unspeakable happened.
304   except:
305     Fdb.close();
306     raise;
307   if Fdb.close() != None:
308     raise "cdbmake gave an error";
309
310 # Generate the anon XEarth marker file 
311 def GenMarkers(l,File):
312   F = None;
313   Fdb = None;
314   try:
315    F = open(File + ".tmp","w");
316    Fdb = None;
317
318    # Fetch all the users
319    global PasswdAttrs;
320    if PasswdAttrs == None:
321       raise "No Users";
322
323    # Write out the position for each user
324    for x in PasswdAttrs:
325       if x[1].has_key("latitude") == 0 or x[1].has_key("longitude") == 0:
326          continue;       
327       try:
328          Line = "%8s %8s \"\""%(DecDegree(GetAttr(x,"latitude"),1),DecDegree(GetAttr(x,"longitude"),1));
329          Line = Sanitize(Line) + "\n";
330          F.write(Line);
331       except:
332          pass;
333       
334   # Oops, something unspeakable happened.
335   except:
336    Die(File,F,Fdb);
337    raise;
338   Done(File,F,Fdb);
339
340 # Generate the debian-private subscription list
341 def GenPrivate(l,File):
342   F = None;
343   Fdb = None;
344   try:
345    F = open(File + ".tmp","w");
346    Fdb = None;
347
348    # Fetch all the users
349    global PasswdAttrs;
350    if PasswdAttrs == None:
351       raise "No Users";
352
353    # Write out the position for each user
354    for x in PasswdAttrs:
355       if x[1].has_key("privatesub") == 0:
356          continue;
357
358       # If the account is locked, do not write it
359       if (string.find(GetAttr(x,"userpassword"),"*LK*")  != -1):
360          continue;
361
362       # If the account has no PGP key, do not write it
363       if x[1].has_key("keyfingerprint") == 0:
364          continue;
365
366       # Must be in the Debian group (yuk, hard coded for now)
367       if GetAttr(x,"gidnumber") != "800":
368          continue;
369
370       try:
371          Line = "%s"%(GetAttr(x,"privatesub"));
372          Line = Sanitize(Line) + "\n";
373          F.write(Line);
374       except:
375          pass;
376       
377   # Oops, something unspeakable happened.
378   except:
379    Die(File,F,Fdb);
380    raise;
381   Done(File,F,Fdb);
382
383 # Generate the DNS Zone file
384 def GenDNS(l,File,BSMTPFile,HomePrefix):
385   F = None;
386   FB = None;
387   try:
388    F = open(File + ".tmp","w");
389    FB = open(BSMTPFile + ".tmp","w");
390    
391    # Fetch all the users
392    global PasswdAttrs;
393    if PasswdAttrs == None:
394       raise "No Users";
395
396    # Write out the zone file entry for each user
397    for x in PasswdAttrs:
398       if x[1].has_key("dnszoneentry") == 0:
399          continue;
400       try:
401          F.write("; %s\n"%(EmailAddress(x)));
402          for z in x[1]["dnszoneentry"]:
403             Split = string.split(string.lower(z));
404             if string.lower(Split[1]) == 'in':
405                for y in range(0,len(Split)):
406                   if Split[y] == "$":
407                      Split[y] = "\n\t";
408                Line = string.join(Split," ") + "\n";
409                F.write(Line);
410                
411                Host = Split[0] + DNSZone;
412                if BSMTPCheck.match(Line) != None:
413                    F.write("; Has BSMTP\n");
414                    FB.write("%s: user=%s group=Debian file=%s%s/bsmtp/%s\n"%(Host,
415                                GetAttr(x,"uid"),HomePrefix,GetAttr(x,"uid"),Host));
416                                
417                # Write some identification information
418                if string.lower(Split[2]) == "a":
419                   Line = "%s IN TXT \"%s\"\n"%(Split[0],EmailAddress(x));
420                   for y in x[1]["keyfingerprint"]:
421                      Line = Line + "%s IN TXT \"PGP %s\"\n"%(Split[0],FormatPGPKey(y));
422                   F.write(Line);
423             else:
424                Line = "; Err %s"%(str(Split));
425                F.write(Line);
426
427          F.write("\n");
428       except:
429          F.write("; Errors\n");
430          pass;
431       
432   # Oops, something unspeakable happened.
433   except:
434    Die(File,F,None);
435    Die(BSMTPFile,FB,None);
436    raise;
437   Done(File,F,None);
438   Done(BSMTPFile,FB,None);
439
440 # Generate the shadow list
441 def GenSSHKnown(l,File):
442   F = None;
443   Fdb = None;
444   try:
445    OldMask = os.umask(0022);
446    F = open(File + ".tmp","w",0644);
447    Fdb = os.popen("cdbmake %s.cdb %s.cdb.tmp"%(File,File),"w");
448    os.umask(OldMask);
449
450    # Fetch all the hosts
451    HostKeys = l.search_s(HostBaseDn,ldap.SCOPE_ONELEVEL,"sshrsahostkey=*",\
452                 ["hostname","sshrsahostkey"]);
453    
454    if HostKeys == None:
455       raise "No Hosts";
456
457    I = 0;
458    for x in HostKeys:
459       if x[1].has_key("hostname") == 0 or \
460          x[1].has_key("sshrsahostkey") == 0:
461          continue;
462       Host = GetAttr(x,"hostname");
463       SHost = string.find(Host,".");
464       for I in x[1]["sshrsahostkey"]:
465          if SHost == None:
466             Line = "%s,%s %s" %(Host,gethostbyname(Host),I);
467          else:
468             Line = "%s,%s,%s %s" %(Host,Host[0:SHost],,gethostbyname(Host),I);
469          Line = Sanitize(Line) + "\n";
470          F.write(Line);
471          Fdb.write("+%d,%d:%s->%s\n"%(len(Host),len(I),Host,I));
472    Fdb.write("\n");
473   # Oops, something unspeakable happened.
474   except:
475    Die(File,F,Fdb);
476    raise;
477   if Fdb.close() != None:
478      raise "cdbmake gave an error";
479   Done(File,F,None);
480
481
482 # Connect to the ldap server
483 l = ldap.open(LDAPServer);
484 F = open(PassDir+"/pass-"+pwd.getpwuid(os.getuid())[0],"r");
485 Pass = string.split(string.strip(F.readline())," ");
486 F.close();
487 l.simple_bind_s("uid="+Pass[0]+","+BaseDn,Pass[1]);
488
489 # Fetch all the groups
490 GroupIDMap = {};
491 Attrs = l.search_s(BaseDn,ldap.SCOPE_ONELEVEL,"gid=*",\
492                   ["gid","gidnumber"]);
493
494 # Generate the GroupMap and GroupIDMap
495 for x in Attrs:
496    if x[1].has_key("gidnumber") == 0:
497       continue;
498    GroupIDMap[x[1]["gid"][0]] = int(x[1]["gidnumber"][0]);
499
500 # Fetch all the users
501 PasswdAttrs = l.search_s(BaseDn,ldap.SCOPE_ONELEVEL,"uid=*",\
502                 ["uid","uidnumber","gidnumber","supplementarygid",\
503                  "gecos","loginshell","userpassword","shadowlastchange",\
504                  "shadowmin","shadowmax","shadowwarning","shadowinactive",
505                  "shadowexpire","emailforward","latitude","longitude",\
506                  "allowedhosts","sshrsaauthkey","dnszoneentry","cn","sn",\
507                  "keyfingerprint","privatesub"]);
508
509 # Open the control file
510 if len(sys.argv) == 1:
511    F = open(GenerateConf,"r");
512 else:
513    F = open(sys.argv[1],"r")
514
515 # Generate global things
516 GlobalDir = GenerateDir+"/";
517 GenSSHShadow(l,GlobalDir+"ssh-rsa-shadow");
518 GenAllForward(l,GlobalDir+"mail-forward.cdb");
519 GenMarkers(l,GlobalDir+"markers");
520 GenPrivate(l,GlobalDir+"debian-private");
521 GenSSHKnown(l,GlobalDir+"ssh_known_hosts");
522
523 # Compatibility.
524 GenForward(l,GlobalDir+"forward-alias");
525    
526 while(1):
527    Line = F.readline();
528    if Line == "":
529       break;
530    Line = string.strip(Line);
531    if Line == "":
532       continue;
533    if Line[0] == '#':
534       continue;
535
536    Split = string.split(Line," ");
537    OutDir = GenerateDir + '/' + Split[0] + '/';
538    try: os.mkdir(OutDir);
539    except: pass;
540
541    # Get the group list and convert any named groups to numerics
542    GroupList = {};
543    ExtraList = {};
544    for I in Split[2:]:
545       if I[0] == '[':
546          ExtraList[I] = None;
547          continue;
548       GroupList[I] = None;
549       if GroupIDMap.has_key(I):
550          GroupList[str(GroupIDMap[I])] = None;
551
552    global Allowed,CurrentHost;
553    Allowed = GroupList;
554    CurrentHost = Split[0];
555
556    sys.stdout.flush();
557    GenPasswd(l,OutDir+"passwd",Split[1]);
558    sys.stdout.flush();
559    GenGroup(l,OutDir+"group");
560    GenShadow(l,OutDir+"shadow");
561         
562    # Link in global things   
563    DoLink(GlobalDir,OutDir,"ssh-rsa-shadow");
564    DoLink(GlobalDir,OutDir,"ssh-rsa-shadow.cdb");
565    DoLink(GlobalDir,OutDir,"markers");
566    DoLink(GlobalDir,OutDir,"mail-forward.cdb");
567    DoLink(GlobalDir,OutDir,"ssh_known_hosts");
568
569    # Compatibility.
570    DoLink(GlobalDir,OutDir,"forward-alias");
571
572    if ExtraList.has_key("[DNS]"):
573       GenDNS(l,OutDir+"dns-zone",OutDir+"bsmtp",Split[1]);
574       
575    if ExtraList.has_key("[PRIVATE]"):
576       DoLink(GlobalDir,OutDir,"debian-private");