LDAP schema changes and fitting changes to ud-host:
[mirror/userdir-ldap.git] / ud-host
1 #!/usr/bin/env python
2 # -*- mode: python -*-
3
4 #   Copyright (c) 2000-2001  Jason Gunthorpe <jgg@debian.org>
5 #   Copyright (c) 2001       Ryan Murray <rmurray@debian.org>
6 #   Copyright (c) 2003       James Troup <troup@debian.org>
7 #   Copyright (c) 2004-2005  Joey Schulze <joey@infodrom.org>
8 #
9 #   This program is free software; you can redistribute it and/or modify
10 #   it under the terms of the GNU General Public License as published by
11 #   the Free Software Foundation; either version 2 of the License, or
12 #   (at your option) any later version.
13 #
14 #   This program is distributed in the hope that it will be useful,
15 #   but WITHOUT ANY WARRANTY; without even the implied warranty of
16 #   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 #   GNU General Public License for more details.
18 #
19 #   You should have received a copy of the GNU General Public License
20 #   along with this program; if not, write to the Free Software
21 #   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
22
23 # This script is an interactive way to manipulate fields in the LDAP directory.
24 # When run it connects to the directory using the current users ID and fetches
25 # all the attributes for the first machine. It then formats them nicely and
26 # allows the user to change them.
27 #
28 #  Usage: userinfo -a <user> -u <user> -c <user> -r
29 #    -a    Set the authentication user (the user whose password you are
30 #          going to enter)
31 #    -h    Set the host to display
32 #    -l    list all hosts and their status
33 #    -f    list all SSH fingerprints
34
35 import string, time, os, pwd, sys, getopt, ldap, crypt, readline, copy;
36 from tempfile import mktemp
37 from os import O_CREAT, O_EXCL, O_WRONLY
38 from userdir_ldap import *;
39
40 RootMode = 0;
41 AttrInfo = {"description": ["Machine Descr.", 1],
42             "hostname": ["Host names", 2],
43             "status": ["Status", 3],
44             "l": ["Location", 4],
45             "sponsor": ["Sponsors", 5],
46             "distribution": ["Distribution", 6],
47             "access": ["Access", 7],
48             "admin": ["Admin", 8],
49             "architecture": ["Architecture", 9],
50             "machine": ["Machine Hardware", 10],
51             "memory": ["Memory", 11],
52             "disk": ["Disk", 12],
53             "physicalHost": ["Physical Host", 13],
54             "sshRSAHostKey": ["SSH Host Keys", 14],
55             "bandwidth": ["Bandwidth", 15],
56             "purpose": ["Purposes", 16],};
57
58 AttrPrompt = {"description": ["Purpose of the machine"],
59               "hostname": ["The hostnames for the box (ipv4/ipv6)"],
60               "status": ["Blank if Up, explaination if not"],
61               "l": ["Physical location"],
62               "sponsor": ["Sponsors and their URLs"],
63               "distribution": ["The distribution version"],
64               "access": ["all, developer only, restricted"],
65               "admin": ["Admin email address"],
66               "architecture": ["Debian Architecture string"],
67               "machine": ["Hardware description"],
68               "memory": ["Installed RAM"],
69               "disk": ["Disk Space, RAID levels, etc"],
70               "physicalHost": ["The box hosting this virtual server"],
71               "sshRSAHostKey": ["A copy of /etc/ssh/ssh_*host_key.pub"],
72               "bandwidth": ["Available outbound"],
73               "purpose": ["The purposes of this host"],};
74
75 # Create a map of IDs to desc,value,attr
76 OrderedIndex = {};
77 for at in AttrInfo.keys():
78    if (AttrInfo[at][1] != 0):
79       OrderedIndex[AttrInfo[at][1]] = [AttrInfo[at][0], "", at];
80 OrigOrderedIndex = copy.deepcopy(OrderedIndex);
81
82 # Print out the automatic time stamp information
83 def PrintModTime(Attrs):
84    Stamp = GetAttr(Attrs,"modifyTimestamp","");
85    if len(Stamp) >= 13:
86       Time = (int(Stamp[0:4]),int(Stamp[4:6]),int(Stamp[6:8]),
87               int(Stamp[8:10]),int(Stamp[10:12]),int(Stamp[12:14]),0,0,-1);
88       print "%-24s:" % ("Record last modified on"), time.strftime("%a %d/%m/%Y %X UTC",Time),
89       print "by",ldap.explode_dn(GetAttr(Attrs,"modifiersName"),1)[0];
90
91    Stamp = GetAttr(Attrs,"createTimestamp","");
92    if len(Stamp) >= 13:
93       Time = (int(Stamp[0:4]),int(Stamp[4:6]),int(Stamp[6:8]),
94               int(Stamp[8:10]),int(Stamp[10:12]),int(Stamp[12:14]),0,0,-1);
95       print "%-24s:" % ("Record created on"), time.strftime("%a %d/%m/%Y %X UTC",Time);
96
97 # Display all of the attributes in a numbered list
98 def ShowAttrs(Attrs):
99    print;
100    PrintModTime(Attrs);
101
102    for at in Attrs[1].keys():
103       if AttrInfo.has_key(at):
104          if AttrInfo[at][1] == 0:
105             print "      %-18s:" % (AttrInfo[at][0]),
106             for x in Attrs[1][at]:
107                print "'%s'" % (x),
108             print;
109          else:
110             OrderedIndex[AttrInfo[at][1]][1] = Attrs[1][at];
111
112    Keys = OrderedIndex.keys();
113    Keys.sort();
114    for at in Keys:
115       if at < 100 or RootMode != 0:
116          print " %3u) %-18s: " % (at,OrderedIndex[at][0]),
117          for x in OrderedIndex[at][1]:
118             print "'%s'" % (re.sub('[\n\r]','?',x)),
119          print;
120
121 def Overview(Attrs):
122    """Display a one-line overview for a given host"""
123    for i in ['host','architecture','distribution','access','status']:
124       if i not in Attrs[1].keys():
125          Attrs[1][i] = ['']
126    print "%-12s  %-10s  %-38s  %-25s %s" % (\
127       Attrs[1]['host'][0], \
128       Attrs[1]['architecture'][0], \
129       Attrs[1]['distribution'][0], \
130       Attrs[1]['access'][0], \
131       Attrs[1]['status'][0])
132
133 # Change a single attribute
134 def ChangeAttr(Attrs,Attr):
135    if (Attr in ["sponsor", "sshRSAHostKey", "purpose"]):
136       return MultiChangeAttr(Attrs,Attr);
137
138    print "Old value: '%s'" % (GetAttr(Attrs,Attr,""));
139    print "Press enter to leave unchanged and a single space to set to empty";
140    NewValue = raw_input("New? ");
141
142    # Empty string
143    if (NewValue == ""):
144       print "Leaving unchanged.";
145       return;
146
147    # Single space designates delete, trap the delete error
148    if (NewValue == " "):
149       print "Deleting.",;
150       try:
151          l.modify_s(HostDn,[(ldap.MOD_DELETE,Attr,None)]);
152       except ldap.NO_SUCH_ATTRIBUTE:
153          pass;
154
155       print;
156       Attrs[1][Attr] = [""];
157       return;
158
159    # Set a new value
160    print "Setting.",;
161    l.modify_s(HostDn,[(ldap.MOD_REPLACE,Attr,NewValue)]);
162    Attrs[1][Attr] = [NewValue];
163    print;
164
165 def MultiChangeAttr(Attrs,Attr):
166    # Make sure that we have an entry
167    if not Attrs[1].has_key(Attr):
168       Attrs[1][Attr] = [];
169
170    Attrs[1][Attr].sort();
171    print "Old values: ",Attrs[1][Attr];
172
173    Mode = string.upper(raw_input("[D]elete or [A]dd? "));
174    if (Mode != 'D' and Mode != 'A'):
175       return;
176
177    NewValue = raw_input("Value? ");
178    # Empty string
179    if (NewValue == ""):
180       print "Leaving unchanged.";
181       return;
182
183    # Delete
184    if (Mode == "D"):
185       print "Deleting.",;
186       try:
187          l.modify_s(HostDn,[(ldap.MOD_DELETE,Attr,NewValue)]);
188       except ldap.NO_SUCH_ATTRIBUTE:
189          print "Failed";
190
191       print;
192       Attrs[1][Attr].remove(NewValue);
193       return;
194
195    # Set a new value
196    print "Setting.",;
197    l.modify_s(HostDn,[(ldap.MOD_ADD,Attr,NewValue)]);
198    Attrs[1][Attr].append(NewValue);
199    print;
200
201 def CalcTempFile():
202    unique = 0
203    while unique == 0:
204       name = mktemp()
205       try:
206          fd = os.open(name, O_CREAT | O_EXCL | O_WRONLY, 0600)
207       except OSError:
208          continue
209       os.close(fd)
210       unique = 1
211    return name
212
213
214 # Main program starts here
215 User = pwd.getpwuid(os.getuid())[0];
216 BindUser = User;
217 ListMode = 0
218 FingerPrints = 0
219 Host = None
220 # Process options
221 try:
222    (options, arguments) = getopt.getopt(sys.argv[1:], "nh:a:rlf")
223 except getopt.GetoptError, data:
224    print data
225    sys.exit(1)
226
227 for (switch, val) in options:
228    if (switch == '-h'):
229       Host = val;
230    elif (switch == '-a'):
231       BindUser = val;
232    elif (switch == '-r'):
233       RootMode = 1;
234    elif (switch == '-n'):
235       BindUser = "";
236    elif (switch == '-l'):
237       BindUser = "";
238       ListMode = 1
239    elif (switch == '-f'):
240       BindUser = "";
241       FingerPrints = 1
242
243 if (BindUser != ""):
244    l = passwdAccessLDAP(LDAPServer, BaseDn, BindUser)
245 else:
246    l = ldap.open(LDAPServer);
247    l.simple_bind_s("","")
248
249 HBaseDn = HostBaseDn
250
251 if ListMode == 1:
252    Attrs = l.search_s(HBaseDn,ldap.SCOPE_ONELEVEL,"host=*")
253    hosts = []
254    for hAttrs in Attrs:
255       hosts.append(hAttrs[1]['host'][0])
256    hosts.sort()
257
258    print "%-12s  %-10s  %-38s  %-25s %s" % ("Host name","Arch","Distribution","Access","Status")
259    print "-"*115
260    for host in hosts:
261       for hAttrs in Attrs:
262          if host == hAttrs[1]['host'][0]:
263             Overview(hAttrs)
264    sys.exit(0)
265 elif FingerPrints == 1:
266    if Host is not None:
267       Attrs = l.search_s(HBaseDn,ldap.SCOPE_ONELEVEL,"host=" + Host)
268    else:
269       Attrs = l.search_s(HBaseDn,ldap.SCOPE_ONELEVEL,"host=*")
270    hosts = []
271    for hAttrs in Attrs:
272       hosts.append(hAttrs[1]['host'][0])
273    hosts.sort()
274
275    tmpfile = CalcTempFile()
276    for host in hosts:
277       for hAttrs in Attrs:
278          if host == hAttrs[1]['host'][0]:
279             if 'sshRSAHostKey' in hAttrs[1].keys():
280                for key in hAttrs[1]['sshRSAHostKey']:
281                   tmp = open(tmpfile, 'w')
282                   tmp.write(key + '\n')
283                   tmp.close()
284                   fp = os.popen('/usr/bin/ssh-keygen -l -f ' + tmpfile, "r")
285                   input = fp.readline()
286                   fp.close()
287                   fingerprint = input.split(' ')
288                   print "%s %s root@%s" % (fingerprint[0], fingerprint[1], host)
289    os.unlink(tmpfile)
290    sys.exit(0)
291
292 HostDn = "host=" + Host + "," + HBaseDn;
293
294 # Query the server for all of the attributes
295 Attrs = l.search_s(HBaseDn,ldap.SCOPE_ONELEVEL,"host=" + Host);
296 if len(Attrs) == 0:
297    print "Host",Host,"was not found.";
298    sys.exit(0);
299
300 # repeatedly show the account configuration
301 while(1):
302    ShowAttrs(Attrs[0]);
303    if (BindUser == ""):
304       sys.exit(0);
305
306    if RootMode == 1:
307       print "   a) Arbitary Change";
308    print "   n) New Host";
309    print "   d) Delete Host";
310    print "   u) Switch Hosts";
311    print "   x) Exit";
312
313    # Prompt
314    Response = raw_input("Change? ");
315    if (Response == "x" or Response == "X" or Response == "q" or
316        Response == "quit" or Response == "exit"):
317       break;
318
319    # Change who we are looking at
320    if (Response == 'u' or Response == 'U'):
321       NewHost = raw_input("Host? ");
322       if NewHost == "":
323          continue;
324       NAttrs = l.search_s(HBaseDn,ldap.SCOPE_ONELEVEL,"host=" + NewHost);
325       if len(NAttrs) == 0:
326          print "Host",NewHost,"was not found.";
327          continue;
328       Attrs = NAttrs;
329       Host = NewHost;
330       HostDn = "host=" + Host + "," + HBaseDn;
331       OrderedIndex = copy.deepcopy(OrigOrderedIndex);
332       continue;
333
334    # Create a new entry and change to it Change who we are looking at
335    if (Response == 'n' or Response == 'N'):
336       NewHost = raw_input("Host? ");
337       if NewHost == "":
338          continue;
339       NAttrs = l.search_s(HBaseDn,ldap.SCOPE_ONELEVEL,"host=" + NewHost);
340       if len(NAttrs) != 0:
341          print "Host",NewHost,"already exists.";
342          continue;
343       NewHostName = raw_input("Hostname? ");
344       if NewHost == "":
345          continue;
346       Dn = "host=" + NewHost + "," + HBaseDn;
347       l.add_s(Dn,[("host", NewHost),
348                   ("hostname", NewHostName),
349                   ("objectClass", ("top", "debianServer"))]);
350
351       # Switch
352       NAttrs = l.search_s(HBaseDn,ldap.SCOPE_ONELEVEL,"host=" + NewHost);
353       if len(NAttrs) == 0:
354          print "Host",NewHost,"was not found.";
355          continue;
356       Attrs = NAttrs;
357       Host = NewHost;
358       HostDn = "host=" + Host + "," + HBaseDn;
359       OrderedIndex = copy.deepcopy(OrigOrderedIndex);
360       continue;
361
362    # Handle changing an arbitary value
363    if (Response == "a"):
364       Attr = raw_input("Attr? ");
365       ChangeAttr(Attrs[0],Attr);
366       continue;
367
368    if (Response == 'd'):
369       Really = raw_input("Really (type yes)? ");
370       if Really != 'yes':
371           continue;
372       print "Deleting",HostDn;
373       l.delete_s(HostDn);
374       continue;
375
376    # Convert the integer response
377    try:
378       ID = int(Response);
379       if (not OrderedIndex.has_key(ID) or (ID > 100 and RootMode == 0)):
380          raise ValueError;
381    except ValueError:
382       print "Invalid";
383       continue;
384
385    # Print the what to do prompt
386    print "Changing LDAP entry '%s' (%s)" % (OrderedIndex[ID][0],OrderedIndex[ID][2]);
387    print AttrPrompt[OrderedIndex[ID][2]][0];
388    ChangeAttr(Attrs[0],OrderedIndex[ID][2]);