#!/usr/bin/perl -wT # # Copyright (c) 2000-2015 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 strict; use English; use Getopt::Std; use Socket; use File::Basename; use File::Temp qw(tempfile :POSIX ); use POSIX qw(:signal_h); use POSIX ":sys_wait_h"; use File::stat; # # Parse an ns file. Since the parser runs arbitrary NS file for the user, # this cannot be safely done on boss without jumping through huge hoops # to secure tcl and the DB. Yuck! So, instead of running the parser on boss, # we run it over on ops. This first version operates like this: # # NB: This script is setuid. # sub usage() { print STDOUT "Usage: rungenilib [options] infile\n"; exit(-1); } my $optlist = "do:pb:W"; my $debug = 0; my $getparams = 0; my $paramfile; my $ofile; my $warningsfatal = 0; # # Configure variables # my $TB = "@prefix@"; my $TBOPS = "@TBOPSEMAIL@"; my $CONTROL = "@USERNODE@"; # Locals my $SAVEUID = $UID; my $this_user; my $file; # Protos sub fatal($); # # Turn off line buffering on output # $| = 1; # # Untaint the path # $ENV{'PATH'} = "$TB/bin:$TB/sbin:/bin:/usr/bin:/sbin:/usr/sbin"; delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'}; if ($EUID != 0) { # We don't want to run this script unless its the real version. die("Must be root! Maybe its a development version?"); } # This script is setuid, so please do not run it as root. Hard to track # what has happened. if ($UID == 0) { die("Please do not run this as root! Its already setuid!"); } # # Testbed Support libraries # use lib "@prefix@/lib"; use libdb; use libtestbed; use User; # # Parse command arguments. Once we return from getopts, all that should # left are the required arguments. # my %options = (); if (! getopts($optlist, \%options)) { usage(); } if (defined($options{"d"})) { $debug = 1; } if (defined($options{"p"})) { $getparams = 1; } if (defined($options{"W"})) { $warningsfatal = 1; } if (defined($options{"b"})) { $paramfile = $options{"b"}; # Must taint check! if ($paramfile =~ /^([-\w\/\.]+)$/) { $paramfile = $1; } else { die("Bad data in argument: $paramfile."); } } if (defined($options{"o"})) { $ofile = $options{"o"}; } if (@ARGV != 1) { usage(); } $file = $ARGV[0]; # # Must taint check! # if ($file =~ /^([-\w\/\.]+)$/) { $file = $1; } else { die("Bad data in argument: $file."); } if (defined($ofile)) { if ($ofile =~ /^([-\w\/\.]+)$/) { $ofile = $1; } else { die("Bad data in argument: $ofile."); } } # # Get DB uid for sending over to ops. # $this_user = User->ThisUser(); if (! defined($this_user)) { tbdie("You ($UID) do not exist!"); } # Run as the user for most of this script. $EUID = $UID; # # If looking for the parameter definition block, then see if there # are any define_parameter calls. If so, run the script to get the # JSON block. # if ($getparams && system("egrep -q -s 'defineParameter' $file")) { # No parameters, return empty block. if (defined($ofile)) { system("echo '' > $ofile"); } exit(0); } my $infile = tmpnam(); my $outfile = tmpnam(); # # Touch the output file, to avoid a root owned, 644 file. # system("touch $outfile") == 0 or fatal("Could not create $outfile"); # # Build up a new command line to run the parser on ops, writing the # result back to a file if not in anonmode. Remember to tack on the # user ID to flip to, when not in testmode. # my $cmdargs = "$TB/libexec/rungenilib.proxy"; # lets try out the python jail $cmdargs .= " -J"; $cmdargs .= " -u " . $this_user->uid(); $cmdargs .= ($getparams ? " -p " : ""); $cmdargs .= ($warningsfatal ? " -W " : ""); # # We want to send over both files via STDIN, so combine them, and pass # the first file size with the -b option # if ($paramfile) { system("cat $paramfile > $infile") == 0 or fatal("Could not copy $paramfile to $infile"); $cmdargs .= " -b " . stat($infile)->size; system("cat $file >> $infile") == 0 or fatal("Could not concat $file to $infile"); } else { system("cat $file > $infile") == 0 or fatal("Could not copy $file to $infile"); } $cmdargs = "sshtb -host $CONTROL $cmdargs < $infile"; if ($debug) { print $cmdargs . "\n"; } # # Run parser, redirecting stdout to a file to capture the parser results. # Stderr is redirected to the ERR filehandle # Must flip to real root to run ssh. $EUID = $UID = 0; open ERR, "$cmdargs 2>&1 >> $outfile |"; $EUID = $UID = $SAVEUID; # # Now read in the results from stderr. # my $errs = ""; while () { $errs .= $_; } close(ERR); my $exit_status = $?; if ($exit_status) { if (WIFSIGNALED($exit_status)) { # The POSIX module doesn't create constants for valid signals # (including SIGBUS), thus we have to do it the hard way. # Get the mapping from signal num. to name use Config; my (%sig_num, @sig_name); my @names = split ' ', $Config{sig_name}; @sig_num{@names} = split ' ', $Config{sig_num}; foreach (@names) {$sig_name[$sig_num{$_}] ||= $_} my $signal = WTERMSIG($exit_status); my $signame = $sig_name[$signal]; if (grep {$_ eq $signame} qw(ILL TRAP EMT FPE BUS SEGV SYS)) { SENDMAIL($TBOPS, "geni-lib converter Crashed", "$errs\n", undef, undef, $file); } fatal("Failed to convert genilib script!"); } if (defined($ofile)) { if (open(OFILE, "> $ofile")) { print OFILE $errs; close(OFILE); } } else { print STDERR $errs; } unlink($outfile); unlink($infile); exit(1); } if (defined($ofile)) { system("cat $outfile > $ofile"); } else { system("cat $outfile"); } unlink($outfile); unlink($infile); exit(0); sub fatal($) { my ($mesg) = $_[0]; print STDERR "*** $0:\n". " $mesg\n"; unlink($outfile) if (defined($outfile)); unlink($infile) if (defined($infile)); exit(-1); }