X-Git-Url: https://git.cameronkatri.com/pw-darwin.git/blobdiff_plain/52b26e50a976ca1ddf479205bb91b1b18f6d4e13..283922b55988102d3f82f638919bb6d03004245d:/adduser/adduser.perl diff --git a/adduser/adduser.perl b/adduser/adduser.perl index 8dcdaf6..322bac6 100644 --- a/adduser/adduser.perl +++ b/adduser/adduser.perl @@ -24,13 +24,17 @@ # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF # SUCH DAMAGE. # -# $Id: adduser.perl,v 1.28 1997/03/09 12:21:45 wosch Exp $ +# $FreeBSD$ # read variables sub variables { $verbose = 1; # verbose = [0-2] - $defaultpasswd = "yes"; # use password for new users + $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 @@ -42,7 +46,7 @@ sub variables { $etc_shells = "/etc/shells"; $etc_passwd = "/etc/master.passwd"; $group = "/etc/group"; - $pwd_mkdb = "pwd_mkdb -p"; # program for building passwd database + @pwd_mkdb = qw(pwd_mkdb -p); # program for building passwd database # List of directories where shells located @@ -65,27 +69,19 @@ sub variables { $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 - # only for me (=Wolfram) - if ($test) { - $home = "/home/w/tmp/adduser/home"; - $etc_shells = "./shells"; - $etc_passwd = "./master.passwd"; - $group = "./group"; - $pwd_mkdb = "pwd_mkdb -p -d ."; - $config = "adduser.conf"; - $send_message = "./adduser.message"; - $logfile = "./log.adduser"; - } - umask 022; # don't give login group write access $ENV{'PATH'} = "/sbin:/bin:/usr/sbin:/usr/bin"; @@ -117,13 +113,13 @@ sub shells_read { } } -# Allow /nonexistent and /bin/date as a valid shell for system utils + # Allow /nonexistent and /bin/date as a valid shell for system utils push(@list, "/nonexistent"); - push(@shellpref, "no"); + push(@shellpref, "no") if !grep(/^no$/, @shellpref); $shell{"no"} = "/nonexistent"; push(@list, "/bin/date"); - push(@shellpref, "date"); + push(@shellpref, "date") if !grep(/^date$/, @shellpref); $shell{"date"} = "/bin/date"; return $err; @@ -185,7 +181,7 @@ sub shell_default_valid { return $s; } -# return default home partition (f.e. "/home") +# return default home partition (e.g. "/home") # create base directory if nesseccary sub home_partition { local($home) = @_; @@ -232,7 +228,7 @@ sub home_partition_valid { # check for valid passwddb sub passwd_check { - system("$pwd_mkdb -c $etc_passwd"); + system(@pwd_mkdb, '-C', $etc_passwd); die "\nInvalid $etc_passwd - cannot add any users!\n" if $?; } @@ -241,7 +237,7 @@ 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 "$passwd: $!\n"; + open(P, "$etc_passwd") || die "$etc_passwd: $!\n"; while(<P>) { chop; @@ -261,7 +257,7 @@ sub passwd_read { if ($verbose && $sh && !$shell{&basename($sh)} && $p_username !~ /^(news|xten|bin|nobody|uucp)$/ && - $sh !~ /\/(pppd|sliplogin)$/); + $sh !~ /\/(pppd|sliplogin|nologin|nonexistent)$/); $uid{$p_uid} = $p_username; $pwgid{$p_gid} = $p_username; } @@ -277,9 +273,13 @@ sub group_read { while(<G>) { chop; push(@group_backup, $_); - # ignore comments + # Ignore empty lines next if /^\s*$/; - next if /^\s*#/; + # Save comments to restore later + if (/^\s*\#/) { + push(@group_comments, $_); + next; + } ($g_groupname, $pw, $g_gid, $memb) = (split(/:/, $_))[0..3]; @@ -316,12 +316,8 @@ sub new_users_name { local($name); while(1) { - $name = &confirm_list("Enter username", 1, "a-z0-9_-", ""); - if (length($name) > 16) { - warn "Username is longer than 16 chars\a\n"; - next; - } - last if (&new_users_name_valid($name) eq $name); + $name = &confirm_list("Enter username", 1, $usernameregexp, ""); + last if (&new_users_name_valid($name)); } return $name; } @@ -329,14 +325,29 @@ sub new_users_name { sub new_users_name_valid { local($name) = @_; - if ($name !~ /^[a-z0-9_][a-z0-9_\-]*$/) { - warn "Wrong username. " . - "Please use only lowercase characters or digits\a\n"; + if ($name eq $usernameregexp) { # user/admin just pressed <Return> + 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 ($username{$name}) { + } 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 $name; + return 1; } # return full name @@ -369,6 +380,20 @@ sub new_users_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) = @_; @@ -381,12 +406,15 @@ sub new_users_id { ! $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; + # return ($u_id_tmp, $g_id) if $u_id_tmp eq $u_id; # recalculate gid $uid_start = $u_id_tmp; return &next_id($name); @@ -397,7 +425,8 @@ sub new_users_class { local($def) = @_; local($class); - $class = &confirm_list("Enter login class", 1, $def, ""); + $class = &confirm_list("Enter login class:", 1, $def, ($def, "default")); + $class = "" if $class eq "default"; return $class; } @@ -460,29 +489,6 @@ sub new_users_grplogin { return ($group_login, $group_login); } -# return login group -sub new_users_grplogin_batch { - local($name, $defaultgroup) = @_; - local($group_login, $group); - - $group_login = $name; - $group_login = $defaultgroup if $defaultgroup ne $group_uniq; - - if (defined $gid{$group_login}) { - # convert numeric groupname (gid) to groupname - $group_login = $gid{$group_login}; - } - - # if (defined($groupname{$group_login})) { - # &add_group($groupname{$group_login}, $name); - # } - - return $group_login - if defined($groupname{$group_login}) || $group_login eq $name; - warn "Group ``$group_login'' does not exist\a\n"; - return 0; -} - # return other groups (string) sub new_users_groups { local($name, $other_groups) = @_; @@ -534,17 +540,30 @@ sub new_users_groups_valid { # 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 <<EOF; Name: $name -Password: **** +Password: $newpasswd Fullname: $fullname Uid: $u_id Gid: $g_id ($group_login) Class: $class Groups: $group_login $new_groups -HOME: $home/$name +HOME: $userhome Shell: $sh EOF @@ -553,12 +572,13 @@ EOF # make password database sub new_users_pwdmkdb { - local($last) = @_; + local($last) = shift; + local($name) = shift; - system("$pwd_mkdb $etc_passwd"); + system(@pwd_mkdb, '-u', $name, $etc_passwd); if ($?) { warn "$last\n"; - warn "``$pwd_mkdb'' failed\n"; + warn "``@pwd_mkdb'' failed\n"; exit($? >> 8); } } @@ -583,6 +603,9 @@ sub new_users_group_update { # 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}"); } @@ -648,9 +671,6 @@ sub sendmessage { sub new_users_password { - # empty password - return "" if $defaultpasswd ne "yes"; - local($password); while(1) { @@ -674,6 +694,29 @@ sub new_users_password { 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 { @@ -685,12 +728,14 @@ sub new_users { # 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; @@ -702,6 +747,7 @@ sub new_users { $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) = @@ -709,25 +755,64 @@ sub new_users { # 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); - $password = &new_users_password; + $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:$home/$name:$sh"; + "\:$u_id\:$g_id\:$class\:0:0:$fullname:$userhome:$sh"; &append_file($etc_passwd, "$new_entry"); - &new_users_pwdmkdb("$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($name, $group_login); + &home_create($userhome, $name, $group_login); } else { $new_users_ok = 0; } @@ -739,51 +824,42 @@ sub new_users { } } -sub batch { - local($name, $groups, $class, $fullname, $password) = @_; - local($sh); - - $defaultshell = &shell_default_valid($defaultshell); - return 0 unless $home = &home_partition_valid($home); - return 0 if $dotdir ne &dotdir_default_valid($dotdir); - $send_message = &message_default; - - return 0 if $name ne &new_users_name_valid($name); - $sh = $shell{$defaultshell}; - ($u_id, $g_id) = &next_id($name); - $group_login = &new_users_grplogin_batch($name, $defaultgroup); - return 0 unless $group_login; - $g_id = $groupname{$group_login} if (defined($groupname{$group_login})); - ($flag, $new_groups) = &new_users_groups_valid($groups); - return 0 if $flag; - - $class = $defaultclass if $class eq ""; - $cryptpwd = ""; - $cryptpwd = crypt($password, &salt) if $password ne ""; - # obscure perl bug - $new_entry = "$name\:" . "$cryptpwd" . - "\:$u_id\:$g_id\:$class\:0:0:$fullname:$home/$name:$sh"; - &append_file($etc_passwd, "$new_entry"); - &new_users_pwdmkdb("$new_entry"); - &new_users_group_update; - &new_users_passwd_update; print "Added user ``$name''\n"; - &sendmessage($name, @message_buffer) if $send_message ne "no"; - &adduser_log("$name:*:$u_id:$g_id($group_login):$fullname"); - &home_create($name, $group_login); -} - # ask for password usage sub password_default { - local($p) = $defaultpasswd; + local($p) = $defaultusepassword; if ($verbose) { - $p = &confirm_yn("Use passwords", $defaultpasswd); + $p = &confirm_yn("Use password-based authentication", $defaultusepassword); $changes++ unless $p; } - return "yes" if (($defaultpasswd eq "yes" && $p) || - ($defaultpasswd eq "no" && !$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; @@ -792,7 +868,6 @@ sub check_root { sub usage { warn <<USAGE; usage: adduser - [-batch username [group[,group]...] [class] [fullname] [password]] [-check_only] [-class login_class] [-config_create] @@ -829,11 +904,11 @@ sub uniq { sub salt { local($salt); # initialization local($i, $rand); - local(@itoa64) = ( 0 .. 9, a .. z, A .. Z ); # 0 .. 63 + local(@itoa64) = ( '0' .. '9', 'a' .. 'z', 'A' .. 'Z' ); # 0 .. 63 warn "calculate salt\n" if $verbose > 1; # to64 - for ($i = 0; $i < 8; $i++) { + for ($i = 0; $i < 27; $i++) { srand(time + $rand + $$); $rand = rand(25*29*17 + $rand); $salt .= $itoa64[$rand & $#itoa64]; @@ -852,10 +927,10 @@ sub copyright { # hints sub hints { if ($verbose) { - print "Use option ``-silent'' if you don't want see " . - "all warnings & questions.\n\n"; + 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 see more warnings & " . + print "Use option ``-verbose'' if you want to see more warnings and " . "questions \nor try to repair bugs.\n\n"; } } @@ -881,8 +956,9 @@ sub parse_arguments { elsif (/^--?(message)$/) { $send_message = $argv[0]; shift @argv; $sendmessage = 1; } elsif (/^--?(batch)$/) { - @batch = splice(@argv, 0, 5); $verbose = 0; - die "batch: too few arguments\n" if $#batch < 0; + 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; } @@ -942,6 +1018,7 @@ sub adduser_log { 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') { @@ -954,17 +1031,28 @@ sub adduser_log { # create HOME directory, copy dotfiles from $dotdir to $HOME sub home_create { - local($name, $group) = @_; - local($homedir) = "$home/$name"; + local($homedir, $name, $group) = @_; + local($rootdir); if (-e "$homedir") { warn "HOME Directory ``$homedir'' already exist\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 "mkdir $homedir: $!\n"; return 0; + if (!mkdir("$homedir", 0755)) { + warn "$dir: $!\n"; return 0; } system 'chown', "$name:$group", $homedir; return !$?; @@ -973,9 +1061,9 @@ sub home_create { # 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 -R $name:$group $homedir"); + system('cp', '-R', $dotdir, $homedir); + system('chmod', '-R', 'u+wrX,go-w', $homedir); + system('chown', '-Rh', "$name:$group", $homedir); # security opendir(D, $homedir); @@ -1000,7 +1088,6 @@ sub mkdir_home { local($user_partition) = "/usr"; local($dirname) = &dirname($dir); - -e $dirname || &mkdirhier($dirname); if (((stat($dirname))[0]) == ((stat("/"))[0])){ @@ -1128,6 +1215,21 @@ sub confirm_yn { 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 { @@ -1324,7 +1426,9 @@ sub config_write { # prepare some variables $send_message = "no" unless $send_message; - $defaultpasswd = "no" unless $defaultpasswd; + $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); @@ -1334,16 +1438,29 @@ sub config_write { # $config - automatic generated by adduser(8) # # Note: adduser read *and* write this file. -# You may change values, but don't add new things befor the +# You may change values, but don't add new things before the # line ``$do_not_delete'' # # verbose = [0-2] verbose = $verbose -# use password for new users -# defaultpasswd = yes | no -defaultpasswd = $defaultpasswd +# regular expression usernames are checked against (see perlre(1)) +# usernameregexp = 'regexp' +usernameregexp = '$usernameregexp' + +# use password-based authentication for new users +# defaultusepassword = "yes" | "no" +defaultusepassword = "$defaultusepassword" + +# enable account password at creation +# (the password will be prepended with a star if the account isn't enabled) +# defaultenableaccount = "yes" | "no" +defaultenableaccount = "$defaultenableaccount" + +# allow blank passwords +# defaultemptypassword = "yes" | "no" +defaultemptypassword = "$defaultemptypassword" # copy dotfiles from this dir ("/usr/share/skel" or "no") dotdir = "$dotdir" @@ -1378,7 +1495,7 @@ defaultgroup = $defaultgroup defaultclass = "$defaultclass" # new users get this uid (1000) -uid_start = 1000 +uid_start = "$uid_start" $do_not_delete ## your own variables, see /etc/adduser.message @@ -1400,7 +1517,7 @@ $check_only = 0; &config_read(@ARGV); # read variables form config-file &parse_arguments(@ARGV); # parse arguments -if (!$check_only && $#batch < 0) { +if (!$check_only) { ©right; &hints; } @@ -1413,16 +1530,26 @@ $changes = 0; &group_check; # check for incon* exit 0 if $check_only; # only check consistence and exit -exit(!&batch(@batch)) if $#batch >= 0; # batch mode # interactive # some questions +$usernameregexp = &usernameregexp_default; # regexp to check usernames against &shells_add; # maybe add some new shells $defaultshell = &shell_default; # enter default shell $home = &home_partition($home); # find HOME partition $dotdir = &dotdir_default; # check $dotdir $send_message = &message_default; # send message to new user -$defaultpasswd = &password_default; # maybe use password +$defaultusepassword = &password_default; # maybe use password +if ($defaultusepassword eq "no") { + if ($verbose) { + print "Creating accounts with a locked password.\n"; + } + $defaultenableaccount = "no"; + $defaultemptypassword = "yes"; +} else { + $defaultenableaccount = &enable_account_default; # enable or disable account + $defaultemptypassword = &enable_empty_password; # use empty password or not +} &config_write(!$verbose); # write variables in file # main loop for creating new users