From 89fe48dea3fd1e3cbbacc0e78f58d277d72168b8 Mon Sep 17 00:00:00 2001 From: Stephen Hemminger Date: Thu, 29 May 2008 16:29:21 -0700 Subject: Rework how user accounts are managed Rewrite the scripts that manage user accounts to: 1) use Posix standard useradd, userdel scripts rather than modifying passwd/group files directly. 2) add home-directory field to account management 3) support adding accounts to additional groups Note: this code should now also work with NIS since it has no direct access to /etc/passwd. --- scripts/system/vyatta_update_login.pl | 85 ++++++++-- scripts/system/vyatta_update_login_user.pl | 241 ----------------------------- 2 files changed, 72 insertions(+), 254 deletions(-) delete mode 100755 scripts/system/vyatta_update_login_user.pl (limited to 'scripts') diff --git a/scripts/system/vyatta_update_login.pl b/scripts/system/vyatta_update_login.pl index a5d1fd2a..bf2b0c10 100755 --- a/scripts/system/vyatta_update_login.pl +++ b/scripts/system/vyatta_update_login.pl @@ -24,7 +24,7 @@ use VyattaConfig; my $uconfig = new VyattaConfig; $uconfig->setLevel("system login user"); -my %users = $uconfig->listNodeStatus(); +my %users = $uconfig->listNodeStatus(); my @user_keys = sort keys %users; if ( ( scalar(@user_keys) <= 0 ) @@ -37,24 +37,83 @@ if ( ( scalar(@user_keys) <= 0 ) exit 1; } +# Exit codes form useradd.8 man page +my %reasons = ( + 0 => 'success', + 1 => 'can´t update password file', + 2 => 'invalid command syntax', + 3 => 'invalid argument to option', + 4 => 'UID already in use (and no -o)', + 6 => 'specified group doesn´t exist', + 9 => 'username already in use', + 10 => 'can´t update group file', + 12 => 'can´t create home directory', + 13 => 'can´t create mail spool', +); + +# Map of level to additional groups +my %level_map = ( + 'admin' => [ 'quaggavty', 'vyattacfg', 'sudo', 'adm', ], + 'operator' => [ 'quaggavty', 'operator', 'adm', ], +); + # we have some users for my $user (@user_keys) { if ( $users{$user} eq 'deleted' ) { - system("sudo /opt/vyatta/sbin/vyatta_update_login_user.pl -d '$user'"); - exit 1 if ( $? >> 8 ); + system("sudo userdel -r '$user'"); + die "userdel failed\n" if ( $? >> 8 ); } elsif ( $users{$user} eq 'added' || $users{$user} eq 'changed' ) { - my $fname = $uconfig->returnValue("$user full-name"); - my $level = $uconfig->returnValue("$user level"); - my $p = - $uconfig->returnValue("$user authentication encrypted-password"); - system( "sudo /opt/vyatta/sbin/vyatta_update_login_user.pl '$user' " - . "'$fname' '$p' '$level'" ); - exit 1 if ( $? >> 8 ); - } - else { + $uconfig->setLevel("system login user $user"); + + # See if this is a modification of existing account + my (undef, undef, $uid, undef, undef, + undef, undef, undef, $shell, undef) = getpwnam($user); + + my $cmd; + # not found in existing passwd, must be new + if ( !defined $uid ) { + $cmd = 'useradd -s /bin/vbash -m'; + } + # is it a vyatta user? + elsif ( $shell ne '/bin/vbash' ) { + die "$user: exists but is not a vyatta login user\n"; + } + else { + $cmd = "usermod"; + } + + my $pwd = $uconfig->returnValue('authentication encrypted-password'); + $pwd or die 'encrypted password not set'; + $cmd .= " -p '$pwd'"; - # not changed. do nothing. + my $fname = $uconfig->returnValue('full-name'); + $cmd .= " -c \"$fname\"" if ( defined $fname ); + + my $home = $uconfig->returnValue('home-directory'); + $cmd .= " -d \"$home\"" if ( defined $home ); + + # map level to group membership + my $level = $uconfig->returnValue('level'); + my $gref = $level_map{$level}; + my @groups = @{$gref}; + + # add any additional groups from configuration + push( @groups, $uconfig->returnValues('group') ); + + $cmd .= ' -G ' . join( ',', @groups ); + + system("sudo $cmd $user"); + if ( $? == -1 ) { + die "failed to exec $cmd"; + } + elsif ( $? & 127 ) { + die "$cmd died with signal" . ( $? & 127 ); + } + elsif ( $? != 0 ) { + my $reason = $reasons{ $? >> 8 }; + die "$cmd failed: $reason\n"; + } } } diff --git a/scripts/system/vyatta_update_login_user.pl b/scripts/system/vyatta_update_login_user.pl deleted file mode 100755 index 0eea2806..00000000 --- a/scripts/system/vyatta_update_login_user.pl +++ /dev/null @@ -1,241 +0,0 @@ -#!/usr/bin/perl - -use strict; -use Fcntl; -use POSIX qw(:unistd_h); - -# arg: login_name -# returns the next available uid if login_name doesn't exist. -# otherwise returns (undef, ). -sub next_uid_if_not_exist { - my $login = shift; - my $min_uid = 1000; - my $max_uid = 60000; - if (open(LOGIN_DEF, "/etc/login.defs")) { - while () { - if (m/^\s*UID_MIN\s+(\d+)/) { - $min_uid = $1; - next; - } - if (m/^\s*UID_MAX\s+(\d+)/) { - $max_uid = $1; - next; - } - } - close LOGIN_DEF; - } - - open(PASSWD, "/etc/passwd") or exit 1; - while () { - chomp; - my @passwd_fields = split /:/; - if ($passwd_fields[0] eq $login) { - close PASSWD; - return (undef, @passwd_fields); - } - if ($min_uid <= $passwd_fields[2]) { - next if ($passwd_fields[2] > $max_uid); - $min_uid = $passwd_fields[2] + 1; - next; - } - } - close PASSWD; - exit 2 if ($min_uid > $max_uid); - return ($min_uid); -} - -# arg: login_name -# returns the corresponding line in shadow or undef if login_name doesn't -# exist. -sub get_shadow_line { - my $login = shift; - open(SHADOW, "/etc/shadow") or exit 3; - while () { - chomp; - if (m/^$login:/) { - close SHADOW; - return $_; - } - } - close SHADOW; - return undef; -} - -# arg: login name -# removes the specified user from group/gshadow -sub remove_user_from_group { - my $user = shift; - my $sed_cmd = 'sed -i \'/^[^:]\+:/{' - . 's/:' . $user . '$/:/;' - . 's/:' . $user . ',/:/;' - . 's/,' . $user . ',/,/;' - . 's/,' . $user . '$//;}\''; - system("$sed_cmd /etc/group"); - exit 1 if ($? >> 8); - system("$sed_cmd /etc/gshadow"); - exit 1 if ($? >> 8); -} - -# arg: login name -# adds the specified user to group/gshadow -sub add_user_to_group { - my $user = shift; - my $group = shift; - my $gcmd = 'grep -q -e \'^' . $group . ':.*[:,]' . $user . '\(,\|$\)\''; - my $ret = system("$gcmd /etc/group"); - my $in_group = (($ret >> 8) == 0) ? 1 : 0; - $ret = system("$gcmd /etc/gshadow"); - my $in_gshadow = (($ret >> 8) == 0) ? 1 : 0; - - my $sed_cmd = 'sed -i \'/^' . $group . ':/{' - . 's/\([^:]\)$/\1,' . $user . '/;' - . 's/:$/:' . $user . '/;}\''; - if (!$in_group) { - system("$sed_cmd /etc/group"); - exit 1 if ($? >> 8); - } - if (!$in_gshadow) { - system("$sed_cmd /etc/gshadow"); - exit 1 if ($? >> 8); - } -} - -my $user = shift; -my $full = shift; -my $encrypted = shift; -my $level = shift; - -# emulate lckpwdf(3). -# difference: we only try to lock it once (non-blocking). lckpwdf will block -# for up to 15 seconds waiting for the lock. -# note that the lock is released when file is closed (e.g., exit), so no need -# for explicit unlock. -my $flock = pack "ssa20", F_WRLCK, SEEK_SET, "\0"; -sysopen(PWDLCK, "/etc/.pwd.lock", O_WRONLY | O_CREAT, 0600) or exit 3; -fcntl(PWDLCK, F_SETLK, $flock) or exit 3; - -if ($user eq "-d") { - $user = $full; - exit 4 if (!defined($user)); - - # check if user is using the system - my @pslines = `ps -U $user -u $user u`; - if ($#pslines != 0) { - # user is using the system - print STDERR "Delete failed: user \"$user\" is using the system\n"; - exit 4; - } - - my $ret = system("sed -i '/^$user:/d' /etc/passwd"); - exit 5 if ($ret >> 8); - $ret = system("sed -i '/^$user:/d' /etc/shadow"); - exit 6 if ($ret >> 8); - $ret = system("rm -rf /home/$user"); - exit 7 if ($ret >> 8); - remove_user_from_group($user); - exit 0; -} - -my %level_map = ( - 'admin' => [ 'users', 'quaggavty', 'vyattacfg', 'sudo', 'adm', ], - 'operator' => [ 'users', 'quaggavty', 'operator', 'adm', ], - ); -exit 4 if (!defined($user) || !defined($full) || !defined($encrypted) - || !defined($level)); -exit 4 if (!defined($level_map{$level})); -my $gref = $level_map{$level}; -my @groups = @{$gref}; -my $def_grp = $groups[0]; -if ($user eq 'root') { - $def_grp = 'root'; -} - -# note that DEF_SHELL doesn't affect root since root is never "added" -my $DEF_SHELL = "/bin/vbash"; - -open(GRP, "/etc/group") or exit 5; -my $def_gid = undef; -while () { - my @group_fields = split /:/; - if ($group_fields[0] eq $def_grp) { - $def_gid = $group_fields[2]; - last; - } -} -exit 6 if (!defined($def_gid)); - -my @vals = next_uid_if_not_exist($user); -my ($new_user, $passwd_line, $shadow_line) = (0, "", ""); -if (defined($vals[0])) { - # add new user - $new_user = 1; - $passwd_line = "$user:x:$vals[0]:${def_gid}:$full:/home/$user:$DEF_SHELL"; - my $sline = get_shadow_line($user); - exit 7 if (defined($sline)); - my $seconds = `date +%s`; - my $days = int($seconds / 3600 / 24); - $shadow_line = "$user:$encrypted:$days:0:99999:7:::"; -} else { - # modify existing user - shift @vals; - $vals[3] = $def_gid; - $vals[4] = $full; - $passwd_line = join(':', @vals); - my $sline = get_shadow_line($user); - exit 8 if (!defined($sline)); - @vals = split /:/, $sline; - $vals[1] = $encrypted; - for (my $padding = (9 - $#vals - 1); $padding > 0; $padding--) { - push @vals, ''; - } - $shadow_line = join(':', @vals); -} - -my $ret = 0; -if (!$new_user) { - $ret = system("sed -i '/^$user:/d' /etc/passwd"); - exit 9 if ($ret >> 8); - $ret = system("sed -i '/^$user:/d' /etc/shadow"); - exit 10 if ($ret >> 8); - remove_user_from_group($user); -} - -open(PASSWD, ">>/etc/passwd") or exit 11; -print PASSWD "$passwd_line\n"; -close PASSWD; -open(SHADOW, ">>/etc/shadow") or exit 12; -print SHADOW "$shadow_line\n"; -close SHADOW; - -# root doesn't need to be added to group -if ($user ne 'root') { - foreach my $group (@groups) { - add_user_to_group($user, $group); - } -} - -if (($new_user) && !(-e "/home/$user")) { - if (-d "/etc/skel") { - $ret = system("cp -a /etc/skel /home/$user"); - exit 13 if ($ret >> 8); - $ret = system("chmod 755 /home/$user"); - exit 14 if ($ret >> 8); - $ret = system("chown -R $user:$def_grp /home/$user"); - exit 15 if ($ret >> 8); - } else { - $ret = system("mkdir -p /home/$user"); - exit 16 if ($ret >> 8); - $ret = system("chmod 755 /home/$user"); - exit 17 if ($ret >> 8); - } -} - -my $vtysh_conf = "/etc/vyatta/quagga/vtysh.conf"; -if (($new_user) && (-e $vtysh_conf)) { - open(VTYSH_CONF, ">>$vtysh_conf") or exit 11; - print VTYSH_CONF "username $user nopassword\n"; - close VTYSH_CONF; -} - -exit 0; - -- cgit v1.2.3