Spacer http://macenterprise.org MacResource.org - Mac OS X enterprise deployment project Spacer
Site Map Contact Us Site Map About Us Top Background
 
Search
 
 
Using niutil to manage user accounts E-mail
Written by Greg Neagle   
Thursday, 30 December 2004
ImageLet's say you've assumed responsibility for managing a new group of Mac OS X machines. These machines were previously set up by a variety of people, and have different local admin account names and passwords. To simplify your life, you want to standardize the local admin account name and password, and additionally, create a way to update the local admin password periodically.

Greg Neagle
Walt Disney Feature Animation
December 2004

If you can access each machine remotely using Apple Remote Desktop or ssh, or you can deliver files remotely using NetOctopus, Timbuktu, FileWave, or radmind, you can use a script and a command utility known as niutil to accomplish this task.

This article will present a specific solution for this specific problem, but the techniques presented can be adapted for a variety of problems.

The main thrust of this article is for OS X 10.3 (Panther) deployments, but see the section on "Alternatives" near the end for options for OS X 10.2 (Jaguar) deployments.

niutil

niutil, which stands for NetInfo Utility, is, unsurprisingly, a command-line utility for working with NetInfo, the database OS X uses to store local account and group information, among other things. niutil can be used to read the local NetInfo database and add, remove, or edit items within the database.

For more info on niutil, type man niutil at a command line prompt.

The fixadmin.pl script

Here's the perl script. It's pretty long, so I'll break it up into pieces and discuss what is happening in each section.
#!/usr/bin/perl

# fixadmin.pl
#
# A script to change the primary admin account
# to a standard name and password
#
# optionally sync the root password or disable root login
#
# by Greg Neagle, Walt Disney Feature Animation
# this version 12/29/2004

use strict;
One of the first things the script does is make sure it is being run as root, and prints an error message if it is not. We can't write to (or read from) the shadow password directory unless we're running as root, so there would be no point in continuing.
my $currentuser = `/usr/bin/whoami`;
chomp $currentuser;

if ($currentuser ne "root") {
   print "This script must be run as root. Use sudo if needed.n";
   exit 1;
}
The next few lines control the short name, long name, and home directory path of the admin account. Avoid using "admin" or "Administrator" as names. Good security practices dictate avoiding easily guessable names for admin accounts -- if an attacker knows the account name, he/she can focus on attacking the password. Of course, this also implies that it's a good idea to keep the root account disabled...
In this example, I've also moved the admin's home directory out of /Users and to someplace more hidden. This way, regular users need not be bothered by the existence of an admin folder inside "/Users". This may be more desireable for one-to-one deployments, where the user has a sense that this is his or her machine, and may be annoyed by the existence of an admin folder in /Users. Of course, you can put this home directory anywhere you want, including in the standard location.
# change these next three lines as needed
my $adminName = "osxadmin";
my $adminRealname = "OS X Admin";
my $adminHomeDir = "/private/var/osxadmin";
Speaking of the root account, by changing the values of $syncRootPassword and $disableRoot you can change the behavior of this script.
See later in the script for what these flags do...
# do you want the root password synchronized with the admin password?
# set $syncRootPassword to 0 if not
my $syncRootPassword = 1;

# do you want root login disabled?
# set $disableRoot to 0 if not
my $disableRoot = 1;
In the interests of simplicity, we are going to use hard-coded values for the "generateduid" properties for the root and admin accounts.
There is some risk to this (see the comments), but the chance of overlapping generateduids is small.
One benefit of having a hard-coded generateduid for these accounts is that in the future, you could change the passwords for these accounts by simply replacing the /private/var/db/shadow/hash/<generateduid> file(s); no need to run this script again.
# Using a hard-coded generateduid is slightly risky,
# as it is possible there might be an existing account with this same generateduid
# but since the generateduid contains part of the MAC layer address of the original machine,
# it is unlikely we will collide with existing generateduids.
#
# A better implementation would look at all the existing generateduids
# and make sure we are unique.
#
# A perfect implementation would use the same method Apple uses to generate
# the generateduid
#
my $adminGeneratedUID = "96575CC3-5478-11D9-B0D9-00306571CDC4";
my $adminShadowFile = "/private/var/db/shadow/hash/$adminGeneratedUID";
my $rootGeneratedUID = "E2DE3ADA-D12C-11D8-B36B-00306571CDC4";
my $rootShadowFile = "/private/var/db/shadow/hash/$rootGeneratedUID";
$shadowPassword contains the encrypted hash of the password you want to use as your standard password. Obtaining this hash is relatively straightforward. Later in the article I'll provide a script that makes it even easier.
The example password here is completely bogus and not internally consistent - I made random changes to the string before posting it here.
# change the next uncommented line to change the password.
# to get this, look at the file /private/var/db/shadow/hash/($adminGeneratedUID)
# after you've changed the password on one client.
my $shadowPassword = "1B7FA311FB698F82E895FE3BE4C9CB1FAE1F4FB188B6DF18AAD3B435B51404EECBECCACC64687283DD31EF431BF309A14697F6E1";
Now we finally use niutil to read some information from the local NetInfo database. We read the short name ("name"), the long name ("realname"), and the home directory path ("home") properties for the user with a UID of 501, which is the first local user created after installing OS X.
my $name501 = `/usr/bin/niutil -readprop . /users/uid=501 name`;
chomp $name501;
my $realName501 = `/usr/bin/niutil -readprop . /users/uid=501 realname`;
chomp $realName501;
my $homeDir501 = `/usr/bin/niutil -readprop . /users/uid=501 home`;
chomp $homeDir501;
We need to check that there actually is a valid 501 user, and print an error and exit if not.
# is there a 501 user?
if ($name501 eq "") {
   print "There is no user with uid 501. No action was performed.n";
   exit 1;
}
This script makes an important assumption about the admin account it will modify. It assumes that the admin account we are standardizing has a uid of 501. This is the uid assigned by the OS to the first local account created after you install Mac OS X. If your machines were set up so that the first account created was an admin account, and regular user accounts were created later or are obtained via Directory Services, then this script will work fine. If, however, you have machines in which the first account created is an actual user account that also has admin rights, this script would rename the account and change the password. Generally, that would be bad. So this next bit of code checks the "short name" of the 501 account against a list of potential admin names. If this list is not empty, and the short name does not match any of the names in the list, an error message is printed and the script exits. This will guard against changing the name and password of a real user's account. The downside is that you must know and add all the short names for all the admin accounts to the $validAdminNames variable.
If you do not want to check the name of the 501 account before proceeding and you want to live dangerously, set $validAdminNames to "".
# is the name of the 501 user in the list of valid admin names?
# this attempts to prevent us from accidentally changing an account
# that belongs to an actual user, instead of a generic admin user
my $validAdminNames = "admin administrator localadmin osxadmin osxboss";
if (($validAdminNames ne "") && !($validAdminNames =~ /b$name501b/)) {
   print "$name501 does not appear to be a generic admin account. It may belong to an actual user. No action was performed.n";
   exit 1;
}
Next, we check to make sure this user (uid 501) is actually a member of the local admin group.
# get the list of admin users
my $adminUsers = `/usr/bin/niutil -readprop . /groups/admin users`;
chomp $adminUsers;

# is it a member of the admin group?
if (!($adminUsers =~ /b$name501b/)) {
   print "User ID 501 ($name501/$realName501) is not an admin user. No action was performed.n";
   exit 1;
}


# We got this far, so lets make our changes.
We'll change the short name if the current name doesn't match the desired name. Note we have to update several related properties. This is why Apple warns against changing an account's short name, but if you update all the related fields, it can be done. Additionally, we update the admin group, removing the old shortname and adding the new shortname.
# do we need to change the short name?
if ($name501 ne $adminName) {
   # remove the old username from the admin group
   `/usr/bin/niutil -destroyval . /groups/admin users $name501`;
   # add the new username to the admin group
   `/usr/bin/niutil -appendprop . /groups/admin users $adminName`;
   # change the username
   `/usr/bin/niutil -createprop . /users/uid=501 name $adminName`;
   `/usr/bin/niutil -createprop . /users/uid=501 _writers_passwd $adminName`;
   `/usr/bin/niutil -createprop . /users/uid=501 _writers_tim_password $adminName`;
   `/usr/bin/niutil -createprop . /users/uid=501 _writers_picture $adminName`;
   `/usr/bin/niutil -createprop . /users/uid=501 _writers_hint $adminName`;
   `/usr/bin/niutil -createprop . /users/uid=501 _writers_realname $adminName`;
   print "Changed admin account name from $name501 to $adminNamen";
}
Now we update the long name:
# do we need to change the long name?
if ($realName501 ne $adminRealname) {
   # change the 'long name'
   `/usr/bin/niutil -createprop . /users/uid=501 realname $adminRealname`;
   print "Changed admin long name from $realName501 to $adminRealnamen";
}
Next, we update the home directory if needed. This involves telling NetInfo where it is, and moving/renaming it, or creating it if needed.
# do we need to change the home directory?
if ($homeDir501 ne $adminHomeDir) {
   # point to the new homeDir
   `/usr/bin/niutil -createprop . /users/uid=501 home $adminHomeDir`;
   if (-d $homeDir501) {
      # move the old home dir to the new path
      `/bin/mv -f $homeDir501 $adminHomeDir`;
      print "Moved admin home directory from $homeDir501 to $adminHomeDirn";
   }
   # if the new home dir doesn't exist, we need to create it
   if (!(-d $adminHomeDir)) {
      `/bin/mkdir $adminHomeDir`;
      print "Created admin home directory at $adminHomeDirn";
   }
}
Next, we update the password:
  1. We set the authentication_authority to ';ShadowHash;' to tell NetInfo we are using shadow passwords (admin accounts created under 10.2 or earlier may still use "Basic" authentication, where the password is stored as a crypt hash in NetInfo).
  2. We set the generateduid to the hard-coded value.
  3. We set the NetInfo passwd property to '********', which is another clue we are using shadow passwords.
  4. Finally, we write out a file containing the shadow hash.
If $syncRootPassword is set to a non-zero value, we do the same thing for the root account (uid=0).
# finally, set the password
`/usr/bin/niutil -createprop . /users/uid=501 authentication_authority ';ShadowHash;'`;
`/usr/bin/niutil -createprop . /users/uid=501 generateduid $adminGeneratedUID`;
`/usr/bin/niutil -createprop . /users/uid=501 passwd '********'`;
if (open (F, ">$adminShadowFile")) {
   print F $shadowPassword;
   close F;
   print "$adminName password updated.n";
} else {
   print "Error setting new $adminName password: could not write shadow file.n";
}

# set root's password the same
if ($syncRootPassword) {
   `/usr/bin/niutil -createprop . /users/uid=0 authentication_authority ';ShadowHash;'`;
   `/usr/bin/niutil -createprop . /users/uid=0 generateduid $rootGeneratedUID`;
   `/usr/bin/niutil -createprop . /users/uid=0 passwd '********'`;
   if (open (F, ">$rootShadowFile")) {
      print F $shadowPassword;
      close F;
      print "root password updated.n";
   } else {
      print "Error setting new root password: could not write shadow file.n";
   }
}
If $disableRoot is set to a non-zero value, we disable the root account by setting its authentication_authority back to Basic and setting the passwd property to "*" which is a UNIXism for disabling the account. You then cannot login as root, though you still can use sudo -s to get a root shell from an admin account.
Disabling root logins is desirable from a security standpoint.
# disable the root account if requested
if ($disableRoot) {
   `/usr/bin/niutil -createprop . /users/uid=0 authentication_authority ';Basic;'`;
   `/usr/bin/niutil -createprop . /users/uid=0 passwd '*'`;
   print "Disabled root account.n";
}

Obtaining the shadow password hash

In order to use this script in a useful way, you'll need to customize (at least) the value of $shadowPassword, which contains the shadow password hash. Getting this value for your desired standard password is straightforward:
  1. On any machine, use the Accounts preference pane or the command line passwd to change the password for any account to your desired password.
  2. On the command line, get the "generateduid" property for the account:
    niutil -readprop . /users/<accountshortname> generateduid
  3. Now read the associated shadow password file:
    sudo cat /private/var/db/shadow/hash<generateduid> ; echo
    This is the hash value.
If that seems too difficult, you can change the password as above, and then run this script:
#!/usr/bin/perl

# getpwhash
#
# A script to get the pw shadow hash
# for a given user
#
# by Greg Neagle, Walt Disney Feature Animation
# this version 12/29/2004

use strict;

my $current user = `/usr/bin/whoami`;
chomp $current user;

if ($current user ne "root") {
   print "This script must be run as root. Use sudo if needed.n";
   exit 1;
}

my $account = $ARGV[0];

if ($account eq "") {
   print "Usage: getpwhash accountnamen";
   exit 1;
}

my $uid = `/usr/bin/niutil -readprop . /users/$account uid`;
chomp $uid;

if ($uid eq "") {
   print "No user found with short name $account.n";
   exit 1;
}

my $generateduid = `/usr/bin/niutil -readprop . /users/$account generateduid`;
chomp $generateduid;

if ($generateduid eq "") {
   print "User $account has no generateduid.n";
   exit 1;
}

if (-f "/private/var/db/shadow/hash/$generateduid") {
   print `cat /private/var/db/shadow/hash/$generateduid`;
   print "n";
} else {
   print "User $account has no shadow password hash.n";
}
Call it like so: /path/to/getpwhash <accountshortname>

Delivering and running the fixadmin.pl script

Once you have made the appropriate changes to the fixadmin.pl script and have it doing what you expect, you need to run it on all the machines you manage. Here are some ways you might accomplish this:

Method one:

If you have ssh access to your machines (referred to as "Remote Login" in the Sharing preference pane), you can use scp and ssh to deliver and run the script:
scp -pv /local/path/to/fixadmin.pl localadmin@remotemachine:/tmp/fixadmin.pl
Then:
ssh localadmin@remotemachine

[remotemachine:~] localadmin% sudo perl /tmp/fixadmin.pl
After a successful run, you may want to remove the script, since the shadow password hash is within the script and is a security risk:
[remotemachine:~] localadmin% sudo rm /tmp/fixadmin.pl

Method two:

Using Apple Remote Desktop 2, you can use the "Send UNIX Command" feature:
  1. Select the machines you want to update.
  2. Choose "Send UNIX Command".
  3. Copy and paste the entire script into the "Run a UNIX shell command on the target computers:" field.
  4. Run the command as the root user, and choose to display all output.
  5. Hit send.
See this article for more on using "Send UNIX Command" with ARD2.

Method three:

Rename the script 501.fixadmin, and using any method (scp, radmind, Timbuktu, ARD, NetOctopus), deliver the file to /private/etc/periodic/daily/
Make sure the execute bit is turned on.
The script will run every day as part of the daily maintenance tasks - usually at 3:15 AM. Output will be written to /private/var/log/daily.out
Since the shadow password hash is within the script and is a security risk, you may want to set the permissions to 700, and owner to root:
sudo chmod 700 /path/to/501.fixadmin sudo chown root /path/to/501.fixadmin
There certainly are other ways to deliver and run this script on your managed machines.
If you need to update the password in the future, just update the shadow hash in the script and run it again on all your machines.

Alternatives

There are several alternative methods to accomplish the same results we achieved with the fixadmin.pl script. Some may be better suited for other situations.
Consider looking at the nicl and dscl commands.

For example, if you are using ARD2, and your admin accounts all have the same shortname, you can quickly and easily change the admin password on all your managed machines by sending this one-line UNIX command (as the root user):
dscl . -passwd /users/<localadminshortname> <newpassword>
Note that this approach is not well suited for inclusion in a script because it would store the new password in plain text.

fixadmin.pl could probably be rewritten to use dscl instead of niutil, which would (hopefully) future-proof it should Apple remove NetInfo in the future, replacing it with something else.

If you are still using OS X 10.2.x (Jaguar), see this older article for an example of using niutil to update the UNIX crypt password instead of the shadow password.

Conclusion

Apple's directory command line utilities (niload, nidump, niutil, nicl, dscl, and others) are very powerful tools for OS X systems administrators. This article demonstrated the use of niutil to rename accounts and change their passwords. I hope you find something useful here for your OS X deployment.
Corrections to this article and suggestions of better methods are welcome!

__________
Greg Neagle
greg dot neagle at disney dot com
Add as favourites (109) | Quote this article on your site | E-mail

Be first to comment this article
RSS comments

Only registered users can write comments.
Please login or register.

Powered by AkoComment Tweaked Special Edition v.1.4.4

Last Updated ( Wednesday, 05 January 2005 )
 
< Prev   Next >