Ruby -vs- PHP Showcase: nsupdate script

These are the source code files for the Ruby -vs- PHP Showcase: nsupdate script Read the full story in my blog.

Source codes

Config files

<?php
# $Id: nsupdate.php,v 1.2 2005/12/26 18:20:38 chip Exp $
# $Source: /home/chip/src/web-nsupdate/RCS/nsupdate.php,v $
#
# Copyright 2005, Chip Rosenthal <chip@unicom.com>.
# See software license at <http://www.unicom.com/sw/license.html>
# for terms of use and distribution.
#

#
# *** Edit this to point to where your definitions file is installed.
#
require_once("/usr/local/lib/web-nsupdate/nsupdate-defs.php");


/**
 * Template to generate command script for nsupdate(8).
 */
$NSUPATE_COMMAND_TEMPLATE = 'server {$p_hostinfo["nameserver"]}
zone $p_domain
update delete $p_host
update add $p_host {$p_hostinfo["ttl"]} A $p_addr
send
';


/**
 * Web page with form for manual entry.
 */
$NSUPDATE_MANUAL_FORM = '<html>
<head>
<title>web-nsupdate: Manual Entry</title>
</head>
<body>
<h1>web-nsupdate: Manual Entry</h1>
<form method="get">
<table border="0" cellspaceing="0" cellpadding="3">
<tr>
	<td><label for="host">Host Name:</label></td>
	<td><input type="text" name="host" /></td>
</tr>
<tr>
	<td><label for="addr">Host Address:</label></td>
	<td><input type="text" name="addr" /></td>
</tr>
<tr>
	<td><label for="key">Password:</label></td>
	<td><input type="password" name="key" /></td>
</tr>
<tr>
	<td>&nbsp;</td>
	<td>
		<input type="hidden" name="verbose" value="1" />
		<input type="submit" /> <input type="reset" />
	</td>
</tr>
</table>
</form>
<hr>
<p style="font-size:smaller">&copy; 2005 <a href="http://www.unicom.com/sw/">Unicom Systems Development</a></p>
</body>
</html>
';


/**
 * Template for web page with success resposne for manual update.
 */
$NSUPATE_MANUAL_RESPONSE = '<html>
<head>
<title>web-nsupdate: Manual Update Successful</title>
</head>
<body>
<h1>web-nsupdate: Manual Update Successful</h1>
<p>Host <i>{$p_host}</i> has been assigned address <i>$p_addr</i>.</p>
<hr>
<p style="font-size:smaller">&copy; 2005 <a href="http://www.unicom.com/sw/">Unicom Systems Development</a></p>
</body>
</html>
';

_EOT_;


/**
 * Retrieve information from $Hosts_Table[] for a specified host.
 * @param $p_host  Name of the host to lookup.
 * @return Array (key/value pairs) of information on the host.
 * The host info is a keyed array with the following items
 *   defined: key, nskey, nameserver, ttl.
 * Values not specified for the host are defined to the default.
 * An error is raised if the host is not defined.
 */
function get_hostinfo($p_host)
{
	global $Hosts_Table;
	if (empty($Hosts_Table[$p_host]))
	{
		trigger_error("Host \"$p_host\" unknown.", E_USER_ERROR);
	}

	$p_hostinfo = $Hosts_Table[$p_host];

	if (empty($p_hostinfo["nskey"]))
	{
		$p_hostinfo["nskey"] = DEFAULT_NSKEY;
	}
	if (empty($p_hostinfo["nameserver"]))
	{
		$p_hostinfo["nameserver"] = DEFAULT_NAMESERVER;
	}
	if (empty($p_hostinfo["ttl"]))
	{
		$p_hostinfo["ttl"] = DEFAULT_TTL;
	}

	return $p_hostinfo;
}


/**
 * Validate an authorization key for a host.
 * @param $p_hostinfo  Host information array.
 * @param $p_key  Key to validate.
 * @return Nothing.
 * An error is raised if validation fails.
 */
function validate_host($p_hostinfo, $p_key)
{
	if ($p_hostinfo["key"] != $p_key)
	{
		trigger_error("Permission denied.", E_USER_ERROR);
	}
}


/**
 * Extract the domain name name portion from a fully qualified host name.
 * @param $p_host  The fully qualified host name.
 * @return  The extracted domain name.
 * An error is raised if the extraction fails.
 */
function extract_domain_from_hostname($p_host)
{
	$pos = strpos($p_host, ".");
	if ($pos === FALSE)
	{
		trigger_error("Cannot extract domain from hostname.", E_USER_ERROR);
	}
	return substr($p_host, $pos+1);
}


/**
 * Write data to a file.
 * @param $filename  Name of the file to create.
 * @param $data  The data to write.
 * @return Number of characters written.
 * An error is raised if any of the operations fail.
 */
function file_write_string($filename, $data)
{
	$fh = fopen($filename, "w");
	if (!$fh)
	{
		trigger_error("fopen() failed.", E_USER_ERROR);
	}
	$rc = fwrite($fh, $data);
	if ($rc === FALSE)
	{
		trigger_error("fwrite() failed.", E_USER_ERROR);
	}
	if (!fclose($fh))
	{
		trigger_error("fclose() failed.", E_USER_ERROR);
	}
	return $rc;
}


########################################################################
#
# Main execution begins here.
#

#
# Retrieve input from user.
#
$p_host = $_REQUEST['host'];
if (empty($p_host))
{
	# If host not specified, present form for manual data entry.
	echo($NSUPDATE_MANUAL_FORM);
	exit(0);
}
$p_addr = $_REQUEST['addr'];
if (empty($p_addr))
{
	trigger_error("Required parameter \"addr\" not specified.", E_USER_ERROR);
}
$p_key = $_REQUEST['key'];
if (empty($p_key))
{
	trigger_error("Required parameter \"key\" not specified.", E_USER_ERROR);
}

#
# Lookup this host and validate the password.
#
$p_hostinfo = get_hostinfo($p_host);
validate_host($p_hostinfo, $p_key);

$p_domain = extract_domain_from_hostname($p_host);

#
# Generate a command script for nsupdate(8).
#
$tmpfname = tempnam("", "nsupdate.");
if (!$tmpfname)
{
	trigger_error("Error generating temporary file name.", E_USER_ERROR);
}
eval("\$fcontent = \"$NSUPATE_COMMAND_TEMPLATE\";");
file_write_string($tmpfname, $fcontent);

#
# Run the nsupdate(8) command.
#
$rc = system("nsupdate -k {$p_hostinfo['nskey']} $tmpfname 2>&1", $ex);
unlink($tmpfname);
if ($rc === FALSE || $ex != 0)
{
	trigger_error("nsupdate command failed.", E_USER_ERROR);
}

#
# Normally we exit quietly on success.
# If we were running manually, send an HTML response.
#
if ($_REQUEST['verbose'])
{
 	eval("echo(\"" . addslashes($NSUPATE_MANUAL_RESPONSE) . "\");");
}

?>
#!/usr/bin/env ruby
require 'yaml' # http://yaml.org/
require 'cgi'  # http://www.ruby-doc.org/stdlib/libdoc/cgi/rdoc/
#
# Ruby CGI showcase
# Copyright (c) 2006 Christian W. Zuckschwerdt <zany@triq.net>
# based on nsupdate.php, Copyright 2005, Chip Rosenthal <chip@unicom.com>.
#

#
# *** Edit this to point to where your config file is installed.
#
config_file = '/home/zany/workspace/ruby-nsupdate/nsupdate.yml'
@config = YAML.load_file config_file

#
# Template to generate command script for nsupdate(8).
#
NSUPATE_COMMAND_TEMPLATE = <<'EOT'
"server #{hostinfo['nameserver']}
zone #{domain}
update delete #{host}
update add #{host} #{hostinfo['ttl']} A #{addr}
send"
EOT

#
# Web page with form for manual entry.
#
NSUPDATE_MANUAL_FORM = <<'EOT'
<html>
  <head>
    <title>web-nsupdate: Manual Entry</title>
  </head>
  <body>
    <h1>web-nsupdate: Manual Entry</h1>
    <form method="get">
    <table border="0" cellspaceing="0" cellpadding="3">
    <tr>
      <td><label for="host">Host Name:</label></td>
      <td><input type="text" name="host" /></td>
    </tr>
    <tr>
      <td><label for="addr">Host Address:</label></td>
      <td><input type="text" name="addr" /></td>
    </tr>
    <tr>
      <td><label for="key">Password:</label></td>
      <td><input type="password" name="key" /></td>
    </tr>
    <tr>
      <td>&nbsp;</td>
      <td>
        <input type="hidden" name="verbose" value="1" />
        <input type="submit" /> <input type="reset" />
      </td>
    </tr>
    </table>
    </form>
    <hr>
    <p style="font-size:smaller"> based on work &copy; 2005 <a href="http://www.unicom.com/sw/">Unicom Systems Development</a></p>
  </body>
</html>
EOT

#
# Template for web page with success response for manual update.
#
NSUPATE_MANUAL_RESPONSE = <<'EOT'
"<html>
  <head>
    <title>web-nsupdate: Manual Update Successful</title>
  </head>
  <body>
    <h1>web-nsupdate: Manual Update Successful</h1>
    <p>Host <i>#{host}</i> has been assigned address <i>#{addr}</i>.</p>
    <hr>
    <p style='font-size:smaller'>based on work &copy; 2005 <a href='http://www.unicom.com/sw/'>Unicom Systems Development</a></p>
  </body>
</html>"
EOT



#
# Retrieve information from @config for a specified host.
# - host  Name of the host to lookup.
# Returns Hash (key/value pairs) of information on the host.
# The host info is a keyed with the following items
#   defined: key, nskey, nameserver, ttl.
# Values not specified for the host are defined to the default.
# An error is raised if the host is not defined.
#
def get_hostinfo(host)
  return unless @config.include? 'defaults'
  return unless @config.include? host
  defaults = @config['defaults'] || {}
  defaults.merge @config[host]
end





















#
# Validate an authorization key for a host.
# - hostinfo  Host information array.
# - key  Key to validate.
# Returns true/false.
#
#
def validate_host(hostinfo, key)
  hostinfo["key"] == key
end






#
# Extract the domain name name portion from a fully qualified host name.
# - host  The fully qualified host name.
# Returns  The extracted domain name.
#
#
def domain_from_hostname(host)
  host[host.index('.')+1..-1] rescue nil
end



































########################################################################
#
# Main execution begins here.
#
cgi = CGI.new
#
# Retrieve input from user.
#

if (host = "#{cgi['host']}").empty?

  # If host not specified, present form for manual data entry.
  puts NSUPDATE_MANUAL_FORM
  exit 0
end

if (addr = "#{cgi['addr']}").empty?

  raise "Required parameter \"addr\" not specified."
end

if (key = "#{cgi['key']}").empty?

  raise "Required parameter \"key\" not specified."
end

#
# Lookup this host and validate the password.
#
raise "Host #{host} unknown." unless hostinfo = get_hostinfo(host)
raise "Permission denied." unless validate_host(hostinfo, key)
domain = domain_from_hostname(host)
raise "Cannot extract domain from hostname." unless domain

#
# Generate a command script for nsupdate(8).
#
content = eval NSUPATE_COMMAND_TEMPLATE







#
# Run the nsupdate(8) command.
#
IO.popen ("nsupdate -k #{hostinfo['nskey']}", 'w') { |io| io.puts content }

#raise "nsupdate command failed." unless rc




#
# Normally we exit quietly on success.
# If we were running manually, send an HTML response.
#
if (cgi['verbose'])
  eval NSUPATE_MANUAL_RESPONSE
else
  "OK"
end