diff options
author | Jason Downs <downsj@cvs.openbsd.org> | 1996-09-28 05:58:37 +0000 |
---|---|---|
committer | Jason Downs <downsj@cvs.openbsd.org> | 1996-09-28 05:58:37 +0000 |
commit | 64669d8d94e359f0aca17c4eaf479fdf34a2b46a (patch) | |
tree | d6cd90d335a5dca0449dbbd5604a2a897fb272c6 /usr.sbin/adduser/adduser.perl | |
parent | f46ba2b0d9f31a8f9ecce96a5a8af1e1783bfa6b (diff) |
Perl adduser(8) from FreeBSD.
TODO: Hack to accept the same arguments as BSD/OS adduser(8). Add rmuser(8).
Diffstat (limited to 'usr.sbin/adduser/adduser.perl')
-rw-r--r-- | usr.sbin/adduser/adduser.perl | 1415 |
1 files changed, 1415 insertions, 0 deletions
diff --git a/usr.sbin/adduser/adduser.perl b/usr.sbin/adduser/adduser.perl new file mode 100644 index 00000000000..fe0152b7bc5 --- /dev/null +++ b/usr.sbin/adduser/adduser.perl @@ -0,0 +1,1415 @@ +#!/usr/bin/perl +# +# $OpenBSD: adduser.perl,v 1.1 1996/09/28 05:58:35 downsj Exp $ +# +# Copyright (c) 1995-1996 Wolfram Schneider <wosch@FreeBSD.org>. 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. +# +# $From: adduser.perl,v 1.19 1996/09/17 19:34:56 wosch Exp $ + + +# read variables +sub variables { + $verbose = 1; # verbose = [0-2] + $defaultpasswd = "yes"; # use password for new users + $dotdir = "/usr/share/skel"; # copy dotfiles from this dir + $dotdir_bak = $dotdir; + $send_message = "no"; # 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 = "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 + + $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 + + # group + $groupname =''; # $groupname{groupname} = gid + $groupmembers = ''; # $groupmembers{gid} = members of group/kommalist + $gid = ''; # $gid{gid} = groupname; gid form group db + + # 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"; + @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(<S>) { + 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"); + $shell{"no"} = "/nonexistent"; + + push(@list, "/bin/date"); + push(@shellpref, "date"); + $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 (f.e. "/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 "$passwd: $!\n"; + + while(<P>) { + chop; + push(@passwd_backup, $_); + ($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 !~ /^(bin|uucp|falcon|nobody)$/ && + $sh !~ /\/(pppd|sliplogin)$/); + $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(<G>) { + chop; + push(@group_backup, $_); + ($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, "a-z0-9", ""); + if (length($name) > 8) { + warn "Username is longer than 8 chars\a\n"; + next; + } + last if (&new_users_name_valid($name) eq $name); + } + return $name; +} + +sub new_users_name_valid { + local($name) = @_; + + if ($name !~ /^[a-z0-9]+$/) { + warn "Wrong username. " . + "Please use only lowercase characters or digits\a\n"; + return 0; + } elsif ($username{$name}) { + warn "Username ``$name'' already exists!\a\n"; return 0; + } + return $name; +} + +# 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 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"; + } 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); +} + +# 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"; + + local(@l) = split(',', $groupmembers{$gid}); + # group(5): A group cannot have more than 200 members. + # The maximum line length of /etc/group is 1024 characters. + # Longer lines will be skiped. + if ($#l >= 200 || + length($groupmembers{$gid}) > 1024 - 50) { # 50 is for group name + warn "WARNING, maybe group line ``$gid{$gid}'' is to long or to\n" . + "much users in group, see group(5)\a\n"; + } + 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 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) = @_; + 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 { + + print <<EOF; + +Name: $name +Password: **** +Fullname: $fullname +Uid: $u_id +Gid: $g_id ($group_login) +Groups: $group_login $new_groups +HOME: $home/$name +Shell: $sh +EOF + + return &confirm_yn("OK?", "yes"); +} + +# make password database +sub new_users_pwdmkdb { + local($last) = @_; + + system("$pwd_mkdb $etc_passwd"); + if ($?) { + warn "$last\n"; + warn "``$pwd_mkdb'' failed\n"; + exit($? >> 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"; + 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 = <STDIN>) { + 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 { + + # empty password + return "" if $defaultpasswd ne "yes"; + + 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 { + + 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 + # u_id: user id + # g_id: group id + # group_login: groupname of g_id + # new_groups: some other groups + local($name, $group_login, $fullname, $sh, $u_id, $g_id, $new_groups); + 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; + ($u_id, $g_id) = &new_users_id($name); + ($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})); + + $new_groups = &new_users_groups($name, $new_groups); + $password = &new_users_password; + + + if (&new_users_ok) { + $new_users_ok = 1; + + $cryptpwd = ""; + $cryptpwd = crypt($password, &salt) if $password ne ""; + # obscure perl bug + $new_entry = "$name\:" . "$cryptpwd" . + "\:$u_id\:$g_id\::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"; + &new_users_sendmessage; + &adduser_log("$name:*:$u_id:$g_id($group_login):$fullname"); + &home_create($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; + } +} + +sub batch { + local($name, $groups, $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; + + $cryptpwd = ""; + $cryptpwd = crypt($password, &salt) if $password ne ""; + # obscure perl bug + $new_entry = "$name\:" . "$cryptpwd" . + "\:$u_id\:$g_id\::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; + if ($verbose) { + $p = &confirm_yn("Use passwords", $defaultpasswd); + $changes++ unless $p; + } + return "yes" if (($defaultpasswd eq "yes" && $p) || + ($defaultpasswd eq "no" && !$p)); + return "no"; # otherwise +} + +# misc +sub check_root { + die "You are not root!\n" if $< && !$test; +} + +sub usage { + warn <<USAGE; +usage: adduser + [-batch username [group[,group]...] [fullname] [password]] + [-check_only] + [-config_create] + [-dotdir dotdir] + [-group login_group] + [-h|-help] + [-home home] + [-message message_file] + [-noconfig] + [-shell shell] + [-s|-silent|-q|-quit] + [-uid uid_start] + [-v|-verbose] + +home=$home shell=$defaultshell dotdir=$dotdir login_group=$defaultgroup +message_file=$send_message uid_start=$uid_start +USAGE + exit 1; +} + +# uniq(1) +sub uniq { + local(@list) = @_; + local($e, $last, @array); + + foreach $e (sort @list) { + push(@array, $e) unless $e eq $last; + $last = $e; + } + return @array; +} + +# see /usr/src/usr.bin/passwd/local_passwd.c or librcypt, crypt(3) +sub salt { + local($salt); # initialization + local($i, $rand); + local(@itoa64) = ( 0 .. 9, a .. z, A .. Z ); # 0 .. 63 + + warn "calculate salt\n" if $verbose > 1; + # to64 + for ($i = 0; $i < 8; $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 see " . + "all warnings & questions.\n\n"; + } else { + print "Use option ``-verbose'' if you want see more warnings & " . + "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|quit)$/) { $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 (/^--?(group)$/) { $defaultgroup = $argv[0]; shift @argv } + elsif (/^--?(check_only)$/) { $check_only = 1 } + elsif (/^--?(message)$/) { $send_message = $argv[0]; shift @argv; + $sendmessage = 1; } + elsif (/^--?(batch)$/) { + @batch = splice(@argv, 0, 4); $verbose = 0; + die "batch: too few arguments\n" if $#batch < 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; + $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($name, $group) = @_; + local($homedir) = "$home/$name"; + + if (-e "$homedir") { + warn "HOME Directory ``$homedir'' already exist\a\n"; + return 0; + } + + if ($dotdir eq 'no') { + if (!mkdir("$homedir",0755)) { + warn "mkdir $homedir: $!\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 -R $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 exist\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 = <STDIN>); + $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 = <STDIN>); + $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; +} + +# 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 <<EOF; +# +# Message file for adduser(8) +# comment: ``#'' +# default variables: \$name, \$fullname, \$password +# other variables: see /etc/adduser.conf after +# line ``$do_not_delete'' +# + +\$fullname, + +your account ``\$name'' was created. Your password is ``\$password''. +Please expire your password. Have fun! + +See also chpass(1), finger(1), passwd(1) +EOF + close M; + return 1; +} + +# read message file into buffer +sub message_read { + local($file) = @_; + @message_buffer = ''; + + if (!open(R, "$file")) { + warn "File ``$file'':$!\n"; return 0; + } + while(<R>) { + 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(<C>) { + # 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; + $defaultpasswd = "no" unless $defaultpasswd; + local($shpref) = "'" . join("', '", @shellpref) . "'"; + local($shpath) = "'" . join("', '", @path) . "'"; + local($user_var) = join('', @user_variable_list); + + print C <<EOF; +# +# $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 +# line ``$do_not_delete'' +# + +# verbose = [0-2] +verbose = $verbose + +# use password for new users +# defaultpasswd = yes | no +defaultpasswd = $defaultpasswd + +# copy dotfiles from this dir ("/usr/share/skel" or "no") +dotdir = "$dotdir" + +# send this file to new user ("/etc/adduser.message" or "no") +send_message = "$send_message" + +# config file for adduser ("/etc/adduser.conf") +config = "$config" + +# logfile ("/var/log/adduser" or "no") +logfile = "$logfile" + +# default HOME directory ("/home") +home = "$home" + +# List of directories where shells located +# path = ('/bin', '/usr/bin', '/usr/local/bin') +path = ($shpath) + +# common shell list, first element has higher priority +# shellpref = ('bash', 'tcsh', 'ksh', 'csh', 'sh') +shellpref = ($shpref) + +# defaultshell if not empty ("bash") +defaultshell = "$defaultshell" + +# defaultgroup ('USER' for same as username or any other valid group +defaultgroup = $defaultgroup + +# new users get this uid (1000) +uid_start = 1000 + +$do_not_delete +## your own variables, see /etc/adduser.message +$user_var + +## end +EOF + close C; +} + +################ +# main +# +$test = 0; # test mode, only for development +$check_only = 0; + +&check_root; # you must be root to run this script! +&variables; # initialize variables +&config_read(@ARGV); # read variables form config-file +&parse_arguments(@ARGV); # parse arguments + +if (!$check_only && $#batch < 0) { + ©right; &hints; +} + +# check +$changes = 0; +&passwd_check; # check for valid passwdb +&shells_read; # read /etc/shells +&passwd_read; # read /etc/master.passwd +&group_read; # read /etc/group +&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 +&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 +&config_write(!$verbose); # write variables in file + +# main loop for creating new users +&new_users; # add new users + +#end |