#!/usr/bin/perl
#
#  	$Id: adduser.pl,v 1.99 1996/05/29 15:03:06 sjp Exp $	
#
# adduser: a utility to add users to the system
# addgroup: a utility to add groups to the system

# Copyright (C) 1995 Ted Hajek <tedhajek@boombox.micro.umn.edu>
#                     Ian A. Murdock <imurdock@gnu.ai.mit.edu>
# General scheme of the program adapted by the original debian 'adduser'
#  program by Ian A. Murdock <imurdock@gnu.ai.mit.edu>.
#
#    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.
#
#
####################
# the program can be called as:
#
#  adduser [--home directory] user
#      add a normal user to the system according to adduser.conf
#      example: adduser fred
#      $action = "adduser"
#
#  adduser --group group
#  addgroup group
#      add a system group
#      example: addgroup --quiet www-data
#      $action = "addgroup"
#
#  adduser --system [--home directory] [--group] name
#      add a system user with optional home directory other than the
#      default.  Optionally create a new group.
#      If "--group" isn't specified, put the user in "nogroup"
#      example: adduser --system --home /home/gopher-data --group gopher
#      $action = "addsysuser"
#
#  adduser user group
#      add the existing user to an existing group.
#      $action - "addusertogroup"
#
#  all commands take the following options:
#      --quiet          don't give progress information on STDOUT
#      --force-badname  disable checking of names for funny characters
#      --debug          we're debugging!
#      --help           usage message
#      --version        version number and copyright
#      --password password specify the password to use
#      --gecos name	Specify the full username
#      --gid nr		Specify the gid to use
#      --uid nr		Specify the uid to use
############

# are we debugging?
$debugging = 0;
# should we be verbose?
$verbose = 1;
# should we allow bad names?
$allow_badname = 0;


##
## global variables to store the states of the password and group
## files.  If we bail out, the error-handling code will patch
## things up.
##
$passwd_state = "unlocked";
$group_state = "unlocked";

# Set up some important things:
$PASSWD = "/etc/passwd";	# the password file
$PBACK = "/etc/passwd~";		# the backup password file
$PLOCK = "/etc/ptmp";		# the password lock file

$GROUP = "/etc/group";		# the group file
$GBACK = "/etc/group~";		# the backup group file
$GLOCK = "/etc/gtmp";		# the group lock file

$SKEL = "/etc/skel";		# the skeleton directory

$min_uid = 101;
$min_gid = 101;

$nobody_id = getpwnam("nobody");
$nobody_id = 65534 unless ($nobody_id);

$nogroup_id = getgrnam("nogroup");
$nogroup_id = 65534 unless ($nogroup_id);

# Defaults that can be overridden later
$new_password = "*";

$DEFAULTS = "/etc/adduser.conf";

$default{"shell"} = "/bin/bash";
$default{"first_uid"} = 1000;
$default{"home"} = "/home";
$default{"skel"} = "/etc/skel";
$default{"usergroups"} = "yes";
$default{"users_gid"} = "100";
$default{"grouphomes"} = "no";
$default{"letterhomes"} = "no";
$default{"keep_nis_at_end"} = "yes";


##
## need to set handlers for 
## signals 1 2 3 and 15 here so user can't kill us
## when things are in some half-baked state.
##
sub handler {
    local ($sig) = @_;
    print "Caught signal $sig; cleaning up...\n";
    &clean_up();
    exit 1;
}
$SIG{'HUP'} = 'handler';
$SIG{'INT'} = 'handler';
$SIG{'QUIT'} = 'handler';
$SIG{'TERM'} = 'handler';

sub clean_up {
    unlink $PLOCK if ($passwd_state ne "unlocked");
    unlink $GLOCK if ($group_state ne "unlocked");
}



##
## what the heck are we doing?
##

$action = "adduser";		# the default action if nothing
				# else is specified



## get the basename of this program
$0 =~ s+.*/++; 
$progname = $0;
if ($progname eq "addgroup") {
    $action = "addgroup";
}

## get those options:
while ($arg = shift(@ARGV)) {
    if ($names[0]) {
	if ($arg =~ /^--/) {	# no options after usernames!
	    &usage();
	    exit 1;
	}
    }
    # general options
    if ($arg eq "--quiet") {
	$verbose = 0;
    } elsif ($arg eq "--force-badname") {
	$allow_badname = 1;
    } elsif ($arg eq "--debug") {
	print "Copious debugging information on.\n";
	$debugging = 1;
    } elsif ($arg eq "--help") {
	&usage();
	exit 0;
    } elsif ($arg eq "--version") {
	&version();
	exit 0;
    } elsif ($arg eq "--system") {
	$action = "addsysuser";
    } elsif ($arg eq "--group") {
	if ($ingroup_name) {
	    print STDERR "adduser: --group option conflicts with --ingroup option\n\n";
	    &usage();
	    exit 1;
	}
	$found_group_opt = 1;
    } elsif ($arg eq "--ingroup") {
	if ($found_group_opt) {
	    print STDERR "adduser: --ingroup option conflicts with --group option\n\n";
	    &usage();
	    exit 1;
	}
	    
	$ingroup_name = shift(@ARGV);
	if (! $ingroup_name) {
	    &usage();
	    exit 1;
	}
	unless (@tmp = getgrnam($ingroup_name)) {
	    print STDERR "adduser: The group \"$ingroup_name\",specified by the --ingroup option, does\n";
	    print STDERR "         not exist.  Please create and try again\n\n";
	    &usage();
	    exit 1;
	}
	$ingroup_gid = $tmp[2];
    } elsif ($arg eq "--home") {
	$special_home = shift(@ARGV);
	if (! $special_home) {
	    &usage();
	    exit 1;
	}
	if ( -d $special_home) {
	    print STDERR "$progname: Warning The home directory you specified already exists.\n";
	}
	if ($special_home !~ /^\// ) {
	    print STDERR "$progname: The home specified must be a complete path and must begin\n\twith a slash (/).\n";
	    &clean_up();
	    exit 1;
	}
    } elsif ($arg eq "--gecos") {
	$new_gecos = shift(@ARGV);
    } elsif ($arg eq "--password") {
	$new_password = shift(@ARGV);
	$new_password = crypt($new_password,substr($new_password,0,2));
    } elsif ($arg eq "--uid") {
	$new_uid = shift(@ARGV);
	&check_numeric($new_uid);
    } elsif ($arg eq "--gid") {
	$new_gid = shift(@ARGV);
	&check_numeric($new_gid);
    } elsif ($arg =~ /^--/) {	# bad argument!
	&usage();
	exit 1;
    } else {			# it's a username!
	push (@names, $arg);
    }
}

##
## we must be root.
##
if ($> != 0) {
  print STDERR "$progname: only root may add a user or group to the system.\n\"$progname --help\" gives more helpful information\n";
  &clean_up();
  exit 1;
}

if (! $names[0]) {		# hey, we need a username!
    &usage();
    exit 1;
}
$num_names = $#names + 1;
if ($num_names > 2) {		# no more than two usernames!
    &usage();
    exit 1;
} elsif ($num_names == 2) {	# this is "addusertogroup"
    if ($action eq "addsysuser" || $found_group_opt) {
	&usage();
	exit 1;
    }
    $action = "addusertogroup";
    $existing_user = shift (@names);
    $existing_group = shift (@names);
} else {
    $new_name = shift (@names);
}

# by this point, we know if the action is "addsysuser" or "addusertogroup"
if ($found_group_opt) {
    if ($action eq "addsysuser") {
	$make_group_also = 1;
    } elsif ($action eq "addusertogroup") { # can't do that!
	&usage();
	exit 1;
    } else {
	$action = "addgroup";
    }
}

if (length($new_name) > 8) {
    if ($action eq "addgroup") {
	print "The group name must be less than 9 characters\n";
    } else {
	print "The user name must be less than 9 characters\n";
    }
    exit 1;
}


#####
# OK, we've processed the arguments.  $action equals one of the following,
# and the appropriate variables have been set:
#
# $action = "adduser"
#    $new_name            - the name of the new user
#    $special_home        - the home directory. if it's unset, use the default.
#    $ingroup_name        - the name of the group to add the user to
# $action = "addgroup"
#    $new_name            - the name of the new group
# $action = "addsysuser"
#    $special_home        - the home directory. if it's unset, use the default.
#    $make_group_also     - boolean, should we also create a group?
#    $new_name            - the name of the new user
#    $ingroup_name        - the name of the group to add the user to
# $action = "addusertogroup"
#    $existing_user       - the user to be added
#    $existing_group      - the group to add her to
#####

##
## do we have a lousy new name?
##
if ($new_name) {
    if ($new_name !~ /^[A-Za-z_][-_A-Za-z0-9]*$/) {
	print STDERR "$progname: to avoid problems, please enter a username consisting\n";
	print STDERR "of lowercase letters and numbers.\n";
	print STDERR "User $new_name not created.\n";
	&clean_up();
	exit 1;
    } elsif ($new_name !~ /^[a-z][a-z0-9]+$/) {
	if (! $allow_badname) {
	    print STDERR "$progname: please enter a username consisting of a lowercase\n";
	    print STDERR "letter followed by one or more lowercase letters or numbers.\n";
	    print STDERR "If you wish to use a name with underscores, dashes, or uppercase\n";
	    print STDERR "characters, please use the option '--force-badname'.\n";
	    print STDERR "User $new_name not created.\n";
	    &clean_up();
	    exit 1;
	} else {
	    print "Allowing use of questionable username.\n" if ($verbose);
	}
    }
}

# read the file of defaults.
%config = &get_config($DEFAULTS, %default);

# override "usergroups" if we have the "--ingroup" flag.
$config{'usergroups'} = 'no' if ($ingroup_name ne '');

if ($action eq "adduser") {	# adduser
    if (&user_exists($new_name)) {
	print STDERR "$progname: the user you specified already exists.\n";
	&clean_up();
	exit 1;
    } elsif ($config{"usergroups"} eq "yes" && &group_exists($new_name)) {
	print STDERR "$progname: the group you specified already exists.\n";
	&clean_up();
	exit 1;
    }
} elsif ($action eq "addgroup") { # addgroup
    if (&group_exists($new_name)) {
	print STDERR "$progname: the group you specified already exists.\n";
	&clean_up();
	exit 1;
    }
} elsif ($action eq "addsysuser") { # adding a system user
    if (&user_exists($new_name)) {
	print STDERR "$progname: the user you specified already exists.\n";
	&clean_up();
	exit 1;
    } elsif ($make_group_also == 1 && &group_exists($new_name)) {
	print STDERR "$progname: the group you specified already exists.\n";
	&clean_up();
	exit 1;
    }
} elsif ($action eq "addusertogroup") {	# adding a user to an existing group
    if (! &user_exists($existing_user)) {
	print STDERR "$progname: the user you specified doesn't exist.\n";
	&clean_up();
	exit 1;
    } elsif (! &group_exists($existing_group)) {
	print STDERR "$progname: the group you specified doesn't exist.\n";
	&clean_up();
	exit 1;
    }
    if (&user_is_member($existing_user, $existing_group)) {
	if ($verbose) {	       
	    print "The user $existing_user is already ";
	    print "a member of $existing_group.\n";
	}
	&clean_up();
	exit 0;			# the user hasn't really done something wrong.
    }
}

##
## check if the password file is locked, if necessary:
##
if ($action eq "adduser" || $action eq "addsysuser") {
    if (-f $PLOCK) {		
	print STDERR "$progname: $PASSWD is locked.  Try again later.\n";
	&clean_up();
	exit 1;
    }
    # lock the password file
    $passwd_state = "locked";
    link $PASSWD, $PLOCK;
}
##
## check if the group file is locked, if necessary:
##
if (($action eq "adduser" && $config{"usergroups"} eq "yes") ||
    ($action eq "addsysuser" && $make_group_also == 1) ||
    ($action eq "addusertogroup") ||
    ($action eq "addgroup")) {
    if (-f $GLOCK) {		
	print STDERR "$progname: $GROUP is locked.  Try again later.\n";
	&clean_up();
	exit 1;
    }    
    # lock the group file
    $group_state = "locked";
    link $GROUP, $GLOCK;
}


##############
## addgroup ##
##############
if ($action eq "addgroup") {
    @current_gids = &get_current_gids;
    print "Adding group $new_name... " if ($verbose);
    if ($new_gid) {
        if ( $current_gid{new_gid} ) {
            print STDERR "$progname: The GID $new_gid you specified is already in use.\n";
	    print STDERR "Group $new_name not created.\n";
	    &clean_up();
	    exit 1;
	}
    } else {
        # find the first available gid greater than
        # MIN_GID and less than first_uid
        print "Minimum gid: ", $min_gid, "\n" if ($debugging);
        $new_gid = &first_avail_id($min_gid, 
			       $config{"first_uid"},
			       @current_gids);
        if ($debugging) {
	    print "Called first_avail_id: \n";
	    print "  min: $min_gid\n";
	    $foo = $config{"first_uid"};
	    print "  max: $foo\n";
	    print "  return: $new_gid\n";
        }

        if ($new_gid == -1) {
            print STDERR "$progname: no GID is available between 100\n";
	    print STDERR "(the first non-reserved GID) and the number defined\n";
	    print STDERR "as ``FIRST_UID'' in the file adduser.conf.\n";
	    print STDERR "Group $new_name not created.\n";
	    &clean_up();
	    exit 1;
        }
    }

    # make the new group:
    &add_group_to_file($new_name, $new_gid);
    print "done.\n" if ($verbose);
    &clean_up();
    exit 0;
}
####################
## addusertogroup ##
####################
elsif ($action eq "addusertogroup") {
    print "Adding user $existing_user to group $existing_group..."
	if ($verbose);
    &add_user_to_group($existing_user, $existing_group);
    print "done.\n" if ($verbose);
    &clean_up();
    exit 0;
}

################
## addsysuser ##
################
elsif ($action eq "addsysuser") {
    @current_uids = &get_current_uids;
    @current_gids = &get_current_gids if ($make_group_also);

    print "Adding system user $new_name...\n" if ($verbose);
    print "Selecting UID... " if ($verbose);
    if (! $make_group_also) {
	if ($new_uid) {
            if ( $current_uids{new_uid} ) {
                print STDERR "$progname: The UID $new_uid you specified is already in use.\n";
	        print STDERR "User $new_name not created.\n";
	        &clean_up();
	        exit 1;
	    }
        } else {
	    $new_uid = &first_avail_id($min_uid,
				   $config{"first_uid"},
				   @current_uids);
	}
	if ($new_gid eq "")
	{
	    if (defined($ingroup_gid)) {
	        $new_gid = $ingroup_gid;
	    }
	    else {
	        $new_gid = $nogroup_id;	# put the user in "nogroup"
	    }
	}
	# make sure $new_gid exists
	$new_group_name = getgrgid($new_gid);
	print "User's group is named $new_group_name.\n" if ($debugging);
	if (! $new_group_name) {
	  print STDERR "A group for GID ($new_gid) does not exist.  User not created.\n";
	  &clean_up();
	  exit 1;
	}
    } else {    # we're creating a group for the user
	if ($new_uid)
        {   if ( $current_uids{new_uid} ) {
                print STDERR "$progname: The UID $new_uid you specified is already in use.\n";
	        print STDERR "User $new_name not created.\n";
	        &clean_up();
	        exit 1;
	    }
	    # Perhaps I should check for a gid spec here?
	    $new_gid=$new_uid;
        } else {
	    $found_one = 0;		# need to get a free uid and gid.
	    $new_uid = $min_uid;
	    until($found_one) {
	        $new_uid = &first_avail_id($new_uid,
				       $config{"first_uid"},
				       @current_uids);
	        if ($new_uid == -1) {
		    last;
	        }
	        $new_group_name = getgrgid($new_uid);
	        print "name associated with id $new_uid: $new_group_name\n"
	 	    if ($debugging);
	        if (! $new_group_name) {
		    $new_gid = $new_uid;
		    $found_one = 1;
	        } else {
		    $new_uid++;
	        }
	    }
	    if (($new_uid != -1) && (! $found_one)) {
	        print STDERR "$progname: Couldn't find free UID/GID pair.\n";
	        print STDERR "System user $new_name not created.\n";
	        &clean_up();
	        exit 1;
	    }
	}
    }

    ##
    ## OK, we have a UID/GID pair.
    ##
    print "$new_uid\n" if ($verbose);

    # consistency check
    if ($new_uid == 0 || $new_uid > $nobody_id) {
	print STDERR "$progname: aborting.\n";
	print STDERR "internal error: generated bad uid.\n";
	&clean_up();
	exit 1;
    }

    ##
    ## add the new user to the password file
    ##
    print "Updating password file..." if ($verbose);
    if ($special_home) {
	$home_dir = $special_home;
    } else {
	$home_base = &homebase($config{"home"}, $new_name, $new_group_name);
	$home_dir = $home_base . "/" . $new_name;
    }

    &add_user_to_file($new_name,
		      $new_password,
		      $new_uid,
		      $new_gid,
		      $new_gecos,
		      $home_dir,
		      "/bin/false");
    print "done.\n" if ($verbose);

    ##
    ## add a new group, if necessary:
    ##
    if ($make_group_also) {
	print "Updating group file... " if ($verbose);
	&add_group_to_file($new_name, $new_gid);
	print "done.\n" if ($verbose);
    } else {
	unlink $GLOCK;
    }

    ##
    ## make home directory
    ##
    if (-d $home_dir) {
	$dir_made = 1;
	if ($verbose) {
	    print "Home directory already exists.\n";
	}
    } else {
	$dir_made = &mktree($home_dir);
	if (! $dir_made) {
	    if ($verbose) {
		print "Couldn't create directory $home_dir.\n";
	    }
	}
    }

    if ($make_group_also) {
	chown ($new_uid, $new_gid, $home_dir);
	chmod (02775, $home_dir);
    } else {
	chown ($new_uid, 0, $home_dir);
	chmod (0755, $home_dir);
    }

    &clean_up();
    exit 0;
}
#############
## adduser ##
#############
elsif ($action eq "adduser") {
    @current_uids = &get_current_uids;
    @current_gids = &get_current_gids if ($config{"usergroups"} eq "yes");

    print "Adding user $new_name...\n" if ($verbose);
    print "Selecting UID... " if ($verbose);
    if ($config{"usergroups"} eq "no") {
	if ($new_uid)
        {   if ( $current_uids{new_uid} ) {
                print STDERR "$progname: The UID $new_uid you specified is already in use.\n";
	        print STDERR "User $new_name not created.\n";
	        &clean_up();
	        exit 1;
	    }
        } else {
	    $new_uid = &first_avail_id($config{"first_uid"},
				   $nobody_id,
				   @current_uids);
        }
	$new_gid = $ingroup_name ? $ingroup_gid : $config{"users_gid"};
	# make sure $new_gid exists
	$new_group_name = getgrgid($new_gid);
	print "User's group is named $new_group_name.\n" if ($debugging);
	if (! $new_group_name) {
	  print STDERR "$progname: the group with the ID specified by the\n";
	  print STDERR "parameter `USERS_GID' in the file $DEFAULTS does\n";
	  print STDERR "not exist.  Please create it, then run this program\n";
	  print STDERR "again.\n";
	  &clean_up();
	  exit 1;
	}
    } elsif ($config{"usergroups"} eq "yes") {    # we're using usergroups.
	if ($new_uid)
        {   if ( $current_uids{new_uid} ) {
                print STDERR "$progname: The UID $new_uid you specified is already in use.\n";
	        print STDERR "User $new_name not created.\n";
	        &clean_up();
	        exit 1;
	    }
	    $new_gid=$new_uid;
        } else {
	   $found_one = 0;		# need to get a free uid and gid.
	   $new_uid = $config{"first_uid"};
	   until($found_one) {
	        $new_uid = &first_avail_id($new_uid,
				       $nobody_id,
				       @current_uids);
	        if ($new_uid == -1) {
		    last;
	        }
	        $new_group_name = getgrgid($new_uid);
	        print "name associated with id $new_uid: $new_group_name\n"
		    if ($debugging);
	        if (! $new_group_name) {
		    $new_gid = $new_uid;
		    $found_one = 1;
	        } else {
		    $new_uid++;
	        }
	    }
	    if (($new_uid != -1) && (! $found_one)) {
	        print STDERR "$progname: Couldn't find free UID/GID pair.\n";
	        print STDERR "User $new_name not created.\n";
	        &clean_up();
	        exit 1;
	    }
	}
    } else {
	print STDERR "$progname: The value of USERGROUPS in the file\n";
	print STDERR "$DEFAULTS must be either `yes' or `no'.\n";
	&clean_up();
	exit 1;
    }
    if ($new_uid == -1) {
     print STDERR "$progname: no ID is available between the number defined\n";
     print STDERR "as ``FIRST_UID'' in the file $DEFAULTS and the\n";
     print STDERR "maximum UID, $nobody_id.\n";
     print STDERR "User $new_name not created.\n";
     &clean_up();
     exit 1;	    
    }

    ##
    ## we have a UID and GID.
    ##
    print "$new_uid\n" if ($verbose);

    # consistency check
    if ($new_uid == 0 || $new_uid > $nobody_id) {
	print STDERR "$progname: aborting.\n";
	print STDERR "internal error: generated bad uid.\n";
	&clean_up();
	exit 1;
    }


    ##
    ## add the new user to the passwd file
    ##
    print "Updating password file... " if ($verbose);
    if ($special_home) {
	$home_dir = $special_home;
    } else {
	$home_base = &homebase($config{"home"}, $new_name, $new_group_name);
	$home_dir = $home_base . "/" . $new_name;
    }

    &add_user_to_file($new_name,
		      $new_password,
		      $new_uid, 
		      $new_gid,
		      $new_gecos,
		      $home_dir, 
		      $config{"shell"});
    print "done.\n" if ($verbose);

    ##
    ## add a new group, if necessary
    ##
    if ($config{"usergroups"} eq "yes") {
	print "Updating group file... " if ($verbose);
	&add_group_to_file($new_name, $new_gid);
	print "done.\n" if ($verbose);
    } else {			# make sure we unlock the group file
	unlink $GLOCK;
    }

    ##
    ## OK, the user is added to the system files, let's take care
    ## of some other administrative details:
    ##

    ##
    ## create the home directory and transfer files from /etc/skel,
    ## if necessary.
    ##
    if (-d $home_dir) {
	if ($verbose) {
	    print "Home directory already exists; ";
	    print "not copying files from $SKEL.\n";
	}
    } else {
	print "Creating home directory... " if ($verbose);
	if (! -d $config{"home"}) {
	    print STDERR "$progname: The directory specified as DHOME in\n";
	    print STDERR "$DEFAULTS does not exist.  Please create it or\n";
	    print STDERR "change the DHOME parameter to another directory.\n";
	    &clean_up();
	    exit 1;
	}
	if ($config{"usergroups"} eq "yes") {
	    $dir_mode = 02775;
	} else {
	    $dir_mode = 0755;
	}
	mkdir ($home_dir, $dir_mode);
	chown ($new_uid, $new_gid, $home_dir);
	chmod ($dir_mode, $home_dir);
	print "done.\n" if ($verbose);

	##
        ## get files from $SKEL
	##
	print "Copying files from $SKEL... " if ($verbose);
	@skelfiles = &find_files_and_dirs($SKEL);
	foreach $skelfile (@skelfiles) {
	    &copy_to_dir($SKEL, $skelfile, $home_dir);
	}

	##
	## change umask lines in appropriate skel files
	## if we're using usergroups.
	##
	local (@statreturn);
	if ($config{"usergroups"} eq "yes") {
	    foreach $file (".login", ".profile", ".bash_profile") {
		$this_file = $home_dir . "/" . $file;
		if (-f $this_file) {
		    open (FILE, "$this_file") || die "open: $!";
		    open (NEWFILE, ">$this_file.new") || die "open: $!";
		    while ($line = <FILE>) {
			$line =~ s/umask 0([267])\1/umask 00$1/;
			print (NEWFILE $line) || die "write: $!";
		    }
                    (@statreturn= stat(FILE)) || die "fstat: $!";
                    $filemode= $statreturn[2];
                    chmod($statreturn[2],"$this_file.new") || die "chmod: $!";
		    close FILE;
		    close(NEWFILE) || die "close: $!";
		    rename ("$this_file.new", "$this_file") || die "rename: $!";
		}
	    }
	}

	##
	## change ownership of files in home dir
	##
	if ($home_dir ne "/") {	# we really don't want to do this to root
	    @homefiles = &find_files_and_dirs($home_dir);
	    foreach $homefile (@homefiles) {
		chown ($new_uid, $new_gid, "$home_dir/$homefile");
	    }
	}
	print "done.\n" if ($verbose);
    }

    ##
    ## change the password
    ##
    ($the_name, $the_passwd) = getpwnam ($new_name);
    while ($the_passwd eq "*") {
	if ((system "passwd $new_name") != 0) {
	    print STDERR "$progname: System call to passwd program failed\n";
	}
        ($the_name, $the_passwd) = getpwnam ($new_name);
    }
    if ($new_gecos eq "") {
	##
	## change the finger information
	## 
	if ((system "chfn $new_name") != 0) {
	    print STDERR "$progname: System call to chfn program failed\n";
	    &clean_up;
	    exit 1;
	}
	while (1) {
	    if (-x "/usr/bin/finger") {
	        if ((system "/usr/bin/finger -m $new_name") != 0) {
		    print STDERR "$progname: System call to finger program failed\n";
		    &clean_up;
		    exit 1;
	        }
	        print "Is this information correct [y/n]? ";
	    } else {
	        print "Is this finger information correct [y/n]? ";
	    }
	    chop ($answer = <STDIN>);
	    last if ($answer eq "y" || $answer eq "Y");
	    if ((system "chfn $new_name") != 0) {
	        print STDERR "$progname: System call to chfn program failed\n";
	        &clean_up;
	        exit 1;
	    }
	}
    }
    print "done.\n";
    &clean_up();
    if (-f "/usr/local/sbin/adduser.local") {
	exec("/usr/local/sbin/adduser.local",
             $new_name, $new_uid, $new_gid, $home_dir);
        die "exec adduser.local: $!";
    }

    exit 0;
}

###################################################
#=================================================#
###################################################

###
### Subroutines
###

#####################################
## homebase
#####################################
sub homebase {
    local ($letter);
    local ($dir);
    local ($default_dir_mode) = 0755;

    # See if base directory is present.
    return $dir if (! -d $_[0]);

    # Get first letter of username.
    $letter = substr($_[1], 0, 1);

    # Find out directory name.
    if ($config{'letterhomes'} =~ m/yes/i) {
       $dir = $_[0] . '/' . $letter;
    } elsif ($config{'grouphomes'} =~ m/yes/i) {
       $dir = $_[0]. '/' . $_[2];
    } else {
       $dir = $_[0];
    }

    # Create directory if needed.
    if (! -d $dir) {
       mkdir($dir, $default_dir_mode);
    }

    # Return directory.
    $dir;
}


#####################################
## mktree
#####################################
# given a path, create the directory specified, creating all leading
# directories if necessary.  If successful, return 1, otherwise, return 0.
sub mktree {
    local ($requested_path) = @_;
    local (@path);
    local ($done, $attempt);
    local ($default_dir_mode);

    $default_dir_mode = 0755;

    # chop off any trailing slash.
    chop ($requested_path) if ($requested_path =~ m%/$%);
    # get rid of leading slash
    $requested_path =~ s@^/(.+)$@$1@;

    # split the path elements into an array
    @path = split(/\//, $requested_path);

    # this'll hold the parts of the path that have already been
    # created.  For example, if we are creating
    #   /usr/local/lib/httpd_data
    # the variable $done will hold
    #   /usr/local
    # after we're sure that /usr and /usr/local exist.
    $done = "";

    # while there are still path elements, create the next.
    while (@path) {
	$attempt = shift(@path);
	mkdir("$done/$attempt", $default_dir_mode);
	if (-d "$done/$attempt") {
	    $done = "$done/$attempt";
	} else {
	    return 0;
	}
    }

    return 1;
}


##
## backup_group_file
##
sub backup_group_file {
    # save the original group file
    open (GBACK, ">$GBACK") || die "open: $!";
    open (GROUP, "$GROUP") || die "open: $!"; # 
    while (<GROUP>) {
	print GBACK;
    }
    close GROUP;
    close GBACK;
    $group_state = "backed";
}

##
## user_is_member
##
sub user_is_member {
    local ($user, $group) = @_;
    local ($name, $passwd, $gid, $members);
    local (@the_members);
    local ($is_member);

    ($name, $passwd, $gid, $members) = getgrnam($group);
    @the_members = split (/ /, $members); # space-separated list
    print "Members: @the_members\n" if ($debugging);
    $is_member = 0;
    foreach $name (@the_members) {
	print "  Testing:\t$name\t$user\n" if ($debugging);
	if ($name eq $user) {
	    $is_member = 1;
	    last;
	}
    }

    return ($is_member);
}


##
## add_user_to_group
##
sub add_user_to_group {
    local ($user, $group) = @_;
    local ($name, $passwd, $gid, $members, $line);

    &backup_group_file();

    open (GROUP, ">$GROUP") || die "open: $!";
    open (GBACK, "$GBACK") || die "open: $!";
    while($line = <GBACK>) {
	chop $line;
	($name, $passwd, $gid, $members) = split(/:/, $line);
	if ($name ne $group) {
	    print GROUP $line, "\n";
	} else {		# add the new member
	    print GROUP "${name}:${passwd}:${gid}:";
	    $members =~ s/ //g;	# get rid of spaces
	    if ($members) {	# we already have members
		print GROUP "${members},${user}\n";
	    } else {
		print GROUP "$user\n";
	    }
	}
    }
    close GROUP;
    close GBACK;
    $group_state = "changed";
    # unlock the file
    unlink $GLOCK;
    $group_state = "unlocked";

    return 1;
}

    
##
## copy_to_dir
##
sub copy_to_dir {
    local ($fromdir, $file, $todir) = @_;
    local (@statreturn,$filemode);
    
    if ( ((! -f "$fromdir/$file") && (! -d "$fromdir/$file")) || 
	(! -d $todir)) {
	return -1;
    }

    if ( -f "$fromdir/$file") {
	open (FILE, "$fromdir/$file") || die "open: $!";
	open (NEWFILE, ">$todir/$file") || die "open: $!";
	
	while (<FILE>) {
	    print(NEWFILE) || die "print: $!";
	}

	close FILE;
	close(NEWFILE)  || die "close: $! ($todir/$file)";

    } else {
	if ( ! -d "$todir/$file") {
	    mkdir("$todir/$file", 755) || die "mkdir: $!";
	}
    }

    (@statreturn= stat("$fromdir/$file")) || die "stat: $!";
    $filemode= $statreturn[2];
    if ($config{"usergroups"} eq "yes") {
        $filemode= ($filemode & 0707) | (($filemode & 0700)>>3);
    } else {
        $filemode= ($filemode & 0707) | (($filemode & 0007)<<3);
    }
    chmod($filemode,"$todir/$file") || die "chmod: $!";

    return 1;
}


##
## find_files
## 
sub find_files {
    local ($dir) = @_;
    local (@file_list);

    if (! -d $dir) {
        return @file_list;
    }

    open (FIND, "/usr/bin/find $dir -type f -print |");
    while (<FIND>) {
        chop;
        push (@file_list, $_);
    }
    close FIND;
    return @file_list;
}


##
## find_files_and_dirs
## 
# returns file names relative to $dir
#
sub find_files_and_dirs {
    local ($dir) = @_;
    local (@file_list);

    if (! -d $dir) {
	return @file_list;
    }

    open (FIND, "(cd $dir; /usr/bin/find . -print )|");
    while (<FIND>) {
	chop;
	push (@file_list, $_);
    }
    close FIND;
    return @file_list;
}
       

##
## add_user_to_file
##
sub add_user_to_file {
    local ($user_name, $new_passwd, $new_uid, $new_gid, $new_gecos, $new_home, $new_shell) = @_;
    local ($added_new_one, $line);
    local ($name, $passwd, $uid, $gid, $quota, $comment, $gcos, $dir, $shell);

    # save the original password file
    open (PBACK, ">$PBACK") || die "open: $!";
    open (PASSWD, "$PASSWD") || die "open: $!";
    while (<PASSWD>) {
	print PBACK;
    }
    close PASSWD;
    close PBACK;
    $passwd_state = "backed";

    # write the new entry at the appropriate place in the passwd file
    open (PASSWD, ">$PASSWD") || die "open: $!";
    open (PBACK, "$PBACK") || die "open: $!";
    $added_new_one = 0;
    while($line = <PBACK>) {
	chop $line;
	($name, $passwd, $uid, $gid, $quota, $comment,
        	$gcos, $dir, $shell) = split(/:/, $line);
	if ($config{'keep_nis_at_end'} ne 'no') {
	    # Leave NIS entries at the end.
	    $uid = $new_uid if ($name =~ m/^\+/);
	}
	if ($uid < $new_uid || $added_new_one || $uid == $nobody_id) {
	    print PASSWD $line, "\n";
	    print STDOUT "UID: $uid\n" if $debugging;
	} else {			# we haven't added the new one yet
	    print PASSWD "${user_name}:${new_passwd}:${new_uid}:${new_gid}:${new_gecos}:";
	    print PASSWD "${new_home}:";
	    print PASSWD "${new_shell}\n";
	    $added_new_one = 1;
	    print STDOUT "Just added new one\n" if $debugging;
	    print PASSWD $line, "\n";
	    print STDOUT "UID: $uid\n" if $debugging;
	}
    }
    if (! $added_new_one) {
	print PASSWD "${user_name}:${new_passwd}:${new_uid}:${new_gid}:${new_gecos}:";
	print PASSWD "${new_home}:";
	print PASSWD "${new_shell}\n";
    }
    close PASSWD;
    close PBACK;
    $passwd_state = "changed";
    # unlock the file
    unlink $PLOCK;
    $passwd_state = "unlocked";

    return 1;
}


##
## add_group_to_file
##
sub add_group_to_file {
    local ($group_name, $new_gid) = @_;
    local ($added_new_one, $line);
    local ($name, $passwd, $gid, $members);

    &backup_group_file();

    # write the new entry at the appropriate place in the group file
    open (GROUP, ">$GROUP") || die "open: $!";
    open (GBACK, "$GBACK") || die "open: $!";
    $added_new_one = 0;
    while($line = <GBACK>) {
	chop $line;
	($name, $passwd, $gid, $members) = split(/:/, $line);
	if ($gid < $new_gid || $added_new_one || $gid == $nobody_id) {
	    print GROUP $line, "\n";
	    print STDOUT "GID: $gid\n" if $debugging;
	} else {			# we haven't added the new one yet
	    print GROUP "${group_name}:*:${new_gid}:\n";
	    $added_new_one = 1;
	    print STDOUT "Just added new one\n" if $debugging;
	    print GROUP $line, "\n";
	    print STDOUT "GID: $gid\n" if $debugging;
	}
    }
    print GROUP "${group_name}::${new_gid}:\n" if (! $added_new_one);

    close GROUP;
    close GBACK;
    $group_state = "changed";
    # unlock the file
    unlink $GLOCK;
    $group_state = "unlocked";

    return 1;
}

sub first_avail_id {
    local ($min, $max, @ids) = @_;
    local (@sorted_ids);
    local ($last_one, $this_one, $lowest_available);

    # sort 'dem IDs
    @sorted_ids = sort {$a <=> $b} @ids;

    # find the first available ID
    $last_one = shift(@sorted_ids);
    while (defined($this_one = shift(@sorted_ids))) {
	if ($this_one <= $min) {
	    $last_one = $this_one; # advance if we're under $min
	    next;
	}
	if ($this_one > $last_one + 1) {
	    $lowest_available = $last_one + 1;
	    last;
	} else {
	    $last_one = $this_one;
	}
    }
    # if we haven't yet set $lowest_available, there's no hole.
    $lowest_available = ($last_one + 1) if (! $lowest_available);
    # make sure we're at the minimum.
    $lowest_available = $min if ($lowest_available < $min);

    # make sure that we're below the ceiling
    if ($lowest_available >= $max) {
	return (-1);
    } else {
	return ($lowest_available);
    }
}

############################
# get_current_gids
############################
# iterate through the group file; return an array
# containing all the GIDs
sub get_current_gids {
    local (@gids);
    local ($name, $passwd, $gid, $members); # the return values of "getgrent";

    setgrent;
    while (($name, $passwd, $gid, $members) = getgrent) {
	push @gids, $gid;
    }
    endgrent;

    return @gids;
}

############################
# get_current_uids
############################
# iterate through the password file; return an array
# containing all the UIDs
sub get_current_uids {
    local (@uids);
    local ($name, $passwd, $uid); # the return values of "getpwent";

    setpwent;
    while (($name, $passwd, $uid) = getpwent) {
	push @uids, $uid;
    }
    endpwent;

    return @uids;
}

############################
# get_first_uid
############################
# parse the defaults file for the first user UID number.
# if the default file or entry doesn't exist, return the
sub get_config {
    local($conf_file, %default) = @_;
    local($current_line);
    local(%config);

    # if there's not a config file, return the default.
    print "Couldn't find $conf_file\n" if ($debugging && (! -f $conf_file));
    return %default if (! -f $conf_file);

    open (CONF, $conf_file) || die "open: $!";
    while ($current_line = <CONF>) {
	chop $current_line;
	$current_line =~ s/"//g;        # get rid of quotes"
        $current_line =~ s/'//g;	# get rid of quotes '
	if ($current_line =~ /^DSHELL\s*=\s*(\S+)/) {
	    $config{"shell"} = $1;
	} elsif ($current_line =~ /^DHOME\s*=\s*(\S+)/) {
	    $config{"home"} = $1;
	} elsif ($current_line =~ /^SKEL\s*=\s*(\S+)/) {
	    $config{"skel"} = $1;
	} elsif ($current_line =~ /^FIRST_UID\s*=\s*(\S+)/) {
	    $config{"first_uid"} = $1;
	} elsif ($current_line =~ /^USERGROUPS\s*=\s*(\S+)/) {
	    $config{"usergroups"} = $1;
	} elsif ($current_line =~ /^USERS_GID\s*=\s*(\S+)/) {
	    $config{"users_gid"} = $1;
	} elsif ($current_line =~ /^GROUPHOMES\s*=\s*(\S+)/) {
	    $config{"grouphomes"} = $1;
	} elsif ($current_line =~ /^LETTERHOMES\s*=\s*(\S+)/) {
	    $config{"letterhomes"} = $1;
	} elsif ($current_line =~ /^KEEP_NIS_AT_END\s*=\s*(\S+)/) {
	    $config{"keep_nis_at_end"} = $1;
	}

	##
	## fill in default values
	##
	foreach $element (sort keys %default) {
	    if (! $config{$element}) {
		$config{$element} = $default{$element};
	    }
	}
    }
    close (CONF);

    if ($debugging) {
	print "Configuration:\n";
	foreach $element (sort keys %config) {
	    print "  $element: $config{$element}\n";
	}
    }

    return %config;
}



#############################
# group_exists
#############################
# takes a group name as an argument.  If the group already exists,
# return 1.  If it doesn't, return 0.
sub group_exists {
    local ($group_name) = @_;
    local ($exists);		# does group exist?
    local ($name, $passwd, $gid, $members); # the return values of "getgrent";

    $exists = 0;
    # reset the group lookup function
    setgrent;
    # iterate through the group file.  If the group exists, return 1.
    print "Looking for group $group_name in group file...\n" if ($debugging);
    while (($name, $passwd, $gid, $members) = getgrent) {
	print "  looking at $name...\n" if ($debugging);
	if ($name eq $group_name) {
	    $exists = 1;
	    last;
	}
    }
    # close the group file
    endgrent;

    return $exists;
}


#############################
# user_exists
#############################
# takes a user name as an argument.  If the user already exists,
# return 1.  If it doesn't, return 0.
sub user_exists {
    local ($user_name) = @_;
    local ($exists);		# does group exist?
    local ($name, $passwd, $uid, $gid); # the return values of "getpwent";

    $exists = 0;
    # reset the passwd lookup function
    setpwent;
    # iterate through the passwd file.  If the user exists, return 1.
    print "Looking for user $user_name in passwd file...\n" if ($debugging);
    while (($name, $passwd, $uid, $gid) = getpwent) {
	print "  looking at $name...\n" if ($debugging);
	if ($name eq $user_name) {
	    $exists = 1;
	    last;
	}
    }
    # close the passwd file
    endpwent;

    return $exists;
}
##########################
# numeric
##########################
# takes one argument which is checked for only containing digits.
sub check_numeric {
  if ( ! /[0-9]*/ ) {
	print STDERR "@_ is not numeric\n";
	print STDERR "The value of --gid or --uid must be numeric\n";
	&clean_up();
	exit 1;
  }
}
########################
# version
########################
sub version {
    print STDERR "$progname: add a user or a group to the system.\n";
    print STDERR " version 	\$Id: adduser.pl,v 1.99 1996/05/29 15:03:06 sjp Exp $	\n";
    print STDERR "\n";
    print STDERR "Copyright (C) 1995 by Ted Hajek <tedhajek\@boombox.micro.umn.edu>\n";
    print STDERR "                  and Ian Murdock <imurdock\@gnu.ai.mit.edu>\n";
    print STDERR "\n";
    print STDERR "This program is free software; you can redistribute it and/or modify\n";
    print STDERR "it under the terms of the GNU General Public License as published by\n";
    print STDERR "the Free Software Foundation; either version 2 of the License, or\n";
    print STDERR "(at your option) any later version.\n";
    print STDERR "\n";
    print STDERR "This program is distributed in the hope that it will be useful,\n";
    print STDERR "but WITHOUT ANY WARRANTY; without even the implied warranty of\n";
    print STDERR "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n";
    print STDERR "GNU General Public License in the file `/usr/doc/copyright/GPL'.\n";
}

#######################
# usage
#######################
sub usage {
    print STDERR "adduser, addgroup - add a user or group\n";
    print STDERR "\n";
    print STDERR "usage:\n";
    print STDERR "\n";
    print STDERR "  adduser [--home directory] [--ingroup group] user\n";
    print STDERR "    Add a user.\n";
    print STDERR "  adduser --group group\n";
    print STDERR "  addgroup group\n";
    print STDERR "    Add a group.\n";
    print STDERR "  adduser user group\n";
    print STDERR "    Add an existing user to an existing group.\n";
    print STDERR "  adduser --system [--home directory] [--group] user\n";
    print STDERR "    Add a user for system use (in other words, not for login use).\n";
    print STDERR "    Optionally use non-default home directory and create a group\n";
    print STDERR "    of the same name.\n";
    print STDERR "\n";
    print STDERR "Other valid options:\n";
    print STDERR "  --quiet			Don't give pesky progress messages.\n";
    print STDERR "  --help			Display this message.\n";
    print STDERR "  --force-badname		Allow questionable characters in names.\n";
    print STDERR "  --version			Display version and copyright information.\n";
    print STDERR "  --debug			Display plenty of debugging information.\n";
    print STDERR "  --password password		Specify a password to use.\n";
    print STDERR "  --gecos content		Specify a gecos fields contents.\n";
    print STDERR "  --gid gid			Specify the numeric group id to use\n";
    print STDERR "  --uid uid			Specify the numeric user id to use\n.";
    print STDERR "Global configuration is in the file '/etc/adduser.conf'\n";
}
