1

I'm in an environment where user/group information is maintained in /etc/passwd and /etc/group files, which are NFS mounted. This is nice because we can just edit flat files to change user/group information. However, the OS X machines in our setup don't like this very much, because Directory Services doesn't pick up on when these files change.

Therefore, I'm planning on setting up a cron job to run something like this once a day or so:

dsimport -g /etc/group /Local/Default O -T xDSStandardGroup -u $ADMIN_USER -p $ADMIN_PASS

The problem is those two last arguments at the end: user and password. I want to avoid writing out passwords in scripts, to reduce the risk of them being compromised. Is there any way of using dscl or dsimport without having to provide a password, but instead having them simply use the privileges of the user running the command? (You know, the way every standard Unix command does.) Or is there some other way of accomplishing this without writing out passwords in cleartext?

6
  • 1
    Any reason why NIS wasn't used? NFS-mounted /etc files just seems... wrong. Dec 14, 2009 at 21:16
  • It's a legacy system. We're working on upgrading it, but for now, it works well enough. Dec 14, 2009 at 21:38
  • Which machine will you run this on? Just one server, or all the clients? Dec 14, 2009 at 21:48
  • Probably run it on all clients. Dec 15, 2009 at 18:53
  • The Mac often reloads files when it knows they are updated. If you had /etc/passwd on a local machine and 'touch'ed it, I suspect that change would take effect almost immediately. (Being NFS mounted probably breaks this, though.) Dec 16, 2009 at 17:23

1 Answer 1

1

Just browsing through my notes on dscl, which I've scripted fairly extensively. I'm fairly sure that the answer is no, there is no way to avoid supplying the password. The only exception might be if you were root on the local box (which, in your example, does appear to be the case). [I've almost exclusively done changes over the network.]

If you use expect or pexpect, you can encoded the password in a script (in a reversible manner), and then call into the program you need. [I've come up with a method to encode/decode something that looks like gobbledygook, but it is security through obscurity, I'm afraid.]

For using pexpect, something along these lines would work [note that this example uses dscl, and not dsimport! (I imagine it could be simplified a fair bit for your purposed; turning on the logging command for the dscl child helps when setting things up)]:

#!/usr/bin/env python

import pexpect
# If you don't have pexpect, you should be able to run
# 'sudo easy_install pexpect' to get it

### Fill in these variables
diradmin = "diradmin"
host = "host"
directory = '/Local/Default'   # '/LDAPv3/127.0.0.1'
# Note: it is possible to encode the data here so it is not in plain text!
password = "password"

DSCL_PROMPT = " > " # Don't change this (unless the dscl tool changes)

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

def RunDSCLCommand(dscl_child, command):
    """Issues one dscl command; returns if it succeeded or failed.

    command = the command to be sent to dscl, such as 'passwd Users/foo newpword'   
    """

    assert dscl_child is not None, "No connection successfully established"

    # We should be logged in with a prompt awaiting us
    expected_list = [ pexpect.EOF, pexpect.TIMEOUT,
                     '(?i)error', 'Invalid Path', DSCL_PROMPT ]
    desired_index = len(expected_list) - 1
    invalid_path_index = desired_index - 1

    dscl_child.sendline(command)
    reply_index = dscl_child.expect(expected_list)
    if reply_index == desired_index:
        return True

    # Find the next prompt so that on the next call to a command like this
    # one, we will know we are at a consistent starting place
    # Looking at the self.dscl_child.before will likely contain
    # the error that occured, but for now:
    dscl_child.expect(DSCL_PROMPT)

    if invalid_path_is_success and reply_index == invalid_path_index:
        # The item doesn't exist, but we will still count it
        # as a success.  (Most likely we were told to delete the item).
        return True

    # one of the error conditions was triggered
    return False


# Here is the part of the program where we start doing things

prompt = DSCL_PROMPT

dscl_child = pexpect.spawn("dscl -u %s -p %s" % (diradmin, host))
#dscl_child.logfile = file("dscl_child.log", "w") # log what is going on

success = False

if (ReplyOnGoodResult(self.dscl_child, "Password:", password) and
   ReplyOnGoodResult(self.dscl_child, prompt, "cd %s" % directory) and
   ReplyOnGoodResult(self.dscl_child, prompt, "auth %s %s" % (diradmin, password)) and
   ReplyOnGoodResult(self.dscl_child, prompt, None)):
 success = True

if success:
 # Now issue a command
 success = RunDSCLCommand(dscl_child, 'passwd Users/foo newpword')

dscl_child.close()

I have posted some of the code that I am using here; I'm afraid it is woefully unsupported (and posted to the pymacadmin group about it here. Unfortunately, it doesn't look like I wrote up anything on how to use it :(

2
  • "I'm fairly sure that the answer is no, there is no way to avoid supplying the password." That's what I was afraid of. What's the benefit of using pexpect rather than bash/cron? Dec 15, 2009 at 19:15
  • If you use straight bash, the username and password are right there on the command line (and can be potentially be compromised, by, say, someone running ps aux). With (p)expect, you run the program without providing the password on the command line, and then provide it when prompted. (IIRC, it does not prompt to stdout or read from stdin, so you need something like (p)expect that simulates a tty and can read and write data to the process.) Dec 15, 2009 at 22:22

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .