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