freebsd-dev/usr.sbin/adduser/adduser.perl
gclarkii c226429abe We DON'T ship bash by default, why is it the default shell?
We also don't ship tcsh or ksh by default.
Correct these two things to make sh the default and increase csh and sh
to be higher priority.
1996-05-06 06:15:19 +00:00

1405 lines
35 KiB
Perl

#!/usr/bin/perl
#
# Copyright (c) 1995 Wolfram Schneider. All rights reserved.
# Alle Rechte vorbehalten. Es gilt das kontinentaleuropische Urheberrecht.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# 3. All advertising materials mentioning features or use of this software
# must display the following acknowledgement:
# This product includes software developed by Wolfram Schneider
# 4. The name of the author may not be used to endorse or promote products
# derived from this software without specific prior written permission
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
# /usr/sbin/adduser - add new user(s)
#
# Email: Wolfram Schneider <wosch@cs.tu-berlin.de>
#
# $Id: adduser.perl,v 1.10 1996/02/10 17:15:47 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 = "/etc/adduser.message"; # send message to new user
$send_message_bak = $send_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 /nonexistant and /bin/date as a valid shell for system utils
push(@list, "/nonexistant");
push(@shellpref, "no");
$shell{"no"} = "/nonexistant";
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 knowned 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;
}
# choise your favourite shell an 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 exist, but is it 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 $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 exist with uid: $username{$p_username}!\n"
if $username{$p_username} && $verbose;
$username{$p_username} = $p_uid;
print "User $p_username: uid $p_uid exist 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 exist twice: $g_groupname:$g_gid -> $g_groupname:$groupname{$g_groupname}\n"
if $groupname{$g_groupname} && $verbose;
$groupname{$g_groupname} = $g_gid;
warn "Groupid exist 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";
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 (&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: $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 ($?) {
local($crash) = "$etc_passwd.crash$$";
warn "$last\n";
warn "``$pwd_mkdb'' failed, try to restore ...\n";
open(R, "> $crash") || die "Sorry, give up\n";
$j = join("\n", @passwd_backup);
$j =~ s/\n//;
print R $j . "\n";
close R;
system("$pwd_mkdb $crash");
die "Sorry, give up\n" if $?;
die "Successfully restore $etc_passwd. Exit.\n";
}
}
# 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:$group_login");
$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}})) {
# 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:$group_login");
}
}
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) {
$password = &confirm_list("Enter password", 1, "", "");
last if $password ne "";
last if &confirm_yn("Use an empty password?", "yes");
}
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 "";
# obskure 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 "";
# obskure 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 {
print <<'EOF';
(c) Copyright 1995 Wolfram Schneider <wosch@cs.tu-berlin.de>
EOF
}
# 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: to 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;
&message_create($send_message);
&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: ``#''
# defaultvariables: \$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) {
&copyright; &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