From 947f8d29cb95cc2cca135ba261063df1806da2ec Mon Sep 17 00:00:00 2001 From: Scott Long Date: Tue, 3 Dec 2002 05:41:09 +0000 Subject: Replace the perl versions of adduser and rmuser with shell script versions. Submitted by: Mike Makonnen Approved by: re --- adduser/Makefile | 2 +- adduser/adduser.8 | 320 +++++++---- adduser/adduser.perl | 1558 -------------------------------------------------- adduser/adduser.sh | 874 ++++++++++++++++++++++++++++ adduser/rmuser.8 | 69 ++- adduser/rmuser.perl | 601 ------------------- adduser/rmuser.sh | 325 +++++++++++ 7 files changed, 1458 insertions(+), 2291 deletions(-) delete mode 100644 adduser/adduser.perl create mode 100644 adduser/adduser.sh delete mode 100644 adduser/rmuser.perl create mode 100644 adduser/rmuser.sh diff --git a/adduser/Makefile b/adduser/Makefile index baccd55..e1a1e3a 100644 --- a/adduser/Makefile +++ b/adduser/Makefile @@ -1,6 +1,6 @@ # $FreeBSD$ -SCRIPTS=adduser.perl rmuser.perl +SCRIPTS=adduser.sh rmuser.sh MAN= adduser.8 rmuser.8 .include diff --git a/adduser/adduser.8 b/adduser/adduser.8 index 3c4cef4..9a3eea4 100644 --- a/adduser/adduser.8 +++ b/adduser/adduser.8 @@ -1,5 +1,7 @@ .\" Copyright (c) 1995-1996 Wolfram Schneider . Berlin. .\" All rights reserved. +.\" Copyright (c) 2002 Michael Telahun Makonnen +.\" All rights reserved. .\" .\" Redistribution and use in source and binary forms, with or without .\" modification, are permitted provided that the following conditions @@ -24,7 +26,7 @@ .\" .\" $FreeBSD$ .\" -.Dd January 9, 1995 +.Dd August 14, 2002 .Dt ADDUSER 8 .Os .Sh NAME @@ -33,37 +35,39 @@ .Sh SYNOPSIS .Nm .Bk -words -.Op Fl check_only -.Op Fl class Ar login_class -.Op Fl config_create -.Op Fl dotdir Ar dotdir -.Op Fl group Ar login_group -.Op Fl h | help -.Op Fl home Ar home -.Op Fl message Ar message_file -.Op Fl noconfig -.Op Fl shell Ar shell -.Op Fl s | silent | q | quiet -.Op Fl uid Ar uid_start -.Op Fl v | verbose +.Op Fl CENhq +.Op Fl G Ar groups +.Op Fl L Ar login_class +.Op Fl d Ar partition +.Op Fl f Ar file +.Op Fl k Ar dotdir +.Op Fl m Ar message_file +.Op Fl s Ar shell +.Op Fl u Ar uid_start +.Op Fl w Ar type .Ek .Sh DESCRIPTION The -.Nm -utility is a simple program for adding new users. -It checks the passwd, group and shell databases. -It creates passwd/group entries, -.Ev HOME -directory, dotfiles and sends the new user a welcome message. +.Nm adduser +program is a shell script, implemented around the +.Xr pw 8 +command, for adding new users. +It creates passwd/group entries, a home directory, +copies dotfiles and sends the new user a welcome message. +It supports two modes of operation. It may be used interactively +at the command line to add one user at a time or it may be directed +to get the list of new users from a file and operate in batch mode +without requiring any user interaction. .Sh RESTRICTIONS .Bl -tag -width Ds -compact .It Sy username Login name. -May contain only lowercase characters or digits. +The user name is restricted to whatever +.Xr pw 8 +will accept. Generally this means it +may contain only lowercase characters or digits. Maximum length -is 16 characters (see -.Xr setlogin 2 -BUGS section). +is 16 characters. The reasons for this limit are "Historical". Given that people have traditionally wanted to break this limit for aesthetic reasons, it's never been of great importance to break @@ -80,20 +84,26 @@ The NIS protocol mandates an 8-character username. If you need a longer login name for e-mail addresses, you can define an alias in .Pa /etc/mail/aliases . -.It Sy fullname -Firstname and surname. +.It Sy full name +This is typically known as the gecos field and usually contains +the user's full name. Additionally, it may contain a comma separated +list of values such as office number and work and home phones. If the +name contains an amperstand it will be replaced by the capitalized +login name when displayed by other programs. The .Ql Pa \&: character is not allowed. .It Sy shell -Only valid shells from the shell database or sliplogin and pppd +Only valid shells from the shell database (/etc/shells) are allowed. In +addition, only the base name of the shell is necessary, not the full path. .It Sy uid -Automatically generated or your choice, must be less than 32000. +Automatically generated or your choice. It must be less than 32000. .It Sy gid/login group -Your choice or automatically generated. +Automatically generated or your choice. It must be less than 32000. .It Sy password -If not empty, password is encoded with -.Xr crypt 3 . +You may choose an empty password, disable the password, use a +randomly generated password or specify your own plaintext password, +which will be encrypted before being stored in the user database. .El .Sh UNIQUE GROUPS Perhaps you're missing what @@ -114,96 +124,183 @@ users into groups and having to muck with the umask when working in a shared area. .Pp I have been using this model for almost 10 years and found that it works -for most situations, and has never gotten in the way. -(Rod Grimes) +for most situations, and has never gotten in the way. (Rod Grimes) .Sh CONFIGURATION -.Bl -enum -.It -Read internal variables. -.It -Read configuration file (/etc/adduser.conf). -.It -Parse command line options. -.El +The +.Nm +utility reads its configuration information from +.Ar /etc/adduser.conf . +If this file does not exist it will use predefined defaults. While +this file may be edited by hand the safer option is to use the +.Op Fl C +command line argument. With this argument +.Nm +will start interactive input, save the answers to its prompts in +.Ar /etc/adduser.conf , +and promptly exit without modifying the user +database. Options specified on the command line will take precedence over +any values saved in this file. .Sh OPTIONS .Bl -tag -width Ds -.It Fl check_only -Check /etc/passwd, /etc/group, /etc/shells and exit. -.It Fl class Ar login_class -Set default login class. -.It Fl config_create -Create new configuration and message file and exit. -.It Fl dotdir Ar directory +.It Fl C +Create new configuration file and exit. This option is mutually exclusive +with the +.Op Fl f +option. +.It Fl d Ar partition +Home partition. Default partition, under which all user directories +will be located. +.It Fl E +Disable the account. This option will lock the account by prepending +the string *LOCKED* to the password field. The account may be unlocked +by the super-user with the +.Xr pw 8 +command: +.Pp +.Dl "pw unlock [name|uid]" +.It Fl f Ar file +Get the list of accounts to create from +.Ar file . +If +.Ar file +is '`-'', then get the list from standard input. If this option +is specified +.Nm +will operate in batch mode and will not seek any user input. If an +error is encountered while processing an account it will write a +message to standard error and move to the next account. The format +of the input file is described below. +.It Fl G Ar groups +Additional group(s). By default the user name is used as the login group. +This option allows the user to specify additional groups to add users to. +.It Fl h +Print a summary of options and exit. +.It Fl k Ar directory Copy files from .Ar directory -into the -.Ev HOME +into the home directory of new users, .Ql Pa dot.foo will be renamed to .Ql Pa .foo . -Don't copy files if -.Ar directory -specified is equal to -.Ar no . -For security make all files writable and readable for owner, -don't allow group or world to write files and allow only owner -to read/execute/write -.Pa .rhost , -.Pa .Xauthority , -.Pa .kermrc , -.Pa .netrc , -.Pa Mail , -.Pa prv , -.Pa iscreen , -.Pa term . -.It Fl group Ar login_group -Login group. -.Ar USER -means that the username is to be used as login group. -.It Fl help , h , \&? -Print a summary of options and exit. -.It Fl home Ar partition -Default home partition where all users located. -.It Fl message Ar file +.It Fl L Ar login_class +Set default login class. +.It Fl m Ar file Send new users a welcome message from .Ar file . Specifying a value of .Ar no for .Ar file -causes no message to be sent to new users. -.It Fl noconfig +causes no message to be sent to new users. Please note that the message +file can reference the internal variables of the +.Nm +script. +.It Fl N Do not read the default configuration file. -.It Fl shell Ar shell -Default shell for new users. -.It Fl silent , s , quiet , q -Few warnings, questions, bug reports. -.It Fl uid Ar uid +.It Fl q +Minimal user feedback. In particular, the random password will not be echoed to +standard output. +.It Fl s Ar shell +Default shell for new users. The +.Ar shell +argument must be the base name of the shell , NOT the full path. +It must exist in +.Ar /etc/shells +to be considered a valid shell. +.It Fl u Ar uid Use uid's from .Ar uid on up. -.It Fl verbose , v -Many warnings, questions. -Recommended for novice users. +.It Fl w Ar type +Password type. The +.Nm +utility allows the user to specify what type of password to create. +The +.Ar type +argument may have one of the following values: +.Bl -tag -width ".Dv random" +.It Dv no +Disable the password. Instead of an encrypted string the passowrd field +will contain a single '`*'' character. +The user may not login until the super-user +manually enables the password. +.It Dv none +Use an empty string as the password. +.It Dv yes +Use a user supplied string as the password. In interactive mode +the user will be prompted for the password. In batch mode, the +last (10th) field in the line is assumed to be the password. +.It Dv random +Generate a random string and use it as a password. The password will +be echoed to standard output. In addition it will be available for +inclusion in the message file in the +.Ar randompass +environment variable. .El -.Sh FORMATS +.Sh FORMAT .Bl -tag -width Ds -compact +When the +.Op Fl f +option is used the account information must be stored in a specific +format. All empty lines or lines beginning with a .Ql Pa # -is a comment. -.It Sy configuration file -The -.Nm -utility reads and writes this file. -See -.Pa /etc/adduser.conf -for more details. -.It Sy message file -Eval variables in this file. -See -.Pa /etc/adduser.message -for more -details. +will be ignored. All other lines must contain ten colon (:) separated +fields as described below. Command line options do not take precedence +over values in the fields. Only the password field may contain a +.Ql Pa : +character as part of the string. +.Pp +.Dl "name:uid:gid:class:change:expire:gecos:home_dir:shell:password" +.Bl -tag -width ".Dv password" +.It Dv name +Login name. This field may not be empty. +.It Dv uid +Numeric login user id. If this field is left empty it will be automatically +generated. +.It Dv gid +Numeric primary group id. If this field is left empty a group with the +same name as the user name will be created and its gid will be used +instead. +.It Dv class +Login class. This field may be left empty. +.It Dv change +Password ageing. +This field denotes the password change date for the account. The format of this +field is the same as the format of the +.Op Fl p +argument to +.Xr pw 8 . +It may be 'dd-mmm-yy[yy]', where 'dd' is for the day, 'mmm' is for the month +in numeric or alphabetical format: '10 or Oct', and 'yy[yy]' is the four or two digit year. +To denote a time relative to the current date the format +is: '+n[mhdwoy]', where 'n' denotes a number, followed by the Minutes, Hours, +Days, Weeks, Months or Years after which the password must be changed. +This field may be left empty to turn it off. +.It Dv expire +Account expiration. This field denotes the expiry date of the account. The account may +not be used after the specified date. The format of this field is the same as that +for password ageing. This field may be left empty to turn it off. +.It Dv gecos +Full name and other extra information about the user. +.It Dv home_dir +Home directory. If this field is left empty it will be automatically +created by appending the username to the home partition. +.It Dv shell +Login Shell. This field should contain the full path to a valid login shell. +.It Dv password +User password. This field should contain a plaintext string, which will +be encrypted before being placed in the user database. If the password type is 'yes' +and this field is empty it is assumed the account will have any empty password. If +the password type is 'random' and this field is NOT empty its contents will be used +as a password. This field will be ignored if the +.Op Fl p +flag is used with a +.Ar no +or +.Ar none +argument. Be carefull not to terminate this field with a closing ':' because it will +be treated as part of the password. .El .Sh FILES .Bl -tag -width /etc/master.passwdxx -compact @@ -226,9 +323,7 @@ logfile for adduser .El .Sh SEE ALSO .Xr chpass 1 , -.Xr finger 1 , .Xr passwd 1 , -.Xr setlogin 2 , .Xr aliases 5 , .Xr group 5 , .Xr login.conf 5 , @@ -239,9 +334,28 @@ logfile for adduser .Xr rmuser 8 , .Xr vipw 8 , .Xr yp 8 -.\" .Sh BUGS .Sh HISTORY The .Nm -utility appeared in +command appeared in .Fx 2.1 . +.Sh AUTHORS +This manual page and the original script, in perl, was written by +.An Wolfram Schneider . The replacement script, written as a Bourne +shell script with some enhancements, and the man page modification that +came with it were done by +.An Mike Makonnen . +.Sh BUGS +In order for +.Nm +to correctly expand variables such as $username and $randompass in the message sent +to new users it must let the shell evaluate each line of the message file. This means +that shell commands can also be embedded in the message file. The +.Nm +utility attemps to mitigate the possibility of an attacker using this feature by +refusing to evaluate the file if it is not owned and writeable only by the root user. +In addition, shell special characters and operators will have to be escaped when +used in the message file. +.Pp +Also, password ageing and account expiry times are currently setable only in batch mode. +The user should be able to set them in interactive mode as well. diff --git a/adduser/adduser.perl b/adduser/adduser.perl deleted file mode 100644 index fb60253..0000000 --- a/adduser/adduser.perl +++ /dev/null @@ -1,1558 +0,0 @@ -#!/usr/bin/perl -# -# Copyright (c) 1995-1996 Wolfram Schneider . Berlin. -# 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. -# -# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. -# -# $FreeBSD$ - - -# read variables -sub variables { - $verbose = 1; # verbose = [0-2] - $usernameregexp = "^[a-z0-9_][a-z0-9_-]*\$"; # configurable - $defaultusernameregexp = $usernameregexp; # remains constant - $defaultusepassword = "yes"; # use password authentication for new users - $defaultenableaccount = "yes"; # enable the account by default - $defaultemptypassword = "no"; # don't create an empty password - $dotdir = "/usr/share/skel"; # copy dotfiles from this dir - $dotdir_bak = $dotdir; - $send_message = "/etc/adduser.message"; # send message to new user - $send_message_bak = '/etc/adduser.message'; - $config = "/etc/adduser.conf"; # config file for adduser - $config_read = 1; # read config file - $logfile = "/var/log/adduser"; # logfile - $home = "/home"; # default HOME - $etc_shells = "/etc/shells"; - $etc_passwd = "/etc/master.passwd"; - $group = "/etc/group"; - @pwd_mkdb = qw(pwd_mkdb -p); # program for building passwd database - - - # List of directories where shells located - @path = ('/bin', '/usr/bin', '/usr/local/bin'); - # common shells, first element has higher priority - @shellpref = ('csh', 'sh', 'bash', 'tcsh', 'ksh'); - - $defaultshell = 'sh'; # defaultshell if not empty - $group_uniq = 'USER'; - $defaultgroup = $group_uniq;# login groupname, $group_uniq means username - $defaultclass = ''; - - $uid_start = 1000; # new users get this uid - $uid_end = 32000; # max. uid - - # global variables - # passwd - $username = ''; # $username{username} = uid - $uid = ''; # $uid{uid} = username - $pwgid = ''; # $pwgid{pwgid} = username; gid from passwd db - - $password = ''; # password for new users - $usepassword = ''; # use password-based auth - $useemptypassword = ''; # use an empty password - $enableaccount = ''; # enable or disable account password at creation - - # group - $groupname =''; # $groupname{groupname} = gid - $groupmembers = ''; # $groupmembers{gid} = members of group/kommalist - $gid = ''; # $gid{gid} = groupname; gid form group db - @group_comments; # Comments in the group file - - # shell - $shell = ''; # $shell{`basename sh`} = sh - - umask 022; # don't give login group write access - - $ENV{'PATH'} = "/sbin:/bin:/usr/sbin:/usr/bin"; - @passwd_backup = ''; - @group_backup = ''; - @message_buffer = ''; - @user_variable_list = ''; # user variables in /etc/adduser.conf - $do_not_delete = '## DO NOT DELETE THIS LINE!'; -} - -# read shell database, see also: shells(5) -sub shells_read { - local($sh); - local($err) = 0; - - print "Check $etc_shells\n" if $verbose; - open(S, $etc_shells) || die "$etc_shells:$!\n"; - - while() { - if (/^\s*\//) { - s/^\s*//; s/\s+.*//; # chop - $sh = $_; - if (-x $sh) { - $shell{&basename($sh)} = $sh; - } else { - warn "Shell: $sh not executable!\n"; - $err++; - } - } - } - - # Allow /nonexistent and /bin/date as a valid shell for system utils - push(@list, "/nonexistent"); - push(@shellpref, "no") if !grep(/^no$/, @shellpref); - $shell{"no"} = "/nonexistent"; - - push(@list, "/bin/date"); - push(@shellpref, "date") if !grep(/^date$/, @shellpref); - $shell{"date"} = "/bin/date"; - - return $err; -} - -# add new shells if possible -sub shells_add { - local($sh,$dir,@list); - - return 1 unless $verbose; - - foreach $sh (@shellpref) { - # all known shells - if (!$shell{$sh}) { - # shell $sh is not defined as login shell - foreach $dir (@path) { - if (-x "$dir/$sh") { - # found shell - if (&confirm_yn("Found shell: $dir/$sh. Add to $etc_shells?", "yes")) { - push(@list, "$dir/$sh"); - push(@shellpref, "$sh"); - $shell{&basename("$dir/$sh")} = "$dir/$sh"; - $changes++; - } - } - } - } - } - &append_file($etc_shells, @list) if $#list >= 0; -} - -# choose your favourite shell and return the shell -sub shell_default { - local($e,$i,$new_shell); - local($sh); - - $sh = &shell_default_valid($defaultshell); - return $sh unless $verbose; - - $new_shell = &confirm_list("Enter your default shell:", 0, - $sh, sort(keys %shell)); - print "Your default shell is: $new_shell -> $shell{$new_shell}\n"; - $changes++ if $new_shell ne $sh; - return $new_shell; -} - -sub shell_default_valid { - local($sh) = @_; - local($s,$e); - - return $sh if $shell{$sh}; - - foreach $e (@shellpref) { - $s = $e; - last if defined($shell{$s}); - } - $s = "sh" unless $s; - warn "Shell ``$sh'' is undefined, use ``$s''\n"; - return $s; -} - -# return default home partition (e.g. "/home") -# create base directory if nesseccary -sub home_partition { - local($home) = @_; - $home = &stripdir($home); - local($h) = $home; - - return $h if !$verbose && $h eq &home_partition_valid($h); - - while(1) { - $h = &confirm_list("Enter your default HOME partition:", 1, $home, ""); - $h = &stripdir($h); - last if $h eq &home_partition_valid($h); - } - - $changes++ if $h ne $home; - return $h; -} - -sub home_partition_valid { - local($h) = @_; - - $h = &stripdir($h); - # all right (I hope) - return $h if $h =~ "^/" && -e $h && -w $h && (-d $h || -l $h); - - # Errors or todo - if ($h !~ "^/") { - warn "Please use absolute path for home: ``$h''.\a\n"; - return 0; - } - - if (-e $h) { - warn "$h exists, but is not a directory or symlink!\n" - unless -d $h || -l $h; - warn "$h is not writable!\n" - unless -w $h; - return 0; - } else { - # create home partition - return $h if &mkdir_home($h); - } - return 0; -} - -# check for valid passwddb -sub passwd_check { - system(@pwd_mkdb, '-C', $etc_passwd); - die "\nInvalid $etc_passwd - cannot add any users!\n" if $?; -} - -# read /etc/passwd -sub passwd_read { - local($p_username, $pw, $p_uid, $p_gid, $sh, %shlist); - - print "Check $etc_passwd\n" if $verbose; - open(P, "$etc_passwd") || die "$etc_passwd: $!\n"; - - while(

) { - chop; - push(@passwd_backup, $_); - # ignore comments - next if /^\s*$/; - next if /^\s*#/; - - ($p_username, $pw, $p_uid, $p_gid, $sh) = (split(/:/, $_))[0..3,9]; - - print "$p_username already exists with uid: $username{$p_username}!\n" - if $username{$p_username} && $verbose; - $username{$p_username} = $p_uid; - print "User $p_username: uid $p_uid exists twice: $uid{$p_uid}\n" - if $uid{$p_uid} && $verbose && $p_uid; # don't warn for uid 0 - print "User $p_username: illegal shell: ``$sh''\n" - if ($verbose && $sh && - !$shell{&basename($sh)} && - $p_username !~ /^(news|xten|bin|nobody|uucp)$/ && - $sh !~ /\/(pppd|sliplogin|nologin|nonexistent)$/); - $uid{$p_uid} = $p_username; - $pwgid{$p_gid} = $p_username; - } - close P; -} - -# read /etc/group -sub group_read { - local($g_groupname,$pw,$g_gid, $memb); - - print "Check $group\n" if $verbose; - open(G, "$group") || die "$group: $!\n"; - while() { - chop; - push(@group_backup, $_); - # Ignore empty lines - next if /^\s*$/; - # Save comments to restore later - if (/^\s*\#/) { - push(@group_comments, $_); - next; - } - - ($g_groupname, $pw, $g_gid, $memb) = (split(/:/, $_))[0..3]; - - $groupmembers{$g_gid} = $memb; - warn "Groupname exists twice: $g_groupname:$g_gid -> $g_groupname:$groupname{$g_groupname}\n" - if $groupname{$g_groupname} && $verbose; - $groupname{$g_groupname} = $g_gid; - warn "Groupid exists twice: $g_groupname:$g_gid -> $gid{$g_gid}:$g_gid\n" - if $gid{$g_gid} && $verbose; - $gid{$g_gid} = $g_groupname; - } - close G; -} - -# check gids /etc/passwd <-> /etc/group -sub group_check { - local($c_gid, $c_username, @list); - - foreach $c_gid (keys %pwgid) { - if (!$gid{$c_gid}) { - $c_username = $pwgid{$c_gid}; - warn "User ``$c_username'' has gid $c_gid but a group with this " . - "gid does not exist.\n" if $verbose; - } - } -} - -# -# main loop for creating new users -# - -# return username -sub new_users_name { - local($name); - - while(1) { - $name = &confirm_list("Enter username", 1, $usernameregexp, ""); - last if (&new_users_name_valid($name)); - } - return $name; -} - -sub new_users_name_valid { - local($name) = @_; - - if ($name eq $usernameregexp) { # user/admin just pressed - warn "Please enter a username\a\n"; - return 0; - } elsif (length($name) > 16) { - warn "Username is longer than 16 characters.\a\n"; - return 0; - } elsif ($name =~ /[:\n]/) { - warn "Username cannot contain colon or newline characters.\a\n"; - return 0; - } elsif ($name !~ /$usernameregexp/) { - if ($usernameregexp eq $defaultusernameregexp) { - warn "Illegal username.\n" . - "Please use only lowercase Roman, decimal, underscore, " . - "or hyphen characters.\n" . - "Additionally, a username should not start with a hyphen.\a\n"; - } else { - warn "Username doesn't match the regexp /$usernameregexp/\a\n"; - } - return 0; - } elsif (defined($username{$name})) { - warn "Username ``$name'' already exists!\a\n"; return 0; - } - return 1; -} - -# return full name -sub new_users_fullname { - local($name) = @_; - local($fullname); - - while(1) { - $fullname = &confirm_list("Enter full name", 1, "", ""); - last if $fullname eq &new_users_fullname_valid($fullname); - } - $fullname = $name unless $fullname; - return $fullname; -} - -sub new_users_fullname_valid { - local($fullname) = @_; - - return $fullname if $fullname !~ /:/; - - warn "``:'' is not allowed!\a\n"; - return 0; -} - -# return shell (full path) for user -sub new_users_shell { - local($sh); - - $sh = &confirm_list("Enter shell", 0, $defaultshell, keys %shell); - return $shell{$sh}; -} - -# return home (full path) for user -# Note that the home path defaults to $home/$name for batch -sub new_users_home { - local($name) = @_; - local($userhome); - - while(1) { - $userhome = &confirm_list("Enter home directory (full path)", 1, "$home/$name", ""); - last if $userhome =~ /^\//; - warn qq{Home directory "$userhome" is not a full path\a\n}; - } - return $userhome; -} - -# return free uid and gid -sub new_users_id { - local($name) = @_; - local($u_id, $g_id) = &next_id($name); - local($u_id_tmp, $e); - - while(1) { - $u_id_tmp = &confirm_list("Uid", 1, $u_id, ""); - last if $u_id_tmp =~ /^[0-9]+$/ && $u_id_tmp <= $uid_end && - ! $uid{$u_id_tmp}; - if ($uid{$u_id_tmp}) { - warn "Uid ``$u_id_tmp'' in use!\a\n"; - $uid_start = $u_id_tmp; - ($u_id, $g_id) = &next_id($name); - next; - } else { - warn "Wrong uid.\a\n"; - } - } - # use calculated uid - # return ($u_id_tmp, $g_id) if $u_id_tmp eq $u_id; - # recalculate gid - $uid_start = $u_id_tmp; - return &next_id($name); -} - -# return login class for user -sub new_users_class { - local($def) = @_; - local($class); - - $class = &confirm_list("Enter login class:", 1, $def, ($def, "default")); - $class = "" if $class eq "default"; - return $class; -} - -# add user to group -sub add_group { - local($gid, $name) = @_; - - return 0 if - $groupmembers{$gid} =~ /^(.+,)?$name(,.+)?$/; - - $groupmembers_bak{$gid} = $groupmembers{$gid}; - $groupmembers{$gid} .= "," if $groupmembers{$gid}; - $groupmembers{$gid} .= "$name"; - - return $name; -} - - -# return login group -sub new_users_grplogin { - local($name, $defaultgroup, $new_users_ok) = @_; - local($group_login, $group); - - $group = $name; - $group = $defaultgroup if $defaultgroup ne $group_uniq; - - if ($new_users_ok) { - # clean up backup - foreach $e (keys %groupmembers_bak) { delete $groupmembers_bak{$e}; } - } else { - # restore old groupmembers, user was not accept - foreach $e (keys %groupmembers_bak) { - $groupmembers{$e} = $groupmembers_bak{$e}; - } - } - - while(1) { - $group_login = &confirm_list("Login group", 1, $group, - ($name, $group)); - last if $group_login eq $group; - last if $group_login eq $name; - last if defined $groupname{$group_login}; - if ($group_login eq $group_uniq) { - $group_login = $name; last; - } - - if (defined $gid{$group_login}) { - # convert numeric groupname (gid) to groupname - $group_login = $gid{$group_login}; - last; - } - warn "Group does not exist!\a\n"; - } - - #if (defined($groupname{$group_login})) { - # &add_group($groupname{$group_login}, $name); - #} - - return ($group_login, $group_uniq) if $group_login eq $name; - return ($group_login, $group_login); -} - -# return other groups (string) -sub new_users_groups { - local($name, $other_groups) = @_; - local($string) = - "Login group is ``$group_login''. Invite $name into other groups:"; - local($e, $flag); - local($new_groups,$groups); - - $other_groups = "no" unless $other_groups; - - while(1) { - $groups = &confirm_list($string, 1, $other_groups, - ("no", $other_groups, "guest")); - # no other groups - return "" if $groups eq "no"; - - ($flag, $new_groups) = &new_users_groups_valid($groups); - last unless $flag; - } - $new_groups =~ s/\s*$//; - return $new_groups; -} - -sub new_users_groups_valid { - local($groups) = @_; - local($e, $new_groups); - local($flag) = 0; - - foreach $e (split(/[,\s]+/, $groups)) { - # convert numbers to groupname - if ($e =~ /^[0-9]+$/ && $gid{$e}) { - $e = $gid{$e}; - } - if (defined($groupname{$e})) { - if ($e eq $group_login) { - # do not add user to a group if this group - # is also the login group. - } elsif (&add_group($groupname{$e}, $name)) { - $new_groups .= "$e "; - } else { - warn "$name is already member of group ``$e''\n"; - } - } else { - warn "Group ``$e'' does not exist\a\n"; $flag++; - } - } - return ($flag, $new_groups); -} - -# your last change -sub new_users_ok { - local ($newpasswd); - # Note that we either show "password disabled" or - # "****" .. we don't show "empty password" since - # the whole point of starring out the password in - # the first place is to stop people looking over your - # shoulder and seeing the password.. -- adrian - if ($usepassword eq "no") { - $newpasswd = "Password disabled"; - } elsif ($enableaccount eq "no") { - $newpasswd = "Password disabled"; - } else { - $newpasswd = "****"; - } - - print <> 8); - } -} - -# update group database -sub new_users_group_update { - local($e, @a); - - # Add *new* group - if (!defined($groupname{$group_login}) && - !defined($gid{$groupname{$group_login}})) { - push(@group_backup, "$group_login:*:$g_id:"); - $groupname{$group_login} = $g_id; - $gid{$g_id} = $group_login; - # $groupmembers{$g_id} = $group_login; - } - - if ($new_groups || defined($groupname{$group_login}) || - defined($gid{$groupname{$group_login}}) && - $gid{$groupname{$group_login}} ne "+") { - # new user is member of some groups - # new login group is already in name space - rename($group, "$group.bak"); - #warn "$group_login $groupname{$group_login} $groupmembers{$groupname{$group_login}}\n"; - - # Restore comments from the top of the group file - @a = @group_comments; - foreach $e (sort {$a <=> $b} (keys %gid)) { - push(@a, "$gid{$e}:*:$e:$groupmembers{$e}"); - } - &append_file($group, @a); - } else { - &append_file($group, "$group_login:*:$g_id:"); - } - -} - -sub new_users_passwd_update { - # update passwd/group variables - push(@passwd_backup, $new_entry); - $username{$name} = $u_id; - $uid{$u_id} = $name; - $pwgid{$g_id} = $name; -} - -# send message to new user -sub new_users_sendmessage { - return 1 if $send_message eq "no"; - - local($cc) = - &confirm_list("Send message to ``$name'' and:", - 1, "no", ("root", "second_mail_address", "no")); - local($e); - $cc = "" if $cc eq "no"; - - foreach $e (@message_buffer) { - print eval "\"$e\""; - } - print "\n"; - - local(@message_buffer_append) = (); - if (!&confirm_yn("Add anything to default message", "no")) { - print "Use ``.'' or ^D alone on a line to finish your message.\n"; - push(@message_buffer_append, "\n"); - while($read = ) { - last if $read eq "\.\n"; - push(@message_buffer_append, $read); - } - } - - &sendmessage("$name $cc", (@message_buffer, @message_buffer_append)) - if (&confirm_yn("Send message", "yes")); -} - -sub sendmessage { - local($to, @message) = @_; - local($e); - - if (!open(M, "| mail -s Welcome $to")) { - warn "Cannot send mail to: $to!\n"; - return 0; - } else { - foreach $e (@message) { - print M eval "\"$e\""; - } - close M; - } -} - - -sub new_users_password { - - local($password); - - while(1) { - system("stty -echo"); - $password = &confirm_list("Enter password", 1, "", ""); - system("stty echo"); - print "\n"; - if ($password ne "") { - system("stty -echo"); - $newpass = &confirm_list("Enter password again", 1, "", ""); - system("stty echo"); - print "\n"; - last if $password eq $newpass; - print "They didn't match, please try again\n"; - } - elsif (&confirm_yn("Use an empty password?", "yes")) { - last; - } - } - - return $password; -} - -sub new_users_use_password { - local ($p) = $defaultusepassword; - $p = &confirm_yn("Use password-based authentication", $defaultusepassword); - return "yes" if (($defaultusepassword eq "yes" && $p) || - ($defaultusepassword eq "no" && !$p)); - return "no"; # otherwise -} - -sub new_users_enable_account { - local ($p) = $defaultenableaccount; - $p = &confirm_yn("Enable account password at creation", $defaultenableaccount); - return "yes" if (($defaultenableaccount eq "yes" && $p) || - ($defaultenableaccount eq "no" && !$p)); - return "no"; # otherwise -} - -sub new_users_empty_password { - local ($p) = $defaultemptypassword; - $p = &confirm_yn("Use an empty password", $defaultemptypassword); - return "yes" if (($defaultemptypassword eq "yes" && $p) || - ($defaultemptypassword eq "no" && !$p)); - return "no"; # otherwise -} - -sub new_users { - - print "\n" if $verbose; - print "Ok, let's go.\n" . - "Don't worry about mistakes. I will give you the chance later to " . - "correct any input.\n" if $verbose; - - # name: Username - # fullname: Full name - # sh: shell - # userhome: home path for user - # u_id: user id - # g_id: group id - # class: login class - # group_login: groupname of g_id - # new_groups: some other groups - local($name, $group_login, $fullname, $sh, $u_id, $g_id, $class, $new_groups); - local($userhome); - local($groupmembers_bak, $cryptpwd); - local($new_users_ok) = 1; - - - $new_groups = "no"; - $new_groups = "no" unless $groupname{$new_groups}; - - while(1) { - $name = &new_users_name; - $fullname = &new_users_fullname($name); - $sh = &new_users_shell; - $userhome = &new_users_home($name); - ($u_id, $g_id) = &new_users_id($name); - $class = &new_users_class($defaultclass); - ($group_login, $defaultgroup) = - &new_users_grplogin($name, $defaultgroup, $new_users_ok); - # do not use uniq username and login group - $g_id = $groupname{$group_login} if (defined($groupname{$group_login})); - - - # The tricky logic: - # If $usepasswd is 0, we use a * as a password - # If $usepasswd is 1, then - # if $enableaccount is 0, we prepend * as a password - # else if $enableaccount is 1 we don't prepend anything - # if $useemptypassword is 0 we ask for a password, - # else we use a blank one - # - # The logic is tasty, I'll give you that, but its flexible and - # it'll stop people shooting themselves in the foot. - - $new_groups = &new_users_groups($name, $new_groups); - - $usepassword = &new_users_use_password; - if ($usepassword eq "no") { - # note that the assignments to enableaccount and - # useemptypassword functionally do the same as - # usepasswd == "no". Just for consistency. - $password = ""; # no password! - $enableaccount = "no"; # doesn't matter here - $useemptypassword = "yes"; # doesn't matter here - } else { - $useemptypassword = &new_users_empty_password; - if ($useemptypassword eq "no") { - $password = &new_users_password; - } - $enableaccount = &new_users_enable_account; - } - - if (&new_users_ok) { - $new_users_ok = 1; - - $cryptpwd = ""; - $cryptpwd = crypt($password, &salt) if $password ne ""; - - if ($usepassword eq "no") { - $cryptpwd = "*"; - } else { - # cryptpwd is valid before this if mess, so if - # blankpasswd is no we don't blank the cryptpwd - if ($useemptypassword eq "yes") { - $cryptpwd = ""; - } - if ($enableaccount eq "no") { - $cryptpwd = "*" . $cryptpwd; - } - } - # obscure perl bug - $new_entry = "$name\:" . "$cryptpwd" . - "\:$u_id\:$g_id\:$class\:0:0:$fullname:$userhome:$sh"; - &append_file($etc_passwd, "$new_entry"); - &new_users_pwdmkdb("$new_entry", $name); - &new_users_group_update; - &new_users_passwd_update; print "Added user ``$name''\n"; - &new_users_sendmessage; - &adduser_log("$name:*:$u_id:$g_id($group_login):$fullname"); - &home_create($userhome, $name, $group_login); - } else { - $new_users_ok = 0; - } - if (!&confirm_yn("Add another user?", "yes")) { - print "Goodbye!\n" if $verbose; - last; - } - print "\n" if !$verbose; - } -} - -# ask for password usage -sub password_default { - local($p) = $defaultusepassword; - if ($verbose) { - $p = &confirm_yn("Use password-based authentication", $defaultusepassword); - $changes++ unless $p; - } - return "yes" if (($defaultusepassword eq "yes" && $p) || - ($defaultusepassword eq "no" && !$p)); - return "no"; # otherwise -} - -# ask for account enable usage -sub enable_account_default { - local ($p) = $defaultenableaccount; - if ($verbose) { - $p = &confirm_yn("Enable account password at creation", $defaultenableaccount); - $changes++ unless $p; - } - return "yes" if (($defaultenableaccount eq "yes" && $p) || - ($defaultenableaccount eq "no" && !$p)); - return "no"; # otherwise -} - -# ask for empty password -sub enable_empty_password { - local ($p) = $defaultemptypassword; - if ($verbose) { - $p = &confirm_yn("Use an empty password", $defaultemptypassword); - $changes++ unless $p; - } - return "yes" if (($defaultemptypassword eq "yes" && $p) || - ($defaultemptypassword eq "no" && !$p)); - return "no"; # otherwise -} - -# misc -sub check_root { - die "You are not root!\n" if $< && !$test; -} - -sub usage { - warn < 1; - # to64 - for ($i = 0; $i < 27; $i++) { - srand(time + $rand + $$); - $rand = rand(25*29*17 + $rand); - $salt .= $itoa64[$rand & $#itoa64]; - } - warn "Salt is: $salt\n" if $verbose > 1; - - return $salt; -} - - -# print banner -sub copyright { - return; -} - -# hints -sub hints { - if ($verbose) { - print "Use option ``-silent'' if you don't want to see " . - "all warnings and questions.\n\n"; - } else { - print "Use option ``-verbose'' if you want to see more warnings and " . - "questions \nor try to repair bugs.\n\n"; - } -} - -# -sub parse_arguments { - local(@argv) = @_; - - while ($_ = $argv[0], /^-/) { - shift @argv; - last if /^--$/; - if (/^--?(v|verbose)$/) { $verbose = 1 } - elsif (/^--?(s|silent|q|quiet)$/) { $verbose = 0 } - elsif (/^--?(debug)$/) { $verbose = 2 } - elsif (/^--?(h|help|\?)$/) { &usage } - elsif (/^--?(home)$/) { $home = $argv[0]; shift @argv } - elsif (/^--?(shell)$/) { $defaultshell = $argv[0]; shift @argv } - elsif (/^--?(dotdir)$/) { $dotdir = $argv[0]; shift @argv } - elsif (/^--?(uid)$/) { $uid_start = $argv[0]; shift @argv } - elsif (/^--?(class)$/) { $defaultclass = $argv[0]; shift @argv } - elsif (/^--?(group)$/) { $defaultgroup = $argv[0]; shift @argv } - elsif (/^--?(check_only)$/) { $check_only = 1 } - elsif (/^--?(message)$/) { $send_message = $argv[0]; shift @argv; - $sendmessage = 1; } - elsif (/^--?(batch)$/) { - warn "The -batch option is not supported anymore.\n", - "Please use the pw(8) command line tool!\n"; - exit(0); - } - # see &config_read - elsif (/^--?(config_create)$/) { &create_conf; } - elsif (/^--?(noconfig)$/) { $config_read = 0; } - else { &usage } - } - #&usage if $#argv < 0; -} - -sub basename { - local($name) = @_; - $name =~ s|/+$||; - $name =~ s|.*/+||; - return $name; -} - -sub dirname { - local($name) = @_; - $name = &stripdir($name); - $name =~ s|/+[^/]+$||; - $name = "/" unless $name; # dirname of / is / - return $name; -} - -# return 1 if $file is a readable file or link -sub filetest { - local($file, $verb) = @_; - - if (-e $file) { - if (-f $file || -l $file) { - return 1 if -r _; - warn "$file unreadable\n" if $verbose; - } else { - warn "$file is not a plain file or link\n" if $verbose; - } - } - return 0; -} - -# create configuration files and exit -sub create_conf { - $create_conf = 1; - if ($send_message ne 'no') { - &message_create($send_message); - } else { - &message_create($send_message_bak); - } - &config_write(1); - exit(0); -} - -# log for new user in /var/log/adduser -sub adduser_log { - local($string) = @_; - local($e); - - return 1 if $logfile eq "no"; - - local($sec, $min, $hour, $mday, $mon, $year) = localtime; - $year += 1900; - $mon++; - - foreach $e ('sec', 'min', 'hour', 'mday', 'mon', 'year') { - # '7' -> '07' - eval "\$$e = 0 . \$$e" if (eval "\$$e" < 10); - } - - &append_file($logfile, "$year/$mon/$mday $hour:$min:$sec $string"); -} - -# create HOME directory, copy dotfiles from $dotdir to $HOME -sub home_create { - local($homedir, $name, $group) = @_; - local($rootdir); - - if (-e "$homedir") { - warn "HOME directory ``$homedir'' already exists.\a\n"; - return 0; - } - - # if the home directory prefix doesn't exist, create it - # First, split the directory into a list; then remove the user's dir - @dir = split('/', $homedir); pop(@dir); - # Put back together & strip to get directory prefix - $rootdir = &stripdir(join('/', @dir)); - - if (!&mkdirhier("$rootdir")) { - # warn already displayed - return 0; - } - - if ($dotdir eq 'no') { - if (!mkdir("$homedir", 0755)) { - warn "$dir: $!\n"; return 0; - } - system 'chown', "$name:$group", $homedir; - return !$?; - } - - # copy files from $dotdir to $homedir - # rename 'dot.foo' files to '.foo' - print "Copy files from $dotdir to $homedir\n" if $verbose; - system('cp', '-R', $dotdir, $homedir); - system('chmod', '-R', 'u+wrX,go-w', $homedir); - system('chown', '-Rh', "$name:$group", $homedir); - - # security - opendir(D, $homedir); - foreach $file (readdir(D)) { - if ($file =~ /^dot\./ && -f "$homedir/$file") { - $file =~ s/^dot\././; - rename("$homedir/dot$file", "$homedir/$file"); - } - chmod(0600, "$homedir/$file") - if ($file =~ /^\.(rhosts|Xauthority|kermrc|netrc)$/); - chmod(0700, "$homedir/$file") - if ($file =~ /^(Mail|prv|\.(iscreen|term))$/); - } - closedir D; - return 1; -} - -# makes a directory hierarchy -sub mkdir_home { - local($dir) = @_; - $dir = &stripdir($dir); - local($user_partition) = "/usr"; - local($dirname) = &dirname($dir); - - -e $dirname || &mkdirhier($dirname); - - if (((stat($dirname))[0]) == ((stat("/"))[0])){ - # home partition is on root partition - # create home partition on $user_partition and make - # a symlink from $dir to $user_partition/`basename $dir` - # For instance: /home -> /usr/home - - local($basename) = &basename($dir); - local($d) = "$user_partition/$basename"; - - - if (-d $d) { - warn "Oops, $d already exists.\n" if $verbose; - } else { - print "Create $d\n" if $verbose; - if (!mkdir("$d", 0755)) { - warn "$d: $!\a\n"; return 0; - } - } - - unlink($dir); # symlink to nonexist file - print "Create symlink: $dir -> $d\n" if $verbose; - if (!symlink("$d", $dir)) { - warn "Symlink $d: $!\a\n"; return 0; - } - } else { - print "Create $dir\n" if $verbose; - if (!mkdir("$dir", 0755)) { - warn "Directory ``$dir'': $!\a\n"; return 0; - } - } - return 1; -} - -sub mkdirhier { - local($dir) = @_; - local($d,$p); - - $dir = &stripdir($dir); - - foreach $d (split('/', $dir)) { - $dir = "$p/$d"; - $dir =~ s|^//|/|; - if (! -e "$dir") { - print "Create $dir\n" if $verbose; - if (!mkdir("$dir", 0755)) { - warn "$dir: $!\n"; return 0; - } - } - $p .= "/$d"; - } - return 1; -} - -# stript unused '/' -# F.i.: //usr///home// -> /usr/home -sub stripdir { - local($dir) = @_; - - $dir =~ s|/+|/|g; # delete double '/' - $dir =~ s|/$||; # delete '/' at end - return $dir if $dir ne ""; - return '/'; -} - -# Read one of the elements from @list. $confirm is default. -# If !$allow accept only elements from @list. -sub confirm_list { - local($message, $allow, $confirm, @list) = @_; - local($read, $c, $print); - - $print = "$message" if $message; - $print .= " " unless $message =~ /\n$/ || $#list == 0; - - $print .= join($", &uniq(@list)); #" - $print .= " " unless $message =~ /\n$/ && $#list == 0; - print "$print"; - print "\n" if (length($print) + length($confirm)) > 60; - print "[$confirm]: "; - - chop($read = ); - $read =~ s/^\s*//; - $read =~ s/\s*$//; - return $confirm if $read eq ""; - return "$read" if $allow; - - foreach $c (@list) { - return $read if $c eq $read; - } - warn "$read: is not allowed!\a\n"; - return &confirm_list($message, $allow, $confirm, @list); -} - -# YES or NO question -# return 1 if &confirm("message", "yes") and answer is yes -# or if &confirm("message", "no") an answer is no -# otherwise 0 -sub confirm_yn { - local($message, $confirm) = @_; - local($yes) = '^(yes|YES|y|Y)$'; - local($no) = '^(no|NO|n|N)$'; - local($read, $c); - - if ($confirm && ($confirm =~ "$yes" || $confirm == 1)) { - $confirm = "y"; - } else { - $confirm = "n"; - } - print "$message (y/n) [$confirm]: "; - chop($read = ); - $read =~ s/^\s*//; - $read =~ s/\s*$//; - return 1 unless $read; - - if (($confirm eq "y" && $read =~ "$yes") || - ($confirm eq "n" && $read =~ "$no")) { - return 1; - } - - if ($read !~ "$yes" && $read !~ "$no") { - warn "Wrong value. Enter again!\a\n"; - return &confirm_yn($message, $confirm); - } - return 0; -} - -# allow configuring usernameregexp -sub usernameregexp_default { - local($r) = $usernameregexp; - - while ($verbose) { - $r = &confirm_list("Usernames must match regular expression:", 1, - $r, ""); - eval "'foo' =~ /$r/"; - last unless $@; - warn "Invalid regular expression\a\n"; - } - $changes++ if $r ne $usernameregexp; - return $r; -} - -# test if $dotdir exist -# return "no" if $dotdir not exist or dotfiles should not copied -sub dotdir_default { - local($dir) = $dotdir; - - return &dotdir_default_valid($dir) unless $verbose; - while($verbose) { - $dir = &confirm_list("Copy dotfiles from:", 1, - $dir, ("no", $dotdir_bak, $dir)); - last if $dir eq &dotdir_default_valid($dir); - } - warn "Do not copy dotfiles.\n" if $verbose && $dir eq "no"; - - $changes++ if $dir ne $dotdir; - return $dir; -} - -sub dotdir_default_valid { - local($dir) = @_; - - return $dir if (-e $dir && -r _ && (-d _ || -l $dir) && $dir =~ "^/"); - return $dir if $dir eq "no"; - warn "Dotdir ``$dir'' is not a directory\a\n"; - return "no"; -} - -# ask for messages to new users -sub message_default { - local($file) = $send_message; - local(@d) = ($file, $send_message_bak, "no"); - - while($verbose) { - $file = &confirm_list("Send message from file:", 1, $file, @d); - last if $file eq "no"; - last if &filetest($file, 1); - - # maybe create message file - &message_create($file) if &confirm_yn("Create ``$file''?", "yes"); - last if &filetest($file, 0); - last if !&confirm_yn("File ``$file'' does not exist, try again?", - "yes"); - } - - if ($file eq "no" || !&filetest($file, 0)) { - warn "Do not send message\n" if $verbose; - $file = "no"; - } else { - &message_read($file); - } - - $changes++ if $file ne $send_message && $verbose; - return $file; -} - -# create message file -sub message_create { - local($file) = @_; - - rename($file, "$file.bak"); - if (!open(M, "> $file")) { - warn "Messagefile ``$file'': $!\n"; return 0; - } - print M <) { - push(@message_buffer, $_) unless /^\s*#/; - } - close R; -} - -# write @list to $file with file-locking -sub append_file { - local($file,@list) = @_; - local($e); - local($LOCK_EX) = 2; - local($LOCK_NB) = 4; - local($LOCK_UN) = 8; - - open(F, ">> $file") || die "$file: $!\n"; - print "Lock $file.\n" if $verbose > 1; - while(!flock(F, $LOCK_EX | $LOCK_NB)) { - warn "Cannot lock file: $file\a\n"; - die "Sorry, give up\n" - unless &confirm_yn("Try again?", "yes"); - } - print F join("\n", @list) . "\n"; - close F; - print "Unlock $file.\n" if $verbose > 1; - flock(F, $LOCK_UN); -} - -# return free uid+gid -# uid == gid if possible -sub next_id { - local($group) = @_; - - $uid_start = 1000 if ($uid_start <= 0 || $uid_start >= $uid_end); - # looking for next free uid - while($uid{$uid_start}) { - $uid_start++; - $uid_start = 1000 if $uid_start >= $uid_end; - print "$uid_start\n" if $verbose > 1; - } - - local($gid_start) = $uid_start; - # group for user (username==groupname) already exist - if ($groupname{$group}) { - $gid_start = $groupname{$group}; - } - # gid is in use, looking for another gid. - # Note: uid an gid are not equal - elsif ($gid{$uid_start}) { - while($gid{$gid_start} || $uid{$gid_start}) { - $gid_start--; - $gid_start = $uid_end if $gid_start < 100; - } - } - return ($uid_start, $gid_start); -} - -# read config file -sub config_read { - local($opt) = @_; - local($user_flag) = 0; - - # don't read config file - return 1 if $opt =~ /-(noconfig|config_create)/ || !$config_read; - - if(!open(C, "$config")) { - warn "$config: $!\n"; return 0; - } - - while() { - # user defined variables - /^$do_not_delete/ && $user_flag++; - # found @array or $variable - if (s/^(\w+\s*=\s*\()/\@$1/ || s/^(\w+\s*=)/\$$1/) { - eval $_; - #warn "$_"; - } - # lines with '^##' are not saved - push(@user_variable_list, $_) - if $user_flag && !/^##/ && (s/^[\$\@]// || /^[#\s]/); - } - #warn "X @user_variable_list X\n"; - close C; -} - - -# write config file -sub config_write { - local($silent) = @_; - - # nothing to do - return 1 unless ($changes || ! -e $config || !$config_read || $silent); - - if (!$silent) { - if (-e $config) { - return 1 if &confirm_yn("\nWrite your changes to $config?", "no"); - } else { - return 1 unless - &confirm_yn("\nWrite your configuration to $config?", "yes"); - } - } - - rename($config, "$config.bak"); - open(C, "> $config") || die "$config: $!\n"; - - # prepare some variables - $send_message = "no" unless $send_message; - $defaultusepassword = "no" unless $defaultusepassword; - $defaultenableaccount = "yes" unless $defaultenableaccount; - $defaultemptypassword = "no" unless $defaultemptypassword; - local($shpref) = "'" . join("', '", @shellpref) . "'"; - local($shpath) = "'" . join("', '", @path) . "'"; - local($user_var) = join('', @user_variable_list); - - print C < +# +# $FreeBSD$ +# + +# err msg +# Display $msg on stderr, unless we're being quiet. +# +err() { + if [ -z "$quietflag" ]; then + echo 1>&2 ${THISCMD}: ERROR: $* + fi +} + +# info msg +# Display $msg on stdout, unless we're being quiet. +# +info() { + if [ -z "$quietflag" ]; then + echo ${THISCMD}: INFO: $* + fi +} + +# get_nextuid +# Output the value of $_uid if it is available for use. If it +# is not, output the value of the next higher uid that is available. +# If a uid is not specified, output the first available uid, as indicated +# by pw(8). +# +get_nextuid () { + _uid=$1 + _nextuid= + + if [ -z "$_uid" ]; then + _nextuid="`${PWCMD} usernext | cut -f1 -d:`" + else + while : ; do + ${PWCMD} usershow $_uid > /dev/null 2>&1 + if [ ! "$?" -eq 0 ]; then + _nextuid=$_uid + break + fi + _uid=$(($_uid + 1)) + done + fi + echo $_nextuid +} + +# show_usage +# Display usage information for this utility. +# +show_usage() { + echo "usage: ${THISCMD} [options]" + echo " options may include:" + echo " -C save to the configuration file only" + echo " -E disable this account after creation" + echo " -G additional groups to add accounts to" + echo " -L login class of the user" + echo " -N do not read configuration file" + echo " -d home directory" + echo " -f file from which input will be received" + echo " -h display this usage message" + echo " -k path to skeleton home directory" + echo " -m user welcome message file" + echo " -q absolute minimal user feedback" + echo " -s shell" + echo " -u uid to start at" + echo " -w password type: no, none, yes or random" +} + +# valid_shells +# Outputs a list of valid shells from /etc/shells. Only the +# basename of the shell is output. +# +valid_shells() { + _prefix= + cat ${ETCSHELLS} | + while read _path _junk ; do + case $_path in + \#*|'') + ;; + *) + echo -n "${_prefix}`basename $_path`" + _prefix=' ' + ;; + esac + done +} + +# fullpath_from_shell shell +# Given $shell, the basename component of a valid shell, get the +# full path to the shell from the /etc/shells file. +# +fullpath_from_shell() { + _shell=$1 + [ -z "$_shell" ] && return 1 + + cat ${ETCSHELLS} | + while read _path _junk ; do + case "$_path" in + \#*|'') + ;; + *) + if [ "`basename $_path`" = "$_shell" ]; then + echo $_path + return 0 + fi + ;; + esac + done + return 1 +} + +# save_config +# Save some variables to a configuration file. +# Note: not all script variables are saved, only those that +# it makes sense to save. +# +save_config() { + echo "# Configuration file for adduser(8)." > ${ADDUSERCONF} + echo "# NOTE: only *some* variables are saved." >> ${ADDUSERCONF} + echo "# Last Modified on `date`." >> ${ADDUSERCONF} + echo '' >> ${ADDUSERCONF} + echo "defaultclass=$uclass" >> ${ADDUSERCONF} + echo "defaultgroups=$ugroups" >> ${ADDUSERCONF} + echo "passwdtype=$passwdtype" >> ${ADDUSERCONF} + echo "homeprefix=$homeprefix" >> ${ADDUSERCONF} + echo "defaultshell=$ushell" >> ${ADDUSERCONF} + echo "udotdir=$udotdir" >> ${ADDUSERCONF} + echo "msgfile=$msgfile" >> ${ADDUSERCONF} + echo "disableflag=$disableflag" >> ${ADDUSERCONF} +} + +# add_user +# Add a user to the user database. If the user chose to send a welcome +# message or lock the account, do so. +# +add_user() { + + # Is this a configuration run? If so, don't modify user database. + # + if [ -n "$configflag" ]; then + save_config + return + fi + + _uid= + _name= + _comment= + _gecos= + _home= + _group= + _grouplist= + _shell= + _class= + _dotdir= + _expire= + _pwexpire= + _passwd= + _upasswd= + _passwdmethod= + + _name="-n $username" + [ -n "$uuid" ] && _uid="-u $uuid" + [ -n "$ulogingroup" ] && _group="-g $ulogingroup" + [ -n "$ugroups" ] && _grouplist="-G $ugroups" + [ -n "$ushell" ] && _shell="-s $ushell" + [ -n "$uhome" ] && _home="-m -d $uhome" + [ -n "$uclass" ] && _class="-L $uclass" + [ -n "$ugecos" ] && _comment="-c '$ugecos'" + [ -n "$udotdir" ] && _dotdir="-k $udotdir" + [ -n "$uexpire" ] && _expire="-e '$uexpire'" + [ -n "$upwexpire" ] && _pwexpire="-p '$upwexpire'" + case $passwdtype in + no) + _passwdmethod="-w no" + _passwd="-h -" + ;; + yes) + _passwdmethod="-w yes" + _passwd="-h 0" + _upasswd="echo $upass |" + ;; + none) + _passwdmethod="-w none" + ;; + random) + _passwdmethod="-w random" + ;; + esac + + _pwcmd="$_upasswd ${PWCMD} useradd $_uid $_name $_group $_grouplist $_comment" + _pwcmd="$_pwcmd $_shell $_class $_home $_dotdir $_passwdmethod $_passwd" + _pwcmd="$_pwcmd $_expire $_pwexpire" + + if ! _output=`eval $_pwcmd` ; then + err "There was an error adding user ($username)." + return 1 + else + info "Successfully added ($username) to the user database." + if [ "random" = "$passwdtype" ]; then + randompass="$_output" + info "Password for ($username) is: $randompass" + fi + fi + + if [ -n "$disableflag" ]; then + if ${PWCMD} lock $username ; then + info "Account ($username) is locked." + else + info "Account ($username) could NOT be locked." + fi + fi + + _line= + _owner= + _perms= + if [ -n "$msgflag" ]; then + [ -r "$msgfile" ] && { + # We're evaluating the contents of an external file. + # Let's not open ourselves up for attack. _perms will + # be empty if it's writeable only by the owner. _owner + # will *NOT* be empty if the file is owned by root. + # + _dir="`dirname $msgfile`" + _file="`basename $msgfile`" + _perms=`/usr/bin/find $_dir -name $_file -perm +07022 -prune` + _owner=`/usr/bin/find $_dir -name $_file -user 0 -prune` + if [ -z "$_owner" -o -n "$_perms" ]; then + err "The message file ($msgfile) may be writeable only by root." + return 1 + fi + cat "$msgfile" | + while read _line ; do + eval echo "$_line" + done | ${MAILCMD} -s"Welcome" ${username} + info "Sent welcome message to ($username)." + } + fi +} + +# get_user +# Reads username of the account from standard input or from a global +# variable containing an account line from a file. The username is +# required. If this is an interactive session it will prompt in +# a loop until a username is entered. If it is batch processing from +# a file it will output an error message and return to the caller. +# +get_user() { + _input= + + # No need to take down user names if this is a configuration saving run. + [ -n "$configflag" ] && return + + while : ; do + if [ -z "$fflag" ]; then + echo -n "Username: " + read _input + else + _input="`echo "$fileline" | cut -f1 -d:`" + fi + + # There *must* be a username. If this is an interactive + # session give the user an opportunity to retry. + # + if [ -z "$_input" ]; then + err "You must enter a username!" + [ -z "$fflag" ] && continue + fi + break + done + username="$_input" +} + +# get_gecos +# Reads extra information about the user. Can be used both in interactive +# and batch (from file) mode. +# +get_gecos() { + _input= + + # No need to take down additional user information for a configuration run. + [ -n "$configflag" ] && return + + if [ -z "$fflag" ]; then + echo -n "Full name: " + read _input + else + _input="`echo "$fileline" | cut -f7 -d:`" + fi + ugecos="$_input" +} + +# get_shell +# Get the account's shell. Works in interactive and batch mode. It +# accepts only the base name of the shell, NOT the full path. +# If an invalid shell is entered it will simply use the default shell. +# +get_shell() { + _input= + _fullpath= + ushell="$defaultshell" + + # Make sure the current value of the shell is a valid one + _shellchk="grep '^$ushell$' ${ETCSHELLS} > /dev/null 2>&1" + eval $_shellchk || { + err "Invalid shell ($ushell). Using default shell ${defaultshell}." + ushell="$defaultshell" + } + + if [ -z "$fflag" ]; then + echo -n "Shell ($shells) [`basename $ushell`]: " + read _input + else + _input="`echo "$fileline" | cut -f9 -d:`" + fi + if [ -n "$_input" ]; then + _fullpath=`fullpath_from_shell $_input` + if [ -n "$_fullpath" ]; then + ushell="$_fullpath" + else + err "Invalid shell selection. Using default shell ${defaultshell}." + ushell="$defaultshell" + fi + fi +} + +# get_homedir +# Reads the account's home directory. Used both with interactive input +# and batch input. +# +get_homedir() { + _input= + if [ -z "$fflag" ]; then + echo -n "Home directory [${homeprefix}/${username}]: " + read _input + else + _input="`echo "$fileline" | cut -f8 -d:`" + fi + + if [ -n "$_input" ]; then + uhome="$_input" + # if this is a configuration run, then user input is the home + # directory prefix. Otherwise it is understood to + # be $prefix/$user + # + [ -z "$configflag" ] && homeprefix="`dirname $uhome`" || homeprefix="$uhome" + else + uhome="${homeprefix}/${username}" + fi +} + +# get_uid +# Reads a numeric userid in an interactive or batch session. Automatically +# allocates one if it is not specified. +# +get_uid() { + uuid=${uidstart} + _input= + _prompt= + + # No need to take down uids for a configuration saving run. + [ -n "$configflag" ] && return + + if [ -n "$uuid" ]; then + _prompt="Uid [$uuid]: " + else + _prompt="Uid (Leave empty for default): " + fi + if [ -z "$fflag" ]; then + echo -n $_prompt + read _input + else + _input="`echo "$fileline" | cut -f2 -d:`" + fi + + [ -n "$_input" ] && uuid=$_input + uuid=`get_nextuid $uuid` + uidstart=$uuid +} + +# get_class +# Reads login class of account. Can be used in interactive or batch mode. +# +get_class() { + uclass="$defaultclass" + _input= + _class=${uclass:-"default"} + + if [ -z "$fflag" ]; then + echo -n "Login class [$_class]: " + read _input + else + _input="`echo "$fileline" | cut -f4 -d:`" + fi + + [ -n "$_input" ] && uclass="$_input" +} + +# get_logingroup +# Reads user's login group. Can be used in both interactive and batch +# modes. The specified value can be a group name or its numeric id. +# This routine leaves the field blank if nothing is provided. The pw(8) +# command will then provide a login group with the same name as the username. +# +get_logingroup() { + ulogingroup= + _input= + + # No need to take down a login group for a configuration saving run. + [ -n "$configflag" ] && return + + if [ -z "$fflag" ]; then + echo -n "Login group [$username]: " + read _input + else + _input="`echo "$fileline" | cut -f3 -d:`" + fi + + # Pw(8) will use the username as login group if it's left empty + [ -n "$_input" ] && ulogingroup="$_input" || ulogingroup= +} + +# get_groups +# Read additional groups for the user. It can be used in both interactive +# and batch modes. +# +get_groups() { + ugroups="$defaultgroups" + _input= + _group=${ulogingroup:-"${username}"} + + if [ -z "$configflag" ]; then + [ -z "$fflag" ] && echo -n "Login group is $_group. Invite $username" + [ -z "$fflag" ] && echo -n " into other groups? [$ugroups]: " + else + [ -z "$fflag" ] && echo -n "Enter additional groups [$ugroups]: " + fi + read _input + + [ -n "$_input" ] && ugroups="$_input" +} + +# get_expire_dates +# Read expiry information for the account and also for the password. This +# routine is used only from batch processing mode. +# +get_expire_dates() { + upwexpire="`echo "$fileline" | cut -f5 -d:`" + uexpire="`echo "$fileline" | cut -f6 -d:`" +} + +# get_password +# Read the password in batch processing mode. The password field matters +# only when the password type is "yes" or "random". If the field is empty and the +# password type is "yes", then it assumes the account has an empty passsword +# and changes the password type accordingly. If the password type is "random" +# and the password field is NOT empty, then it assumes the account will NOT +# have a random password and set passwdtype to "yes." +# +get_password() { + # We may temporarily change a password type. Make sure it's changed + # back to whatever it was before we process the next account. + # + [ -n "$savedpwtype" ] && { + passwdtype=$savedpwtype + savedpwtype= + } + + # There may be a ':' in the password + upass=${fileline#*:*:*:*:*:*:*:*:*:} + + if [ -z "$upass" ]; then + case $passwdtype in + yes) + # if it's empty, assume an empty password + passwdtype=none + savedpwtype=yes + ;; + esac + else + case $passwdtype in + random) + passwdtype=yes + savedpwtype=random + ;; + esac + fi +} + +# input_from_file +# Reads a line of account information from standard input and +# adds it to the user database. +# +input_from_file() { + _field= + + while read fileline ; do + case "$fileline" in + \#*|'') + return 0 + ;; + esac + + get_user || continue + get_gecos + get_uid + get_logingroup + get_class + get_shell + get_homedir + get_password + get_expire_dates + + add_user + done +} + +# input_interactive +# Prompts for user information interactively, and commits to +# the user database. +# +input_interactive() { + + _disable= + _pass= + _passconfirm= + _random="no" + _emptypass="no" + _usepass="yes" + case $passwdtype in + none) + _emptypass="yes" + _usepass="yes" + ;; + no) + _usepass="no" + ;; + random) + _random="yes" + ;; + esac + + get_user + get_gecos + get_uid + get_logingroup + get_groups + get_class + get_shell + get_homedir + + while : ; do + echo -n "Use password-based authentication? [$_usepass]: " + read _input + [ -z "$_input" ] && _input=$_usepass + case $_input in + [Nn][Oo]|[Nn]) + passwdtype="no" + ;; + [Yy][Ee][Ss]|[Yy][Ee]|[Yy]) + while : ; do + echo -n "Use an empty password? (yes/no) [$_emptypass]: " + read _input + [ -n "$_input" ] && _emptypass=$_input + case $_emptypass in + [Nn][Oo]|[Nn]) + echo -n "Use a random password? (yes/no) [$_random]: " + read _input + [ -n "$_input" ] && _random="$_input" + case $_random in + [Yy][Ee][Ss]|[Yy][Ee]|[Yy]) + passwdtype="random" + break + ;; + esac + passwdtype="yes" + trap 'stty echo; exit' 0 1 2 3 15 + stty -echo + echo -n "Enter password: " + read upass + echo'' + echo -n "Enter password again: " + read _passconfirm + echo '' + stty echo + # if user entered a blank password + # explicitly ask again. + [ -z "$upass" -a -z "$_passconfirm" ] \ + && continue + ;; + [Yy][Ee][Ss]|[Yy][Ee]|[Yy]) + passwdtype="none" + break; + ;; + *) + # invalid answer; repeat the loop + continue + ;; + esac + if [ "$upass" != "$_passconfirm" ]; then + echo "Passwords did not match!" + continue + fi + break + done + ;; + *) + # invalid answer; repeat loop + continue + ;; + esac + break; + done + _disable=${disableflag:-"no"} + while : ; do + echo -n "Lock out the account after creation? [$_disable]: " + read _input + [ -z "$_input" ] && _input=$_disable + case $_input in + [Nn][Oo]|[Nn]) + disableflag= + ;; + [Yy][Ee][Ss]|[Yy][Ee]|[Yy]) + disableflag=yes + ;; + *) + # invalid answer; repeat loop + continue + ;; + esac + break + done + + # Display the information we have so far and prompt to + # commit it. + # + _disable=${disableflag:-"no"} + [ -z "$configflag" ] && printf "%-10s : %s\n" Username $username + case $passwdtype in + yes) + _pass='*****' + ;; + no) + _pass='' + ;; + none) + _pass='' + ;; + random) + _pass='' + ;; + esac + printf "%-10s : %s\n" "Password" "$_pass" + [ -z "$configflag" ] && printf "%-10s : %s\n" "Full Name" "$ugecos" + [ -z "$configflag" ] && printf "%-10s : %s\n" "Uid" "$uuid" + printf "%-10s : %s\n" "Class" "$uclass" + [ -z "$configflag" ] && printf "%-10s : %s %s\n" "Groups" "${ulogingroup:-$username}" "$ugroups" + printf "%-10s : %s\n" "Home" "$uhome" + printf "%-10s : %s\n" "Shell" "$ushell" + printf "%-10s : %s\n" "Locked" "$_disable" + while : ; do + echo -n "OK? (yes/no): " + read _input + case $_input in + [Nn][Oo]|[Nn]) + return 1 + ;; + [Yy][Ee][Ss]|[Yy][Ee]|[Yy]) + add_user + ;; + *) + continue + ;; + esac + break + done + return 0 +} + +#### END SUBROUTINE DEFENITION #### + +THISCMD=`/usr/bin/basename $0` +DEFAULTSHELL=/bin/sh +ADDUSERCONF="${ADDUSERCONF:-/etc/adduser.conf}" +PWCMD="${PWCMD:-/usr/sbin/pw}" +MAILCMD="${MAILCMD:-mail}" +ETCSHELLS="${ETCSHELLS:-/etc/shells}" + +# Set default values +# +username= +uuid= +uidstart= +ugecos= +ulogingroup= +uclass= +uhome= +upass= +ushell= +udotdir=/usr/share/skel +ugroups= +uexpire= +upwexpire= +shells="`valid_shells`" +passwdtype="yes" +msgfile=/etc/adduser.msg +msgflag= +quietflag= +configflag= +fflag= +infile= +disableflag= +readconfig="yes" +homeprefix="/home" +randompass= +fileline= +savedpwtype= +defaultclass= +defaultgoups= +defaultshell="${DEFAULTSHELL}" + +# Make sure the user running this program is root. This isn't a security +# measure as much as it is a usefull method of reminding the user to +# 'su -' before he/she wastes time entering data that won't be saved. +# +procowner=${procowner:-`/usr/bin/id -u`} +if [ "$procowner" != "0" ]; then + err 'you must be the super-user (uid 0) to use this utility.' + exit 1 +fi + +# Overide from our conf file +# Quickly go through the commandline line to see if we should read +# from our configuration file. The actual parsing of the commandline +# arguments happens after we read in our configuration file (commandline +# should override configuration file). +# +for _i in $* ; do + if [ "$_i" = "-N" ]; then + readconfig= + break; + fi +done +if [ -n "$readconfig" ]; then + # On a long-lived system, the first time this script is run it + # will barf upon reading the configuration file for its perl predecessor. + if ( . ${ADDUSERCONF} > /dev/null 2>&1 ); then + [ -r ${ADDUSERCONF} ] && . ${ADDUSERCONF} > /dev/null 2>&1 + fi +fi + +# Proccess command-line options +# +for _switch ; do + case $_switch in + -L) + defaultclass="$2" + shift; shift + ;; + -C) + configflag=yes + shift + ;; + -E) + disableflag=yes + shift + ;; + -k) + udotdir="$2" + shift; shift + ;; + -f) + [ "$2" != "-" ] && infile="$2" + fflag=yes + shift; shift + ;; + -G) + defaultgroups="$2" + shift; shift + ;; + -h) + show_usage + exit 0 + ;; + -d) + homeprefix="$2" + shift; shift + ;; + -m) + case "$2" in + [Nn][Oo]) + msgflag= + ;; + *) + msgflag=yes + msgfile="$2" + ;; + esac + shift; shift + ;; + -N) + readconfig= + shift + ;; + -w) + case "$2" in + no|none|random|yes) + passwdtype=$2 + ;; + *) + show_usage + exit 1 + ;; + esac + shift; shift + ;; + -q) + quietflag=yes + shift + ;; + -s) + defaultshell="`fullpath_from_shell $2`" + shift; shift + ;; + -u) + uidstart=$2 + shift; shift + ;; + esac +done + +# If the -f switch was used, get input from a file. Otherwise, +# this is an interactive session. +# +if [ -n "$fflag" ]; then + if [ -z "$infile" ]; then + input_from_file + elif [ -n "$infile" ]; then + if [ -r "$infile" ]; then + input_from_file < $infile + else + err "File ($infile) is unreadable or does not exist." + fi + fi +else + input_interactive +fi diff --git a/adduser/rmuser.8 b/adduser/rmuser.8 index 2e55694..85d6657 100644 --- a/adduser/rmuser.8 +++ b/adduser/rmuser.8 @@ -26,7 +26,7 @@ .\" .\" $FreeBSD$ .\" -.Dd February 23, 1997 +.Dd May 10, 2002 .Dt RMUSER 8 .Os .Sh NAME @@ -35,11 +35,13 @@ .Sh SYNOPSIS .Nm .Op Fl y -.Op Ar username +.Op Fl f Ar file +.Op Ar username ... .Sh DESCRIPTION The .Nm -utility +utility removes one or more users submitted on the command line +or from a file. In removing a user from the system, this utility .Pp .Bl -enum .It @@ -77,29 +79,27 @@ the group is removed; this complements per-user unique groups). .El .Pp -The +The .Nm -utility -politely refuses to remove users whose uid is 0 (typically root), since +utility refuses to remove users whose uid is 0 (typically root), since certain actions (namely, killing all the user's processes, and perhaps removing the user's home directory) would cause damage to a running system. If it is necessary to remove a user whose uid is 0, see .Xr vipw 8 -for information on directly editing the password file, by which the desired -user's -.Xr passwd 5 -entry may be removed manually. +for information on directly editing the password file .Pp -If not running "affirmatively" (i.e., option -.Fl y -is not specified), -.Nm -shows the selected user's password file entry and asks for confirmation -that you wish to remove the user. If the user's home directory is owned -by the user, +If .Nm -asks whether you wish to remove the user's home directory and everything -below. +was not invoked with the +.Fl y +switch it will +show the selected user's password file entry and ask for confirmation +that the user be removed. It will then ask for confirmation to delete +the user's home directory. If the answer is in the affirmative, the home +directory and any files and subdirectories under it will be deleted only if +they are owned by the user. See +.Xr pw 8 +for more details. .Pp As .Nm @@ -112,13 +112,27 @@ Available options: .Pp .Bl -tag -width username .It Fl y -Affirm - any question that would be asked is answered implicitly in -the affirmative (i.e., yes). A username must also be specified on the -command line if this option is used. +Implicitly answer "yes" to any and all prompts. Currently this includes +prompts on whether to remove the specified user and whether to remove +the home directory. This option requires that either the +.Fl f +option be used or one or more user names be given as commmand line +arguments. +.It Fl f +The +.Nm +utility will get a list of users to be removed from +.Ar file , +which will contain one user per line. Anything following a hash mark (#), +including the hash mark itself, is considered a comment and will not +be processed. If the file is owned by anyone other than a user with +uid 0 or is writeable by anyone other than the owner +.Nm +will refuse to continue. .It Ar \&username -Identifies the user to be removed; if not present, +Identifies one or more users to be removed; if not present, .Nm -interactively asks for the user to be removed. +interactively asks for one or more users to be removed. .El .Sh FILES .Bl -tag -width /etc/master.passwd -compact @@ -137,12 +151,13 @@ interactively asks for the user to be removed. .Xr group 5 , .Xr passwd 5 , .Xr adduser 8 , +.Xr pw 8 , .Xr pwd_mkdb 8 , .Xr vipw 8 .Sh HISTORY The .Nm -utility appeared in +command appeared in .Fx 2.2 . .\" .Sh AUTHOR .\" Guy Helmer, Ames, Iowa @@ -152,9 +167,7 @@ The utility does not comprehensively search the filesystem for all files owned by the removed user and remove them; to do so on a system of any size is prohibitively slow and I/O intensive. -The -.Nm -utility also is unable to remove symbolic links that were created by the +It is also unable to remove symbolic links that were created by the user in .Pa /tmp or diff --git a/adduser/rmuser.perl b/adduser/rmuser.perl deleted file mode 100644 index 1ffac08..0000000 --- a/adduser/rmuser.perl +++ /dev/null @@ -1,601 +0,0 @@ -#!/usr/bin/perl -# -*- perl -*- -# Copyright 1995, 1996, 1997 Guy Helmer, Ames, Iowa 50014. -# 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 as -# the first lines of this file unmodified. -# 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. 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 GUY HELMER ``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 GUY HELMER 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. -# -# rmuser - Perl script to remove users -# -# Guy Helmer , 02/23/97 -# -# $FreeBSD$ - -use Fcntl; - -sub LOCK_SH {0x01;} -sub LOCK_EX {0x02;} -sub LOCK_NB {0x04;} -sub LOCK_UN {0x08;} -sub F_SETFD {2;} - -$ENV{"PATH"} = "/bin:/sbin:/usr/bin:/usr/sbin"; -umask(022); -$whoami = $0; -$passwd_file = "/etc/master.passwd"; -$passwd_tmp = "/etc/ptmp"; -$group_file = "/etc/group"; -$new_group_file = "${group_file}.new.$$"; -$mail_dir = "/var/mail"; -$crontab_dir = "/var/cron/tabs"; -$affirm = 0; - -#$debug = 1; - -sub cleanup { - local($sig) = @_; - - print STDERR "Caught signal SIG$sig -- cleaning up.\n"; - &unlockpw; - if (-e $new_passwd_file) { - unlink $new_passwd_file; - } - exit(0); -} - -sub lockpw { - # Open the password file for reading - if (!open(MASTER_PW, "$passwd_file")) { - print STDERR "${whoami}: Error: Couldn't open ${passwd_file}: $!\n"; - exit(1); - } - # Set the close-on-exec flag just in case - fcntl(MASTER_PW, &F_SETFD, 1); - # Apply an advisory lock the password file - if (!flock(MASTER_PW, &LOCK_EX|&LOCK_NB)) { - print STDERR "${whoami}: Error: Couldn't lock ${passwd_file}: $!\n"; - exit(1); - } -} - -sub unlockpw { - flock(MASTER_PW, &LOCK_UN); -} - -$SIG{'INT'} = 'cleanup'; -$SIG{'QUIT'} = 'cleanup'; -$SIG{'HUP'} = 'cleanup'; -$SIG{'TERM'} = 'cleanup'; - -if ($#ARGV == 1 && $ARGV[0] eq '-y') { - shift @ARGV; - $affirm = 1; -} - -if ($#ARGV > 0) { - print STDERR "usage: ${whoami} [-y] [username]\n"; - exit(1); -} - -if ($< != 0) { - print STDERR "${whoami}: Error: you must be root to use ${whoami}\n"; - exit(1); -} - -&lockpw; - -if ($#ARGV == 0) { - # Username was given as a parameter - $login_name = pop(@ARGV); -} else { - if ($affirm) { - print STDERR "${whoami}: Error: -y option given without username!\n"; - &unlockpw; - exit 1; - } - # Get the user name from the user - $login_name = &get_login_name; -} - -($name, $password, $uid, $gid, $change, $class, $gecos, $home_dir, $shell) = - (getpwnam("$login_name")); - -if (!defined $uid) { - print STDERR "${whoami}: Error: User ${login_name} not in password database\n"; - &unlockpw; - exit 1; -} - -if ($uid == 0) { - print "${whoami}: Error: I'd rather not remove a user with a uid of 0.\n"; - &unlockpw; - exit 1; -} - -if (! $affirm) { - print "Matching password entry:\n\n$name\:$password\:$uid\:$gid\:$class\:$change\:0\:$gecos\:$home_dir\:$shell\n\n"; - - $ans = &get_yn("Is this the entry you wish to remove? "); - - if ($ans eq 'N') { - print "${whoami}: Informational: User ${login_name} not removed.\n"; - &unlockpw; - exit 0; - } -} - -# -# Get owner of user's home directory; don't remove home dir if not -# owned by $login_name - -$remove_directory = 1; - -if (-l $home_dir) { - $real_home_dir = &resolvelink($home_dir); -} else { - $real_home_dir = $home_dir; -} - -# -# If home_dir is a symlink and points to something that isn't a directory, -# or if home_dir is not a symlink and is not a directory, don't remove -# home_dir -- seems like a good thing to do, but probably isn't necessary... - -if (((-l $home_dir) && ((-e $real_home_dir) && !(-d $real_home_dir))) || - (!(-l $home_dir) && !(-d $home_dir))) { - print STDERR "${whoami}: Informational: Home ${home_dir} is not a directory, so it won't be removed\n"; - $remove_directory = 0; -} - -if (length($real_home_dir) && -d $real_home_dir) { - $dir_owner = (stat($real_home_dir))[4]; # UID - if ($dir_owner != $uid) { - print STDERR "${whoami}: Informational: Home dir ${real_home_dir} is" . - " not owned by ${login_name} (uid ${dir_owner})\n," . - "\tso it won't be removed\n"; - $remove_directory = 0; - } -} - -if ($remove_directory && ! $affirm) { - $ans = &get_yn("Remove user's home directory ($home_dir)? "); - if ($ans eq 'N') { - $remove_directory = 0; - } -} - -#exit 0 if $debug; - -# -# Remove the user's crontab, if there is one -# (probably needs to be done before password databases are updated) - -if (-e "$crontab_dir/$login_name") { - print STDERR "Removing user's crontab:"; - system('/usr/bin/crontab', '-u', $login_name, '-r'); - print STDERR " done.\n"; -} - -# -# Remove the user's at jobs, if any -# (probably also needs to be done before password databases are updated) - -&remove_at_jobs($login_name); - -# -# Kill all the user's processes - -&kill_users_processes($login_name, $uid); - -# -# Copy master password file to new file less removed user's entry - -&update_passwd_file; - -# -# Remove the user from all groups in /etc/group - -&update_group_file($login_name); - -# -# Remove the user's home directory - -if ($remove_directory) { - print STDERR "Removing user's home directory ($home_dir):"; - &remove_dir($home_dir); - print STDERR " done.\n"; -} - -# -# Remove files related to the user from the mail directory - -#&remove_files_from_dir($mail_dir, $login_name, $uid); -$file = "$mail_dir/$login_name"; -if (-e $file || -l $file) { - print STDERR "Removing user's incoming mail file ${file}:"; - unlink $file || - print STDERR "\n${whoami}: Warning: unlink on $file failed ($!) - continuing\n"; - print STDERR " done.\n"; -} - -# -# Remove some pop daemon's leftover file - -$file = "$mail_dir/.${login_name}.pop"; -if (-e $file || -l $file) { - print STDERR "Removing pop daemon's temporary mail file ${file}:"; - unlink $file || - print STDERR "\n${whoami}: Warning: unlink on $file failed ($!) - continuing\n"; - print STDERR " done.\n"; -} - -# -# Remove files belonging to the user from the directories /tmp, /var/tmp, -# and /var/tmp/vi.recover. Note that this doesn't take care of the -# problem where a user may have directories or symbolic links in those -# directories -- only regular files are removed. - -&remove_files_from_dir('/tmp', $login_name, $uid); -&remove_files_from_dir('/var/tmp', $login_name, $uid); -&remove_files_from_dir('/var/tmp/vi.recover', $login_name, $uid) - if (-e '/var/tmp/vi.recover'); - -# -# All done! - -exit 0; - -sub get_login_name { - # - # Get new user's name - local($done, $login_name); - - for ($done = 0; ! $done; ) { - print "Enter login name for user to remove: "; - $login_name = <>; - chomp $login_name; - if (not getpwnam("$login_name")) { - print STDERR "Sorry, login name not in password database.\n"; - } else { - $done = 1; - } - } - - print "User name is ${login_name}\n" if $debug; - return($login_name); -} - -sub get_yn { - # - # Get a yes or no answer; return 'Y' or 'N' - local($prompt) = @_; - local($done, $ans); - - for ($done = 0; ! $done; ) { - print $prompt; - $ans = <>; - chop $ans; - $ans =~ tr/a-z/A-Z/; - if (!($ans =~ /^[YN]/)) { - print STDERR "Please answer (y)es or (n)o.\n"; - } else { - $done = 1; - } - } - - return(substr($ans, 0, 1)); -} - -sub update_passwd_file { - local($skipped); - - print STDERR "Updating password file,"; - seek(MASTER_PW, 0, 0); - - sysopen(NEW_PW, $passwd_tmp, O_RDWR|O_CREAT|O_EXCL, 0600) || - die "\n${whoami}: Error: Couldn't open file ${passwd_tmp}:\n $!\n"; - - $skipped = 0; - while () { - if (/^\Q$login_name:/o) { - print STDERR "Dropped entry for $login_name\n" if $debug; - $skipped = 1; - } else { - print NEW_PW; - # The other perl password tools assume all lowercase entries. - # Add a warning to help unsuspecting admins who might be - # using the wrong tool for the job, or might otherwise - # be unwittingly holding a loaded foot-shooting device. - if (/^\Q$login_name:/io) { - my $name = $_; - $name =~ s#\:.*\n##; - print STDERR "\n\n\tThere is also an entry for $name in your", - "password file.\n\tThis can cause problems in some ", - "situations.\n\n"; - } - } - } - close(NEW_PW); - seek(MASTER_PW, 0, 0); - - if ($skipped == 0) { - print STDERR "\n${whoami}: Whoops! Didn't find ${login_name}'s entry second time around!\n"; - unlink($passwd_tmp) || - print STDERR "\n${whoami}: Warning: couldn't unlink $passwd_tmp ($!)\n\tPlease investigate, as this file should not be left in the filesystem\n"; - &unlockpw; - exit 1; - } - - # - # Run pwd_mkdb to install the updated password files and databases - - print STDERR " updating databases,"; - system('/usr/sbin/pwd_mkdb', '-p', ${passwd_tmp}); - print STDERR " done.\n"; - - close(MASTER_PW); # Not useful anymore -} - -sub update_group_file { - local($login_name) = @_; - - local($i, $j, $grmember_list, $new_grent, $changes); - local($grname, $grpass, $grgid, $grmember_list, @grmembers); - - $changes = 0; - print STDERR "Updating group file:"; - open(GROUP, $group_file) || - die "\n${whoami}: Error: couldn't open ${group_file}: $!\n"; - if (!flock(GROUP, &LOCK_EX|&LOCK_NB)) { - print STDERR "\n${whoami}: Error: couldn't lock ${group_file}: $!\n"; - exit 1; - } - local($group_perms, $group_uid, $group_gid) = - (stat(GROUP))[2, 4, 5]; # File Mode, uid, gid - open(NEW_GROUP, ">$new_group_file") || - die "\n${whoami}: Error: couldn't open ${new_group_file}: $!\n"; - chmod($group_perms, $new_group_file) || - printf STDERR "\n${whoami}: Warning: could not set permissions of new group file to %o ($!)\n\tContinuing, but please check permissions of $group_file!\n", $group_perms; - chown($group_uid, $group_gid, $new_group_file) || - print STDERR "\n${whoami}: Warning: could not set owner/group of new group file to ${group_uid}/${group_gid} ($!)\n\rContinuing, but please check ownership of $group_file!\n"; - while ($i = ) { - if (!($i =~ /\Q$login_name\E/)) { - # Line doesn't contain any references to the user, so just add it - # to the new file - print NEW_GROUP $i; - } else { - # - # Remove the user from the group - if ($i =~ /\n$/) { - chop $i; - } - ($grname, $grpass, $grgid, $grmember_list) = split(/:/, $i); - @grmembers = split(/,/, $grmember_list); - undef @new_grmembers; - local(@new_grmembers); - foreach $j (@grmembers) { - if ($j ne $login_name) { - push(@new_grmembers, $j); - } else { - print STDERR " $grname"; - $changes = 1; - } - } - if ($grname eq $login_name && $#new_grmembers == -1) { - # Remove a user's personal group if empty - print STDERR " (removing group $grname -- personal group is empty)"; - $changes = 1; - } else { - $grmember_list = join(',', @new_grmembers); - $new_grent = join(':', $grname, $grpass, $grgid, $grmember_list); - print NEW_GROUP "$new_grent\n"; - } - } - } - close(NEW_GROUP); - rename($new_group_file, $group_file) || # Replace old group file with new - die "\n${whoami}: Error: couldn't rename $new_group_file to $group_file ($!)\n"; - close(GROUP); # File handle is worthless now - print STDERR " (no changes)" if (! $changes); - print STDERR " done.\n"; -} - -sub remove_dir { - # Remove the user's home directory - local($dir) = @_; - local($linkdir); - - if (-l $dir) { - $linkdir = &resolvelink($dir); - # Remove the symbolic link - unlink($dir) || - warn "${whoami}: Warning: could not unlink symlink $dir: $!\n"; - if (!(-e $linkdir)) { - # - # Dangling symlink - just return now - return; - } - # Set dir to be the resolved pathname - $dir = $linkdir; - } - if (!(-d $dir)) { - print STDERR "${whoami}: Warning: $dir is not a directory\n"; - unlink($dir) || warn "${whoami}: Warning: could not unlink $dir: $!\n"; - return; - } - system('/bin/rm', '-rf', $dir); -} - -sub remove_files_from_dir { - local($dir, $login_name, $uid) = @_; - local($path, $i, $owner); - - print STDERR "Removing files belonging to ${login_name} from ${dir}:"; - - if (!opendir(DELDIR, $dir)) { - print STDERR "\n${whoami}: Warning: couldn't open directory ${dir} ($!)\n"; - return; - } - while ($i = readdir(DELDIR)) { - next if $i eq '.'; - next if $i eq '..'; - - $owner = (stat("$dir/$i"))[4]; # UID - if ($uid == $owner) { - if (-f "$dir/$i") { - print STDERR " $i"; - unlink "$dir/$i" || - print STDERR "\n${whoami}: Warning: unlink on ${dir}/${i} failed ($!) - continuing\n"; - } else { - print STDERR " ($i not a regular file - skipped)"; - } - } - } - closedir(DELDIR); - - printf STDERR " done.\n"; -} - - -sub invoke_atq { - local *ATQ; - my($user) = (shift || ""); - my($path_atq) = "/usr/bin/atq"; - my(@at) = (); - my($pid, $line); - - return @at if ($user eq ""); - - if (!defined($pid = open(ATQ, "-|"))) { - die("creating pipe to atq: $!\n"); - } elsif ($pid == 0) { - exec($path_atq, $user); - die("executing $path_atq: $!\n"); - } - - while(defined($_ = )) { - chomp; - if (/^\d\d.\d\d.\d\d\s+\d\d.\d\d.\d\d\s+(\S+)\s+\S+\s+(\d+)$/) { - push(@at, $2) if ($1 eq $user); - } - } - close ATQ; - return @at; -} - -sub invoke_atrm { - local *ATRM; - my($user) = (shift || ""); - my($path_atrm) = "/usr/bin/atrm"; - my(@jobs) = @_; - my($pid); - my($txt) = ""; - - return "Invalid arguments" if (($user eq "") || ($#jobs == -1)); - - if (!defined($pid = open(ATRM, "-|"))) { - die("creating pipe to atrm: $!\n"); - } elsif ($pid == 0) { - exec($path_atrm, $user, @jobs); - } - - while(defined($_ = )) { - $txt .= $_; - } - close ATRM; - return $txt; -} - -sub remove_at_jobs { - my($user) = (shift || ""); - my(@at, $atrm); - - return 1 if ($user eq ""); - - @at = invoke_atq($user); - return 0 if ($#at == -1); - - print STDERR "Removing user's at jobs:"; - print STDERR " @at:"; - $atrm = invoke_atrm($user, @at); - if ($atrm ne "") { - print STDERR " -- $atrm\n"; - return 1; - } - - print STDERR " done.\n"; - return 0; -} - -sub resolvelink { - local($path) = @_; - local($l); - - while (-l $path && -e $path) { - if (!defined($l = readlink($path))) { - die "${whoami}: readlink on $path failed (but it should have worked!): $!\n"; - } - if ($l =~ /^\//) { - # Absolute link - $path = $l; - } else { - # Relative link - $path =~ s/\/[^\/]+\/?$/\/$l/; # Replace last component of path - } - } - return $path; -} - -sub kill_users_processes { - local($login_name, $uid) = @_; - local($pid, $result); - - # - # Do something a little complex: fork a child that changes its - # real and effective UID to that of the removed user, then issues - # a "kill(9, -1)" to kill all processes of the same uid as the sender - # (see kill(2) for details). - # The parent waits for the exit of the child and then returns. - - if ($pid = fork) { - # Parent process - waitpid($pid, 0); - } elsif (defined $pid) { - # Child process - $< = $uid; - $> = $uid; - if ($< != $uid || $> != $uid) { - print STDERR "${whoami}: Error (kill_users_processes):\n" . - "\tCouldn't reset uid/euid to ${uid}: current uid/euid's are $< and $>\n"; - exit 1; - } - $result = kill(9, -1); - print STDERR "Killed process(es) belonging to $login_name.\n" - if $result; - exit 0; - } else { - # Couldn't fork! - print STDERR "${whoami}: Error: couldn't fork to kill ${login_name}'s processes - continuing\n"; - } -} diff --git a/adduser/rmuser.sh b/adduser/rmuser.sh new file mode 100644 index 0000000..afc978c --- /dev/null +++ b/adduser/rmuser.sh @@ -0,0 +1,325 @@ +#!/bin/sh +# +# Copyright (c) 2002 Michael Telahun Makonnen. 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. 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. +# +# Email: Mike Makonnen +# +# $FreeBSD$ +# + +ATJOBDIR="/var/at/jobs" +CRONJOBDIR="/var/cron/tabs" +MAILSPOOL="/var/mail" +SIGKILL="-KILL" +TEMPDIRS="/tmp /var/tmp /var/tmp/vi.recover" +THISCMD=`/usr/bin/basename $0` + +# err msg +# Display $msg on stderr. +# +err() { + echo 1>&2 ${THISCMD}: $* +} + +# rm_files login +# Removes files or empty directories belonging to $login from various +# temporary directories. +# +rm_files() { + # The argument is required + [ -n $1 ] && login=$1 || return + + for _dir in ${TEMPDIRS} ; do + if [ ! -d $_dir ]; then + err "$_dir is not a valid directory." + continue + fi + echo -n "Removing files owned by ($login) in $_dir:" + filecount=0 + _ownedfiles=`find 2>/dev/null $_dir -maxdepth 1 -user $login -print` + for _file in $_ownedfiles ; do + rm -fd $_file + filecount=`expr $filecount + 1` + done + echo " $filecount removed." + done +} + +# rm_mail login +# Removes unix mail and pop daemon files belonging to the user +# specified in the $login argument. +# +rm_mail() { + # The argument is required + [ -n $1 ] && login=$1 || return + + echo -n "Removing mail spool(s) for ($login):" + if [ -f ${MAILSPOOL}/$login ]; then + echo -n " ${MAILSPOOL}/$login" + rm ${MAILSPOOL}/$login + fi + if [ -f ${MAILSPOOL}/${login}.pop ]; then + echo -n " ${MAILSPOOL}/${login}.pop" + rm ${MAILSPOOL}/${login}.pop + fi + echo '.' +} + +# kill_procs login +# Send a SIGKILL to all processes owned by $login. +# +kill_procs() { + # The argument is required + [ -n $1 ] && login=$1 || return + + echo -n "Terminating all processes owned by ($login):" + killcount=0 + proclist=`ps 2>/dev/null -U $login | grep -v '^\ *PID' | awk '{print $1}'` + for _pid in $proclist ; do + kill 2>/dev/null ${SIGKILL} $_pid + killcount=`expr $killcount + 1` + done + echo " ${SIGKILL} signal sent to $killcount processes." +} + +# rm_at_jobs login +# Remove at (1) jobs belonging to $login. +# +rm_at_jobs() { + # The argument is required + [ -n $1 ] && login=$1 || return + + atjoblist=`find 2>/dev/null ${ATJOBDIR} -maxdepth 1 -user $login -print` + jobcount=0 + echo -n "Removing at(1) jobs owned by ($login):" + for _atjob in $atjoblist ; do + rm -f $_atjob + jobcount=`expr $jobcount + 1` + done + echo " $jobcount removed." +} + +# rm_crontab login +# Removes crontab file belonging to user $login. +# +rm_crontab() { + # The argument is required + [ -n $1 ] && login=$1 || return + + echo -n "Removing crontab for ($login):" + if [ -f ${CRONJOBDIR}/$login ]; then + echo -n " ${CRONJOBDIR}/$login" + rm -f ${CRONJOBDIR}/$login + fi + echo '.' +} + +# rm_user login +# Remove user $login from the system. This subroutine makes use +# of the pw(8) command to remove a user from the system. The pw(8) +# command will remove the specified user from the user database +# and group file and remove any crontabs. His home +# directory will be removed if it is owned by him and contains no +# files or subdirectories owned by other users. Mail spool files will +# also be removed. +# +rm_user() { + # The argument is required + [ -n $1 ] && login=$1 || return + + echo -n "Removing user ($login)" + [ -n "$pw_rswitch" ] && echo -n " (including home directory)" + echo -n " from the system:" + pw userdel -n $login $pw_rswitch + echo ' Done.' +} + +# prompt_yesno msg +# Prompts the user with a $msg. The answer is expected to be +# yes, no, or some variation thereof. This subroutine returns 0 +# if the answer was yes, 1 if it was not. +# +prompt_yesno() { + # The argument is required + [ -n "$1" ] && msg=$1 || return + + while : ; do + echo -n $msg + read _ans + case $_ans in + [Nn][Oo]|[Nn]) + return 1 + ;; + [Yy][Ee][Ss]|[Yy][Ee]|[Yy]) + return 0 + ;; + *) + ;; + esac + done +} + +# show_usage +# (no arguments) +# Display usage message. +# +show_usage() { + echo "usage: ${THISCMD} [-y] [-f file] [user ...]" + echo " if the -y switch is used, either the -f switch or" + echo " one or more user names must be given" +} + +#### END SUBROUTINE DEFENITION #### + +ffile= +fflag= +procowner= +pw_rswitch= +userlist= +yflag= + +procowner=`/usr/bin/id -u` +if [ "$procowner" != "0" ]; then + err 'you must be root (0) to use this utility.' + exit 1 +fi + +args=`getopt 2>/dev/null yf: $*` +if [ "$?" != "0" ]; then + show_usage + exit 1 +fi +set -- $args +for _switch ; do + case $_switch in + -y) + yflag=1 + shift + ;; + -f) + fflag=1 + ffile="$2" + shift; shift + ;; + --) + shift + break + ;; + esac +done + +# Get user names from a file if the -f switch was used. Otherwise, +# get them from the commandline arguments. If we're getting it +# from a file, the file must be owned by and writable only by root. +# +if [ $fflag ]; then + _insecure=`find $ffile ! -user 0 -or -perm +0022` + if [ -n "$_insecure" ]; then + err "file ($ffile) must be owned by and writeable only by root." + exit 1 + fi + if [ -r "$ffile" ]; then + userlist=`cat $ffile | while read _user _junk ; do + case $_user in + \#*|'') + ;; + *) + echo -n "$userlist $_user" + ;; + esac + done` + fi +else + while [ $1 ] ; do + userlist="$userlist $1" + shift + done +fi + +# If the -y or -f switch has been used and the list of users to remove +# is empty it is a fatal error. Otherwise, prompt the user for a list +# of one or more user names. +# +if [ ! "$userlist" ]; then + if [ $fflag ]; then + err "($ffile) does not exist or does not contain any user names." + exit 1 + elif [ $yflag ]; then + show_usage + exit 1 + else + echo -n "Please enter one or more user name's: " + read userlist + fi +fi + +_user= +_uid= +for _user in $userlist ; do + # Make sure the name exists in the passwd database and that it + # does not have a uid of 0 + # + userrec=`pw 2>/dev/null usershow -n $_user` + if [ "$?" != "0" ]; then + err "user ($_user) does not exist in the password database." + continue + fi + _uid=`echo $userrec | awk -F: '{print $3}'` + if [ "$_uid" = "0" ]; then + err "user ($_user) has uid 0. You may not remove this user." + continue + fi + + # If the -y switch was not used ask for confirmation to remove the + # user and home directory. + # + if [ -z "$yflag" ]; then + echo "Matching password entry:" + echo + echo $userrec + echo + if ! prompt_yesno "Is this the entry you wish to remove? " ; then + continue + fi + _homedir=`echo $userrec | awk -F: '{print $9}'` + if prompt_yesno "Remove user's home directory ($_homedir)?: "; then + pw_rswitch="-r" + fi + else + pw_rswitch="-r" + fi + + # Disable any further attempts to log into this account + pw 2>/dev/null lock $_user + + # Remove crontab, mail spool, etc. Then obliterate the user from + # the passwd and group database. + rm_crontab $_user + rm_at_jobs $_user + kill_procs $_user + rm_mail $_user + rm_files $_user + rm_user $_user +done -- cgit v1.2.3-56-ge451