modgroups.in 9.32 KB
Newer Older
1 2
#!/usr/bin/perl -wT
#
Leigh Stoller's avatar
Leigh Stoller committed
3
# Copyright (c) 2005-2015 University of Utah and the Flux Group.
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
# 
# {{{EMULAB-LICENSE
# 
# This file is part of the Emulab network testbed software.
# 
# This file is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or (at
# your option) any later version.
# 
# This file 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 Affero General Public
# License for more details.
# 
# You should have received a copy of the GNU Affero General Public License
# along with this file.  If not, see <http://www.gnu.org/licenses/>.
# 
# }}}
23 24 25
#
use English;
use Getopt::Std;
26
use strict;
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42

#
# Modify groups (add and subtract in DB) for a user, and then call
# other scripts that need to do something about it.
#
# Note that this script does not create accounts or groups. That should
# already have been done with other scripts.
#
sub usage()
{
    print STDOUT
	"Usage: modgroups [-a pid:gid:trust[,pid:gid:trust]...] ".
	                 "[-m pid:gid:trust[,pid:gid:trust]...] ".
	                 "[-r pid:gid[,pid:gid]...] user\n";
    exit(-1);
}
43
my $optlist = "dr:a:m:s";
44
my $debug   = 0;
45
my $silent  = 0;
46 47 48 49
my $user;
my @addlist = ();
my @modlist = ();	# Just changing the trust value ...
my @remlist = ();
50
my $optarg  = "";
51 52 53 54 55 56 57 58 59 60 61 62

#
# Configure variables
#
my $TB		   = "@prefix@";
my $TBOPS	   = "@TBOPSEMAIL@";
my $TBLOGS	   = "@TBLOGSEMAIL@";
my $CONTROL        = "@USERNODE@";
my $BOSSNODE       = "@BOSSNODE@";
my $WIKISUPPORT    = @WIKISUPPORT@;
my $BUGDBSUPPORT   = @BUGDBSUPPORT@;
my $CHATSUPPORT    = @CHATSUPPORT@;
63
my $ELABINELAB	   = @ELABINELAB@;
64
my $GENELISTS      = "$TB/sbin/genelists";
65
my $MODBUDDIES     = "$TB/sbin/modjabberbuddies";
66
my $SETCHATMEMBERS = "$TB/sbin/setchatmembers";
67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88
my $SETGROUPS	   = "$TB/sbin/setgroups";
my $SSH		   = "$TB/bin/sshtb";
my $SAVEUID	   = $UID;

#
# Untaint the path
#
$ENV{'PATH'} = "/bin:/usr/bin";
delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};

#
# Turn off line buffering on output
#
$| = 1;

#
# Load the Testbed support stuff.
#
use lib "@prefix@/lib";
use libaudit;
use libdb;
use libtestbed;
89
use User;
Mike Hibler's avatar
Mike Hibler committed
90 91
use Group;
use Project;
92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107

# Protos
sub fatal($);

#
# Please do not run it as root. Hard to track what has happened.
#
if ($UID == 0) {
    die("*** $0:\n".
	"    Please do not run this as root!\n");
}

#
# Parse command arguments. Once we return from getopts, all that should be
# left are the required arguments.
#
108
my %options = ();
109 110 111 112
if (! getopts($optlist, \%options)) {
    usage();
}
if (defined($options{"d"})) {
113 114
    $debug  = 1;
    $optarg = "-d";
115
}
116 117 118
if (defined($options{"s"})) {
    $silent  = 1;
}
119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168
if (defined($options{"a"})) {
    my @tokens = split(",", $options{"a"});

    foreach my $token (@tokens) {
	#
	# Untaint,
	#
	if ($token =~ /^([-\w]+):([-\w]+):([-\w]+)$/) {
	    push(@addlist, "$1:$2:$3");
	}
	else {
	    die("Bad data in token: $token.");
	}
    }
}
if (defined($options{"m"})) {
    my @tokens = split(",", $options{"m"});

    foreach my $token (@tokens) {
	#
	# Untaint,
	#
	if ($token =~ /^([-\w]+):([-\w]+):([-\w]+)$/) {
	    push(@modlist, "$1:$2:$3");
	}
	else {
	    die("Bad data in token: $token.");
	}
    }
}
if (defined($options{"r"})) {
    my @tokens = split(",", $options{"r"});

    foreach my $token (@tokens) {
	#
	# Untaint,
	#
	if ($token =~ /^([-\w]+):([-\w]+)$/) {
	    push(@remlist, "$1:$2");
	}
	else {
	    die("Bad data in token: $token.");
	}
    }
}
usage()
    if (@ARGV != 1);
$user = $ARGV[0];

# Untaint the user.
Leigh Stoller's avatar
Leigh Stoller committed
169
if ($user =~ /^([-\w]+)$/) {
170 171 172 173 174 175
    $user = $1;
}
else {
    die("Bad user name: $user.");
}

176 177 178 179
# Map invoking user to object.
my $this_user = User->ThisUser();
if (! defined($this_user)) {
    fatal("You ($UID) do not exist!");
180 181
}

182 183 184 185
# Map target user to object.
my $target_user = User->Lookup($user);
if (! defined($target_user)) {
    fatal("$user does not exist!");
186 187
}

188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206
#
# Permission checks. Do this later.
#

#
# This script is always audited. Mail is sent automatically upon exit.
#
if (AuditStart(0)) {
    #
    # Parent exits normally
    #
    exit(0);
}

#
# Add groups. Do any necessary callouts.
#
foreach my $token (@addlist) {
    my ($pid,$gid,$trust) = split(":", $token);
207
    my $sendemail = !$silent;
208

209 210 211 212
    my $group = Group->LookupByPidGid($pid, $gid);
    if (! defined($group)) {
	fatal("Cannot find group object for $pid/$gid");
    }
213 214 215 216
    my $project = $group->GetProject();
    if (! defined($project)) {
	fatal("Cannot find project object for $group");
    }
217

218 219 220 221 222
    #
    # Look to see if there is already an entry; this happens as the result
    # of either a join request or a new project request. The row is inserted
    # but with a trust value of "none". We probably want a pending table ...
    #
223
    my $membership = $group->LookupUser($target_user);
224

225
    if (defined($membership)) {
226 227 228
	#
	# Sanity check; better be trust=none.
	#
229
	my $curtrust = $membership->trust();
230 231 232

	fatal("Group membership table problem; ".
	      "$user,$pid,$gid,$trust,$curtrust\n")
233
	    if ($curtrust ne "none" && $curtrust ne $trust && !$ELABINELAB);
234

235
	$membership->ModifyTrust($trust) == 0
236
	    or fatal("Could not modify $membership to $trust");
237 238

	$sendemail = 0
239 240 241
	    if ($silent ||
		($ELABINELAB &&
		 $group->IsProjectGroup() && $group->IsLeader($target_user)));
242 243
    }
    else {
244
	$group->AddMemberShip($target_user, $trust) == 0
245
	    or fatal("Could not add $user to $group with trust $trust");
246
    }
247

248 249 250
    #
    # Tell chat subsystem to change groups ...
    #
251 252
    if ($CHATSUPPORT &&
	!($project->IsNonLocal() || $target_user->nocollabtools())) {
253
	system("$MODBUDDIES $optarg -a $pid:$gid $user") == 0 or
254
	    fatal("$MODBUDDIES '-a $pid:$gid $user' failed!");
255 256 257

	# Does not work yet.
	if (0) {
258
	    system("$SETCHATMEMBERS $optarg -g $gid $pid") == 0 or
259 260
		fatal("$SETCHATMEMBERS '-g $gid $pid' failed!");
	}
261
    }
262

263
    if ($sendemail && !$project->IsNonLocal()) {
264 265 266
	$group->SendApprovalEmail($this_user, $target_user) == 0 or
	    fatal("Could not send approval email to $target_user in $group");
    }
267 268 269 270 271 272 273 274
}

#
# Update groups. Do any necessary callouts.
#
foreach my $token (@modlist) {
    my ($pid,$gid,$trust) = split(":", $token);

275 276 277 278 279 280 281 282 283 284
    my $group = Group->LookupByPidGid($pid, $gid);
    if (! defined($group)) {
	fatal("Cannot find group object for $pid/$gid");
    }
    my $membership = $group->LookupUser($target_user);
    if (! defined($membership)) {
	fatal("Cannot find membership object for $user in $group");
    }
    $membership->ModifyTrust($trust) == 0
	    or fatal("Could not modify $membership to '$trust'");
285

286 287 288 289
    if (!$silent) {
	$group->SendTrustChangeEmail($this_user, $target_user) == 0
	    or fatal("Could not send trust change email to $target_user");
    }
290 291 292 293 294 295 296 297 298
}

#
# Remove groups. Do any necessary callouts.
#
foreach my $token (@remlist) {
    my ($pid,$gid)  = split(":", $token);
    my @delgroups   = ();

299 300 301 302
    my $group = Group->LookupByPidGid($pid, $gid);
    if (! defined($group)) {
	fatal("Cannot find group object for $pid/$gid");
    }
303 304 305 306
    my $project = $group->GetProject();
    if (! defined($project)) {
	fatal("Cannot find project object for $group");
    }
307

308 309 310 311 312 313
    #
    # Special case; if pid==gid then delete from the project entirely.
    # However, we want to get a list of the groups about to be deleted
    # so we know what to hand off to the callouts.
    #
    if ($pid eq $gid) {
314 315
	$project->DeleteUser($target_user, \@delgroups) == 0
	    or fatal("Could not delete $user from $project");
316 317
    }
    else {
318 319 320 321
	@delgroups = ($group);

	$group->DeleteMemberShip($target_user) == 0
	    or fatal("Could not delete $user from $group");
322 323
    }

324 325 326 327 328 329 330 331 332
    #
    # When deleting a user from a group, need to call genelists on the
    # project, not the user, since that information is obviously gone from
    # the DB, and genelists will not generate the right lists.
    #
    print "Updating email lists for project $pid\n";
    system("$GENELISTS $optarg -p $pid") == 0 or
	fatal("$GENELISTS -p $pid failed!");

333 334 335
    #
    # Tell chat subsystem to change groups ...
    #
336 337
    if ($CHATSUPPORT && 
	!($project->IsNonLocal() || $target_user->nocollabtools())) {
338
	my @optlist = ();
339

340 341 342 343
	foreach my $delgroup (@delgroups) {
	    my $groupname = $delgroup->gid();
	    
	    push(@optlist, "$pid:$groupname");
344

345 346
	    # Does not work yet.
	    if (0) {
347 348
		system("$SETCHATMEMBERS $optarg -g $groupname $pid") == 0 or
		    fatal("$SETCHATMEMBERS '-g $groupname $pid' failed!");
349
	    }
350
	}
351 352 353
	if (@optlist) {
	    my $chatargs = join(",", @optlist);
	
354
	    system("$MODBUDDIES $optarg -r $chatargs $user") == 0 or
355 356
		fatal("$MODBUDDIES '-r $chatargs $user' failed!");
	}
357 358 359
    }
}

360 361 362 363
#
# Call genelists, but just when adding or modifying. For deletion, it
# is handled above.
#
364
if (@addlist || @modlist) {
365 366 367 368 369 370
    print "Updating email lists for user $user\n";
    
    system("$GENELISTS $optarg -u $user") == 0 or
	fatal("$GENELISTS -u $user failed!");
}

371
#
Leigh Stoller's avatar
Leigh Stoller committed
372 373
# Finally, call setgroups to do the rest. Note that setgroups will skip
# nonlocal users. I wish I remember why.
374
#
Leigh Stoller's avatar
Leigh Stoller committed
375 376
system("$SETGROUPS $user") == 0 or
    fatal("$SETGROUPS $user failed!");
377

378 379 380 381
# XXX Mark the user as modified so the portal daemon withh push
# out the new groups. This is wrong, but a real fix will need to wait.
$target_user->BumpModified();

382 383 384 385 386 387 388 389
exit(0);

sub fatal($) {
    my($mesg) = $_[0];

    die("*** $0:\n".
	"    $mesg\n");
}