3 # This script is an interactive way to manipulate fields in the LDAP directory.
4 # When run it connects to the directory using the current users ID and fetches
5 # all the attributes for that user. It then formats them nicely and allows
6 # the user to change them.
7 # It is possible to authenticate as someone differnt than you are viewing/changing
8 # this allows administrative functions and also allows users to view
9 # restricted information about others, such as phone numbers and addresses.
11 # Usage: userinfo -a <user> -u <user> -c <user> -r
12 # -a Set the authentication user (the user whose password you are
14 # -u Set the user to display
15 # -c Set both -a and -u, use this if your login uid is not in the
17 # -r Enable 'root' functions, do this if your uid has access to
18 # restricted variables.
20 # Copyright (c) 1999-2001 Jason Gunthorpe <jgg@debian.org>
21 # Copyright (c) 2004-2005,7 Joey Schulze <joey@infodrom.org>
22 # Copyright (c) 2001-2006 Ryan Murray <rmurray@debian.org>
23 # Copyright (c) 2008 Peter Palfrader <peter@palfrader.org>
24 # Copyright (c) 2008 Martin Zobel-Helas <zobel@debian.org>
25 # Copyright (c) 2008 Marc 'HE' Brockschmidt <he@debian.org>
26 # Copyright (c) 2008 Mark Hymers <mhy@debian.org>
28 # This program is free software; you can redistribute it and/or modify
29 # it under the terms of the GNU General Public License as published by
30 # the Free Software Foundation; either version 2 of the License, or
31 # (at your option) any later version.
33 # This program is distributed in the hope that it will be useful,
34 # but WITHOUT ANY WARRANTY; without even the implied warranty of
35 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
36 # GNU General Public License for more details.
38 # You should have received a copy of the GNU General Public License
39 # along with this program; if not, write to the Free Software
40 # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
42 import time, os, pwd, sys, getopt, ldap, crypt, readline, copy;
43 from userdir_ldap import *;
46 AttrInfo = {"cn": ["First Name", 101],
47 "mn": ["Middle Name", 102],
48 "sn": ["Surname", 103],
49 "c": ["Country Code",1],
51 "ou": ["Membership",0],
52 "facsimileTelephoneNumber": ["Fax Phone Number",3],
53 "telephoneNumber": ["Phone Number",4],
54 "postalAddress": ["Mailing Address",5],
55 "postalCode": ["Postal Code",6],
56 "uid": ["Unix User ID",0],
57 "loginShell": ["Unix Shell",7],
58 "supplementaryGid": ["Unix Groups",0],
59 "allowedHost": ["Host ACL",0],
60 "member": ["LDAP Group",0],
61 "emailForward": ["Email Forwarding",8],
62 "ircNick": ["IRC Nickname",9],
63 "onVacation": ["Vacation Message",10],
64 "labeledURI": ["Home Page",11],
65 "latitude": ["Latitude",12],
66 "longitude": ["Longitude",13],
67 "icqUin": ["ICQ UIN",14],
68 "jabberJID": ["Jabber ID",15],
69 "privateSub": ["Debian-Private",16],
70 "gender": ["Gender",17],
71 "birthDate": ["Date of Birth",18],
72 "mailDisableMessage": ["Mail Disabled",19],
73 "mailGreylisting": ["Mail Greylisting",20],
74 "mailCallout": ["Mail Callouts",21],
75 "mailRBL": ["Mail RBLs",22],
76 "mailRHSBL": ["Mail RHSBLs",23],
77 "mailWhitelist": ["Mail Whitelist",24],
78 "VoIP": ["VoIP Address",25],
79 "comment": ["Comment",116],
80 "userPassword": ["Crypted Password",117],
81 "dnsZoneEntry": ["d.net Entry",118],
82 "accountStatus": ["DD status",301],
83 "accountComment": ["DD status comment",302],
86 AttrPrompt = {"cn": ["Common name or first name"],
87 "mn": ["Middle name (or initial if it ends in a dot)"],
88 "sn": ["Surname or last name"],
89 "c": ["ISO 2 letter country code, such as US, DE, etc"],
90 "l": ["City name, State/Provice (Locality)\n e.g. Dallas, Texas"],
91 "facsimileTelephoneNumber": ["Fax phone number, with area code and country code"],
92 "telephoneNumber": ["Voice phone number"],
93 "postalAddress": ["Complete mailing address including postal codes and country designations\nSeperate lines using a $ character"],
94 "postalCode": ["Postal Code or Zip Code"],
95 "loginShell": ["Login shell with full path (no check is done for validity)"],
96 "emailForward": ["EMail address to send all mail to or blank to disable"],
97 "ircNick": ["IRC nickname if you use IRC"],
98 "onVacation": ["A message if on vaction, indicating the time of departure and return"],
99 "userPassword": ["The users Crypt'd password"],
100 "comment": ["Admin Comment about the account"],
101 "supplementaryGid": ["Groups the user is in"],
102 "allowedHost": ["Grant access to certain hosts"],
103 "privateSub": ["Debian-Private mailing list subscription"],
104 "gender": ["ISO5218 Gender code (1=male,2=female,9=unspecified)"],
105 "birthDate": ["Date of Birth (YYYYMMDD)"],
106 "mailDisableMessage": ["Error message to return via SMTP"],
107 "mailGreylisting": ["SMTP Greylisting (TRUE/FALSE)"],
108 "mailCallout": ["SMTP Callouts (TRUE/FALSE)"],
109 "mailRBL": ["SMTP time RBL lists"],
110 "mailRHSBL": ["SMTP time RHSBL lists"],
111 "mailWhitelist": ["SMTP time whitelist from other checks"],
112 "member": ["LDAP Group Member for slapd ACLs"],
113 "latitude": ["XEarth latitude in ISO 6709 format - see /usr/share/zoneinfo/zone.tab or etak.com"],
114 "longitude": ["XEarth latitude in ISO 6709 format - see /usr/share/zoneinfo/zone.tab or etak.com"],
115 "dnsZoneEntry": ["DNS Zone fragment associated this this user"],
116 "labeledURI": ["Web home page"],
117 "jabberJID": ["Jabber ID"],
118 "icqUin": ["ICQ UIN Number"],
119 "VoIP": ["VoIP Address"],
120 "accountStatus": ["DD status"],
121 "accountComment": ["DD status comment"],
124 # Create a map of IDs to desc,value,attr
126 for at in AttrInfo.keys():
127 if (AttrInfo[at][1] != 0):
128 OrderedIndex[AttrInfo[at][1]] = [AttrInfo[at][0], "", at];
129 OrigOrderedIndex = copy.deepcopy(OrderedIndex);
131 for id in OrderedIndex:
132 if not AttrPrompt.has_key( OrderedIndex[id][2] ):
133 print "Warning: no AttrPrompt for %s"%(id)
135 # Show shadow information
136 def PrintShadow(Attrs):
137 Changed = int(GetAttr(Attrs,"shadowLastChange","0"));
138 MinDays = int(GetAttr(Attrs,"shadowMin","0"));
139 MaxDays = int(GetAttr(Attrs,"shadowMax","0"));
140 WarnDays = int(GetAttr(Attrs,"shadowWarning","0"));
141 InactDays = int(GetAttr(Attrs,"shadowInactive","0"));
142 Expire = int(GetAttr(Attrs,"shadowExpire","0"));
144 print "%-24s:" % ("Password last changed"),
145 print time.strftime("%a %d/%m/%Y %Z",time.localtime(Changed*24*60*60));
147 print "%-24s:" % ("Account expires on"),
148 print time.strftime("%a %d/%m/%Y %Z",time.localtime(Expire*24*60*60));
149 if (InactDays >= 0 and MaxDays < 99999):
150 print "Account aging is active, you must change your password every", MaxDays, "days."
152 # Print out the automatic time stamp information
153 def PrintModTime(Attrs):
154 Stamp = GetAttr(Attrs,"modifyTimestamp","");
156 Time = (int(Stamp[0:4]),int(Stamp[4:6]),int(Stamp[6:8]),
157 int(Stamp[8:10]),int(Stamp[10:12]),int(Stamp[12:14]),0,0,-1);
158 print "%-24s:" % ("Record last modified on"), time.strftime("%a %d/%m/%Y %X UTC",Time),
159 print "by",ldap.explode_dn(GetAttr(Attrs,"modifiersName"),1)[0];
161 Stamp = GetAttr(Attrs,"createTimestamp","");
163 Time = (int(Stamp[0:4]),int(Stamp[4:6]),int(Stamp[6:8]),
164 int(Stamp[8:10]),int(Stamp[10:12]),int(Stamp[12:14]),0,0,-1);
165 print "%-24s:" % ("Record created on"), time.strftime("%a %d/%m/%Y %X UTC",Time);
167 # Print the PGP key for a user
168 def PrintKeys(Attrs):
169 if Attrs[1].has_key("keyFingerPrint") == 0:
172 for x in Attrs[1]["keyFingerPrint"]:
174 print "%-24s:" % ("PGP/GPG Key Fingerprints"),
177 print "%-24s:" % (""),
178 print FormatPGPKey(x);
180 # Print the SSH RSA Authentication keys for a user
181 def PrintSshRSAKeys(Attrs):
182 if Attrs[1].has_key("sshRSAAuthKey") == 0:
185 for x in Attrs[1]["sshRSAAuthKey"]:
187 print "%-24s:" % ("SSH Auth Keys"),
190 print "%-24s:" % (""),
192 print FormatSSHAuth(x);
194 # Display all of the attributes in a numbered list
195 def ShowAttrs(Attrs):
197 print EmailAddress(Attrs);
201 PrintSshRSAKeys(Attrs);
203 for at in Attrs[1].keys():
204 if AttrInfo.has_key(at):
205 if AttrInfo[at][1] == 0:
206 print " %-18s:" % (AttrInfo[at][0]),
207 for x in Attrs[1][at]:
210 print "(id=%s, gid=%s)" % (GetAttr(Attrs,"uidNumber","-1"),GetAttr(Attrs,"gidNumber","-1")),
213 OrderedIndex[AttrInfo[at][1]][1] = Attrs[1][at];
215 Keys = OrderedIndex.keys();
218 if at < 100 or RootMode != 0:
219 print " %3u) %-18s: " % (at,OrderedIndex[at][0]),
220 for x in OrderedIndex[at][1]:
221 print "'%s'" % (re.sub('[\n\r]','?',x)),
224 # Change a single attribute
225 def ChangeAttr(Attrs,Attr):
226 if (Attr == "supplementaryGid" or Attr == "allowedHost" or \
227 Attr == "member" or Attr == "dnsZoneEntry" or Attr == "mailWhitelist" or \
228 Attr == "mailRBL" or Attr == "mailRHSBL"):
229 return MultiChangeAttr(Attrs,Attr);
231 print "Old value: '%s'" % (GetAttr(Attrs,Attr,""));
232 print "Press enter to leave unchanged and a single space to set to empty";
233 NewValue = raw_input("New? ");
237 print "Leaving unchanged.";
240 # Single space designates delete, trap the delete error
241 if (NewValue == " "):
244 l.modify_s(UserDn,[(ldap.MOD_DELETE,Attr,None)]);
245 except ldap.NO_SUCH_ATTRIBUTE:
249 Attrs[1][Attr] = [""];
254 l.modify_s(UserDn,[(ldap.MOD_REPLACE,Attr,NewValue)]);
255 Attrs[1][Attr] = [NewValue];
258 def MultiChangeAttr(Attrs,Attr):
259 # Make sure that we have an entry
260 if not Attrs[1].has_key(Attr):
263 Attrs[1][Attr].sort();
264 print "Old values: ",Attrs[1][Attr];
266 Mode = raw_input("[D]elete or [A]dd? ").upper()
267 if (Mode != 'D' and Mode != 'A'):
270 NewValue = raw_input("Value? ");
273 print "Leaving unchanged.";
280 l.modify_s(UserDn,[(ldap.MOD_DELETE,Attr,NewValue)]);
281 except ldap.NO_SUCH_ATTRIBUTE:
285 Attrs[1][Attr].remove(NewValue);
290 l.modify_s(UserDn,[(ldap.MOD_ADD,Attr,NewValue)]);
291 Attrs[1][Attr].append(NewValue);
294 def Lock(UserDn, Attrs, DisableMail=True):
295 shadowLast = str(int(time.time()/24/60/60));
297 (ldap.MOD_REPLACE,"userPassword","{crypt}*LK*"),
298 (ldap.MOD_REPLACE,"shadowLastChange",shadowLast),
299 (ldap.MOD_REPLACE,"shadowExpire","1")];
301 recs.append( (ldap.MOD_REPLACE,"mailDisableMessage","account locked") )
302 Attrs[0][1]["mailDisableMessage"] = ["account locked"];
303 l.modify_s(UserDn,recs);
304 Attrs[0][1]["userPassword"] = ["{crypt}*LK*"];
305 Attrs[0][1]["shadowLastChange"] = [shadowLast];
306 Attrs[0][1]["shadowExpire"] = ["1"];
308 # Main program starts here
309 User = pwd.getpwuid(os.getuid())[0];
313 (options, arguments) = getopt.getopt(sys.argv[1:], "nu:c:a:r")
314 except getopt.GetoptError, data:
318 for (switch, val) in options:
321 elif (switch == '-a'):
323 elif (switch == '-c'):
326 elif (switch == '-r'):
328 elif (switch == '-n'):
332 print "Accessing LDAP entry for '" + User + "'",
333 if (BindUser != User):
335 print "as '" + BindUser + "'";
339 Password = getpass(BindUser + "'s password: ");
341 # Connect to the ldap server
343 UserDn = "uid=" + BindUser + "," + BaseDn;
345 l.simple_bind_s(UserDn,Password);
347 l.simple_bind_s("","");
348 UserDn = "uid=" + User + "," + BaseDn;
350 # Enable changing of supplementary gid's
352 # Items that root can edit
353 list = ["supplementaryGid","allowedHost","member"];
356 AttrInfo[x][1] = 200 + Count;
357 OrderedIndex[AttrInfo[x][1]] = [AttrInfo[x][0], "",x];
358 OrigOrderedIndex[AttrInfo[x][1]] = [AttrInfo[x][0], "",x];
361 # Query the server for all of the attributes
362 Attrs = l.search_s(BaseDn,ldap.SCOPE_ONELEVEL,"uid=" + User);
364 print "User",User,"was not found.";
367 # repeatedly show the account configuration
374 print " a) Arbitary Change";
375 print " r) retire developer";
376 print " R) Randomize Password";
377 print " L) Lock account and disable mail";
378 print " p) Change Password";
379 print " u) Switch Users";
383 Response = raw_input("Change? ");
384 if (Response == "x" or Response == "X" or Response == "q" or
385 Response == "quit" or Response == "exit"):
388 # Change who we are looking at
389 if (Response == 'u' or Response == 'U'):
390 NewUser = raw_input("User? ");
393 NAttrs = l.search_s(BaseDn,ldap.SCOPE_ONELEVEL,"uid=" + NewUser);
395 print "User",NewUser,"was not found.";
399 UserDn = "uid=" + User + "," + BaseDn;
400 OrderedIndex = copy.deepcopy(OrigOrderedIndex);
403 # Handle changing the password
404 if (Response == "p"):
405 print "Please enter a new password. Your password can be of unlimited length,";
406 print "contain spaces and other special characters. No checking is done on the";
407 print "strength of the passwords so pick good ones please!";
409 Pass1 = getpass(User + "'s new password: ");
410 Pass2 = getpass(User + "'s new password again: ");
412 print "Passwords did not match";
413 raw_input("Press a key");
417 Pass = HashPass(Pass1);
419 print "%s: %s\n" %(sys.exc_type,sys.exc_value);
420 raw_input("Press a key");
423 print "Setting password..";
424 Pass = "{crypt}" + Pass;
425 shadowLast = str(int(time.time()/24/60/60));
426 l.modify_s(UserDn,[(ldap.MOD_REPLACE,"userPassword",Pass),
427 (ldap.MOD_REPLACE,"shadowLastChange",shadowLast)]);
428 Attrs[0][1]["userPassword"] = [Pass];
429 Attrs[0][1]["shadowLastChange"] = [shadowLast];
433 if Response == 'r' and RootMode == 1:
434 if Attrs[0][1].has_key("accountStatus") == 0:
435 curStatus = "<not set>"
437 curStatus = Attrs[0][1]["accountStatus"][0]
438 if Attrs[0][1].has_key("accountComment") == 0:
439 curComment = "<not set>"
441 curComment = Attrs[0][1]["accountComment"][0]
442 print "\n\nCurrent status is %s"%curStatus
443 print "Current comment is %s\n"%curComment
445 print "Set account to:"
446 print " 1) retiring (lock account but do not disable mail):"
447 print " 2) inactive (removed/emeritus/... - lock account and disable mail):"
448 print " 3) memorial (lock account and disable mail):"
449 print " 4) active (do not change other settings, you will have to deal with them)"
450 print " q) return (no change)"
451 Resp = raw_input("Action? ")
452 if Resp == "1" or Resp == "2":
453 Lock(UserDn, Attrs, Resp == "2")
455 newstatus = "retiring %s"%(time.strftime("%Y-%m-%d"))
457 newstatus = "inactive %s"%(time.strftime("%Y-%m-%d"))
458 l.modify_s(UserDn,[(ldap.MOD_REPLACE,"accountStatus",newstatus)])
459 Attrs[0][1]["accountStatus"] = [newstatus]
461 Resp2 = raw_input("Optional RT ticket number? ")
463 comment = "RT#%s"%(Resp2)
464 l.modify_s(UserDn,[(ldap.MOD_REPLACE,"accountComment",comment)])
465 Attrs[0][1]["accountComment"] = [comment]
468 newstatus = "memorial"
469 l.modify_s(UserDn,[(ldap.MOD_REPLACE,"accountStatus",newstatus)])
470 Attrs[0][1]["accountStatus"] = [newstatus]
473 l.modify_s(UserDn,[(ldap.MOD_REPLACE,"accountStatus",newstatus)])
474 Attrs[0][1]["accountStatus"] = [newstatus]
480 if Response == 'R' and RootMode == 1:
481 Resp = raw_input("Randomize Users Password? [no/yes]");
485 # Generate a random password
487 Password = GenPass();
488 Pass = HashPass(Password);
490 print "%s: %s\n" %(sys.exc_type,sys.exc_value);
491 raw_input("Press a key");
494 print "Setting password..";
495 Pass = "{crypt}" + Pass;
496 shadowLast = str(int(time.time()/24/60/60));
497 l.modify_s(UserDn,[(ldap.MOD_REPLACE,"userPassword",Pass),
498 (ldap.MOD_REPLACE,"shadowLastChange",shadowLast)]);
499 Attrs[0][1]["userPassword"] = [Pass];
500 Attrs[0][1]["shadowLastChange"] = [shadowLast];
504 if Response == 'L' and RootMode == 1:
505 Resp = raw_input("Really lock account? [no/yes]");
509 print "Setting password..";
513 # Handle changing an arbitary value
514 if (Response == "a"):
515 Attr = raw_input("Attr? ");
516 ChangeAttr(Attrs[0],Attr);
519 # Convert the integer response
522 if (not OrderedIndex.has_key(ID) or (ID > 100 and RootMode == 0)):
528 # Print the what to do prompt
529 print "Changing LDAP entry '%s' (%s)" % (OrderedIndex[ID][0],OrderedIndex[ID][2]);
530 print AttrPrompt[OrderedIndex[ID][2]][0];
531 ChangeAttr(Attrs[0],OrderedIndex[ID][2]);