*BSD News Article 40180


Return to BSD News archive

Xref: sserve comp.os.386bsd.questions:15550 comp.os.386bsd.misc:4671
Path: sserve!newshost.anu.edu.au!harbinger.cc.monash.edu.au!msunews!uwm.edu!cs.utexas.edu!swrinde!pipex!uunet!heifetz.msen.com!zib-berlin.de!zrz.TU-Berlin.DE!cs.tu-berlin.de!wosch
From: wosch@cs.tu-berlin.de (Wolfram Schneider)
Newsgroups: comp.os.386bsd.questions,comp.os.386bsd.misc
Subject: Re: sysadmin
Date: 02 Jan 1995 21:21:01 GMT
Organization: Hohenschoensiehstenich
Lines: 612
Message-ID: <3e9ptn$nip@news.cs.tu-berlin.de>
References: <adammD1GH4K.JAw@netcom.com> <1994Dec27.220201.21267@fsl.noaa.gov>
	<3drd01$5ql@elaine.teleport.com>
NNTP-Posting-Host: beryll.cs.tu-berlin.de
Mime-Version: 1.0
Content-Type: text/plain; charset=ISO-8859-1
Content-Transfer-Encoding: 8bit
In-reply-to: bmk@teleport.com's message of 28 Dec 1994 02:00:33 -0800

In article <3drd01$5ql@elaine.teleport.com> bmk@teleport.com (bmk) writes:

>Quite a long time ago I found a script called 'adduser' that is written
>in perl, that pretty much automates the process of adding new users.
>Yeah, I know there's nothing to it, but I'd like to be able to delegate
>the task to someone who won't require a ton of training.

#!/bin/sh
# This is a shell archive (produced by shar 3.49)
# To extract the files from this archive, save it to a file, remove
# everything above the "!/bin/sh" line above, and type "sh file_name".
#
# made 01/02/1995 12:16 UTC by wosch@ole
# Source directory /tmp_amd/fiesta/export/all3/w/wosch/tmp/adduser
#
# existing files will NOT be overwritten unless -c is specified
#
# This shar contains:
# length  mode       name
# ------ ---------- ------------------------------------------
#  16306 -rwxr-xr-x adduser
#
# ============= adduser ==============
if test -f 'adduser' -a X"$1" != X"-c"; then
	echo 'x - skipping adduser (File already exists)'
else
echo 'x - extracting adduser (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'adduser' &&
#!/usr/bin/perl
#
# (c) Copyright 1995 Wolfram Schneider. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in the
#    documentation and/or other materials provided with the distribution.
# 3. All advertising materials mentioning features or use of this software
#    must display the following acknowledgement:
#   This product includes software developed by Wolfram Schneider
# 4. The name of the author may not be used to endorse or promote products
#    derived from this software without specific prior written permission
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
# /usr/sbin/adduser - add new user(s)
#
# Bugs: sure (my english!)
#   Email: Wolfram Schneider <wosch@cs.tu-berlin.de>
#
# $Id: adduser,v 1.17 1995/01/02 00:08:43 w Exp w $
#
X
sub variables {
X    $verbose = 1;
X    $batch = 0;                         # batch mode
X    $defaultpasswd = 0;
X    $dotdir = "/usr/share/skel";
X
X    if (1) {
X    $home = "/home";
X    $shells = "/etc/shells";
X    $passwd = "/etc/master.passwd";
X    $group = "/etc/group";
X    $pwd_mkdb = "pwd_mkdb -p";
X    } else {
X    $home = "/home/w/tmp/adduser/home";
X    $shells = "./shells";
X    $passwd = "./master.passwd";
X    $group = "./group";
X    $pwd_mkdb = "pwd_mkdb -p -d .";
X    }
X
X    @path = ('/bin', '/usr/bin', '/usr/local/bin');
X    @shellpref = ('bash', 'tcsh', 'ksh', 'csh', 'sh');
X    $uid_start = 1000;      # new users get this uid
X    $uid_end   = 32000;
X
X    # global variables
X    $username = '';         # $username{username} = uid
X    $uid = '';              # $uid{uid} = username
X    $pwgid = '';            # $pwgid{pwgid} = username; gid from passwd db
X    $groupname ='';         # $groupname{groupname} = gid
X    $gid = '';              # $gid{gid} = groupname;    gid form group db
X    $defaultshell = '';
X    @passwd_backup = '';
}
X
# read shell database
# See also: shells(5)
sub shells_read {
X    local($s, @dummy);
X    open(S, $shells) || die "$shells:$!\n";
X    while(<S>) {
X        if (/^[ \t]*\//) {
X            ($s, @dummy) = split;
X            if (-x  $s) {
X                $shell{&basename($s)} = $s;
X            } else {
X                warn "Shell: $s not executable!\n";
X            }
X        }
X    }
}
X
# add new/local shells
sub shells_add {
X    local($e,$dir,@list);
X    foreach $e (@shellpref) {
X        if (!$shell{$e}) {
X            foreach $dir (@path) {
X                if (-x "$dir/$e") {
X                    push(@list, "$dir/$e") if
X                    &confirm_yn("Found shell: $dir/$e. Add to $shells?", "yes");
X                }
X            }
X        }
X    }
X    if ($#list >= 0) {
X        foreach $e (@list) {
X            $shell{&basename($e)} = $e;
X            #print "$e\n";
X        }
X        &append_file($shells, @list);
X    }
}
X
# choise your favourite shell
sub shells_pref {
X    local($e,$i,$s);
X
X    $i = 0;
X    while($i < $#shellpref) {
X        last if $shell{$shellpref[$i]};
X        $i++;
X    }
X    $s = &confirm_list("Enter Your default shell:", 0,
X                        $shellpref[$i], sort(keys %shell));
X    print "Your default shell is: $s -> $shell{$s}\n" if $verbose;
X    $defaultshell = $s;
}
X
# return default home partition
sub home_partition {
X    local($h);
X
X    $h = &confirm_list("Enter Your default HOME partition:", 1, $home, "");
X    if (-e "$h") {
X        if (!(-d _ || -l $h)) {
X            warn "$h exist, but is it not a directory or symlink!\n";
X            return &home_partition;
X        }
X        if (! -w _) {
X            warn "$h is not writable!\n";
X            return &home_partition;
X        }
X    } else {
X        return &home_partition unless &mkdirhier($h);
X    }
X	
X	$home = $h;
X    return $h;
}
X
# check for valid passwddb
sub passwd_check {
X    print "Check $passwd\n" if $verbose > 0;
X    system("$pwd_mkdb $passwd");
X    die "\nInvalid $passwd - cannot add any users!\n" if $?;
}
X
# read /etc/passwd
sub passwd_read {
X    local($un, $pw, $ui, $gi);
X
X    open(P, "$passwd") || die "$passwd: $!\n";
X    while(<P>) {
X        chop;
X        push(@passwd_backup, $_);
X        ($un, $pw, $ui, $gi) = (split(/:/, $_))[0..3];
X        print "$un already exist with uid: $username{$un}!\n"
X            if $username{$un};
X        $username{$un} = $ui;
X        print "User $un: uid $ui exist twice: $uid{$ui}\n"
X            if $uid{$ui} && $verbose;
X        $uid{$ui} = $un;
X        $pwgid{$gi} = $un;
X    }
X    close P;
}
X
# read /etc/group
sub group_read {
X    local($gn,$pw,$gi);
X
X    open(G, "$group") || die "$group: $!\n";
X    while(<G>) {
X        ($gn, $pw, $gi) = (split(/:/, $_))[0..2];
X        warn "Groupname exist twice: $gn:$gi -> $gn:$groupname{$gn}\n"
X            if $groupname{$gn};
X        $groupname{$gn} = $gi;
X        warn "Groupid exist twice:   $gn:$gi -> $gid{$gi}:$gi\n"
X            if $gid{$gi};
X        $gid{$gi} = $gn;
X    }
X    close G;
}
X
# check gids /etc/passwd <-> /etc/group
sub group_check {
X    local($e, $user, @list);
X
X    foreach $e (keys %pwgid) {
X        if (!$gid{$e}) {
X            $user = $pwgid{$e};
X            warn "Gid $e is defined in $passwd for user ``$user''\n";
X            warn "but not in $group!\n";
X            if ($groupname{$user}) {
X                warn <<EOF;
I'm confused! Maybe the gids ($e <-> $groupname{$user}) for user ``$user''
in $passwd & $group are wrong.
See $passwd ``$user:*:$username{$user}:$e''
See $group ``$user:*:$groupname{$user}''
EOF
X            } else {
X                push(@list, "$user:*:$e:$user")
X                if (&confirm_yn("Add group``$user'' gid $e to $group?", "y"));
X            }
X        }
X    }
X    &append_file($group, @list) if $#list >= 0;
}
X
sub new_users {
X    local(@userlist) = @_;
X    local($name);
X    local($defaultname) = "a-z0-9";
X
X    print "\nOk, let's go.\n";
X    print "Don't worry about mistakes. I ask You later for " .
X          "correct input.\n" if $verbose;
X
X    while(1) {
X        $name = &confirm_list("Enter username", 1, $defaultname, "");
X        if ($name !~ /^[a-z0-9]+$/) {
X            warn "Wrong username. " .
X                "Please use only lowercase characters or digits\n";
X        } elsif ($username{$name}) {
X            warn "Username ``$name'' already exists!\n";
X        } else {
X            last;
X        }
X    }
X    local($fullname);
X    while(($fullname = &confirm_list("Enter full name", 1, "", "")) =~ /:/) {
X        warn "``:'' is not allowed!\n";
X    }
X    $fullname = $name unless $fullname;
X    local($sh) = &confirm_list("Enter shell", 0, $defaultshell, keys %shell);
X    $sh = $shell{$sh};
X    local($u_id, $g_id) = &next_id($name);
X    print <<EOF;
X
Name:     $name
Passwd:   none, is empty
Fullname: $fullname
Uid:      $u_id
Gid:      $g_id
HOME:     $home/$name
Shell:    $sh
EOF
X    if (&confirm_yn("Ok?", "yes")) {
X        local($new_entry) =
X            "$name::$u_id:$g_id::0:0:$fullname:$home/$name:$sh";
X
X        &append_file($passwd, $new_entry);
X
X        system("$pwd_mkdb $passwd");
X        if ($?) {
X            local($crash) = "$passwd.crash$$";
X            warn "$pwd_mkdb failed, try to restore ...\n";
X
X            open(R, "> $crash") || die "Sorry, give up\n";
X            $j = join("\n", @passwd_backup);
X            $j =~ s/\n//;
X            print R $j . "\n";
X            close R;
X
X            system("$pwd_mkdb $crash");
X            die "Sorry, give up\n" if $?;
X            die "Successfully restore $passwd. Exit.\n";
X        }
X        # Add new group
X        &append_file($group, "$name:*:$g_id:$name")
X            unless $groupname{$name};
X
X        # update passwd/group variables
X        push(@passwd_backup, $new_entry);
X        $username{$name} = $u_id;
X        $uid{$u_id} = $name;
X        $pwgid{$g_id} = $name;
X        $groupname{$name} = $g_id;
X        $gid{$g_id} = $name;
X
X        print "Added user ``$name''\n";
X        local($a) = &confirm_yn("Change password", $defaultpasswd);
X        if (($a && $defaultpasswd) || (!$a && !$defaultpasswd)) {
X            while(1) {
X                system("passwd $name");
X                last unless $?;
X                last unless
X                    &confirm_yn("Passwd $name failed. Try again?", "yes");
X            }
X        }
X    	&home_create($name);
X	}
X   	if (&confirm_yn("Continue with next user?", "yes")) {
X       	&new_users;
X    } else {
X        print "Good by.\n" if $verbose;
X    }
}
X
#
sub password_pref {
X    $defaultpasswd = !&confirm_yn("Use empty passwords", "yes");
}
X
# misc
sub check_root {
X    die "You are not root!\n" if $<;
}
X
sub usage {
X    warn <<USAGE;
X
usage: adduser [options]
X
OPTIONS:
-help                   this help
-silent                 opposite of verbose
-verbose                verbose
-home home              default HOME partition [$home]
-shell shell            default SHELL
-dotdir dir             copy files from dir, default $dotdir
USAGE
X    exit 1;
}
X
#
sub parse_arguments {
X    local(@argv) = @_;
X
X    while ($_ = $argv[0], /^-/) {
X        shift @argv;
X        last if /^--$/;
X        if    (/^--?(debug|verbose)$/)  { $verbose = 1 }
X        elsif (/^--?(silent|guru|wizard)$/) { $verbose = 0 }
X        elsif (/^--?(verbose)$/)        { $verbose = 1 }
X        elsif (/^--?(h|help|\?)$/)      { &usage }
X        elsif (/^--?(home)$/)           { $home = $argv[0]; shift @argv }
X        elsif (/^--?(shell)$/)          { $shell = $argv[0]; shift @argv }
X        elsif (/^--?(dotdir)$/)         { $dotdir = $argv[0]; shift @argv }
X        elsif (/^--?(batch)$/)          { $batch = 1; }
X        else                            { &usage }
X    }
X    #&usage if $#argv < 0;
}
X
sub basename {
X    local($name) = @_;
X    $name =~ s|.*/||;
X    return $name;
}
X
#
sub home_create {
X    local($name) = @_;
X    local(@list);
X    local($e,$from, $to);
X
X    print "Create HOME directory\n";
X    if(!mkdir("$home/$name", 0755)) {
X        warn "Cannot create HOME directory for $name: $!\n";
X        return 0;
X    }
X    push(@list, "$home/$name");
X    if ($dotdir) {
X        opendir(D, "$dotdir") || warn "$dotdir: $!\n";
X        foreach $from (readdir(D)) {
X            if ($from !~ /^(\.|\.\.)$/) {
X                $to = $from;
X                $to =~ s/^dot\././;
X                $to = "$home/$name/$to";
X                push(@list, $to);
X                &cp("$dotdir/$from", "$to", 1);
X            }
X        }
X        closedir D;
X    }
X	#warn "Chown: $name, $name, @list\n";
X	#chown in perl does not work 
X    system("chown $name:$name @list") || warn "$!\n" && return 0;
X    return 1;
}
X
# makes a directory hierarchy
sub mkdirhier {
X    local($dir) = @_;
X
X    if ($dir =~ "^/[^/]+$") {
X        print "Create /usr/$dir\n" if $verbose;
X        if (!mkdir("/usr$dir", 0755)) {
X            warn "/usr/$dir: $!\n"; return 0;
X        }
X        print "Create symlink: /usr$dir -> $dir\n" if $verbose;
X        if (!symlink("/usr$dir", $dir)) {
X            warn "$dir: $!\n"; return 0;
X        }
X    } else {
X        local($d,$p);
X        foreach $d (split('/', $dir)) {
X            $dir = "$p/$d";
X            $dir =~ s|^//|/|;
X            if (! -e "$dir") {
X                print "Create $dir\n" if $verbose;
X                if (!mkdir("$dir", 0755)) {
X                    warn "$dir: $!\n"; return 0;
X                }
X            }
X            $p .= "/$d";
X        }
X    }
X    return 1;
}
X
X
# Read one of the elements from @list. $confirm is default.
# If !$allow accept only elements from @list.
sub confirm_list {
X    local($message, $allow, $confirm, @list) = @_;
X    local($read, $c);
X
X    print "$message " if $message;
X    print "@list [$confirm]: ";
X    chop($read = <STDIN>);
X    $read =~ s/^[ \t]*//;
X    $read =~ s/[ \t\n]*$//;
X    return $confirm unless $read;
X    return $read if $allow;
X
X    foreach $c (@list) {
X        return $read if $c eq $read;
X    }
X    warn "$read: is not allowed!\n";
X    return &confirm_list($message, $allow, $confirm, @list);
}
X
# YES or NO question
# $confirm => 'y' or 'n'. Return true if answer 'y' (or 'n')
sub confirm_yn {
X    local($message, $confirm) = @_;
X    local($yes) = "^(yes|YES|y|Y)$";
X    local($no) = "^(no|NO|n|N)$";
X    local($read, $c);
X
X    if ($confirm && ($confirm =~ "$yes" || $confirm == 1)) {
X        $confirm = "y";
X    } else {
X        $confirm = "n";
X    }
X    print "$message (y/n) [$confirm]: ";
X    chop($read = <STDIN>);
X    $read =~ s/^[ \t]*//;
X    $read =~ s/[ \t\n]*$//;
X    return 1 unless $read;
X
X    if (($confirm eq "y" && $read =~ "$yes") ||
X        ($confirm eq "n" && $read =~ "$no")) {
X        return 1;
X    }
X
X    if ($read !~ "$yes" && $read !~ "$no") {
X        warn "Wrong value. Enter again!\a\n";
X        return &confirm_yn($message, $confirm);
X    }
X    return 0;
}
X
# test if $dotdir exist
sub dotdir_check {
X    return 1 if -e $dotdir && -r _ && (-d _ || -l $dotdir);
X    warn "Directory: $dotdir does not exist or unreadable. " .
X         "Cannot copy dotfiles!\n";
X    $dotdir = '';
X    return 0;
}
X
# write @list to $file with file-locking
sub append_file {
X    local($file,@list) = @_;
X    local($e);
X    local($LOCK_EX) = 2;
X    local($LOCK_UN) = 8;
X
X    open(F, ">> $file") || die "$file: $!\n";
X    print "Lock $file.\n" if $verbose > 1;
X    flock(F, $LOCK_EX);
X    print F join("\n", @list) . "\n";
X    close F;
X    print "Unlock $file.\n" if $verbose > 1;
X    flock(F, $LOCK_UN);
}
X
# return free uid+gid
# uid == gid if possible
sub next_id {
X    local($group) = @_;
X
X    # looking for next free uid
X    while($uid{$uid_start}) {
X        $uid_start++;
X        print "$uid_start\n" if $verbose > 1;
X    }
X
X    local($gid_start) = $uid_start;
X    # group for user (username==groupname) already exist
X    if ($groupname{$group}) {
X        $gid_start = $groupname{$group};
X    }
X    # gid is in use, looking for another gid.
X    # Note: uid an gid are not equal
X    elsif ($gid{$uid_start}) {
X        while($gid{$gid_start} || $uid{$gid_start}) {
X            $gid_start--;
X            $gid_start = $uid_end if $gid_start < 100;
X        }
X    }
X    return ($uid_start, $gid_start);
}
X
sub cp {
X    local($from, $to, $tilde) = @_;
X
X    if (-e "$to") {
X        warn "cp: ``$to'' already exist, do not overwrite\n"; return 0;
X    } elsif (!(-f $from || -l $from)) {
X        warn "$from is not a file or symlink!\n"; return 0;
X    } elsif (!open(F, "$from")) {
X        warn "$from: $!\n"; return 0;
X    } elsif (!open(T, "> $to")) {
X        warn "$to: $!\n"; return 0;
X    }
X
X    if ($tilde) {
X        $tilde = $to;
X        $tilde =~ s|.*/([^/]+/[^/]+)$|~$1|;
X    } else {
X        $tilde = $to;
X    }
X    print "copy $from to $tilde\n" if $verbose;
X    while(<F>) {
X        print T $_;
X    }
X
X    close F;
X    close T;
X    return 1;
}
X
################
# main
#
&check_root;            # You must be root to run this script!
&variables;             # initialize variables
&parse_arguments(@ARGV);    # parse arguments
X
&passwd_check;          # check for valid passwdb
&passwd_read;           # read /etc/master.passwd
&group_read;            # read /etc/group
&group_check;           # check for incon*
&dotdir_check;          # check $dotdir
print "\n";
&home_partition;        # find HOME partition
&shells_read;           # read /etc/shells
&shells_add;            # maybe add some new shells
&shells_pref;           # enter default shell
&password_pref;         # maybe use password
X
&new_users;             # add new users
X
#end
SHAR_EOF
chmod 0755 adduser ||
echo 'restore of adduser failed'
Wc_c="`wc -c < 'adduser'`"
test 16306 -eq "$Wc_c" ||
	echo 'adduser: original size 16306, current size' "$Wc_c"
fi
exit 0