#!/usr/bin/env python # -*- mode: python -*- # This script is an interactive way to manipulate fields in the LDAP directory. # When run it connects to the directory using the current users ID and fetches # all the attributes for that user. It then formats them nicely and allows # the user to change them. # It is possible to authenticate as someone differnt than you are viewing/changing # this allows administrative functions and also allows users to view # restricted information about others, such as phone numbers and addresses. # # Usage: userinfo -a -u -c -r # -a Set the authentication user (the user whose password you are # going to enter) # -u Set the user to display # -c Set both -a and -u, use this if your login uid is not in the # database # -r Enable 'root' functions, do this if your uid has access to # restricted variables. # Copyright (c) 1999-2001 Jason Gunthorpe # Copyright (c) 2004-2005,7,8 Joey Schulze # Copyright (c) 2001-2006 Ryan Murray # Copyright (c) 2008,2009 Peter Palfrader # Copyright (c) 2008 Martin Zobel-Helas # Copyright (c) 2008 Marc 'HE' Brockschmidt # Copyright (c) 2008 Mark Hymers # Copyright (c) 2008 Thomas Viehmann # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. import time, os, pwd, sys, getopt, ldap, crypt, readline, copy, getpass from userdir_ldap import *; RootMode = 0; AttrInfo = {"cn": ["First Name", 101], "mn": ["Middle Name", 102], "sn": ["Surname", 103], "c": ["Country Code",1], "l": ["Locality",2], "ou": ["Membership",0], "facsimileTelephoneNumber": ["Fax Phone Number",3], "telephoneNumber": ["Phone Number",4], "postalAddress": ["Mailing Address",5], "postalCode": ["Postal Code",6], "uid": ["Unix User ID",0], "loginShell": ["Unix Shell",7], "supplementaryGid": ["Unix Groups",0], "allowedHost": ["Host ACL",0], "member": ["LDAP Group",0], "emailForward": ["Email Forwarding",8], "ircNick": ["IRC Nickname",9], "onVacation": ["Vacation Message",10], "labeledURI": ["Home Page",11], "latitude": ["Latitude",12], "longitude": ["Longitude",13], "icqUin": ["ICQ UIN",14], "jabberJID": ["Jabber ID",15], "privateSub": ["Debian-Private",16], "birthDate": ["Date of Birth",18], "mailDisableMessage": ["Mail Disabled",19], "mailGreylisting": ["Mail Greylisting",20], "mailCallout": ["Mail Callouts",21], "mailRBL": ["Mail RBLs",22], "mailRHSBL": ["Mail RHSBLs",23], "mailWhitelist": ["Mail Whitelist",24], "mailContentInspectionAction": ["mail C-I Action",25], "VoIP": ["VoIP Address",26], "comment": ["Comment",116], "userPassword": ["Crypted Password",117], "dnsZoneEntry": ["d.net Entry",118], "accountStatus": ["DD status",301], "accountComment": ["DD status comment",302], }; AttrPrompt = {"cn": ["Common name or first name"], "mn": ["Middle name (or initial if it ends in a dot)"], "sn": ["Surname or last name"], "c": ["ISO 2 letter country code, such as US, DE, etc"], "l": ["City name, State/Provice (Locality)\n e.g. Dallas, Texas"], "facsimileTelephoneNumber": ["Fax phone number, with area code and country code"], "telephoneNumber": ["Voice phone number"], "postalAddress": ["Complete mailing address including postal codes and country designations\nSeperate lines using a $ character"], "postalCode": ["Postal Code or Zip Code"], "loginShell": ["Login shell with full path (no check is done for validity)"], "emailForward": ["EMail address to send all mail to or blank to disable"], "ircNick": ["IRC nickname if you use IRC"], "onVacation": ["A message if on vaction, indicating the time of departure and return"], "userPassword": ["The users Crypt'd password"], "comment": ["Admin Comment about the account"], "supplementaryGid": ["Groups the user is in"], "allowedHost": ["Grant access to certain hosts"], "privateSub": ["Debian-Private mailing list subscription"], "birthDate": ["Date of Birth (YYYYMMDD)"], "mailDisableMessage": ["Error message to return via SMTP"], "mailGreylisting": ["SMTP Greylisting (TRUE/FALSE)"], "mailCallout": ["SMTP Callouts (TRUE/FALSE)"], "mailRBL": ["SMTP time RBL lists"], "mailRHSBL": ["SMTP time RHSBL lists"], "mailWhitelist": ["SMTP time whitelist from other checks"], "mailContentInspectionAction": ["Content Inspection Action (reject, blackhole, markup)"], "member": ["LDAP Group Member for slapd ACLs"], "latitude": ["XEarth latitude in ISO 6709 format - see /usr/share/zoneinfo/zone.tab or etak.com"], "longitude": ["XEarth latitude in ISO 6709 format - see /usr/share/zoneinfo/zone.tab or etak.com"], "dnsZoneEntry": ["DNS Zone fragment associated this this user"], "labeledURI": ["Web home page"], "jabberJID": ["Jabber ID"], "icqUin": ["ICQ UIN Number"], "VoIP": ["VoIP Address"], "accountStatus": ["DD status"], "accountComment": ["DD status comment"], }; # Create a map of IDs to desc,value,attr OrderedIndex = {}; for at in AttrInfo.keys(): if (AttrInfo[at][1] != 0): OrderedIndex[AttrInfo[at][1]] = [AttrInfo[at][0], "", at]; OrigOrderedIndex = copy.deepcopy(OrderedIndex); for id in OrderedIndex: if not AttrPrompt.has_key( OrderedIndex[id][2] ): print "Warning: no AttrPrompt for %s"%(id) # Show shadow information def PrintShadow(Attrs): Changed = int(GetAttr(Attrs,"shadowLastChange","0")); MinDays = int(GetAttr(Attrs,"shadowMin","0")); MaxDays = int(GetAttr(Attrs,"shadowMax","0")); WarnDays = int(GetAttr(Attrs,"shadowWarning","0")); InactDays = int(GetAttr(Attrs,"shadowInactive","0")); Expire = int(GetAttr(Attrs,"shadowExpire","0")); print "%-24s:" % ("Password last changed"), print time.strftime("%a %d/%m/%Y %Z",time.localtime(Changed*24*60*60)); if (Expire > 0): print "%-24s:" % ("Account expires on"), print time.strftime("%a %d/%m/%Y %Z",time.localtime(Expire*24*60*60)); if (InactDays >= 0 and MaxDays < 99999): print "Account aging is active, you must change your password every", MaxDays, "days." # Print out the automatic time stamp information def PrintModTime(Attrs): Stamp = GetAttr(Attrs,"modifyTimestamp",""); if len(Stamp) >= 13: Time = (int(Stamp[0:4]),int(Stamp[4:6]),int(Stamp[6:8]), int(Stamp[8:10]),int(Stamp[10:12]),int(Stamp[12:14]),0,0,-1); print "%-24s:" % ("Record last modified on"), time.strftime("%a %d/%m/%Y %X UTC",Time), print "by",ldap.explode_dn(GetAttr(Attrs,"modifiersName"),1)[0]; Stamp = GetAttr(Attrs,"createTimestamp",""); if len(Stamp) >= 13: Time = (int(Stamp[0:4]),int(Stamp[4:6]),int(Stamp[6:8]), int(Stamp[8:10]),int(Stamp[10:12]),int(Stamp[12:14]),0,0,-1); print "%-24s:" % ("Record created on"), time.strftime("%a %d/%m/%Y %X UTC",Time); # Print the PGP key for a user def PrintKeys(Attrs): if Attrs[1].has_key("keyFingerPrint") == 0: return; First = 0; for x in Attrs[1]["keyFingerPrint"]: if First == 0: print "%-24s:" % ("PGP/GPG Key Fingerprints"), First = 1; else: print "%-24s:" % (""), print FormatPGPKey(x); # Print the SSH RSA Authentication keys for a user def PrintSshRSAKeys(Attrs): if Attrs[1].has_key("sshRSAAuthKey") == 0: return; First = 0; for x in Attrs[1]["sshRSAAuthKey"]: if First == 0: print "%-24s:" % ("SSH Auth Keys"), First = 1; else: print "%-24s:" % (""), print FormatSSHAuth(x); # Display all of the attributes in a numbered list def ShowAttrs(Attrs): print; print EmailAddress(Attrs); PrintModTime(Attrs); PrintShadow(Attrs); PrintKeys(Attrs); PrintSshRSAKeys(Attrs); for at in Attrs[1].keys(): if AttrInfo.has_key(at): if AttrInfo[at][1] == 0: print " %-18s:" % (AttrInfo[at][0]), for x in Attrs[1][at]: print "'%s'" % (x), if at == "uid": print "(id=%s, gid=%s)" % (GetAttr(Attrs,"uidNumber","-1"),GetAttr(Attrs,"gidNumber","-1")), print; else: OrderedIndex[AttrInfo[at][1]][1] = Attrs[1][at]; Keys = OrderedIndex.keys(); Keys.sort(); for at in Keys: if at < 100 or RootMode != 0: print " %3u) %-19s: " % (at,OrderedIndex[at][0]), for x in OrderedIndex[at][1]: print "'%s'" % (re.sub('[\n\r]','?',x)), print; # Change a single attribute def ChangeAttr(Attrs,Attr): if (Attr == "supplementaryGid" or Attr == "allowedHost" or \ Attr == "member" or Attr == "dnsZoneEntry" or Attr == "mailWhitelist" or \ Attr == "mailRBL" or Attr == "mailRHSBL"): return MultiChangeAttr(Attrs,Attr); print "Old value: '%s'" % (GetAttr(Attrs,Attr,"")); print "Press enter to leave unchanged and a single space to set to empty"; NewValue = raw_input("New? "); # Empty string if (NewValue == ""): print "Leaving unchanged."; return; # Single space designates delete, trap the delete error if (NewValue == " "): print "Deleting.",; try: l.modify_s(UserDn,[(ldap.MOD_DELETE,Attr,None)]); except ldap.NO_SUCH_ATTRIBUTE: pass; print; Attrs[1][Attr] = [""]; return; if (Attr == "mailGreylisting" or Attr == "mailCallout"): if (NewValue.lower() != "true" and NewValue.lower() != "false"): if (NewValue == "1"): NewValue = "true" else: if (NewValue == "0"): NewValue = "false" else: print "Need a boolean value" return NewValue = NewValue.upper() # Set a new value print "Setting.",; l.modify_s(UserDn,[(ldap.MOD_REPLACE,Attr,NewValue)]); Attrs[1][Attr] = [NewValue]; print; def MultiChangeAttr(Attrs,Attr): # Make sure that we have an entry if not Attrs[1].has_key(Attr): Attrs[1][Attr] = []; Attrs[1][Attr].sort(); print "Old values: ",Attrs[1][Attr]; Mode = raw_input("[D]elete or [A]dd? ").upper() if (Mode != 'D' and Mode != 'A'): return; NewValue = raw_input("Value? "); # Empty string if (NewValue == ""): print "Leaving unchanged."; return; # Delete if (Mode == "D"): print "Deleting.",; try: l.modify_s(UserDn,[(ldap.MOD_DELETE,Attr,NewValue)]); except ldap.NO_SUCH_ATTRIBUTE: print "Failed"; print; Attrs[1][Attr].remove(NewValue); return; # Set a new value print "Setting.",; l.modify_s(UserDn,[(ldap.MOD_ADD,Attr,NewValue)]); Attrs[1][Attr].append(NewValue); print; def Lock(UserDn, Attrs, DisableMail=True): shadowLast = str(int(time.time()/24/60/60)); recs = [ (ldap.MOD_REPLACE,"userPassword","{crypt}*LK*"), (ldap.MOD_REPLACE,"shadowLastChange",shadowLast), (ldap.MOD_REPLACE,"shadowExpire","1")]; if DisableMail: recs.append( (ldap.MOD_REPLACE,"mailDisableMessage","account locked") ) Attrs[0][1]["mailDisableMessage"] = ["account locked"]; l.modify_s(UserDn,recs); Attrs[0][1]["userPassword"] = ["{crypt}*LK*"]; Attrs[0][1]["shadowLastChange"] = [shadowLast]; Attrs[0][1]["shadowExpire"] = ["1"]; # Main program starts here User = pwd.getpwuid(os.getuid())[0]; BindUser = User; # Process options try: (options, arguments) = getopt.getopt(sys.argv[1:], "nu:c:a:r") except getopt.GetoptError, data: print data sys.exit(1) for (switch, val) in options: if (switch == '-u'): User = val; elif (switch == '-a'): BindUser = val; elif (switch == '-c'): BindUser = val; User = val; elif (switch == '-r'): RootMode = 1; elif (switch == '-n'): BindUser = ""; if (BindUser != ""): print "Accessing LDAP entry for '" + User + "'", if (BindUser != User): if (BindUser != ""): print "as '" + BindUser + "'"; else: print; # Connect to the ldap server l = connectLDAP() UserDn = "uid=" + User + "," + BaseDn if (BindUser != ""): Password = getpass.getpass(BindUser + "'s password: ") BindUserDn = "uid=" + BindUser + "," + BaseDn else: Password = "" BindUserDn = "" try: l.simple_bind_s(BindUserDn,Password) except ldap.LDAPError,e: print >> sys.stderr, "LDAP error:", e.args[0]['desc'] print >> sys.stderr, " ", e.args[0]['info'] sys.exit(1) # Enable changing of supplementary gid's if (RootMode == 1): # Items that root can edit list = ["supplementaryGid","allowedHost","member"]; Count = 0; for x in list: AttrInfo[x][1] = 200 + Count; OrderedIndex[AttrInfo[x][1]] = [AttrInfo[x][0], "",x]; OrigOrderedIndex[AttrInfo[x][1]] = [AttrInfo[x][0], "",x]; Count = Count + 1; # Query the server for all of the attributes Attrs = l.search_s(BaseDn,ldap.SCOPE_ONELEVEL,"uid=" + User); if len(Attrs) == 0: print "User",User,"was not found."; sys.exit(0); # repeatedly show the account configuration while(1): ShowAttrs(Attrs[0]); if (BindUser == ""): sys.exit(0); if RootMode == 1: print " a) Arbitary Change"; print " r) retire developer"; print " R) Randomize Password"; print " L) Lock account and disable mail"; print " p) Change Password"; print " u) Switch Users"; print " x) Exit"; # Prompt Response = raw_input("Change? "); if (Response == "x" or Response == "X" or Response == "q" or Response == "quit" or Response == "exit"): break; # Change who we are looking at if (Response == 'u' or Response == 'U'): NewUser = raw_input("User? "); if NewUser == "": continue; NAttrs = l.search_s(BaseDn,ldap.SCOPE_ONELEVEL,"uid=" + NewUser); if len(NAttrs) == 0: print "User",NewUser,"was not found."; continue; Attrs = NAttrs; User = NewUser; UserDn = "uid=" + User + "," + BaseDn; OrderedIndex = copy.deepcopy(OrigOrderedIndex); continue; # Handle changing the password if (Response == "p"): print "Please enter a new password. Your password can be of unlimited length,"; print "contain spaces and other special characters. No checking is done on the"; print "strength of the passwords so pick good ones please!"; Pass1 = getpass.getpass(User + "'s new password: ") Pass2 = getpass.getpass(User + "'s new password again: ") if Pass1 != Pass2: print "Passwords did not match"; raw_input("Press a key"); continue; try: Pass = HashPass(Pass1); except: print "%s: %s\n" %(sys.exc_type,sys.exc_value); raw_input("Press a key"); continue; print "Setting password.."; Pass = "{crypt}" + Pass; shadowLast = str(int(time.time()/24/60/60)); l.modify_s(UserDn,[(ldap.MOD_REPLACE,"userPassword",Pass), (ldap.MOD_REPLACE,"shadowLastChange",shadowLast)]); Attrs[0][1]["userPassword"] = [Pass]; Attrs[0][1]["shadowLastChange"] = [shadowLast]; continue; # retire DD if Response == 'r' and RootMode == 1: if Attrs[0][1].has_key("accountStatus") == 0: curStatus = "" else: curStatus = Attrs[0][1]["accountStatus"][0] if Attrs[0][1].has_key("accountComment") == 0: curComment = "" else: curComment = Attrs[0][1]["accountComment"][0] print "\n\nCurrent status is %s"%curStatus print "Current comment is %s\n"%curComment print "Set account to:" print " 1) retiring (lock account but do not disable mail):" print " 2) inactive (removed/emeritus/... - lock account and disable mail):" print " 3) memorial (lock account and disable mail):" print " 4) active (do not change other settings, you will have to deal with them)" print " q) return (no change)" Resp = raw_input("Action? ") if Resp == "1" or Resp == "2": Lock(UserDn, Attrs, Resp == "2") if Resp == "1": newstatus = "retiring %s"%(time.strftime("%Y-%m-%d")) else: newstatus = "inactive %s"%(time.strftime("%Y-%m-%d")) l.modify_s(UserDn,[(ldap.MOD_REPLACE,"accountStatus",newstatus)]) Attrs[0][1]["accountStatus"] = [newstatus] Resp2 = raw_input("Optional RT ticket number? ") if (Resp2 != ''): comment = "RT#%s"%(Resp2) l.modify_s(UserDn,[(ldap.MOD_REPLACE,"accountComment",comment)]) Attrs[0][1]["accountComment"] = [comment] elif Resp == "3": Lock(UserDn, Attrs) newstatus = "memorial" l.modify_s(UserDn,[(ldap.MOD_REPLACE,"accountStatus",newstatus)]) Attrs[0][1]["accountStatus"] = [newstatus] elif Resp == "4": newstatus = "active" l.modify_s(UserDn,[(ldap.MOD_REPLACE,"accountStatus",newstatus)]) Attrs[0][1]["accountStatus"] = [newstatus] continue; # Randomize password if Response == 'R' and RootMode == 1: Resp = raw_input("Randomize Users Password? [no/yes]"); if Resp != "yes": continue; # Generate a random password try: Password = GenPass(); Pass = HashPass(Password); except: print "%s: %s\n" %(sys.exc_type,sys.exc_value); raw_input("Press a key"); continue; print "Setting password.."; Pass = "{crypt}" + Pass; shadowLast = str(int(time.time()/24/60/60)); l.modify_s(UserDn,[(ldap.MOD_REPLACE,"userPassword",Pass), (ldap.MOD_REPLACE,"shadowLastChange",shadowLast)]); Attrs[0][1]["userPassword"] = [Pass]; Attrs[0][1]["shadowLastChange"] = [shadowLast]; continue; # Lock account if Response == 'L' and RootMode == 1: Resp = raw_input("Really lock account? [no/yes]"); if Resp != "yes": continue; print "Setting password.."; Lock(UserDn, Attrs) continue; # Handle changing an arbitary value if (Response == "a"): Attr = raw_input("Attr? "); ChangeAttr(Attrs[0],Attr); continue; # Convert the integer response try: ID = int(Response); if (not OrderedIndex.has_key(ID) or (ID > 100 and RootMode == 0)): raise ValueError; except ValueError: print "Invalid"; continue; # Print the what to do prompt print "Changing LDAP entry '%s' (%s)" % (OrderedIndex[ID][0],OrderedIndex[ID][2]); print AttrPrompt[OrderedIndex[ID][2]][0]; ChangeAttr(Attrs[0],OrderedIndex[ID][2]);