#!/usr/bin/perl -wT # # Copyright (c) 2005-2011 University of Utah and the Flux Group. # # {{{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 . # # }}} # use English; use Getopt::Std; use Errno qw(EEXIST); use strict; # # Control the privilege tables on the ops DB. # sub usage() { print STDOUT "Usage: opsdb_control adduser \n"; exit(-1); } my $optlist = "d"; my $debug = 0; # # Configure variables # my $TB = "@prefix@"; my $TBOPS = "@TBOPSEMAIL@"; my $CONTROL = "@USERNODE@"; my $BOSSNODE = "@BOSSNODE@"; my $OPSDBSUPPORT= @OPSDBSUPPORT@; # Locals my $SSH = "$TB/bin/sshtb"; my $OPSDBPROXY = "$TB/sbin/opsdb_control.proxy"; 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; # Protos sub AddUser(@); sub DelUser(@); sub AddProj(@); sub AddGroup(@); sub DelProj(@); sub DelGroup(@); sub SetGroups(@); sub SetGroupsAux(@); sub AddExpDB(@); sub DelExpDB(@); sub AddTempDB(@); sub LoadTempDB(@); sub DelTempDB(@); sub DumpExpDB(@); sub CleanExpDB(@); sub GraphDB(@); sub DoOpsStuff($;$); sub Initialize(); sub fatal($); # # Load the Testbed support stuff. # use lib "@prefix@/lib"; use libdb; use libtestbed; use User; use Group; use Experiment; # # We don't want to run this script unless its the real version. # if ($EUID != 0) { die("*** $0:\n". " Must be setuid! Maybe its a development version?\n"); } # # This script is setuid, so 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! Its already setuid!\n"); } # # If no opsdb support, just exit. # if (! $OPSDBSUPPORT) { print "OPS DB support is not enabled. Exiting ...\n"; exit(0); } # # Verify user and get his DB uid. # my $this_user = User->ThisUser(); if (! defined($this_user)) { fatal("You ($UID) do not exist!"); } my $user_dbid = $this_user->dbid(); my $user_uid = $this_user->uid(); # # Parse command arguments. Once we return from getopts, all that should be # left are the required arguments. # my %options = (); if (! getopts($optlist, \%options)) { usage(); } if (defined($options{"d"})) { $debug = 1; } usage() if (@ARGV < 1); my $action = shift(@ARGV); if ($action eq "adduser") { exit(AddUser(@ARGV)); } elsif ($action eq "deluser") { exit(DelUser(@ARGV)); } elsif ($action eq "addproj") { exit(AddProj(@ARGV)); } elsif ($action eq "addgroup") { exit(AddGroup(@ARGV)); } elsif ($action eq "delproj") { exit(DelProj(@ARGV)); } elsif ($action eq "delgroup") { exit(DelGroup(@ARGV)); } elsif ($action eq "setgroups") { exit(SetGroups(@ARGV)); } elsif ($action eq "addexpdb") { exit(AddExpDB(@ARGV)); } elsif ($action eq "delexpdb") { exit(DelExpDB(@ARGV)); } elsif ($action eq "addtempdb") { exit(AddTempDB(@ARGV)); } elsif ($action eq "deltempdb") { exit(DelTempDB(@ARGV)); } elsif ($action eq "loadtempdb") { exit(LoadTempDB(@ARGV)); } elsif ($action eq "dumpexpdb") { exit(DumpExpDB(@ARGV)); } elsif ($action eq "cleanexpdb") { exit(CleanExpDB(@ARGV)); } elsif ($action eq "graphdb") { exit(GraphDB(@ARGV)); } elsif ($action eq "setup") { exit(Initialize()); } else { die("*** $0:\n". " Do not know what to do with '$action'!\n"); } exit(0); # # Add user. The user ID and password are added to the user table on ops, # but with no privs to do anything. # sub AddUser(@) { my ($target_uid) = @_; usage() if (@_ != 1); # # Untaint args. # if ($target_uid =~ /^([-\w]+)$/) { $target_uid= $1; } else { die("Bad data in uid: $target_uid"); } # Map target user to object. my $target_user = User->Lookup($target_uid); fatal("No such user in DB: $target_uid!") if (!defined($target_uid)); my $password = $target_user->mailman_password(); fatal("No password defined for $target_uid!") if (!defined($password) || $password eq ""); print "Adding user '$target_uid' to mysql database on $CONTROL.\n"; my $retval = DoOpsStuff("adduser $target_uid", $password); if ($retval) { if ($retval == EEXIST()) { # Not an error. return 0; } fatal("$OPSDBPROXY failed on $CONTROL!"); } return 0; } # # Delete user. # sub DelUser(@) { my ($target_uid) = @_; usage() if (@_ != 1); # # Untaint args. # if ($target_uid =~ /^([-\w]+)$/) { $target_uid= $1; } else { die("Bad data in uid: $target_uid"); } # Map target user to object. my $target_user = User->Lookup($target_uid); fatal("No such user in DB: $target_uid!") if (!defined($target_uid)); print "Removing user '$target_uid' from mysql database on $CONTROL.\n"; my $retval = DoOpsStuff("deluser $target_uid"); if ($retval) { fatal("$OPSDBPROXY failed on $CONTROL!"); } return 0; } # # Add a project or group to the list of DBs on ops. # sub AddProj(@) { my ($pid) = @_; usage() if (@_ != 1); return AddGroup($pid, $pid); } sub AddGroup(@) { usage() if (@_ < 1 || @_ > 3); my $group = Group->Lookup($_[0], $_[1]); if (!defined($group)) { fatal("No such group: @_!"); } my $pid = $group->pid(); my $gid = $group->gid(); my $dbname = ($pid eq $gid ? $pid : "$pid,$gid"); print "Adding DB '$dbname' to mysql database on $CONTROL.\n"; my $retval = DoOpsStuff("adddb $dbname"); if ($retval) { fatal("$OPSDBPROXY failed on $CONTROL!"); } return 0; } # # Delete project or group from the list of DBs on ops. # sub DelProj(@) { my ($pid) = @_; usage() if (@_ != 1); return DelGroup($pid, $pid); } sub DelGroup(@) { usage() if (@_ < 1 || @_ > 3); my $group = Group->Lookup($_[0], $_[1]); if (!defined($group)) { fatal("No such group: @_!"); } my $pid = $group->pid(); my $gid = $group->gid(); my $dbname = ($pid eq $gid ? $pid : "$pid,$gid"); print "Removing DB '$dbname' from mysql database on $CONTROL.\n"; my $retval = DoOpsStuff("deldb $dbname"); if ($retval) { fatal("$OPSDBPROXY failed on $CONTROL!"); } return 0; } # # Set the groups for a user; the list of DBs they can use on OPS. # sub SetGroups(@) { my @userlist = (); usage() if (@_ < 1); foreach my $uid (@_) { my $user = User->Lookup($uid); if (! defined($user)) { fatal("No such user $uid!"); } push(@userlist, $user); } return SetGroupsAux(@userlist); } sub SetGroupsAux(@) { my @userlist = @_; my @uids = (); my $input = ""; foreach my $user (@userlist) { my @groups = (); my @glist = (); my $uid = $user->uid(); my $uid_idx = $user->uid_idx(); next if ($user->status() ne $User::USERSTATUS_ACTIVE); # Debug printf below. push(@uids, $uid); $user->GroupMembershipList(\@groups) == 0 or fatal("Could not get group list for $user"); foreach my $group (@groups) { my $pid = $group->pid(); my $gid = $group->gid(); if ($pid eq $gid) { push(@glist, "$pid"); } else { push(@glist, "$pid,$gid"); } # # Now get the per-experiment DBs inside this group. # my $experiment_result = $group->TableLookUp("experiments", "dpdbname", "dpdb!=0"); while (my ($dbname) = $experiment_result->fetchrow_array()) { if (defined($dbname) && $dbname ne "") { push(@glist, $dbname); } } # # Now get additional temporary DBs. # my $databases_result = $group->TableLookUp("datapository_databases", "dbname"); while (my ($dbname) = $databases_result->fetchrow_array()) { if (defined($dbname) && $dbname ne "") { push(@glist, $dbname); } } } $input .= "$uid @glist\n"; } print "Setting DB access list for user(s) '@uids' on $CONTROL.\n" if ($debug); my $retval = DoOpsStuff("setdbs", $input); if ($retval) { fatal("$OPSDBPROXY failed on $CONTROL!"); } return 0; } # # Add and delete a DB for a specific experiment. We use the pid,gid of # the experiment to set the access list. # sub AddExpDB(@) { my @uids = (); usage() if (@_ < 1 || @_ > 3); my $experiment = Experiment->Lookup($_[0], $_[1]); if (!defined($experiment)) { fatal("No such experiment: @_!"); } my $pid = $experiment->pid(); my $eid = $experiment->eid(); my $gid = $experiment->gid(); my $dbname = "${pid}+${eid}"; my $exptidx= $experiment->idx(); if (defined($experiment->dpdbname())) { # Allow override. $dbname = $experiment->dpdbname(); } my $dpdbpassword = $experiment->dpdbpassword(); if (!defined($dpdbpassword)) { $dpdbpassword = TBGenSecretKey(); $dpdbpassword = substr($dpdbpassword, 0, 10); } # # XXX See if there are any traced links. # my $query_result = $experiment->TableLookUp("virt_lans", "vname", "trace_db!=0"); my $traceflag = ($query_result->numrows ? "-s" : ""); print "Adding DB '$dbname' to mysql database on $CONTROL.\n"; my $retval = DoOpsStuff("adddb $dbname $traceflag"); if ($retval) { fatal("$OPSDBPROXY failed on $CONTROL!"); } $experiment->Update({"dpdbpassword" => "$dpdbpassword", "dpdbname" => "$dbname"}) == 0 or fatal("Could not update dpdbname,dpdbpassword for $experiment"); $experiment->TableUpdate("experiment_stats", {"dpdbname" => "$dbname"}) == 0 or fatal("Could not update experiment_stats for $experiment"); # # Add a user that is named by this DB, with the password from above. # Basically, a per-experiment DB user for the DB. # my $dbuser = "E${exptidx}"; print "Adding user '$dbuser' to mysql database on $CONTROL.\n"; $retval = DoOpsStuff("adduser $dbuser", $dpdbpassword); if ($retval && $retval != EEXIST()) { fatal("$OPSDBPROXY failed on $CONTROL!"); } $retval = DoOpsStuff("setdbs", "$dbuser $dbname"); if ($retval) { fatal("$OPSDBPROXY failed on $CONTROL!"); } # # Now we need to update the access list for everyone in the group. # my $group = $experiment->GetGroup(); my @userlist; $group->MemberList(\@userlist) == 0 or fatal("Could not get user list for $group"); SetGroupsAux(@userlist) == 0 or fatal("Could not setgroups for $experiment!"); return 0; } sub DelExpDB(@) { usage() if (@_ < 1 || @_ > 3); my $experiment = Experiment->Lookup($_[0], $_[1]); if (!defined($experiment)) { return 0; } my $dbname = $experiment->dpdbname(); my $exptidx= $experiment->idx(); return -1 if (!defined($dbname)); # # Always do a DB removal if the experiment has a dbname set. # my $dbuser = "E${exptidx}"; print "Deleting user '$dbuser' from mysql database on $CONTROL.\n"; my $retval = DoOpsStuff("deluser $dbuser"); if ($retval) { fatal("$OPSDBPROXY failed on $CONTROL!"); } print "Removing DB '$dbname' from mysql database on $CONTROL.\n"; $retval = DoOpsStuff("deldb $dbname"); if ($retval) { fatal("$OPSDBPROXY failed on $CONTROL!"); } # # No need to do a setgroups here since the proxy on ops removed the # DB and any user entries for that DB. # return 0; } sub DumpExpDB(@) { usage() if (@_ < 2 || @_ > 4); my $experiment = Experiment->Lookup($_[0], $_[1]); if (!defined($experiment)) { return 0; } my $dbname = $experiment->dpdbname(); my $filename = $_[2]; if ($filename =~ /^([-\w\/\.\+,]+)$/) { $filename = $1; } else { die("Bad data in filename: $filename"); } return -1 if (!defined($dbname)); print "Dumping mysql DB '$dbname' to $filename on $CONTROL.\n"; my $retval = DoOpsStuff("dumpdb $dbname $filename"); if ($retval) { fatal("$OPSDBPROXY failed on $CONTROL!"); } return 0; } sub CleanExpDB(@) { usage() if (@_ < 2 || @_ > 3); my $experiment = Experiment->Lookup($_[0], $_[1]); if (!defined($experiment)) { return 0; } my $dbname = $experiment->dpdbname(); return -1 if (!defined($dbname)); print "Cleaning mysql DB '$dbname' on $CONTROL.\n"; my $retval = DoOpsStuff("cleandb $dbname"); if ($retval) { fatal("$OPSDBPROXY failed on $CONTROL!"); } return 0; } # # Add a temporary DB to ops. # sub AddTempDB(@) { usage() if (@_ < 2 || @_ > 4); my $group = Group->Lookup($_[0], $_[1]); if (!defined($group)) { fatal("No such group: @_!"); } my $dbname = $_[2]; if ($dbname =~ /^([-\w,\+]+)$/) { $dbname = $1; } else { die("Bad data in dbname: $dbname"); } my $pid = $group->pid(); my $gid = $group->gid(); my $pid_idx = $group->pid_idx(); my $gid_idx = $group->gid_idx(); # # Add DB record for it. # if (! DBQueryWarn("insert into datapository_databases set ". " pid='$pid', gid='$gid', ". " pid_idx='$pid_idx', gid_idx='$gid_idx', ". " uid='$user_uid', uid_idx='$user_dbid', ". " dbname='$dbname', created=now()")) { fatal("Failed to add temporary dbname to database"); } print "Adding temporary DB '$dbname' to mysql database on $CONTROL.\n"; my $retval = DoOpsStuff("adddb $dbname -t"); if ($retval) { DBQueryWarn("delete from datapository_databases ". "where dbname='$dbname'"); fatal("$OPSDBPROXY failed on $CONTROL!"); } # # Now we need to update the access list for everyone in the group. # my @userlist; $group->MemberList(\@userlist) == 0 or fatal("Could not get user list for $group"); SetGroupsAux(@userlist) == 0 or fatal("Could not setgroups for $group!"); return 0; } sub LoadTempDB(@) { my ($dbname, $filename) = @_; usage() if (@_ != 2); # # Untaint args. # if ($dbname =~ /^([-\w,\+]+)$/) { $dbname = $1; } else { die("Bad data in dbname: $dbname"); } if ($filename =~ /^([-\w\/\.\+,]+)$/) { $filename = $1; } else { die("Bad data in filename: $filename"); } print "Loading mysql DB '$dbname' from $filename on $CONTROL.\n"; my $retval = DoOpsStuff("loaddb $dbname $filename"); if ($retval) { fatal("$OPSDBPROXY failed on $CONTROL!"); } return 0; } # # Delete a temporary DB from ops. # sub DelTempDB(@) { my ($dbname) = @_; usage() if (@_ != 1); # # Untaint args. # if ($dbname =~ /^([-\w,\+]+)$/) { $dbname = $1; } else { die("Bad data in dbname: $dbname"); } print "Deleting temporary DB '$dbname' from mysql database on $CONTROL.\n" if ($debug); my $retval = DoOpsStuff("deldb $dbname"); if ($retval) { fatal("$OPSDBPROXY failed on $CONTROL!"); } DBQueryFatal("delete from datapository_databases ". "where dbname='$dbname'"); return 0; } # # Hack graph support. # sub GraphDB(@) { usage() if (@_ < 2); my $dbname = shift(@_); my $which = shift(@_); my @optargs = (); # # Untaint args. # if ($dbname =~ /^([-\w,\+]+)$/) { $dbname = $1; } else { die("Bad data in dbname: $dbname"); } if ($which =~ /^([\w]*)$/) { $which = $1; } else { die("Bad data in which: $which"); } # Check the rest of the args with a generic test. foreach my $arg (@_) { # Note different taint check (allow /). if ($arg =~ /^([-\w\.\/\:]+)$/) { $arg = $1; } else { tbdie("Bad data in argument: $arg"); } push(@optargs, $arg); } print "Graphing DB '$dbname' from mysql database on $CONTROL.\n" if ($debug); my $retval = DoOpsStuff("graphdb $dbname $which @optargs"); if ($retval) { fatal("$OPSDBPROXY failed on $CONTROL!"); } return 0; } # Wrapper for ssh. sub DoOpsStuff($;$) { my ($remote_command, $input) = @_; my $retval = 0; my $flag = ($debug ? "-d" : ""); my ($command); # For ssh. $UID = $EUID; $command = "$SSH -host $CONTROL $OPSDBPROXY $flag $remote_command"; print "Running command '$command'\n" if ($debug); # Echo stuff to remote stdin if (defined($input) && $input ne "") { if (!open(OPS, "| $command")) { print "Could not start '$SSH'\n"; $retval = -1; } else { print OPS $input; if (! close(OPS)) { print(($! ? "Error closing pipe: $!\n" : "Exit status $? from pipe\n")); $retval = -1; } } } else { system($command); if ($?) { $retval = $? >> 8; } } $EUID = $SAVEUID; return $retval; } # # Initial setup for pre-existing emulabs. # sub Initialize() { my @uids = (); # # Initialize a mailman password for all users, just in case not set. # my $query_result = DBQueryFatal("select uid,uid_idx from users ". "where mailman_password is NULL"); while (my ($uid,$uid_idx) = $query_result->fetchrow_array()) { print "Setting initial mailman password for $uid ($uid_idx)\n" if ($debug); my $password = TBGenSecretKey(); $password = substr($password, 0, 10); DBQueryFatal("update users set mailman_password='$password' ". "where uid_idx='$uid_idx'"); } # # Now add all active users. # my $users_result = DBQueryFatal("select distinct g.uid ". " from group_membership as g ". "left join users as u on u.uid_idx=g.uid_idx ". "where u.status='active' or u.status='frozen' ". # " and (g.pid='testbed' or g.pid='emulab-ops' or ". # " g.pid='tbres' or g.pid='utahstud')" . "order by u.admin"); # Need to do this when we want to seek around inside the results. $users_result = $users_result->WrapForSeek(); while (my ($uid) = $users_result->fetchrow_array()) { AddUser(($uid)) == 0 or fatal("Could not add user $uid to DB on $CONTROL"); } # # Add project and group DBs. # my $projects_result = DBQueryFatal("select g.pid,g.gid from groups as g ". "left join projects as p on p.pid_idx=g.pid_idx ". "where p.approved=1 ". # "and (p.pid='testbed' or p.pid='emulab-ops' or ". # " p.pid='tbres' or p.pid='utahstud') ". ""); while (my ($pid,$gid) = $projects_result->fetchrow_array()) { AddGroup(($pid, $gid)) == 0 or fatal("Could not add database for $pid,$gid to DB on $CONTROL"); } # # Now go back to the users list and set the access lists # $users_result->dataseek(0); while (my ($uid) = $users_result->fetchrow_array()) { push(@uids, $uid); } SetGroups(@uids) == 0 or fatal("Could not set groups for @uids!"); return 0; } sub fatal($) { my($mesg) = $_[0]; die("*** $0:\n". " $mesg\n"); }