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