diff --git a/backend/GNUmakefile.in b/backend/GNUmakefile.in index 0920c92f4fe38a2eecda3fb55e05dda27bc3d425..e5c818c4d1a3e5a624f78671db89385b8466f599 100644 --- a/backend/GNUmakefile.in +++ b/backend/GNUmakefile.in @@ -13,9 +13,9 @@ UNIFIED = @UNIFIED_BOSS_AND_OPS@ include $(OBJDIR)/Makeconf BIN_SCRIPTS = moduserinfo newgroup newmmlist editexp editimageid \ - editnodetype + editnodetype editsitevars WEB_BIN_SCRIPTS = webmoduserinfo webnewgroup webnewmmlist webeditimageid \ - webeditnodetype + webeditnodetype webeditsitevars WEB_SBIN_SCRIPTS= LIBEXEC_SCRIPTS = $(WEB_BIN_SCRIPTS) $(WEB_SBIN_SCRIPTS) diff --git a/backend/editsitevars.in b/backend/editsitevars.in new file mode 100644 index 0000000000000000000000000000000000000000..c69d4172881e2f413dc78f2269f610364cae0909 --- /dev/null +++ b/backend/editsitevars.in @@ -0,0 +1,282 @@ +#!/usr/bin/perl -wT +# +# EMULAB-COPYRIGHT +# Copyright (c) 2000-2007 University of Utah and the Flux Group. +# All rights reserved. +# +use English; +use strict; +use Getopt::Std; +use XML::Simple; +use Data::Dumper; + +# +# Back-end script to edit site variables. +# +sub usage() +{ + print("Usage: editsitevars [-v] <xmlfile>\n"); + exit(-1); +} +my $optlist = "dv"; +my $debug = 0; +my $verify = 0; # Check data and return status only. + +# +# Configure variables +# +my $TB = "@prefix@"; +my $TBOPS = "@TBOPSEMAIL@"; +my $TBAUDIT = "@TBAUDITEMAIL@"; + +# +# Untaint the path +# +$ENV{'PATH'} = "$TB/bin:$TB/sbin:/bin:/usr/bin:/usr/bin:/usr/sbin"; +delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'}; + +# +# Turn off line buffering on output +# +$| = 1; + +# +# Load the Testbed support stuff. +# +use lib "@prefix@/lib"; +use libdb; +use libtestbed; +use User; +use Project; + +# Protos +sub fatal($); +sub UserError(;$); +sub escapeshellarg($); + +# +# 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; +} +if (defined($options{"v"})) { + $verify = 1; +} +if (@ARGV != 1) { + usage(); +} +my $xmlfile = shift(@ARGV); + +# +# Map invoking user to object. +# If invoked as "nobody" we are coming from the web interface and the +# current user context is "implied" (see tbauth.php3). +# +my $this_user; + +if (getpwuid($UID) ne "nobody") { + $this_user = User->ThisUser(); + + if (! defined($this_user)) { + fatal("You ($UID) do not exist!"); + } + fatal("You must have admin privledges to edit site variables.") + if (!$this_user->IsAdmin()); +} +else { + # + # Check the filename when invoked from the web interface; must be a + # file in /tmp. + # + if ($xmlfile =~ /^([-\w\.\/]+)$/) { + $xmlfile = $1; + } + else { + fatal("Bad data in pathname: $xmlfile"); + } + + # Use realpath to resolve any symlinks. + my $translated = `realpath $xmlfile`; + if ($translated =~ /^(\/tmp\/[-\w\.\/]+)$/) { + $xmlfile = $1; + } + else { + fatal("Bad data in translated pathname: $xmlfile"); + } + + # The web interface (and in the future the xmlrpc interface) sets this. + $this_user = User->ImpliedUser(); + + if (! defined($this_user)) { + fatal("Cannot determine implied user!"); + } +} + +# +# These are the fields that we allow to come in from the XMLfile. +# +my $SLOT_OPTIONAL = 0x1; # The field is not required. +my $SLOT_REQUIRED = 0x2; # The field is required and must be non-null. +my $SLOT_ADMINONLY = 0x4; # Only admins can set this field. +# +# XXX We should encode all of this in the DB so that we can generate the +# forms on the fly, as well as this checking code. +# +my %xmlfields = + # XML Field Name DB slot name Flags Default + ("name" => ["name", $SLOT_REQUIRED], + "value" => ["value", $SLOT_OPTIONAL], + "reset" => ["reset", $SLOT_OPTIONAL]); + +# +# Must wrap the parser in eval since it exits on error. +# +my $xmlparse = eval { XMLin($xmlfile, + VarAttr => 'name', + ContentKey => '-content', + SuppressEmpty => undef); }; +fatal($@) + if ($@); + +# +# Process and dump the errors (formatted for the web interface). +# We should probably XML format the errors instead but not sure I want +# to go there yet. +# +my %errors = (); + +# +# Make sure all the required arguments were provided. +# +my $key; +foreach $key (keys(%xmlfields)) { + my (undef, $required, undef) = @{$xmlfields{$key}}; + + $errors{$key} = "Required value not provided" + if ($required & $SLOT_REQUIRED && + ! exists($xmlparse->{'attribute'}->{"$key"})); +} +UserError() + if (keys(%errors)); + +# +# We build up an array of arguments to reference as we check +# the attributes. +# +my %editsitevars_args = (); + +foreach $key (keys(%{ $xmlparse->{'attribute'} })) { + my $value = $xmlparse->{'attribute'}->{"$key"}->{'value'}; + + if ($debug) { + print STDERR "User attribute: '$key' -> '$value'\n"; + } + + $errors{$key} = "Unknown attribute" + if (!exists($xmlfields{$key})); + + my ($dbslot, $required, $default) = @{$xmlfields{$key}}; + + if ($required & $SLOT_REQUIRED) { + # A slot that must be provided, so do not allow a null value. + if (!defined($value)) { + $errors{$key} = "Must provide a non-null value"; + next; + } + } + if ($required & $SLOT_OPTIONAL) { + # Optional slot. If value is null skip it. Might not be the correct + # thing to do all the time? + if (!defined($value)) { + next + if (!defined($default)); + $value = $default; + } + } + if ($required & $SLOT_ADMINONLY) { + # Admin implies optional, but thats probably not correct approach. + $errors{$key} = "Administrators only" + if (! $this_user->IsAdmin()); + } + + # Now check that the value is legal. + if (! TBcheck_dbslot($value, "sitevariables", + $dbslot, TBDB_CHECKDBSLOT_ERROR)) { + $errors{$key} = TBFieldErrorString(); + next; + } + + $editsitevars_args{$dbslot} = $value; +} +UserError() + if (keys(%errors)); + +# +# Now do special checks. +# + +if (!exists($editsitevars_args{"value"}) && + !exists($editsitevars_args{"reset"})) { + UserError("Site Var: Must give a new value, or reset it to default."); +} +if (exists($editsitevars_args{"value"}) && + exists($editsitevars_args{"reset"})) { + UserError("Site Var: Can't both give a new value, and reset to default."); +} + +exit(0) + if ($verify); + +# +# Now safe to set the site variable value. +# +my $name = $editsitevars_args{"name"}; +my $value = exists($editsitevars_args{"reset"}) ? "NULL" : + "'" . escapeshellarg($editsitevars_args{"value"}) . "'"; + +DBQueryFatal("UPDATE sitevariables ". + "SET value=$value ". + "WHERE name='$name'"); +exit(0); + + +sub fatal($) +{ + my ($mesg) = @_; + + print STDERR "*** $0:\n". + " $mesg\n"; + # Exit with negative status so web interface treats it as system error. + exit(-1); +} + +sub UserError(;$) +{ + my ($mesg) = @_; + + if (keys(%errors)) { + foreach my $key (keys(%errors)) { + my $val = $errors{$key}; + print "${key}: $val\n"; + } + } + print "$mesg\n" + if (defined($mesg)); + + # Exit with positive status so web interface treats it as user error. + exit(1); +} + +sub escapeshellarg($) +{ + my ($str) = @_; + + $str =~ s/[^[:alnum:]]/\\$&/g; + return $str; +} diff --git a/configure b/configure index 725c8ead1e0cd4d4b92a30ddc561c62e6c5c4946..3c33e5433dfafb67674339a84aa8cb45952ce521 100755 --- a/configure +++ b/configure @@ -2429,6 +2429,7 @@ outfiles="$outfiles Makeconf GNUmakefile \ account/quotamail account/mkusercert account/newproj account/newuser \ backend/GNUmakefile backend/moduserinfo backend/newgroup \ backend/newmmlist backend/editexp backend/editimageid backend/editnodetype \ + backend/editsitevars \ tbsetup/GNUmakefile tbsetup/console_setup tbsetup/spewlogfile \ tbsetup/spewrpmtar tbsetup/gentopofile tbsetup/power_sgmote.pm \ tbsetup/console_reset tbsetup/bwconfig tbsetup/power_rpc27.pm \ diff --git a/configure.in b/configure.in index 78230730aa97bdefa0049cea66df719341ea48cc..a2f954815fb07b639b9dc0cca86a98a99e0ae50d 100755 --- a/configure.in +++ b/configure.in @@ -809,6 +809,7 @@ outfiles="$outfiles Makeconf GNUmakefile \ account/quotamail account/mkusercert account/newproj account/newuser \ backend/GNUmakefile backend/moduserinfo backend/newgroup \ backend/newmmlist backend/editexp backend/editimageid backend/editnodetype \ + backend/editsitevars \ tbsetup/GNUmakefile tbsetup/console_setup tbsetup/spewlogfile \ tbsetup/spewrpmtar tbsetup/gentopofile tbsetup/power_sgmote.pm \ tbsetup/console_reset tbsetup/bwconfig tbsetup/power_rpc27.pm \ diff --git a/sql/database-fill.sql b/sql/database-fill.sql index da646eceb2604051006aff8d3588bd16a37fa569..63f8f8029285c5315d4bb3833b0ce6dbf77dc0fb 100644 --- a/sql/database-fill.sql +++ b/sql/database-fill.sql @@ -897,6 +897,12 @@ REPLACE INTO table_regex VALUES ('os_info','op_mode','text','regex','^[-\\w]*$', REPLACE INTO table_regex VALUES ('os_info','nextosid','text','redirect','os_info:osid',0,0,NULL); REPLACE INTO table_regex VALUES ('os_info','reboot_waittime','int','redirect','default:int',0,2000,NULL); +REPLACE INTO table_regex VALUES ('sitevariables','name','text','regex','^[\\w\\/]+$',1,255,NULL); +REPLACE INTO table_regex VALUES ('sitevariables','value','text','redirect','default:text',0,0,NULL); +REPLACE INTO table_regex VALUES ('sitevariables','reset','text','redirect','default:boolean',0,0,NULL); +REPLACE INTO table_regex VALUES ('sitevariables','defaultvalue','text','redirect','default:text',0,0,NULL); +REPLACE INTO table_regex VALUES ('sitevariables','description','text','redirect','default:text',0,0,NULL); + -- -- Dumping data for table `testsuite_preentables` -- diff --git a/www/editsitevars.php3 b/www/editsitevars.php3 index cd682bcf9eaa11f648495058429afe960c02a73a..3749b0946c516d07ac758dc18ee967d7da6a505d 100644 --- a/www/editsitevars.php3 +++ b/www/editsitevars.php3 @@ -23,20 +23,108 @@ if (! $isadmin) { } # -# Verify Page agruments. +# Verify page arguments. # -$optargs = OptionalPageArguments("edit", PAGEARG_STRING, - "name", PAGEARG_STRING, - "value", PAGEARG_ANYTHING, +$optargs = OptionalPageArguments(# Edit greenballs pull up an Edit subform. + "edit", PAGEARG_STRING, + "formfields", PAGEARG_ARRAY, + # Edit has three submit buttons. "defaulted", PAGEARG_STRING, "edited", PAGEARG_STRING, "canceled", PAGEARG_STRING); -if (isset($edit)) { +function SPIT_MSGS($message, $errors) +{ + if ($message !== "") { + echo "<H3>$message</H3>\n"; + } + + if ($errors) { + echo "<table class=nogrid + align=center border=0 cellpadding=6 cellspacing=0> + <tr> + <th align=center colspan=2> + <font size=+1 color=red> + Oops, please fix the following errors! + </font> + </td> + </tr>\n"; + + while (list ($name, $text) = each ($errors)) { + echo "<tr> + <td align=right> + <font color=red>$name: </font></td> + <td align=left> + <font color=red>$text</font></td> + </tr>\n"; + } + echo "</table><br>\n"; + } +} + +function SPITFORM($message, $errors) { + SPIT_MSGS($message, $errors); + + echo "<table> + <tr> + <th> Name </th> + <th> Value </th> + <th><font size='-1'>Edit</font></th> + <th> Default Value </th> + <th> Description </th> + </tr>"; $result = DBQueryFatal("SELECT name, value, defaultvalue, description ". "FROM sitevariables ". - "WHERE name='$edit'"); + "ORDER BY name"); + + while ($row = mysql_fetch_row($result)) { + $name = $row[0]; + $value = $row[1]; + $defaultvalue = $row[2]; + $description = $row[3]; + $cginame = urlencode($name); + + echo "<tr><td> <b>$name</b> </td>\n"; + + echo "<td> "; + if (isset($value)) { + if (0 != strcmp($value, "")) { + $wrapped_value = wordwrap($value, 30, "<br />", 1); + echo "<code>$wrapped_value</code> </td>"; + } else { + echo "<font color='#00B040'><i>empty string</i></font> </td>"; + } + } else { + echo "<font color='#00B040'><i>default</i></font> </td>"; + } + echo "<td align=center>"; + echo " <a href='editsitevars.php3?edit=$cginame'>"; + echo "<img border='0' src='greenball.gif' /></a>"; + echo " </td>"; + + echo "<td nowrap='1'> "; + if (0 != strcmp($defaultvalue, "")) { + echo "<code>$defaultvalue</code> </td>"; + } else { + echo "<font color='#00B040'><i>empty string</i></font> </td>"; + } + + echo "<td> $description </td></tr>\n"; + } + + echo "</table>"; +} + +# The "Edit" greenball column displays an edit subform for individual vars. +function SPIT_SUBFORM($formfields, $message, $errors) +{ + SPIT_MSGS($message, $errors); + + $name = $formfields["name"]; + $result = DBQueryFatal("SELECT name, value, defaultvalue, description ". + "FROM sitevariables ". + "WHERE name='$name'"); if ($row = mysql_fetch_row($result)) { $name = $row[0]; @@ -46,7 +134,10 @@ if (isset($edit)) { echo "<center>"; echo "<form action='editsitevars.php3' method='post'>"; - echo "<input type='hidden' name='name' value='$name'></input>"; + + echo "<input type='hidden' name=\"formfields[name]\" + value=\"" . $formfields["name"] . "\">"; + echo "<table><tr><th colspan=2>"; echo "Editing site variable '<b>$name</b>':"; echo "</th></tr><tr><td>"; @@ -59,116 +150,168 @@ if (isset($edit)) { echo "<font color='#00B040'><i>Empty String</i></font>"; } echo "</td></tr><tr><td> </td><td>"; - echo "<input type='submit' name='defaulted' value='Reset to Default Value'></input>"; + + echo "<input type='submit' name='defaulted' ". + " value='Reset to Default Value'></input>"; + echo "</td></tr><tr><td>"; echo "<b>New value:</b></td><td>"; - echo "<input size='60' type='text' name='value' value='$value'></input>"; + echo "<input size='60' type='text' + name=\"formfields[value]\" + value=\"" . $value . "\">"; echo "</td></tr><tr><td> </td><td>"; - echo "<input type='submit' name='edited' value='Change to New Value'></input>"; + + echo "<input type='submit' name='edited' + value='Change to New Value'></input>"; echo " "; - echo "<input type='submit' name='canceled' value='Cancel'></input>"; + + echo "<input type='submit' name='canceled' + value='Cancel'>"; + echo "</td></tr></table>"; echo "</form>"; echo "</center>"; - - } else { - TBERROR("Couldn't find variable '$edit'!"); } - - PAGEFOOTER(); - return; } -if (isset($edited)) { - $value = addslashes("$value"); - - DBQueryFatal("UPDATE sitevariables ". - "SET value='$value' ". - "WHERE name='$name'"); - - echo "<h3>'$name' changed.</h3>"; - echo "<form action='editsitevars.php3' method='get'>"; - echo "<input type='submit' name='yadda' value='Return to list'></input>"; - echo "</form>"; - PAGEFOOTER(); - return; -} +# +# Accumulate error reports for the user, e.g. +# $errors["Key"] = "Msg"; +# Required page args may need to be checked early. +$errors = array(); -if (isset($defaulted)) { - DBQueryFatal("UPDATE sitevariables ". - "SET value=NULL ". - "WHERE name='$name'"); +# Show an optional status message. +$message = ""; - echo "<h3>'$name' reset to default.</h3>"; - echo "<form action='editsitevars.php3' method='get'>"; - echo "<input type='submit' name='yadda' value='Return to list'></input>"; - echo "</form>"; +# +# On first load, just display current values. +# +if (!isset($edit) && + !isset($edited) && !isset($defaulted) && !isset($canceled)) { + SPITFORM(0, $errors); PAGEFOOTER(); return; } -if (isset($canceled)) { - echo "<h3>Operation canceled.</h3>"; - echo "<form action='editsitevars.php3' method='get'>"; - echo "<input type='submit' name='yadda' value='Return to list'></input>"; - echo "</form>"; - PAGEFOOTER(); - return; +# The "Edit" greenball column displays an edit subform for individual vars. +if (isset($edit)) { + if (!TBSiteVarExists($edit)) { + USERERROR("Couldn't find variable '$edit'!", 1); + } + else { + $formfields["name"] = $edit; + SPIT_SUBFORM($formfields, $message, $errors); + PAGEFOOTER(); + return; + } } -$result = DBQueryFatal("SELECT name, value, defaultvalue, description ". - "FROM sitevariables ". - "ORDER BY name"); - -echo "<table> - <tr> - <th> Name </th> - <th> Value </th> - <th><font size='-1'>Edit</font></th> - <th> Default Value </th> - <th> Description </th> - </tr>"; - -while ($row = mysql_fetch_row($result)) { - $name = $row[0]; - $value = $row[1]; - $defaultvalue = $row[2]; - $description = $row[3]; - $cginame = urlencode($name); - - echo "<tr><td> <b>$name</b> </td>\n"; - - echo "<td> "; - if (isset($value)) { - if (0 != strcmp($value, "")) { - $wrapped_value = wordwrap($value, 30, "<br />", 1); - echo "<code>$wrapped_value</code> </td>"; - } else { - echo "<font color='#00B040'><i>empty string</i></font> </td>"; - } - } else { - echo "<font color='#00B040'><i>default</i></font> </td>"; - } - echo "<td align=center>"; - echo " <a href='editsitevars.php3?edit=$cginame'>"; - echo "<img border='0' src='greenball.gif' /></a>"; - echo " </td>"; - - echo "<td nowrap='1'> "; - if (0 != strcmp($defaultvalue, "")) { - echo "<code>$defaultvalue</code> </td>"; - } else { - echo "<font color='#00B040'><i>empty string</i></font> </td>"; +# +# Build up argument array to pass along. +# +$args = array(); +$name = ""; +if (isset($formfields["name"]) && $formfields["name"] != "") { + $name = $formfields["name"]; +} + +# Actions for the Edit subform. +$do_change = 0; +if (isset($canceled)) { # Action for the Cancel submit button. + $message = "Operation canceled."; +} +elseif (isset($edited)) { # Action for the "Change to new value" button. + if (isset($formfields["value"]) && $formfields["value"] != "") { + $args["value"] = $value = $formfields["value"]; } + $message = "Setting '$name' to '$value'."; + $do_change = 1; +} +elseif (isset($defaulted)) { # Action for "Reset to Default value" button. + $message = "Resetting '$name' to default."; + $do_change = 1; + $args["reset"] = "1"; +} - echo "<td> $description </td></tr>\n"; +if ($do_change) { + $result = SetSiteVar($name, $args, $errors); } -echo "</table>"; +# Respit the form to see the result. +SPITFORM($message, $errors); # # Standard Testbed Footer # PAGEFOOTER(); -?> +return; +# +# Edit site variables. (No class for them at present.) +function SetSiteVar($name, $args, &$errors) { + global $suexec_output, $suexec_output_array; + + # + # Generate a temporary file and write in the XML goo. + # + $xmlname = tempnam("/tmp", "editsitevars"); + if (! $xmlname) { + TBERROR("Could not create temporary filename", 0); + $errors[] = "Transient error(1); please try again later."; + return null; + } + if (! ($fp = fopen($xmlname, "w"))) { + TBERROR("Could not open temp file $xmlname", 0); + $errors[] = "Transient error(2); please try again later."; + return null; + } + + # Add these. Maybe caller should do this? + $args["name"] = $name; + + fwrite($fp, "<sitevar>\n"); + foreach ($args as $name => $value) { + fwrite($fp, "<attribute name=\"$name\">"); + fwrite($fp, " <value>" . htmlspecialchars($value) . "</value>"); + fwrite($fp, "</attribute>\n"); + } + fwrite($fp, "</sitevar>\n"); + fclose($fp); + chmod($xmlname, 0666); + + $retval = SUEXEC("nobody", "nobody", "webeditsitevars $xmlname", + SUEXEC_ACTION_IGNORE); + + if ($retval) { + if ($retval < 0) { + $errors[] = "Transient error(3); please try again later."; + SUEXECERROR(SUEXEC_ACTION_CONTINUE); + } + else { + # unlink($xmlname); + if (count($suexec_output_array)) { + for ($i = 0; $i < count($suexec_output_array); $i++) { + $line = $suexec_output_array[$i]; + if (preg_match("/^([-\w]+):\s*(.*)$/", + $line, $matches)) { + $errors[$matches[1]] = $matches[2]; + } + else + $errors[] = $line; + } + } + else + $errors[] = "Transient error(4); please try again later."; + } + return null; + } + + # There are no return value(s) to parse at the end of the output. + + # Unlink this here, so that the file is left behind in case of error. + # We can then create the sitevar by hand from the xmlfile, if desired. + unlink($xmlname); + return true; +} + +?>