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