f6d6c56b8244d022bc9d9ab8c43221176f487316
[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 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             "allowedGroups": ["Groups", 17],
58             "exportOptions": ["Export-Opts", 18],
59             "ipHostNumber": ["IP Address", 19],
60             "mXRecord": ["MXRecord", 20],
61             "sshdistAuthKeysHost": ["extra authkeys ip", 21],
62             }
63
64 AttrPrompt = {"description": ["Purpose of the machine"],
65               "hostname": ["The hostnames for the box (ipv4/ipv6)"],
66               "status": ["Blank if Up, explaination if not"],
67               "l": ["Physical location"],
68               "sponsor": ["Sponsors and their URLs"],
69               "distribution": ["The distribution version"],
70               "access": ["all, developer only, restricted"],
71               "admin": ["Admin email address"],
72               "architecture": ["Debian Architecture string"],
73               "machine": ["Hardware description"],
74               "memory": ["Installed RAM"],
75               "disk": ["Disk Space, RAID levels, etc"],
76               "physicalHost": ["The box hosting this virtual server"],
77               "sshRSAHostKey": ["A copy of /etc/ssh/ssh_*host_key.pub"],
78               "bandwidth": ["Available outbound"],
79               "purpose": ["The purposes of this host"],
80               "allowedGroups": ["allowed Groups on this host"],
81               "exportOptions": ["additional export options"],
82               "ipHostNumber": ["IP Addresses(es) of the machine"],
83               "mXRecord": ["Mail Exchanger for this machine"],
84               "sshdistAuthKeysHost": ["additional hosts for sshdist's authkeys file"],
85               };
86
87 # Create a map of IDs to desc,value,attr
88 OrderedIndex = {};
89 for at in AttrInfo.keys():
90    if (AttrInfo[at][1] != 0):
91       OrderedIndex[AttrInfo[at][1]] = [AttrInfo[at][0], "", at];
92 OrigOrderedIndex = copy.deepcopy(OrderedIndex);
93
94 # Print out the automatic time stamp information
95 def PrintModTime(Attrs):
96    Stamp = GetAttr(Attrs,"modifyTimestamp","");
97    if len(Stamp) >= 13:
98       Time = (int(Stamp[0:4]),int(Stamp[4:6]),int(Stamp[6:8]),
99               int(Stamp[8:10]),int(Stamp[10:12]),int(Stamp[12:14]),0,0,-1);
100       print "%-24s:" % ("Record last modified on"), time.strftime("%a %d/%m/%Y %X UTC",Time),
101       print "by",ldap.explode_dn(GetAttr(Attrs,"modifiersName"),1)[0];
102
103    Stamp = GetAttr(Attrs,"createTimestamp","");
104    if len(Stamp) >= 13:
105       Time = (int(Stamp[0:4]),int(Stamp[4:6]),int(Stamp[6:8]),
106               int(Stamp[8:10]),int(Stamp[10:12]),int(Stamp[12:14]),0,0,-1);
107       print "%-24s:" % ("Record created on"), time.strftime("%a %d/%m/%Y %X UTC",Time);
108
109 # Display all of the attributes in a numbered list
110 def ShowAttrs(Attrs):
111    print;
112    PrintModTime(Attrs);
113
114    for at in Attrs[1].keys():
115       if AttrInfo.has_key(at):
116          if AttrInfo[at][1] == 0:
117             print "      %-18s:" % (AttrInfo[at][0]),
118             for x in Attrs[1][at]:
119                print "'%s'" % (x),
120             print;
121          else:
122             OrderedIndex[AttrInfo[at][1]][1] = Attrs[1][at];
123
124    Keys = OrderedIndex.keys();
125    Keys.sort();
126    for at in Keys:
127       if at < 100 or RootMode != 0:
128          print " %3u) %-18s: " % (at,OrderedIndex[at][0]),
129          for x in OrderedIndex[at][1]:
130             print "'%s'" % (re.sub('[\n\r]','?',x)),
131          print;
132
133 def Overview(Attrs):
134    """Display a one-line overview for a given host"""
135    for i in ['host','architecture','distribution','access','status']:
136       if i not in Attrs[1].keys():
137          Attrs[1][i] = ['']
138    print "%-12s  %-10s  %-38s  %-25s %s" % (\
139       Attrs[1]['host'][0], \
140       Attrs[1]['architecture'][0], \
141       Attrs[1]['distribution'][0], \
142       Attrs[1]['access'][0], \
143       Attrs[1]['status'][0])
144
145 # Change a single attribute
146 def ChangeAttr(Attrs,Attr):
147    if (Attr in ["sponsor", "sshRSAHostKey", "purpose", "allowedGroups", "exportOptions", "ipHostNumber", "mXRecord", "sshdistAuthKeysHost"]):
148       return MultiChangeAttr(Attrs,Attr);
149
150    print "Old value: '%s'" % (GetAttr(Attrs,Attr,""));
151    print "Press enter to leave unchanged and a single space to set to empty";
152    NewValue = raw_input("New? ");
153
154    # Empty string
155    if (NewValue == ""):
156       print "Leaving unchanged.";
157       return;
158
159    # Single space designates delete, trap the delete error
160    if (NewValue == " "):
161       print "Deleting.",;
162       try:
163          l.modify_s(HostDn,[(ldap.MOD_DELETE,Attr,None)]);
164       except ldap.NO_SUCH_ATTRIBUTE:
165          pass;
166
167       print;
168       Attrs[1][Attr] = [""];
169       return;
170
171    # Set a new value
172    print "Setting.",;
173    l.modify_s(HostDn,[(ldap.MOD_REPLACE,Attr,NewValue)]);
174    Attrs[1][Attr] = [NewValue];
175    print;
176
177 def MultiChangeAttr(Attrs,Attr):
178    # Make sure that we have an entry
179    if not Attrs[1].has_key(Attr):
180       Attrs[1][Attr] = [];
181
182    Attrs[1][Attr].sort();
183    print "Old values: ",Attrs[1][Attr];
184
185    Mode = raw_input("[D]elete or [A]dd? ").upper()
186    if (Mode != 'D' and Mode != 'A'):
187       return;
188
189    NewValue = raw_input("Value? ");
190    # Empty string
191    if (NewValue == ""):
192       print "Leaving unchanged.";
193       return;
194
195    # Delete
196    if (Mode == "D"):
197       print "Deleting.",;
198       try:
199          l.modify_s(HostDn,[(ldap.MOD_DELETE,Attr,NewValue)]);
200       except ldap.NO_SUCH_ATTRIBUTE:
201          print "Failed";
202
203       print;
204       Attrs[1][Attr].remove(NewValue);
205       return;
206
207    # Set a new value
208    print "Setting.",;
209    l.modify_s(HostDn,[(ldap.MOD_ADD,Attr,NewValue)]);
210    Attrs[1][Attr].append(NewValue);
211    print;
212
213 def CalcTempFile():
214    unique = 0
215    while unique == 0:
216       name = mktemp()
217       try:
218          fd = os.open(name, O_CREAT | O_EXCL | O_WRONLY, 0600)
219       except OSError:
220          continue
221       os.close(fd)
222       unique = 1
223    return name
224
225
226 # Main program starts here
227 User = pwd.getpwuid(os.getuid())[0];
228 BindUser = User;
229 ListMode = 0
230 FingerPrints = 0
231 Host = None
232 # Process options
233 try:
234    (options, arguments) = getopt.getopt(sys.argv[1:], "nh:a:rlf")
235 except getopt.GetoptError, data:
236    print data
237    sys.exit(1)
238
239 for (switch, val) in options:
240    if (switch == '-h'):
241       Host = val;
242    elif (switch == '-a'):
243       BindUser = val;
244    elif (switch == '-r'):
245       RootMode = 1;
246    elif (switch == '-n'):
247       BindUser = "";
248    elif (switch == '-l'):
249       BindUser = "";
250       ListMode = 1
251    elif (switch == '-f'):
252       BindUser = "";
253       FingerPrints = 1
254
255 if (BindUser != ""):
256    l = passwdAccessLDAP(BaseDn, BindUser)
257 else:
258    l = connectLDAP()
259    l.simple_bind_s("","")
260
261 if ListMode == 1:
262    Attrs = l.search_s(HostBaseDn,ldap.SCOPE_ONELEVEL,"host=*")
263    hosts = []
264    for hAttrs in Attrs:
265       hosts.append(hAttrs[1]['host'][0])
266    hosts.sort()
267
268    print "%-12s  %-10s  %-38s  %-25s %s" % ("Host name","Arch","Distribution","Access","Status")
269    print "-"*115
270    for host in hosts:
271       for hAttrs in Attrs:
272          if host == hAttrs[1]['host'][0]:
273             Overview(hAttrs)
274    sys.exit(0)
275 elif FingerPrints == 1:
276    if Host is not None:
277       Attrs = l.search_s(HostBaseDn,ldap.SCOPE_ONELEVEL,"host=" + Host)
278    else:
279       Attrs = l.search_s(HostBaseDn,ldap.SCOPE_ONELEVEL,"host=*")
280    hosts = []
281    for hAttrs in Attrs:
282       hosts.append(hAttrs[1]['host'][0])
283    hosts.sort()
284
285    tmpfile = CalcTempFile()
286    for host in hosts:
287       for hAttrs in Attrs:
288          if host == hAttrs[1]['host'][0]:
289             if 'sshRSAHostKey' in hAttrs[1].keys():
290                for key in hAttrs[1]['sshRSAHostKey']:
291                   tmp = open(tmpfile, 'w')
292                   tmp.write(key + '\n')
293                   tmp.close()
294                   fp = os.popen('/usr/bin/ssh-keygen -l -f ' + tmpfile, "r")
295                   input = fp.readline()
296                   fp.close()
297                   fingerprint = input.split(' ')
298                   print "%s %s root@%s" % (fingerprint[0], fingerprint[1], host)
299    os.unlink(tmpfile)
300    sys.exit(0)
301
302 HostDn = "host=" + Host + "," + HostBaseDn;
303
304 # Query the server for all of the attributes
305 Attrs = l.search_s(HostBaseDn,ldap.SCOPE_ONELEVEL,"host=" + Host);
306 if len(Attrs) == 0:
307    print "Host",Host,"was not found.";
308    sys.exit(0);
309
310 # repeatedly show the account configuration
311 while(1):
312    ShowAttrs(Attrs[0]);
313    if (BindUser == ""):
314       sys.exit(0);
315
316    if RootMode == 1:
317       print "   a) Arbitary Change";
318    print "   n) New Host";
319    print "   d) Delete Host";
320    print "   u) Switch Hosts";
321    print "   x) Exit";
322
323    # Prompt
324    Response = raw_input("Change? ");
325    if (Response == "x" or Response == "X" or Response == "q" or
326        Response == "quit" or Response == "exit"):
327       break;
328
329    # Change who we are looking at
330    if (Response == 'u' or Response == 'U'):
331       NewHost = raw_input("Host? ");
332       if NewHost == "":
333          continue;
334       NAttrs = l.search_s(HostBaseDn,ldap.SCOPE_ONELEVEL,"host=" + NewHost);
335       if len(NAttrs) == 0:
336          print "Host",NewHost,"was not found.";
337          continue;
338       Attrs = NAttrs;
339       Host = NewHost;
340       HostDn = "host=" + Host + "," + HostBaseDn;
341       OrderedIndex = copy.deepcopy(OrigOrderedIndex);
342       continue;
343
344    # Create a new entry and change to it Change who we are looking at
345    if (Response == 'n' or Response == 'N'):
346       NewHost = raw_input("Host? ");
347       if NewHost == "":
348          continue;
349       NAttrs = l.search_s(HostBaseDn,ldap.SCOPE_ONELEVEL,"host=" + NewHost);
350       if len(NAttrs) != 0:
351          print "Host",NewHost,"already exists.";
352          continue;
353       NewHostName = raw_input("Hostname? ");
354       if NewHost == "":
355          continue;
356       Dn = "host=" + NewHost + "," + HostBaseDn;
357       l.add_s(Dn,[("host", NewHost),
358                   ("hostname", NewHostName),
359                   ("objectClass", ("top", "debianServer"))]);
360
361       # Switch
362       NAttrs = l.search_s(HostBaseDn,ldap.SCOPE_ONELEVEL,"host=" + NewHost);
363       if len(NAttrs) == 0:
364          print "Host",NewHost,"was not found.";
365          continue;
366       Attrs = NAttrs;
367       Host = NewHost;
368       HostDn = "host=" + Host + "," + HostBaseDn;
369       OrderedIndex = copy.deepcopy(OrigOrderedIndex);
370       continue;
371
372    # Handle changing an arbitary value
373    if (Response == "a"):
374       Attr = raw_input("Attr? ");
375       ChangeAttr(Attrs[0],Attr);
376       continue;
377
378    if (Response == 'd'):
379       Really = raw_input("Really (type yes)? ");
380       if Really != 'yes':
381           continue;
382       print "Deleting",HostDn;
383       l.delete_s(HostDn);
384       continue;
385
386    # Convert the integer response
387    try:
388       ID = int(Response);
389       if (not OrderedIndex.has_key(ID) or (ID > 100 and RootMode == 0)):
390          raise ValueError;
391    except ValueError:
392       print "Invalid";
393       continue;
394
395    # Print the what to do prompt
396    print "Changing LDAP entry '%s' (%s)" % (OrderedIndex[ID][0],OrderedIndex[ID][2]);
397    print AttrPrompt[OrderedIndex[ID][2]][0];
398    ChangeAttr(Attrs[0],OrderedIndex[ID][2]);