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