3

I understand Open Directory to be OpenLDAP + SASL (Password Server) + Kerberos. It appears that OpenLDAP defers to SASL for authentication; I don't know about Kerberos.

I want to change user passwords from a script, preferably remotely, and I want the password to be changed properly. (ie. in no event do I want a user to have a different password depending upon which of the three services that go into Open Directory they authenticate against.)

I can do a dsimport over the network just fine from a machine that is not bound to the directory, but, when you try to import a password (depite setting the AuthType to dsAuthMethodStandard:dsAuthClearText), it will work only if the password has not been set before. (I believe it is possible to set a Crypt password, but I fear that means that only the LDAP portion of OD will know the current password.)

Is there anything I can do short of initiating an ssh session to the server and changing the passwords there? If I do that, is there any command that will let me specify a number of users and their new passwords on one line?

Which commands work to change all open directory passwords, and is there any one to prefer?

apropos password gives me these interesting results:

  • kpasswd(1) - change a user's Kerberos password
  • ldappasswd(1) - change the password of an LDAP entry
  • lppasswd(1) - add, change, or delete digest passwords
  • passwd(1) - modify a user's password
  • pwpolicy(8) - gets and sets password policies
  • saslpasswd2(8) - set a user's sasl password
  • slappasswd(8) - OpenLDAP password utility

I'll look at some of the man pages, and I'm under the impression that pwpolicy is the best choice, but I'd love to know if there are any subtleties to using these (such as, don't change the Kerberos password without also changing the LDAP and SASL passwords), and if any of them work remotely without an ssh session.

Clinton Blackmore
  • 3,510
  • 6
  • 35
  • 61

4 Answers4

5

The handiest answer I've come across is to use the passwd command in conjunction with dscl. Here is the output from an interactive session (with the passwords replaced by asterices):

$ dscl -u diradmin -p ces 
Password: 
 > cd /LDAPv3/127.0.0.1/
/LDAPv3/127.0.0.1 > auth diradmin *****
/LDAPv3/127.0.0.1 > passwd Users/Atwo807 *****
/LDAPv3/127.0.0.1 > passwd Users/Atwo249 *****
/LDAPv3/127.0.0.1 > passwd Users/doesnotexist foobar
passwd: Invalid Path
<dscl_cmd> DS Error: -14009 (eDSUnknownNodeName)
/LDAPv3/127.0.0.1 > exit
Goodbye

Here is a python script to make the changes. You will need the pexpect module (sudo easy_install pexpect should get it for you; I don't think you need the dev tools installed).

#!/usr/bin/env python

import pexpect

def ChangePasswords(host, path, diradmin, diradmin_password, user_passwords, record_type='Users'):
    """Changes passwords in a Open Directory or similar directory service.

    host = the dns name or IP of the computer hosting the directory
    path = the pathname to the directory (ex. '/LDAPv3/127.0.0.1')
    diradmin = the directory administrator's shortname (ex. 'diradmin')
    diradmin_password = the directory administrator's password
    user_passwords = a dictionary mapping record names (typically, user's short
                     names) onto their new password
    record_type = the sort of records you are updating.  Typically 'Users'

    Returns a tuple.  The first entry is a list of all records (users) who
        failed to update.  The second entry is a list of all records (users)
        who successfully updated.
    """

    failed_list = []
    succeeded_list = []
    prompt = " > "

    child = pexpect.spawn("dscl -u %s -p %s" % (diradmin, host))

    if not (ReplyOnGoodResult(child, "Password:", diradmin_password) and
       ReplyOnGoodResult(child, prompt, "cd %s" % path) and
       ReplyOnGoodResult(child, prompt,
                        "auth %s %s"  % (diradmin, diradmin_password)) and
       ReplyOnGoodResult(child, prompt, None)):
        print "Failed to log in and authenticate"
        failed_list = user_passwords.keys()
        return (failed_list, succeeded_list)

    # We are now logged in, and have a prompt waiting for us
    expected_list = [ pexpect.EOF, pexpect.TIMEOUT,
                     '(?i)error', 'Invalid Path', prompt ]
    desired_index = len(expected_list) - 1
    for record_name in user_passwords:        
        #print "Updating password for %s" % record_name,

        child.sendline("passwd %s/%s %s" % (record_type, record_name,
                                            user_passwords[record_name]))
        if child.expect(expected_list) == desired_index:
            #print ": Succeeded"
            succeeded_list.append(record_name)
        else:
            #print ": Failed"
            failed_list.append(record_name)
            child.expect(prompt)

    child.sendline("exit")
    child.expect(pexpect.EOF)

    return (failed_list, succeeded_list)


def ReplyOnGoodResult(child, desired, reply):
    """Helps analyze the results as we try to set passwords.

    child = a pexpect child process
    desired = The value we hope to see 
    reply = text to send if we get the desired result (or None for no reply)
    If we do get the desired result, we send the reply and return true.
    If not, we return false."""

    expectations = [ pexpect.EOF, pexpect.TIMEOUT, '(?i)error', desired ]
    desired_index = len(expectations) - 1

    index = child.expect(expectations)
    if index == desired_index:
        if reply:
            child.sendline(reply)
        return True
    else:
        return False

You can use it as follows:

# This example assumes that you have named the script given above 'pwchange.py'
# and that it is in the current working directory
import pwchange 

(failed, succeeded) = pwchange.ChangePasswords("ces", "/LDAPv3/127.0.0.1", 
     "diradmin", "******", 
     { 'Atwo807' : '*****', 'Atwo249' : '*****', 
       'Nonexist' : 'foobar', 'Bad' : 'bad' })

print failed, succeeded
['Bad', 'Nonexist'] ['Atwo249', 'Atwo807']
Clinton Blackmore
  • 3,510
  • 6
  • 35
  • 61
1

try using

dscl -u xxxxxx -P xxxxxxx /LDAPv3/127.0.0.1/ -authonly username

and then

dscl -u diradmin -P xxxxx /LDAPv3/127.0.0.1/ -passwd Users/username newpassword

these commands can run from the local machine or a remote machine. The remote client must be connected to the directory in Directory Utility. A remote authentication check from an authorized machine looks like

dscl -u diradmin -P 123456 /LDAPv3/ldap.remote.com/ -authonly username

i have also used

dscl -u diradmin -P 123456 -url //LDAPv3/ldap.remote.com/ -authonly username
drfoo
  • 11
  • 1
1

Keep in mind that the password on each user's Login keychain is normally kept synchronized with their Open Directory password. Mac OS X is smart enough to use the password entered at the login window to not only authenticate the user against OD, but also unlock their keychain. If the two passwords get out of sync, it will be inconvenient and confusing for most users.

AFAIK, none of the methods of changing the OD password on the server side are able to modify the user's keychain password. However, the people at AFP548 create a solution to this problem called Keychain Minder that helps users who get into this situation.

lukecyca
  • 2,185
  • 13
  • 20
  • Huh. I thought that the keychain only did this for users with a local account. I've never had a network user complain about the keychain when we've changed their password. – Clinton Blackmore Aug 24 '09 at 17:44
0

Here is some useful stuff I've learned:

From the command-line admin doc for OS X 10.5, p 261, "Managing Open Directory Passwords":

Managing Open Directory Passwords

When a user’s account has a password type of Open Directory, the user can be authenticated by Kerberos or the Open Directory Password Server. Kerberos is a network authentication system that uses credentials issued by a trusted server.

The Open Directory Password Server supports traditional password authentication methods that some network services or users’ client applications require. Services can be configured to not allow Kerberos. In that case they use Password Server for user accounts with Open Directory passwords.

Neither Kerberos nor the Open Directory Password Server stores the password in the user’s account. Both Kerberos and the Open Directory Password Server store passwords in secure databases apart from the directory domain and they never allow passwords to be read. Passwords can only be set and verified.

Open Directory Password Server

Password Server uses standard Simple Authentication and Security Layer (SASL) technology to negotiate an authentication method between a client and a service. Password Server supports multiple authentication methods, including APOP, CRAM- MD5, DHX, Digest-MD5, MS-CHAPv2, NTLMv1 and NTLMv2, LAN Manager, and WebDAV-Digest.

Open Directory also provides authentication services using shadow passwords, which support the same authentication methods as Password Server.

Regarding using ldappasswd, the command-line admin doc further states:

Apple recommends using passwd instead of ldappasswd. For more information, see the passwd man page.

man passwd refers to an OpenSSL tool that computes password hashes. To get the man page for the passwd command you really want, man /usr/share/man/man1/passwd.1.gz is more useful.

Clinton Blackmore
  • 3,510
  • 6
  • 35
  • 61
  • You don't need to dive into /usr/share/... to view that manpage, just use `man 1 passwd`. – Kyle Smith Mar 13 '10 at 16:56
  • 1
    @Kyle - When I type `man passwd` I get PASSWD(1) OpenSSL "passwd - compute password hashes". When I use the full path, I get PASSWD(1) BSD General Commands Manual "passwd -- modify a user's password". When I try `man 1 passwd` I get PASSWD(1) OpenSSL "passwd - compute password hashes", which is not the one I want. I appreciate the tip, though. I don't understand why there would be two `passwd` commands in the man 1 section. – Clinton Blackmore Mar 15 '10 at 16:56