Received: (at submit) by bugs.debian.org; 26 Aug 1999 08:58:45 +0000 Received: (qmail 22872 invoked from network); 26 Aug 1999 08:58:15 -0000 Received: from nimbus.zsd.co.za (qmailr@196.25.198.2) by master.debian.org with SMTP; 26 Aug 1999 08:58:15 -0000 Received: (qmail 16970 invoked from network); 26 Aug 1999 08:57:12 -0000 Received: from unknown (HELO discus) (192.168.1.7) by nimbus.zsd.co.za with SMTP; 26 Aug 1999 08:57:12 -0000 From: "I. Forbes" To: submit@bugs.debian.org Date: Thu, 26 Aug 1999 10:54:16 +0200 MIME-Version: 1.0 Content-type: text/plain; charset=US-ASCII Content-transfer-encoding: 7BIT Subject: "adduser" does not check for qmail "~alias/.qmail-newuser" alias Priority: normal X-mailer: Pegasus Mail for Win32 (v3.01d) Package: adduser Version: 3.8 The "qmail" MTA (see www.qmail.org) is now available on debian via the package "qmail-src". This MTA does a few unconventional things, one of them is an alternate method of handling e- mail "alias" names. (Latest versions also honour the accepted /etc/aliases file). There is a user "alias" defined on the system. If a file named: ~alias/.qmail-newuser is present, the contents of that file are treated as a ".qmail" file, (which is a variation of sendmail's .forward file). Mail addressed to "newuser" will then be delivered according to the instructions contained in that file. This is well and good for qmail - it is all a bit different but it works very well. The trouble is that if the file ~alias/.qmail-newuser exists, it is not a good idea to go and create a new user called "newuser" as unpredictable things will happen to his mail. My "adduser" script has been patched to complain if you try and do this. I have attached it for your information and possible inclusion in future released of adduser. Search for "qmail" for a quick look at what has changed. The qmail mechanism is complex. It can react to ~alias/.qmail-newuser-default or ~alias/.qmail-new-user My patches were put together by a student who helped me (Craig Bates craig@zsd.co.za), I am not sure they will work for all of these possibilities. Thanks Ian Forbes #!/usr/bin/perl -w # # adduser 3.8 # # adduser: a utility to add users to the system # addgroup: a utility to add groups to the system # Copyright (C) 1997 Guy Maor # Copyright (C) 1995 Ted Hajek # Ian A. Murdock # General scheme of the program adapted by the original debian 'adduser' # program by Ian A. Murdock . # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. # # #################### # the program can be called as: # # adduser [--home DIR] [--uid ID] [--ingroup GROUP | --gid ID] # [--disabled-password] [--gecos GECOS] user # add a normal user to the system # example: adduser fred # $action = "adduser" # # adduser --group [--gid ID] group # addgroup [--gid ID] group # add a system group # example: addgroup --quiet www-data # $action = "addgroup" # # adduser --system [--home DIR] [--uid ID] [--group | --ingroup GROUP # | --gid ID] [--disabled-password] [--gecos GECOS] user # add a system user. Create a like-named, like-id'd group with # --group, add to an existing group with --ingroup or --gid. Add # to "nogroup" with neither. # example: adduser --system --home /home/gopher-data --group gopher # $action = "addsysuser" # # adduser user group # add the existing user to an existing group. # $action = "addusertogroup" # # all commands take the following options: # --quiet don't give progress information on STDOUT # --force-badname disable checking of names for funny characters # --help usage message # --version version number and copyright # --conf FILE use FILE instead of /etc/adduser.conf ############ # Modified by Craig Bates - 29-01-99 to check qmail aliases. $verbose = 1; # should we be verbose? $allow_badname = 0; # should we allow bad names? $ask_passwd = 1; # ask for a passwd? $defaults = "/etc/adduser.conf"; $nogroup_id = getgrnam("nogroup") || 65534; $0 =~ s+.*/++; $config{"qmail_alias_dir"} = "/var/qmail/alias/"; $config{"dshell"} = "/bin/bash"; $config{"first_system_uid"} = 100; $config{"last_system_uid"} = 999; $config{"first_uid"} = 1000; $config{"last_uid"} = 29999; $config{"dhome"} = "/home"; $config{"skel"} = "/etc/skel"; $config{"usergroups"} = "yes"; $config{"users_gid"} = "100"; $config{"grouphomes"} = "no"; $config{"letterhomes"} = "no"; $config{"quotauser"} = ""; $action = $0 eq "addgroup" ? "addgroup" : "adduser"; while ($arg = shift(@ARGV)) { die "$0: No options allowed after names.\n" if ($names[0] && $arg =~ /^--/); if ($arg eq "--quiet") { $verbose = 0; } elsif ($arg eq "--force-badname") { $allow_badname = 1; } elsif ($arg eq "--help") { &usage(); exit 0; } elsif ($arg eq "--version") { &version(); exit 0; } elsif ($arg eq "--system") { $action = "addsysuser"; } elsif ($arg eq "--group") { $found_group_opt = 1; } elsif ($arg eq "--ingroup") { die "$0: --ingroup requires an argument.\n" if (!($ingroup_name = shift(@ARGV))); } elsif ($arg eq "--home") { die "$0: --home requires an argument.\n" if (!($special_home = shift(@ARGV))); print "$0: Warning: The home dir you specified already exists.\n" if (-d $special_home && $verbose); die "$0: The home dir must be an absolute path.\n" if ($special_home !~ m+^/+ ); } elsif ($arg eq "--gecos") { $new_gecos = shift(@ARGV); } elsif ($arg eq "--disabled-password") { $ask_passwd = 0; } elsif ($arg eq "--uid") { die "$0: --uid requires a numeric argument.\n" if (!($new_uid = shift(@ARGV)) || $new_uid !~ /^\d+$/); } elsif ($arg eq "--gid") { die "$0: --gid requires a numeric argument.\n" if (!($new_gid = shift(@ARGV)) || $new_gid !~ /^\d+$/); } elsif ($arg eq "--conf") { die "$0: --conf requires an argument.\n" if (!($defaults = shift(@ARGV))); die "$0: `$defaults' doesn't exist.\n" if (! -f $defaults); } elsif ($arg eq "--debug") { $debugging = 1; } elsif ($arg =~ /^--/) { # bad argument! die "$0: Unknown argument `$arg'.\n"; } else { # it's a username push (@names, $arg); } } die "$0: Only root may add a user or group to the system.\n" if ($> != 0); die "$0: I need a name to add.\n" if (@names == 0); die "$0: No more than two names.\n" if (@names > 2); if (@names == 2) { # must be addusertogroup die "$0: Specify only one name in this mode.\n" if ($action eq "addsysuser" || $found_group_opt); $action = "addusertogroup"; $existing_user = shift (@names); $existing_group = shift (@names); } else { $new_name = shift (@names); } if ($found_group_opt) { if ($action eq "addsysuser") { $make_group_also = 1; } else { $action = "addgroup"; } } die "$0: --group, --ingroup, and --gid options are mutually exclusive.\n" if ($action ne "addgroup" && defined($found_group_opt) +defined($ingroup_name) +defined($new_gid) > 1); ##### # OK, we've processed the arguments. $action equals one of the following, # and the appropriate variables have been set: # # $action = "adduser" # $new_name - the name of the new user. # $ingroup_name | $new_gid - the group to add the user to # $special_home, $new_uid, $new_gecos - optional overrides # $action = "addgroup" # $new_name - the name of the new group # $new_gid - optional override # $action = "addsysuser" # $new_name - the name of the new user # $make_group_also | $ingroup_name | $new_gid | 0 - which group # $special_home, $new_uid, $new_gecos - optional overrides # $action = "addusertogroup" # $existing_user - the user to be added # $existing_group - the group to add her to ##### &read_config($defaults); &checkname($new_name) if $new_name; $SIG{'INT'} = $SIG{'QUIT'} = $SIG{'HUP'} = 'handler'; ############## ## addgroup ## ############## if ($action eq "addgroup") { die "$0: The group `$new_name' already exists.\n" if (getgrnam($new_name)); die "$0: The GID `$new_gid' is already in use.\n" if (defined($new_gid) && getgrgid($new_gid)); if (!defined($new_gid)) { $new_gid = &first_avail_id($config{"first_system_uid"}, $config{"last_system_uid"}, &get_current_gids); die "$0: No GID is available in the range ", "$config{\"first_system_uid\"} - $config{\"last_system_uid\"}\n", "(FIRST_SYS_UID - LAST_SYS_UID). ", "Group `$new_name' not created.\n" if ($new_gid == -1); } print "Adding group $new_name ($new_gid)...\n" if $verbose; &systemcall('groupadd', '-g', $new_gid, $new_name); print "Done.\n" if $verbose; exit 0; } #################### ## addusertogroup ## #################### elsif ($action eq "addusertogroup") { die "$0: The user `$existing_user' doesn't exist.\n" if (!getpwnam($existing_user)); die "$0: The group `$existing_group' doesn't exist.\n" if (!getgrnam($existing_group)); if (&user_is_member($existing_user, $existing_group)) { print "The user `$existing_user' is already a member of ", "`$existing_group'.\n" if $verbose; exit 0; # not really an error } print "Adding user $existing_user to group $existing_group...\n" if $verbose; &systemcall('usermod', '-G', join(",", get_users_groups($existing_user), $existing_group), $existing_user); print "Done.\n" if $verbose; exit 0; } ################ ## addsysuser ## ################ elsif ($action eq "addsysuser") { $new_gid = $nogroup_id if (!$ingroup_name && !defined($new_gid) && !$make_group_also); &check_user_group(); print "Adding system user $new_name...\n" if $verbose; if (!defined($new_uid) && $make_group_also) { $new_uid = &first_avail_id($config{"first_system_uid"}, $config{"last_system_uid"}, &get_current_uids, &get_current_gids); die "$0: No UID/GID pair is available in the range ", "$config{\"first_system_uid\"} - $config{\"last_system_uid\"}\n", "(FIRST_SYS_UID - LAST_SYS_UID). ", "User `$new_name' not created.\n" if ($new_uid == -1); $new_gid = $new_uid; $ingroup_name = $new_name; } elsif (!defined($new_uid) && !$make_group_also) { $new_uid = &first_avail_id($config{"first_system_uid"}, $config{"last_system_uid"}, &get_current_uids); die "$0: No UID is available in the range ", "$config{\"first_system_uid\"} - $config{\"last_system_uid\"}\n", "(FIRST_SYS_UID - LAST_SYS_UID). ", "User `$new_name' not created.\n" if ($new_uid == -1); if (defined($new_gid)) { $ingroup_name = getgrgid($new_gid); } elsif ($ingroup_name) { $new_gid = getgrnam($ingroup_name); } else { die "Internal error"; } } else { if (defined($new_gid)) { $ingroup_name = getgrgid($new_gid); } elsif ($ingroup_name) { $new_gid = getgrnam($ingroup_name); } elsif ($make_group_also){ $new_gid=$new_uid; $ingroup_name=$new_name; } else { die "Internal error"; } } if ($make_group_also) { print "Adding new group $new_name ($new_gid).\n" if $verbose; $undogroup = $new_name; &systemcall('groupadd', '-g', $new_gid, $new_name); } print "Adding new user $new_name ($new_uid) with group $ingroup_name.\n" if $verbose; $home_dir = $special_home || &homedir($new_name, $ingroup_name); $undouser = $new_name; &systemcall('useradd', '-d', $home_dir, '-g', $ingroup_name, '-s', '/bin/false', '-u', $new_uid, $new_name); &systemcall('chfn', '-f', $new_gecos, $new_name) if ($new_gecos); if (-d $home_dir) { print "Home directory $home_dir already exists.\n" if $verbose; } else { print "Creating home directory $home_dir.\n" if $verbose; $undohome = $home_dir; &mktree($home_dir) || &cleanup("Couldn't create $home_dir: $!.\n"); chown($new_uid, $new_gid, $home_dir) || &cleanup("chown $new_uid:$new_gid $home_dir: $!\n"); $dir_mode = $make_group_also ? 02755 : 0755; chmod ($dir_mode, $home_dir) || &cleanup("chmod $dir_mode $home_dir: $!\n"); } exit 0; } ############# ## adduser ## ############# elsif ($action eq "adduser") { if (!$ingroup_name && !defined($new_gid)) { if ($config{"usergroups"} eq "yes") { $make_group_also = 1; } else { $new_gid = $config{"users_gid"}; } } &check_user_group(); print "Adding user $new_name...\n" if $verbose; if (!defined($new_uid) && $make_group_also) { $new_uid = &first_avail_id($config{"first_uid"}, $config{"last_uid"}, &get_current_uids, &get_current_gids); die "$0: No UID/GID pair is available in the range ", "$config{\"first_uid\"} - $config{\"last_uid\"}\n", "(FIRST_UID - LAST_UID). ", "User `$new_name' not created.\n" if ($new_uid == -1); $new_gid = $new_uid; $ingroup_name = $new_name; } elsif (!defined($new_uid) && !$make_group_also) { $new_uid = &first_avail_id($config{"first_uid"}, $config{"last_uid"}, &get_current_uids); die "$0: No UID is available in the range ", "$config{\"first_uid\"} - $config{\"last_uid\"}\n", "(FIRST_UID - LAST_UID). ", "User `$new_name' not created.\n" if ($new_uid == -1); if (defined($new_gid)) { $ingroup_name = getgrgid($new_gid); } elsif ($ingroup_name) { $new_gid = getgrnam($ingroup_name); } else { die "Internal error"; } } else { if (defined($new_gid)) { $ingroup_name = getgrgid($new_gid); } elsif ($ingroup_name) { $new_gid = getgrnam($ingroup_name); } elsif ($make_group_also){ $new_gid=$new_uid; $ingroup_name=$new_name; } else { die "Internal error"; } } if ($make_group_also) { print "Adding new group $new_name ($new_gid).\n" if $verbose; $undogroup = $new_name; &systemcall('groupadd', '-g', $new_gid, $new_name); } print "Adding new user $new_name ($new_uid) with group $ingroup_name.\n" if $verbose; $home_dir = $special_home || &homedir($new_name, $ingroup_name); $undouser = $new_name; &systemcall('useradd', '-d', $home_dir, '-g', $ingroup_name, '-s', $config{dshell}, '-u', $new_uid, $new_name); if (-d $home_dir) { print "Home directory $home_dir already exists. Not copying from", " $config{skel}\n" if $verbose; } else { print "Creating home directory $home_dir.\n" if $verbose; $undohome = $home_dir; &mktree($home_dir) || &cleanup("Couldn't create $home_dir: $!.\n"); chown($new_uid, $new_gid, $home_dir) || &cleanup("chown $new_uid:$new_gid $home_dir: $!\n"); $dir_mode = $make_group_also ? 02755 : 0755; chmod ($dir_mode, $home_dir) || &cleanup("chmod $dir_mode $home_dir: $!\n"); if ($config{"skel"}) { print "Copying files from $config{skel}\n" if $verbose; open(FIND, "cd $config{skel}; find . -print |") || &cleanup("fork for find: $!\n"); while () { chop; next if ($_ eq "."); ©_to_dir($config{"skel"}, $_, $home_dir, $new_uid, $new_gid, $make_group_also); } } } if ($ask_passwd) { &systemcall('passwd', $new_name); } if ($new_gecos) { &systemcall('chfn', '-f', $new_gecos, $new_name); } else { for (;;) { &systemcall('chfn', $new_name); print "Is the information correct? [y/n] "; chop ($answer=); last if ($answer =~ /^y/i); } } if ($config{"quotauser"}) { print "Setting quota from $config{quotauser}.\n"; &systemcall('edquota', '-p', $config{quotauser}, $new_name); } &systemcall('/usr/local/sbin/adduser.local', $new_name, $new_uid, $new_gid, $home_dir) if (-x "/usr/local/sbin/adduser.local"); exit 0; } # calculate home directory sub homedir { my $dir = $config{"dhome"}; $dir .= '/' . $_[1] if ($config{"grouphomes"} =~ /yes/i); $dir .= '/' . substr($_[0],0,1) if ($config{"letterhomes"} =~ /yes/i); $dir .= '/' . $_[0]; } # create a directory and all leading directories sub mktree { my($tree) = @_; my($done, @path); my $default_dir_mode = 0755; $tree =~ s:^/*(.*)/*$:$1:; # chop off leading & trailing slashes @path = split(/\//, $tree); $done = ""; while (@path) { $done .= '/' . shift(@path); -d $done || mkdir($done, $default_dir_mode) || return 0; } 1; } sub check_user_group() { die "$0: The user `$new_name' already exists.\n" if (getpwnam($new_name)); # qmail alias check added by CVB opendir(DIR, $config{"qmail_alias_dir"}); @file = readdir(DIR); closedir(DIR); foreach (@file) { if ($_ eq ".qmail-$new_name") { die "$new_name is a qmail alias\n"; } } ######### die "$0: The UID `$new_uid' already exists.\n" if (defined($new_uid) && getpwuid($new_uid)); if ($make_group_also) { die "$0: The group `$new_name' already exists.\n" if (getgrnam($new_name)); die "$0: The GID `$new_uid' already exists.\n" if (defined($new_uid) && getgrgid($new_uid)); } else { die "$0: The group `$ingroup_name' doesn't exist.\n" if ($ingroup_name && !getgrnam($ingroup_name)); die "$0: The GID `$new_gid' doesn't exist.\n" if (defined($new_gid) && !getgrgid($new_gid)); } } # copy files, directories, symlinks sub copy_to_dir { my($fromdir, $file, $todir, $newu, $newg, $sgiddir) = @_; if (-l "$fromdir/$file") { symlink(readlink("$fromdir/$file"), "$todir/$file") || &cleanup("symlink: $!\n"); } elsif (-f "$fromdir/$file") { open (FILE, "$fromdir/$file") || &cleanup("open $fromdir/$file: $!"); open (NEWFILE, ">$todir/$file") || &cleanup("open >$todir/$file: $!"); (print NEWFILE ) || &cleanup("print $todir/$file: $!"); close FILE; close(NEWFILE) || &cleanup("close $todir/$file "); } elsif (-d "$fromdir/$file") { mkdir("$todir/$file", 700) || &cleanup("mkdir: $!"); } else { &cleanup("Can't deal with $fromdir/$file. " ."Not a dir, file, or symlink.\n"); } chown($newu, $newg, "$todir/$file") || &cleanup("chown $newu:$newg $todir/$file: $!\n"); $perm = (stat("$fromdir/$file"))[2] & 07777; $perm |= 02000 if (-d "$fromdir/$file" && ($perm & 010) && $sgiddir); chmod($perm, "$todir/$file") || &cleanup("chmod $todir/$file: $!\n"); } # is name ok? sub checkname { my ($name) = @_; if ($allow_badname && $name !~ /^[A-Za-z_][-_A-Za-z0-9]*$/) { print STDERR "$0: To avoid problems, the username should consist of a letter or underscore followed by letters, digits, underscores, and dashes.\n"; exit 1; } elsif ($name !~ /^[a-z][a-z0-9]*$/) { if (!$allow_badname) { print STDERR "$0: Please enter a username consisting of a lower case letter followed by lower case letters and numbers. Use the `--force-badname' option to allow underscores, dashes, and uppercase.\n"; exit 1; } print "Allowing use of questionable username.\n" if ($verbose); } } # return the smallest X such that # $min <= X <= $max, and X is not an element of @ids # or -1 if no such X sub first_avail_id { my ($min, $max, @ids) = @_; @ids = sort {$a <=> $b} @ids; print "Selecting from $min $max (" . join(",",@ids) . ").\n" if $debugging; while ($min <= $max) { return $min if ($min < $ids[0] || @ids==0); shift @ids if ($min > $ids[0]); $min++ if ($min == $ids[0]); } -1; # nothing available } # return an array containing all the GIDs sub get_current_gids { my(@gids, $gid); setgrent; push @gids, $gid while defined($gid = (getgrent)[2]); endgrent; @gids; } # return an array containing all the UIDs sub get_current_uids { my(@uids, $uid); setpwent; push @uids, $uid while defined($uid = (getpwent)[2]); endpwent; @uids; } # return a user's groups sub get_users_groups { my($user) = @_; my($name,$members,@groups); setgrent; while (($name,$members) = (getgrent)[0,3]) { for (split(/ /, $members)) { if ($user eq $_) { push @groups, $name; last; } } } endgrent; @groups; } # user is member of group? sub user_is_member { my($user, $group) = @_; for (split(/ /, (getgrnam($group))[3])) { return 1 if ($user eq $_); } 0; } # parse the configuration file sub read_config { my ($conf_file) = @_; my ($var, $lcvar, $val); if (! -f $conf_file) { print "$0: $conf_file doesn't exist. Using defaults.\n" if $verbose; return; } open (CONF, $conf_file) || die "$0: $conf_file: $!\n"; while () { chomp; next if /^#/ || /^\s*$/; if ((($var, $val) = /^\s*(\S+)\s*=\s*(.*)/) != 2) { warn "$0: Couldn't parse $conf_file:$..\n"; next; } $lcvar = lc $var; if (!defined($config{$lcvar})) { warn "$0: Unknown variable `$var' at $conf_file:$..\n"; next; } $val =~ s/^"(.*)"$/$1/; $val =~ s/^'(.*)'$/$1/; $config{$lcvar} = $val; } close CONF || die "$!"; } sub systemcall { my $c = join(' ', @_); print "$c\n" if $debugging; system(@_) && &cleanup("$0: `$c' returned error code $?. Aborting.\n"); } sub cleanup { print "@{_}Cleaning up.\n"; if ($undohome) { print "Removing directory `$undohome'\n"; system('rm', '-rf', $undohome); } if ($undouser) { print "Removing user `$undouser'.\n"; system('userdel', $undouser); } if ($undogroup) { print "Removing group `$undogroup'.\n"; system('groupdel', $undogroup); } exit 1; } sub handler { my($sig) = @_; &cleanup("Caught a SIG$sig.\n"); } sub version { print "$0: add a user or group to the system. Version 3.8 Copyright (C) 1997 Guy Maor Copyright (C) 1995 Ian Murdock , Ted Hajek , This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License, /usr/doc/copyright/GPL, for more details. "; } sub usage { print "adduser [--home DIR] [--uid ID] [--ingroup GROUP | --gid ID] [--disabled-password] [--gecos GECOS] user Add a normal user adduser --system [--home DIR] [--uid ID] [--group | --ingroup GROUP | --gid ID] [--disabled-password] [--gecos GECOS] user Add a system user adduser --group [--gid ID] group addgroup [--gid ID] group Add a system group adduser user group Add an existing user to an existing group Global configuration is in the file $defaults. Other options are [--quiet] [--force-badname] [--help] [--version] [--conf FILE]. "; } --------------------------------------------------------------------- Ian Forbes ZSD http://www.zsd.co.za Office: +27 +21 683-1388 Fax: +27 +21 64-1106 Snail Mail: P.O. Box 46827, Glosderry, 7702, South Africa ---------------------------------------------------------------------