Sunday, 26 October 2008

Bulk Change of Passwords on Solaris (Repost)

Just filing this one for future use.

I needed to change the password of a large number of accounts on a Solaris box. Unfortunately, I didn't have anything rational like Expect. Also, the Solaris passwd command won't read from stdin. Probably a good thing. :-)

Here's a script that might come in handy. Should only be used on Solaris boxes, unless you grok the Linux shadow password format. But on Linux you can probably get Expect running and that would be a less... primitive way of getting the job done. The problem with this script is that it bypasses PAM so if you're using anything other than shadow files it won't work.

The code is below. If it's getting corrupted then you can also download the script.


#!/usr/bin/perl -w
#
# change_lotsa_passwords.pl
#
# Usage: ./change_lotsa_passwords.pl [/etc/shadow [list of account names]]
#
#

use warnings;
use strict;


# List your accounts here. Or just say @accounts = @ARGV if you
# want to list them on the command line
#
my $shadow_file = shift @ARGV || '/etc/shadow';
my @accounts    = @ARGV;

# Other defaults
use constant PASSWORD_LENGTH => 8;


# Doing it this way lets us make one pass over /etc/shadow and preserve
# its line order
#
my %new_passwords = ();
foreach my $a (@accounts) {
    $new_passwords{$a} = generate_password();
}


# Backup /etc/shadow
#
my $backup_file = $shadow_file . ".BACKUP";
system("cp -p $shadow_file $backup_file");
die "cp failed to backup $shadow_file to $backup_file"
  if ( $? != 0 );


# Re-write /etc/shadow
#
open( my $backup, '<', $backup_file )
  || die "open: Unable to read $backup_file ($!)\n";
open( my $shadow, '>', $shadow_file )
  || die "open: Unable to write $shadow_file ($!)\n";

process_shadow( $backup, $shadow );

close($backup);
close($shadow);

exit(0);


# process_shadow
#   Given two file handles, read in the first file handle and copy to the second.
#   When the first file handle reads in a shadow record for a user whose password
#   we are changing, we will swap out the password and print the new password
#   on stdout.
#
sub process_shadow {
    my ( $backup, $shadow ) = @_;

    # Days since UNIX epoch, the time format used by Solaris in /etc/shadow
    my $last_changed = int( time() / ( 60 * 60 * 24 ) );

    # Read each line from backup, modify if the user is having their password
    # changed, and print the new password on stdout
    #
    my $line = 0;
    while (<$backup>) {
        $line++;
        if (/^ ([^:]+) : ([^:]{2})/x) {
            my $user = $1;
            my $salt = generate_password(2);

            if ( defined( $new_passwords{$user} ) ) {
                print "$user,$new_passwords{$user}\n";
                my $hashed = crypt( $new_passwords{$user}, $salt );
                s/^ ([^:]+:) [^:]+ : \d+:/$1$hashed:$last_changed:/x;
            }
            print $shadow $_;

        }
        else {
            warn "$shadow: Unable to parse line $line\n";
        }
    }

    return;
}

# Return a new (random) password.
# Props: http://perl.about.com/od/perltutorials/a/genpassword.htm
#
sub generate_password {
    my ($length) = @_;
    $length ||= PASSWORD_LENGTH;

    my $ALLOWED =
      'abcdefghjkmnpqrstuvwxyz23456789ABCDEFGHJKLMNPQRSTUVWXYZ!@$%^&*();.';

    my $password = '';
    while ( length($password) < $length ) {
        $password .= substr( $ALLOWED, ( int( rand( length $ALLOWED ) ) ), 1 );
    }
    return $password;
}


Here endeth the hackery. :-)

Postscript: The was originally filed as "Mass Change of Passwords on Solaris" 26 Oct 2008. It was re-posted 2 July 2009 because the code had become corrupted somehow.

4 comments:

John said...

write text files solution

SimonM said...

I think the script got munged in the posting, any chance of a repost?

I have to change the passwords for a stack of users across a pile of servers and your script will save much valuable time

hissohathair said...

Ack! What happened there? Sorry about that. Will repost shortly.

hissohathair said...

Thanks for letting me know Simon. I've updated the code in the blog post and you can also download it here.