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