#!/usr/bin/perl -w
######################################################################
#
# $Id: webjob-setup-server,v 1.87 2012/01/07 08:01:24 mavrik Exp $
#
######################################################################
#
# Copyright 2005-2012 The WebJob Project, All Rights Reserved.
#
######################################################################
#
# Purpose: Install and configure WebJob server-side components.
#
######################################################################

use strict;
use File::Basename;
use File::Copy;
use FindBin qw($Bin $RealBin); use lib ("$Bin/../lib/perl5/site_perl", "$RealBin/../lib/perl5/site_perl", "/usr/local/webjob/lib/perl5/site_perl", "/opt/local/webjob/lib/perl5/site_perl");
use Getopt::Std;
use Sys::Hostname;
use WebJob::DsvRoutines 1.037;
use WebJob::FdaRoutines 1.011;
use WebJob::KvpRoutines 1.029;
use WebJob::Properties 1.036;

BEGIN
{
  ####################################################################
  #
  # The Properties hash is essentially private. Those parts of the
  # program that wish to access or modify the data in this hash need
  # to call GetProperties() to obtain a reference.
  #
  ####################################################################

  my (%hProperties);

  ####################################################################
  #
  # Initialize regex variables.
  #
  ####################################################################

  $hProperties{'CommonRegexes'} = PropertiesGetGlobalRegexes();

  ####################################################################
  #
  # Initialize platform-specific variables.
  #
  ####################################################################

  if ($^O =~ /MSWin(32|64)/i)
  {
    $hProperties{'OsClass'} = "WINX";
  }
  else
  {
    $hProperties{'OsClass'} = "UNIX";
  }

  ####################################################################
  #
  # Define helper routines.
  #
  ####################################################################

  sub GetProperties
  {
    return \%hProperties;
  }
}

######################################################################
#
# Main Routine
#
######################################################################

  ####################################################################
  #
  # Punch in and go to work.
  #
  ####################################################################

  my ($phProperties);

  $phProperties = GetProperties();

  $$phProperties{'Program'} = basename(__FILE__);

  ####################################################################
  #
  # Get Options.
  #
  ####################################################################

  my (%hOptions);

  if (!getopts('Ff:p:', \%hOptions))
  {
    Usage($$phProperties{'Program'});
  }

  ####################################################################
  #
  # ForceWrite, -F, is optional.
  #
  ####################################################################

  $$phProperties{'ForceWrite'} = (exists($hOptions{'F'})) ? 1 : 0;

  ####################################################################
  #
  # PropertiesFile, -f,  is required.
  #
  ####################################################################

  $$phProperties{'PropertiesFile'} = (exists($hOptions{'f'})) ? $hOptions{'f'} : undef;

  if (!defined($$phProperties{'PropertiesFile'}) || length($$phProperties{'PropertiesFile'}) < 1)
  {
    Usage($$phProperties{'Program'});
  }

  ####################################################################
  #
  # PrintConfig, -p, is optional.
  #
  ####################################################################

  $$phProperties{'PrintConfig'} = (exists($hOptions{'p'})) ? $hOptions{'p'} : undef;

  ####################################################################
  #
  # If any arguments remain, it's an error.
  #
  ####################################################################

  if (scalar(@ARGV) > 0)
  {
    Usage($$phProperties{'Program'});
  }

  ####################################################################
  #
  # Get configuration properties.
  #
  ####################################################################

  my $sError;

  if (!GetConfigProperties($phProperties, \$sError))
  {
    print STDERR "$$phProperties{'Program'}: Error='$sError'\n";
    exit(2);
  }
  PrintConfigProperties($phProperties, $$phProperties{'PrintConfig'}) if ($$phProperties{'PrintConfig'});

  ####################################################################
  #
  # Determine the hostname, and set the server ID.
  #
  ####################################################################

  my ($sHostname);

  if (exists($$phProperties{'Config'}{'Hostname'}) && defined($$phProperties{'Config'}{'Hostname'}))
  {
    $sHostname = $$phProperties{'Config'}{'Hostname'};
  }
  else
  {
    $sHostname = hostname();
  }

  if (!defined($sHostname) || length($sHostname) < 1)
  {
    print STDERR "$$phProperties{'Program'}: Error='Unable to determine hostname.'\n";
    exit(2);
  }
  if ($sHostname !~ /^$$phProperties{'CommonRegexes'}{'Fqdn'}$/)
  {
    print STDERR "$$phProperties{'Program'}: Error='Hostname ($sHostname) does not pass muster. A FQDN is required.'\n";
    exit(2);
  }

  $$phProperties{'Hostname'} = $sHostname;
  $sHostname =~ s/[.].*$//;
  $$phProperties{'ServerId'} = "server_$sHostname";

  ####################################################################
  #
  # Create directories.
  #
  ####################################################################

  if (!CreateWebJobDirectories($phProperties, \$sError))
  {
    print STDERR "$$phProperties{'Program'}: Error='$sError'\n";
  }

  ####################################################################
  #
  # Create DSV files.
  #
  ####################################################################

  if (!CreateDsvFiles($phProperties, \$sError))
  {
    print STDERR "$$phProperties{'Program'}: Error='$sError'\n";
  }

  ####################################################################
  #
  # Create JQD files.
  #
  ####################################################################

  if (!CreateJqdFiles($phProperties, \$sError))
  {
    print STDERR "$$phProperties{'Program'}: Error='$sError'\n";
  }

  ####################################################################
  #
  # Create MLDBM files.
  #
  ####################################################################

  if (!CreateMldbmFiles($phProperties, \$sError))
  {
    print STDERR "$$phProperties{'Program'}: Error='$sError'\n";
  }

  ####################################################################
  #
  # Create skel files.
  #
  ####################################################################

  if (!CreateSkelFiles($phProperties, \$sError))
  {
    print STDERR "$$phProperties{'Program'}: Error='$sError'\n";
  }

  ####################################################################
  #
  # Create nph-*.log.
  #
  ####################################################################

  if (!CreateNphLogFiles($phProperties, \$sError))
  {
    print STDERR "$$phProperties{'Program'}: Error='$sError'\n";
  }

  ####################################################################
  #
  # Create nph-*.cfg.
  #
  ####################################################################

  if (!CreateNphConfig($phProperties, \$sError))
  {
    print STDERR "$$phProperties{'Program'}: Error='$sError'\n";
  }

  ####################################################################
  #
  # Create nph-*-hosts.access.
  #
  ####################################################################

  if (!CreateHostAccessFile($phProperties, \$sError))
  {
    print STDERR "$$phProperties{'Program'}: Error='$sError'\n";
  }

  ####################################################################
  #
  # Create webjob.conf.
  #
  ####################################################################

  if (!CreateWebJobConf($phProperties, \$sError))
  {
    print STDERR "$$phProperties{'Program'}: Error='$sError'\n";
  }

  ####################################################################
  #
  # Modify httpd.conf.
  #
  ####################################################################

  if (!ModifyApacheHttpdConf($phProperties, \$sError))
  {
    print STDERR "$$phProperties{'Program'}: Error='$sError'\n";
  }

  ####################################################################
  #
  # Create htaccess files.
  #
  ####################################################################

  if (!CreateHtAccessFiles($phProperties, \$sError))
  {
    print STDERR "$$phProperties{'Program'}: Error='$sError'\n";
  }

  ####################################################################
  #
  # Create htpasswd files.
  #
  ####################################################################

  if (!CreateHtpasswdFiles($phProperties, \$sError))
  {
    print STDERR "$$phProperties{'Program'}: Error='$sError'\n";
  }

  ####################################################################
  #
  # Create profiles.
  #
  ####################################################################

  if (!CreateProfiles($phProperties, \$sError))
  {
    print STDERR "$$phProperties{'Program'}: Error='$sError'\n";
  }

  1;


######################################################################
#
# GetConfigProperties
#
######################################################################

sub GetConfigProperties
{
  my ($phProperties, $psError) = @_;

  ####################################################################
  #
  # Pull in user-defined properties.
  #
  ####################################################################

  $$phProperties{'Config'} = {}; # Initialize the Config hash.
  $$phProperties{'Derived'} = {}; # Initialize the Derived hash.

  my %hLArgs =
  (
    'File'           => $$phProperties{'PropertiesFile'},
    'Properties'     => $$phProperties{'Config'},
    'RequireAllKeys' => 0,
    'Template'       => PropertiesGetGlobalTemplates()->{'webjob.server'},
    'VerifyValues'   => 1,
  );
  if (!KvpGetKvps(\%hLArgs))
  {
    $$psError = $hLArgs{'Error'};
    return undef;
  }

  ####################################################################
  #
  # Finalize WebJobUmask, and set the umask.
  #
  ####################################################################

  if (!defined($$phProperties{'Config'}{'WebJobUmask'}))
  {
    $$phProperties{'Config'}{'WebJobUmask'} = 0022;
  }
  else
  {
    $$phProperties{'Config'}{'WebJobUmask'} = oct($$phProperties{'Config'}{'WebJobUmask'});
  }
  umask($$phProperties{'Config'}{'WebJobUmask'});

  ####################################################################
  #
  # Finalize Apache variables.
  #
  ####################################################################

  my (%hSubject);

  if (!defined($$phProperties{'Config'}{'ApacheConfDirectory'}))
  {
    $$phProperties{'Config'}{'ApacheConfDirectory'} = "/usr/local/apache2/conf";
  }

  if (!defined($$phProperties{'Config'}{'ApacheOwner'}))
  {
    $$phProperties{'Config'}{'ApacheOwner'} = "www";
  }
  %hLArgs =
  (
    'Owner' => $$phProperties{'Config'}{'ApacheOwner'},
  );
  $$phProperties{'Derived'}{'ApacheUid'} = FdaOwnerToUid(\%hLArgs);
  if (!defined($$phProperties{'Derived'}{'ApacheUid'}))
  {
    $$psError = $hLArgs{'Error'};
    return undef;
  }

  if (!defined($$phProperties{'Config'}{'ApacheGroup'}))
  {
    $$phProperties{'Config'}{'ApacheGroup'} = "www";
  }
  %hLArgs =
  (
    'Group' => $$phProperties{'Config'}{'ApacheGroup'},
  );
  $$phProperties{'Derived'}{'ApacheGid'} = FdaGroupToGid(\%hLArgs);
  if (!defined($$phProperties{'Derived'}{'ApacheGid'}))
  {
    $$psError = $hLArgs{'Error'};
    return undef;
  }

  if (!defined($$phProperties{'Config'}{'ApacheSslBundledCAsFile'}))
  {
    $$phProperties{'Config'}{'ApacheSslBundledCAsFile'} = $$phProperties{'Config'}{'ApacheConfDirectory'} . "/ca-bundle.crt";
  }
  if (!-f $$phProperties{'Config'}{'ApacheSslBundledCAsFile'})
  {
    $$psError = "Apache bundled CAs file ($$phProperties{'Config'}{'ApacheSslBundledCAsFile'}) does not exist or is not regular.";
    return undef;
  }

  if (!defined($$phProperties{'Config'}{'ApacheSslPublicCertFile'}))
  {
    $$phProperties{'Config'}{'ApacheSslPublicCertFile'} = $$phProperties{'Config'}{'ApacheConfDirectory'} . "/server.crt";
  }
  if (!-f $$phProperties{'Config'}{'ApacheSslPublicCertFile'})
  {
    $$psError = "Apache certificate file ($$phProperties{'Config'}{'ApacheSslPublicCertFile'}) does not exist or is not regular.";
    return undef;
  }
  %hLArgs =
  (
    'File' => $$phProperties{'Config'}{'ApacheSslPublicCertFile'},
    'OpenSslExe' => "openssl",
    'Subject' => \%hSubject,
    'Type' => "x509",
  );
  if (!DsvGetSubject(\%hLArgs))
  {
    $$psError = $hLArgs{'Error'};
    return undef;
  }
  $$phProperties{'Config'}{'ApacheSubject'} = \%hSubject;

  ####################################################################
  #
  # Finalize capability-related variables.
  #
  ####################################################################

  $$phProperties{'Config'}{'DsvRequireSignatures'} = "N" if (!defined($$phProperties{'Config'}{'DsvRequireSignatures'}));
  $$phProperties{'Config'}{'EnableAutoActivation'} = "N" if (!defined($$phProperties{'Config'}{'EnableAutoActivation'}));
  $$phProperties{'Config'}{'EnableAutoRegistration'} = "N" if (!defined($$phProperties{'Config'}{'EnableAutoRegistration'}));
  $$phProperties{'Config'}{'EnableHostAccessList'} = "N" if (!defined($$phProperties{'Config'}{'EnableHostAccessList'}));

  ####################################################################
  #
  # Finalize WebJobOwner/WebJobGroup variables.
  #
  ####################################################################

  if (!defined($$phProperties{'Config'}{'WebJobOwner'}))
  {
    $$phProperties{'Config'}{'WebJobOwner'} = "root";
  }
  %hLArgs =
  (
    'Owner' => $$phProperties{'Config'}{'WebJobOwner'},
  );
  $$phProperties{'Derived'}{'WebJobUid'} = FdaOwnerToUid(\%hLArgs);
  if (!defined($$phProperties{'Derived'}{'WebJobUid'}))
  {
    $$psError = $hLArgs{'Error'};
    return undef;
  }

  if (!defined($$phProperties{'Config'}{'WebJobGroup'}))
  {
    $$phProperties{'Config'}{'WebJobGroup'} = "wheel";
  }
  %hLArgs =
  (
    'Group' => $$phProperties{'Config'}{'WebJobGroup'},
  );
  $$phProperties{'Derived'}{'WebJobGid'} = FdaGroupToGid(\%hLArgs);
  if (!defined($$phProperties{'Derived'}{'WebJobGid'}))
  {
    $$psError = $hLArgs{'Error'};
    return undef;
  }

  ####################################################################
  #
  # Finalize WebJobDirectory variables.
  #
  ####################################################################

  if (!defined($$phProperties{'Config'}{'WebJobDirectory'}))
  {
    $$phProperties{'Config'}{'WebJobDirectory'} = "/usr/local/webjob";
  }

  if (!defined($$phProperties{'Config'}{'WebJobDirectoryOwner'}))
  {
    $$phProperties{'Config'}{'WebJobDirectoryOwner'} = $$phProperties{'Config'}{'WebJobOwner'};
  }
  %hLArgs =
  (
    'Owner' => $$phProperties{'Config'}{'WebJobDirectoryOwner'},
  );
  $$phProperties{'Derived'}{'WebJobDirectoryUid'} = FdaOwnerToUid(\%hLArgs);
  if (!defined($$phProperties{'Derived'}{'WebJobDirectoryUid'}))
  {
    $$psError = $hLArgs{'Error'};
    return undef;
  }

  if (!defined($$phProperties{'Config'}{'WebJobDirectoryGroup'}))
  {
    $$phProperties{'Config'}{'WebJobDirectoryGroup'} = $$phProperties{'Config'}{'WebJobGroup'};
  }
  %hLArgs =
  (
    'Group' => $$phProperties{'Config'}{'WebJobDirectoryGroup'},
  );
  $$phProperties{'Derived'}{'WebJobDirectoryGid'} = FdaGroupToGid(\%hLArgs);
  if (!defined($$phProperties{'Derived'}{'WebJobDirectoryGid'}))
  {
    $$psError = $hLArgs{'Error'};
    return undef;
  }

  if (!defined($$phProperties{'Config'}{'WebJobDirectoryPermissions'}))
  {
    $$phProperties{'Config'}{'WebJobDirectoryPermissions'} = "0755"; # This value must be specified as a string.
  }
  %hLArgs =
  (
    'Permissions' => $$phProperties{'Config'}{'WebJobDirectoryPermissions'},
    'Umask'       => $$phProperties{'Config'}{'WebJobUmask'},
  );
  $$phProperties{'Derived'}{'WebJobDirectoryMode'} = FdaPermissionsToMode(\%hLArgs);
  if (!defined($$phProperties{'Derived'}{'WebJobDirectoryMode'}))
  {
    $$psError = $hLArgs{'Error'};
    return undef;
  }

  foreach my $sName ("Bin", "Cgi", "Dsv", "Etc", "Lib", "Run", "Web")
  {
    my ($sKey, $sGidKey, $sUidKey, $sModeKey);

    ##################################################################
    #
    # Directory Paths.
    #
    ##################################################################

    $sKey = "WebJob" . $sName . "Directory";
    if
    (
      !exists($$phProperties{'Config'}{$sKey}) ||
      !defined($$phProperties{'Config'}{$sKey}) ||
      length($$phProperties{'Config'}{$sKey}) < 1
    )
    {
      $$phProperties{'Config'}{$sKey} = $$phProperties{'Config'}{'WebJobDirectory'} . "/" . lc($sName);
    }

    ##################################################################
    #
    # Directory Owner.
    #
    ##################################################################

    $sKey = "WebJob" . $sName . "DirectoryOwner";
    if
    (
      !exists($$phProperties{'Config'}{$sKey}) ||
      !defined($$phProperties{'Config'}{$sKey}) ||
      length($$phProperties{'Config'}{$sKey}) < 1
    )
    {
      $$phProperties{'Config'}{$sKey} = $$phProperties{'Config'}{'WebJobOwner'};
    }
    $sUidKey = "WebJob" . $sName . "DirectoryUid";
    %hLArgs =
    (
      'Owner' => $$phProperties{'Config'}{$sKey},
    );
    $$phProperties{'Derived'}{$sUidKey} = FdaOwnerToUid(\%hLArgs);
    if (!defined($$phProperties{'Derived'}{$sUidKey}))
    {
      $$psError = $hLArgs{'Error'};
      return undef;
    }

    ##################################################################
    #
    # Directory Group.
    #
    ##################################################################

    $sKey = "WebJob" . $sName . "DirectoryGroup";
    if
    (
      !exists($$phProperties{'Config'}{$sKey}) ||
      !defined($$phProperties{'Config'}{$sKey}) ||
      length($$phProperties{'Config'}{$sKey}) < 1
    )
    {
      $$phProperties{'Config'}{$sKey} = $$phProperties{'Config'}{'WebJobGroup'};
    }
    $sGidKey = "WebJob" . $sName . "DirectoryGid";
    %hLArgs =
    (
      'Group' => $$phProperties{'Config'}{$sKey},
    );
    $$phProperties{'Derived'}{$sGidKey} = FdaGroupToGid(\%hLArgs);
    if (!defined($$phProperties{'Derived'}{$sGidKey}))
    {
      $$psError = $hLArgs{'Error'};
      return undef;
    }

    ##################################################################
    #
    # Directory Permissions.
    #
    ##################################################################

    $sKey = "WebJob" . $sName . "DirectoryPermissions";
    if
    (
      !exists($$phProperties{'Config'}{$sKey}) ||
      !defined($$phProperties{'Config'}{$sKey}) ||
      length($$phProperties{'Config'}{$sKey}) < 1
    )
    {
      $$phProperties{'Config'}{$sKey} = ($sName =~ /^Run$/) ? "0700" : "0755"; # This value must be specified as a string.
    }
    $sModeKey = "WebJob" . $sName . "DirectoryMode";
    %hLArgs =
    (
      'Permissions' => $$phProperties{'Config'}{$sKey},
      'Umask'       => $$phProperties{'Config'}{'WebJobUmask'},
    );
    $$phProperties{'Derived'}{$sModeKey} = FdaPermissionsToMode(\%hLArgs);
    if (!defined($$phProperties{'Derived'}{$sModeKey}))
    {
      $$psError = $hLArgs{'Error'};
      return undef;
    }
  }

  ####################################################################
  #
  # Finalize WebJobServerDirectory variables.
  #
  ####################################################################

  if (!defined($$phProperties{'Config'}{'WebJobServerDirectory'}))
  {
    $$phProperties{'Config'}{'WebJobServerDirectory'} = "/var/webjob";
  }

  if (!defined($$phProperties{'Config'}{'WebJobServerDirectoryOwner'}))
  {
    $$phProperties{'Config'}{'WebJobServerDirectoryOwner'} = $$phProperties{'Config'}{'WebJobOwner'};
  }
  %hLArgs =
  (
    'Owner' => $$phProperties{'Config'}{'WebJobServerDirectoryOwner'},
  );
  $$phProperties{'Derived'}{'WebJobServerDirectoryUid'} = FdaOwnerToUid(\%hLArgs);
  if (!defined($$phProperties{'Derived'}{'WebJobServerDirectoryUid'}))
  {
    $$psError = $hLArgs{'Error'};
    return undef;
  }

  if (!defined($$phProperties{'Config'}{'WebJobServerDirectoryGroup'}))
  {
    $$phProperties{'Config'}{'WebJobServerDirectoryGroup'} = $$phProperties{'Config'}{'WebJobGroup'};
  }
  %hLArgs =
  (
    'Group' => $$phProperties{'Config'}{'WebJobServerDirectoryGroup'},
  );
  $$phProperties{'Derived'}{'WebJobServerDirectoryGid'} = FdaGroupToGid(\%hLArgs);
  if (!defined($$phProperties{'Derived'}{'WebJobServerDirectoryGid'}))
  {
    $$psError = $hLArgs{'Error'};
    return undef;
  }

  if (!defined($$phProperties{'Config'}{'WebJobServerDirectoryPermissions'}))
  {
    $$phProperties{'Config'}{'WebJobServerDirectoryPermissions'} = "0755"; # This value must be specified as a string.
  }
  %hLArgs =
  (
    'Permissions' => $$phProperties{'Config'}{'WebJobServerDirectoryPermissions'},
    'Umask'       => $$phProperties{'Config'}{'WebJobUmask'},
  );
  $$phProperties{'Derived'}{'WebJobServerDirectoryMode'} = FdaPermissionsToMode(\%hLArgs);
  if (!defined($$phProperties{'Derived'}{'WebJobServerDirectoryMode'}))
  {
    $$psError = $hLArgs{'Error'};
    return undef;
  }

  foreach my $sName ("Archives", "Config", "Db", "Dynamic", "Incoming", "Logfiles", "Profiles", "Spool", "Users")
  {
    my ($sKey, $sGidKey, $sUidKey, $sModeKey);

    ##################################################################
    #
    # Directory Paths.
    #
    ##################################################################

    $sKey = "WebJob" . $sName . "Directory";
    if
    (
      !exists($$phProperties{'Config'}{$sKey}) ||
      !defined($$phProperties{'Config'}{$sKey}) ||
      length($$phProperties{'Config'}{$sKey}) < 1
    )
    {
      if (defined($$phProperties{'Config'}{'WebJobServerDirectory'}) && length($$phProperties{'Config'}{'WebJobServerDirectory'}) > 0)
      {
        $$phProperties{'Config'}{$sKey} = $$phProperties{'Config'}{'WebJobServerDirectory'} . "/" . lc($sName);
      }
      else
      {
        $$phProperties{'Config'}{$sKey} = $$phProperties{'Config'}{'WebJobDirectory'} . "/" . lc($sName);
      }
    }

    ##################################################################
    #
    # Directory Owner.
    #
    ##################################################################

    $sKey = "WebJob" . $sName . "DirectoryOwner";
    if
    (
      !exists($$phProperties{'Config'}{$sKey}) ||
      !defined($$phProperties{'Config'}{$sKey}) ||
      length($$phProperties{'Config'}{$sKey}) < 1
    )
    {
      if ($sName =~ /^(Dynamic|Incoming)$/)
      {
        $$phProperties{'Config'}{$sKey} = $$phProperties{'Config'}{'ApacheOwner'};
      }
      else
      {
        $$phProperties{'Config'}{$sKey} = $$phProperties{'Config'}{'WebJobOwner'};
      }
    }
    $sUidKey = "WebJob" . $sName . "DirectoryUid";
    %hLArgs =
    (
      'Owner' => $$phProperties{'Config'}{$sKey},
    );
    $$phProperties{'Derived'}{$sUidKey} = FdaOwnerToUid(\%hLArgs);
    if (!defined($$phProperties{'Derived'}{$sUidKey}))
    {
      $$psError = $hLArgs{'Error'};
      return undef;
    }

    ##################################################################
    #
    # Directory Group.
    #
    ##################################################################

    $sKey = "WebJob" . $sName . "DirectoryGroup";
    if
    (
      !exists($$phProperties{'Config'}{$sKey}) ||
      !defined($$phProperties{'Config'}{$sKey}) ||
      length($$phProperties{'Config'}{$sKey}) < 1
    )
    {
      $$phProperties{'Config'}{$sKey} = $$phProperties{'Config'}{'WebJobGroup'};
    }
    $sGidKey = "WebJob" . $sName . "DirectoryGid";
    %hLArgs =
    (
      'Group' => $$phProperties{'Config'}{$sKey},
    );
    $$phProperties{'Derived'}{$sGidKey} = FdaGroupToGid(\%hLArgs);
    if (!defined($$phProperties{'Derived'}{$sGidKey}))
    {
      $$psError = $hLArgs{'Error'};
      return undef;
    }

    ##################################################################
    #
    # Directory Permissions.
    #
    ##################################################################

    $sKey = "WebJob" . $sName . "DirectoryPermissions";
    if
    (
      !exists($$phProperties{'Config'}{$sKey}) ||
      !defined($$phProperties{'Config'}{$sKey}) ||
      length($$phProperties{'Config'}{$sKey}) < 1
    )
    {
      $$phProperties{'Config'}{$sKey} = "0755"; # This value must be specified as a string.
    }
    $sModeKey = "WebJob" . $sName . "DirectoryMode";
    %hLArgs =
    (
      'Permissions' => $$phProperties{'Config'}{$sKey},
      'Umask'       => $$phProperties{'Config'}{'WebJobUmask'},
    );
    $$phProperties{'Derived'}{$sModeKey} = FdaPermissionsToMode(\%hLArgs);
    if (!defined($$phProperties{'Derived'}{$sModeKey}))
    {
      $$psError = $hLArgs{'Error'};
      return undef;
    }
  }

  1;
}


######################################################################
#
# CreateHostAccessFile
#
######################################################################

sub CreateHostAccessFile
{
  my ($phProperties, $psError) = @_;

  ####################################################################
  #
  # Check required inputs.
  #
  ####################################################################

  if (!defined($phProperties))
  {
    $$psError = "Unable to proceed due to missing or undefined inputs.";
    return undef;
  }

  ####################################################################
  #
  # Create nph-*-hosts.access.
  #
  ####################################################################

foreach my $sType ("config", "webjob")
{
  my $sFile = $$phProperties{'Config'}{'WebJobConfigDirectory'} . "/nph-$sType/nph-$sType-hosts.access";
  if (-f $sFile && !$$phProperties{'ForceWrite'})
  {
    print "Skipping $sFile (already exists)\n";
  }
  else
  {
    print "Creating $sFile\n";
    if (open(FH, "> $sFile"))
    {
      print FH <<EOF;
#client_1=192.168.1.1/32
EOF
      close(FH);
      if (!chown($$phProperties{'Derived'}{'WebJobUid'}, $$phProperties{'Derived'}{'WebJobGid'}, $sFile))
      {
        print STDERR "$$phProperties{'Program'}: Error='Unable to set owner/group for $sFile ($!).'\n";
      }
    }
    else
    {
      print STDERR "$$phProperties{'Program'}: Error='Unable to create $sFile ($!).'\n";
    }
  }
}

  1;
}


######################################################################
#
# CreateHtAccessFiles
#
######################################################################

sub CreateHtAccessFiles
{
  my ($phProperties, $psError) = @_;

  ####################################################################
  #
  # Check required inputs.
  #
  ####################################################################

  if (!defined($phProperties))
  {
    $$psError = "Unable to proceed due to missing or undefined inputs.";
    return undef;
  }

  ####################################################################
  #
  # Create .htaccess for the cgi-client directory.
  #
  ####################################################################

  my ($sFile);

  $sFile = $$phProperties{'Config'}{'WebJobCgiDirectory'} . "/cgi-client/.htaccess";

  if (-f $sFile && !$$phProperties{'ForceWrite'})
  {
    print "Skipping $sFile (already exists)\n";
  }
  else
  {
    print "Creating $sFile\n";
    if (open(FH, "> $sFile"))
    {
      print FH <<EOF;
AuthType Basic
AuthName "WebJob Client Realm"
AuthUserFile $$phProperties{'Config'}{'WebJobConfigDirectory'}/apache/ht-client
require valid-user
EOF
      close(FH);
      if (!chown($$phProperties{'Derived'}{'WebJobUid'}, $$phProperties{'Derived'}{'WebJobGid'}, $sFile))
      {
        print STDERR "$$phProperties{'Program'}: Error='Unable to set owner/group for $sFile ($!).'\n";
      }
    }
    else
    {
      print STDERR "$$phProperties{'Program'}: Error='Unable to create $sFile ($!).'\n";
    }
  }

  ####################################################################
  #
  # Create .htaccess for the cgi-public directory.
  #
  ####################################################################

  $sFile = $$phProperties{'Config'}{'WebJobCgiDirectory'} . "/cgi-public/.htaccess";

  if (-f $sFile && !$$phProperties{'ForceWrite'})
  {
    print "Skipping $sFile (already exists)\n";
  }
  else
  {
    print "Creating $sFile\n";
    if (open(FH, "> $sFile"))
    {
      print FH <<EOF;
AuthType Basic
AuthName "WebJob Public Realm"
AuthUserFile $$phProperties{'Config'}{'WebJobConfigDirectory'}/apache/ht-public
require valid-user
EOF
      close(FH);
      if (!chown($$phProperties{'Derived'}{'WebJobUid'}, $$phProperties{'Derived'}{'WebJobGid'}, $sFile))
      {
        print STDERR "$$phProperties{'Program'}: Error='Unable to set owner/group for $sFile ($!).'\n";
      }
    }
    else
    {
      print STDERR "$$phProperties{'Program'}: Error='Unable to create $sFile ($!).'\n";
    }
  }

  ####################################################################
  #
  # Create .htaccess for the web directory.
  #
  ####################################################################

  $sFile = $$phProperties{'Config'}{'WebJobWebDirectory'} . "/.htaccess";

  if (-f $sFile && !$$phProperties{'ForceWrite'})
  {
    print "Skipping $sFile (already exists)\n";
  }
  else
  {
    print "Creating $sFile\n";
    if (open(FH, "> $sFile"))
    {
      print FH <<EOF;
AuthType Basic
AuthName "WebJob Public Realm"
AuthUserFile $$phProperties{'Config'}{'WebJobConfigDirectory'}/apache/ht-public
require valid-user
EOF
      close(FH);
      if (!chown($$phProperties{'Derived'}{'WebJobUid'}, $$phProperties{'Derived'}{'WebJobGid'}, $sFile))
      {
        print STDERR "$$phProperties{'Program'}: Error='Unable to set owner/group for $sFile ($!).'\n";
      }
    }
    else
    {
      print STDERR "$$phProperties{'Program'}: Error='Unable to create $sFile ($!).'\n";
    }
  }

  1;
}


######################################################################
#
# CreateHtpasswdFiles
#
######################################################################

sub CreateHtpasswdFiles
{
  my ($phProperties, $psError) = @_;

  ####################################################################
  #
  # Check required inputs.
  #
  ####################################################################

  if (!defined($phProperties))
  {
    $$psError = "Unable to proceed due to missing or undefined inputs.";
    return undef;
  }

  ####################################################################
  #
  # Create ht-client (empty).
  #
  ####################################################################

  my ($sFile);

  $sFile = $$phProperties{'Config'}{'WebJobConfigDirectory'} . "/apache/ht-client";

  if (-f $sFile && !$$phProperties{'ForceWrite'})
  {
    print "Skipping $sFile (already exists)\n";
  }
  else
  {
    print "Creating $sFile\n";
    if (open(FH, "> $sFile"))
    {
      print FH <<EOF;
EOF
      close(FH);
      if (!chown($$phProperties{'Derived'}{'WebJobUid'}, $$phProperties{'Derived'}{'WebJobGid'}, $sFile))
      {
        print STDERR "$$phProperties{'Program'}: Error='Unable to set owner/group for $sFile ($!).'\n";
      }
    }
    else
    {
      print STDERR "$$phProperties{'Program'}: Error='Unable to create $sFile ($!).'\n";
    }
  }

  ####################################################################
  #
  # Create ht-public (empty).
  #
  ####################################################################

  $sFile = $$phProperties{'Config'}{'WebJobConfigDirectory'} . "/apache/ht-public";

  if (-f $sFile && !$$phProperties{'ForceWrite'})
  {
    print "Skipping $sFile (already exists)\n";
  }
  else
  {
    print "Creating $sFile\n";
    if (open(FH, "> $sFile"))
    {
      print FH <<EOF;
EOF
      close(FH);
      if (!chown($$phProperties{'Derived'}{'WebJobUid'}, $$phProperties{'Derived'}{'WebJobGid'}, $sFile))
      {
        print STDERR "$$phProperties{'Program'}: Error='Unable to set owner/group for $sFile ($!).'\n";
      }
    }
    else
    {
      print STDERR "$$phProperties{'Program'}: Error='Unable to create $sFile ($!).'\n";
    }
  }

  1;
}


######################################################################
#
# CreateDsvFiles
#
######################################################################

sub CreateDsvFiles
{
  my ($phProperties, $psError) = @_;

  ####################################################################
  #
  # Check required inputs.
  #
  ####################################################################

  if (!defined($phProperties))
  {
    $$psError = "Unable to proceed due to missing or undefined inputs.";
    return undef;
  }

  ####################################################################
  #
  # Generate the key and write it to the specified file.
  #
  ####################################################################

  my ($sKeyFile, $sKeyGenerated);

  $sKeyFile = $$phProperties{'Config'}{'WebJobConfigDirectory'} . "/dsv/webjob-key.pem";

  $sKeyGenerated = 0;

  if (-f $sKeyFile && !$$phProperties{'ForceWrite'})
  {
    print "Skipping $sKeyFile (already exists)\n";
  }
  else
  {
    print "Creating $sKeyFile\n";
    my %hLArgs =
    (
      'ForceWrite' => $$phProperties{'ForceWrite'},
      'KeyFile' => $sKeyFile,
      'DsvToolExe' => "webjob-dsvtool",
    );
    if (!DsvCreateKey(\%hLArgs))
    {
      $$psError = $hLArgs{'Error'};
      return undef;
    }
    $sKeyGenerated = 1;
  }

  ####################################################################
  #
  # Create certificate file.
  #
  ####################################################################

  my (%hSubject, $sCertFile);

  $sCertFile = $$phProperties{'Config'}{'WebJobConfigDirectory'} . "/dsv/webjob-dsv.pem";

  %hSubject = %{$$phProperties{'Config'}{'ApacheSubject'}};
  delete($hSubject{'emailAddress'});

  if (-f $sCertFile && !$$phProperties{'ForceWrite'} && !$sKeyGenerated)
  {
    print "Skipping $sCertFile (already exists)\n";
  }
  else
  {
    print "Creating $sCertFile\n";
    my $sForceWrite = ($sKeyGenerated) ? 1 : $$phProperties{'ForceWrite'};
    my %hLArgs =
    (
      'CertFile' => $sCertFile,
      'OpenSslCfg' => "/etc/ssl/openssl.cnf",
      'OpenSslExe' => "openssl",
      'ForceWrite' => $sForceWrite,
      'KeyFile' => $sKeyFile,
      'Subject' => \%hSubject,
    );
    if (!DsvCreateCertificate(\%hLArgs))
    {
      $$psError = $hLArgs{'Error'};
      return undef;
    }
  }

  ####################################################################
  #
  # Generate a null signature.
  #
  ####################################################################

  $$phProperties{'Config'}{'DsvNullSignature'} = qx(webjob-dsvtool --sign-payload --key-file $sKeyFile - < /dev/null);
  $$phProperties{'Config'}{'DsvNullSignature'} =~ s/[\r\n]+$//;

  1;
}


######################################################################
#
# CreateJqdFiles
#
######################################################################

sub CreateJqdFiles
{
  my ($phProperties, $psError) = @_;

  ####################################################################
  #
  # Check required inputs.
  #
  ####################################################################

  if (!defined($phProperties))
  {
    $$psError = "Unable to proceed due to missing or undefined inputs.";
    return undef;
  }

  ####################################################################
  #
  # Create the groups, queue.index, and queue.index.lock files.
  #
  ####################################################################

  my $sPrefix1 = $$phProperties{'Config'}{'WebJobConfigDirectory'} . "/jqd";
  my $sPrefix2 = $$phProperties{'Config'}{'WebJobSpoolDirectory'} . "/jqd";
  my $sMode1 = 0644 & ~$$phProperties{'Config'}{'WebJobUmask'};
  my $sMode2 = 0664 & ~$$phProperties{'Config'}{'WebJobUmask'} | 0660; # This file must be group writable.

  $$phProperties{'Pfmugs'} = # Prefix, File, Mode, UID, GID
  [
    [ $sPrefix1, "groups",           $sMode1, $$phProperties{'Derived'}{'WebJobUid'}, $$phProperties{'Derived'}{'WebJobGid'} ],
    [ $sPrefix2, "queue.index",      $sMode1, $$phProperties{'Derived'}{'WebJobUid'}, $$phProperties{'Derived'}{'WebJobGid'} ],
    [ $sPrefix2, "queue.index.lock", $sMode2, $$phProperties{'Derived'}{'WebJobUid'}, $$phProperties{'Derived'}{'ApacheGid'} ],
  ];

  foreach my $paPfmug (@{$$phProperties{'Pfmugs'}})
  {
    my %hPfmugArgs =
    (
      'Prefix'    => $$paPfmug[0],
      'File'      => $$paPfmug[1],
      'Mode'      => $$paPfmug[2],
      'Uid'       => $$paPfmug[3],
      'Gid'       => $$paPfmug[4],
    );
    if (!defined(FdaCreateFile(\%hPfmugArgs)))
    {
      print STDERR "$$phProperties{'Program'}: Error='$hPfmugArgs{'Error'}'\n";
      next;
    }
  }

  ####################################################################
  #
  # Conditionally create the 'all' and 'all_local' groups.
  #
  ####################################################################

  my $sGroupsFile = $sPrefix1 . "/" . "groups";

  qx(webjob-jqd-update-group -G $sGroupsFile -g all -a);
  qx(webjob-jqd-update-group -G $sGroupsFile -g all_local -a);

  1;
}


######################################################################
#
# CreateMldbmFiles
#
######################################################################

sub CreateMldbmFiles
{
  my ($phProperties, $psError) = @_;

  ####################################################################
  #
  # Check required inputs.
  #
  ####################################################################

  if (!defined($phProperties))
  {
    $$psError = "Unable to proceed due to missing or undefined inputs.";
    return undef;
  }

  ####################################################################
  #
  # Create client.db.
  #
  ####################################################################

  my $sClientDb = $$phProperties{'Config'}{'WebJobDbDirectory'} . "/mldbm/client.db";

  if (-f $sClientDb)
  {
    print "Skipping $sClientDb (already exists)\n";
  }
  else
  {
    qx(webjob-mldbm-create-db -d $sClientDb);
  }

  1;
}


######################################################################
#
# CreateNphConfig
#
######################################################################

sub CreateNphConfig
{
  my ($phProperties, $psError) = @_;

  ####################################################################
  #
  # Check required inputs.
  #
  ####################################################################

  if (!defined($phProperties))
  {
    $$psError = "Unable to proceed due to missing or undefined inputs.";
    return undef;
  }

  ####################################################################
  #
  # Create nph-config.cfg.
  #
  ####################################################################

  my ($sFile);

  $sFile = $$phProperties{'Config'}{'WebJobConfigDirectory'} . "/nph-config/nph-config.cfg";

  if (-f $sFile && !$$phProperties{'ForceWrite'})
  {
    print "Skipping $sFile (already exists)\n";
  }
  else
  {
    print "Creating $sFile\n";
    my $sEnableHostAccessList = ($$phProperties{'Config'}{'EnableHostAccessList'} =~ /^[Yy]$/) ? "#EnableHostAccessList=N\nEnableHostAccessList=Y" : "#EnableHostAccessList=N";
    if (open(FH, "> $sFile"))
    {
      print FH <<EOF;
#BaseDirectory=/var/webjob
BaseDirectory=$$phProperties{'Config'}{'WebJobServerDirectory'}
#CapContentLength=N
#ConfigDirectory=/var/webjob/config
#ConfigSearchOrder=clients:configs
#DynamicDirectory=/var/webjob/dynamic
#EnableConfigOverrides=Y
$sEnableHostAccessList
#EnableLogging=Y
#FolderList=common
#GetHookCommandLine=
#GetHookEnable=N
#GetHookLogDivertedOutput=N
#GetHookStatus=0
#GetHookStatusMap=
#LogfilesDirectory=/var/webjob/logfiles
#MaxContentLength=100000000
#ProfilesDirectory=/var/webjob/profiles
#RequireMatch=Y
#RequireUser=Y
#ServerId=server_1
ServerId=$$phProperties{'ServerId'}
#SpoolDirectory=/var/webjob/spool
#SslRequireCn=N
#SslRequireMatch=N
#SslRequireSsl=Y
#UseGmt=N
EOF
      close(FH);
      if (!chown($$phProperties{'Derived'}{'WebJobUid'}, $$phProperties{'Derived'}{'WebJobGid'}, $sFile))
      {
        print STDERR "$$phProperties{'Program'}: Error='Unable to set owner/group for $sFile ($!).'\n";
      }
    }
    else
    {
      print STDERR "$$phProperties{'Program'}: Error='Unable to create $sFile ($!).'\n";
    }
  }

  ####################################################################
  #
  # Create nph-webjob.cfg.
  #
  ####################################################################

  $sFile = $$phProperties{'Config'}{'WebJobConfigDirectory'} . "/nph-webjob/nph-webjob.cfg";

  if (-f $sFile && !$$phProperties{'ForceWrite'})
  {
    print "Skipping $sFile (already exists)\n";
  }
  else
  {
    print "Creating $sFile\n";
    my $sDsvRequireSignatures = ($$phProperties{'Config'}{'DsvRequireSignatures'} =~ /^[Yy]$/) ? "#DsvRequireSignatures=N\nDsvRequireSignatures=Y" : "#DsvRequireSignatures=N";
    my $sEnableHostAccessList = ($$phProperties{'Config'}{'EnableHostAccessList'} =~ /^[Yy]$/) ? "#EnableHostAccessList=N\nEnableHostAccessList=Y" : "#EnableHostAccessList=N";
    if (open(FH, "> $sFile"))
    {
      print FH <<EOF;
#BaseDirectory=/var/webjob
BaseDirectory=$$phProperties{'Config'}{'WebJobServerDirectory'}
#CapContentLength=N
#ConfigDirectory=/var/webjob/config
#ConfigSearchOrder=clients:commands
#DsvMaxSignatureLength=256
#DsvNullSignature=
DsvNullSignature=$$phProperties{'Config'}{'DsvNullSignature'}
$sDsvRequireSignatures
#DsvSignatureSuffix=.sig
#DynamicDirectory=/var/webjob/dynamic
#EnableConfigOverrides=Y
#EnableGetService=Y
$sEnableHostAccessList
#EnableJobQueues=Y
#EnableLogging=Y
#EnablePutService=Y
#EnableRequestTracking=N
#FolderList=common
#GetHookCommandLine=
#GetHookEnable=N
#GetHookLogDivertedOutput=N
#GetHookStatus=0
#GetHookStatusMap=
#GetTriggerCommandLine=
#GetTriggerEnable=N
#IncomingDirectory=/var/webjob/incoming
#JobQueueActive=Y
#JobQueueDirectory=/var/webjob/spool/jqd
#JobQueuePqActiveLimit=0
#JobQueuePqAnswerLimit=0
#JobQueueSqActiveLimit=1
#JobQueueSqAnswerLimit=0
#LogfilesDirectory=/var/webjob/logfiles
#MaxContentLength=100000000
#OverwriteExistingFiles=N
#ProfilesDirectory=/var/webjob/profiles
#PutHookCommandLine=
#PutHookEnable=N
#PutHookLogDivertedOutput=N
#PutHookStatus=0
#PutHookStatusMap=
#PutNameFormat=%cid/%cmd/%Y-%m-%d/%H.%M.%S.%pid
#PutTriggerCommandLine=
#PutTriggerEnable=N
#RequireMatch=Y
#RequireUser=Y
#ServerId=server_1
ServerId=$$phProperties{'ServerId'}
#SpoolDirectory=/var/webjob/spool
#SslRequireCn=N
#SslRequireMatch=N
#SslRequireSsl=Y
#UseGmt=N
EOF
      close(FH);
      if (!chown($$phProperties{'Derived'}{'WebJobUid'}, $$phProperties{'Derived'}{'WebJobGid'}, $sFile))
      {
        print STDERR "$$phProperties{'Program'}: Error='Unable to set owner/group for $sFile ($!).'\n";
      }
    }
    else
    {
      print STDERR "$$phProperties{'Program'}: Error='Unable to create $sFile ($!).'\n";
    }
  }

  1;
}


######################################################################
#
# CreateNphLogFiles
#
######################################################################

sub CreateNphLogFiles
{
  my ($phProperties, $psError) = @_;

  ####################################################################
  #
  # Check required inputs.
  #
  ####################################################################

  if (!defined($phProperties))
  {
    $$psError = "Unable to proceed due to missing or undefined inputs.";
    return undef;
  }

  ####################################################################
  #
  # Create log files. Don't abort on a single error, just print out a
  # message and keep on going.
  #
  ####################################################################

  my $sPrefix = $$phProperties{'Config'}{'WebJobLogfilesDirectory'};
  my $sUid = $$phProperties{'Derived'}{'ApacheUid'};
  my $sGid = $$phProperties{'Derived'}{'WebJobGid'};
  my $sMode = 0644 & ~$$phProperties{'Config'}{'WebJobUmask'};

  $$phProperties{'Pfmugs'} = # Prefix, File, Mode, UID, GID
  [
    [ $sPrefix, "jqd.log",                $sMode, $sUid, $sGid ],
    [ $sPrefix, "nph-config.log",         $sMode, $sUid, $sGid ],
    [ $sPrefix, "nph-config-hook.err",    $sMode, $sUid, $sGid ],
    [ $sPrefix, "nph-config-hook.log",    $sMode, $sUid, $sGid ],
    [ $sPrefix, "nph-config-hook.out",    $sMode, $sUid, $sGid ],
    [ $sPrefix, "nph-webjob.log",         $sMode, $sUid, $sGid ],
    [ $sPrefix, "nph-webjob-hook.err",    $sMode, $sUid, $sGid ],
    [ $sPrefix, "nph-webjob-hook.log",    $sMode, $sUid, $sGid ],
    [ $sPrefix, "nph-webjob-hook.out",    $sMode, $sUid, $sGid ],
    [ $sPrefix, "nph-webjob-trigger.log", $sMode, $sUid, $sGid ],
    [ $sPrefix, "register.log",           $sMode, $sUid, $sGid ],
  ];

  foreach my $paPfmug (@{$$phProperties{'Pfmugs'}})
  {
    my %hPfmugArgs =
    (
      'Prefix'    => $$paPfmug[0],
      'File'      => $$paPfmug[1],
      'Mode'      => $$paPfmug[2],
      'Uid'       => $$paPfmug[3],
      'Gid'       => $$paPfmug[4],
    );
    if (!defined(FdaCreateFile(\%hPfmugArgs)))
    {
      print STDERR "$$phProperties{'Program'}: Error='$hPfmugArgs{'Error'}'\n";
      next;
    }
  }

  1;
}


######################################################################
#
# CreateProfiles
#
######################################################################

sub CreateProfiles
{
  my ($phProperties, $psError) = @_;

  ####################################################################
  #
  # Check required inputs.
  #
  ####################################################################

  if (!defined($phProperties))
  {
    $$psError = "Unable to proceed due to missing or undefined inputs.";
    return undef;
  }

  ####################################################################
  #
  # Create each profile.
  #
  ####################################################################

  my (%hProfiles, %hSubject);

  $hProfiles{'webjob_config'}{'Ipv4Address'} = "0.0.0.0";
  $hProfiles{'webjob_config'}{'HostAccessList'} = "0.0.0.0/0";

  $hProfiles{'webjob_signer'}{'Ipv4Address'} = "0.0.0.0";
  $hProfiles{'webjob_signer'}{'HostAccessList'} = "0.0.0.0/32";

  %hSubject = %{$$phProperties{'Config'}{'ApacheSubject'}};
  delete($hSubject{'emailAddress'});

  foreach my $sClientId ("webjob_config", "webjob_signer")
  {
    ##################################################################
    #
    # Create the profile.
    #
    ##################################################################

    my (@aOpts, $sCommandLine, $sStatus, $sWebJobHome);

    push(@aOpts, "LocalProfile");
    push(@aOpts, "Force") if ($$phProperties{'ForceWrite'});
    $sWebJobHome = $$phProperties{'Config'}{'WebJobProfilesDirectory'} . "/" . $sClientId;
    $sCommandLine = qq(webjob-create-profile -C "$sWebJobHome" -h $$phProperties{'Hostname'} -s $$phProperties{'Hostname'} -p $$phProperties{'OsClass'} -c $sClientId);
    $sCommandLine .= " -o " . join(",", @aOpts);
    if (!open(PH, "$sCommandLine 2>&1 |"))
    {
      print STDERR "$$phProperties{'Program'}: Error='Failed to execute webjob-create-profile while creating the $sClientId profile ($!).'\n";
      next;
    }
    while (my $sLine = <PH>)
    {
      next if ($sLine =~ /^webjob-create-profile:/ && !$$phProperties{'Debug'});
      print $sLine;
    }
    close(PH);
    $sStatus = ($? >> 8) & 0xff;
    if ($sStatus != 0)
    {
      if ($sStatus == 3)
      {
        print STDERR "$$phProperties{'Program'}: Error='Encountered one or more errors while creating the $sClientId profile.'\n";
      }
      elsif ($sStatus == 4)
      {
        print STDERR "$$phProperties{'Program'}: Error='The $sClientId profile is locked. Use webjob-create-profile to update it manually (if needed).'\n";
      }
      else
      {
        print STDERR "$$phProperties{'Program'}: Error='Unable to create the $sClientId profile.'\n";
      }
      next;
    }

    ##################################################################
    #
    # Create identity files.
    #
    ##################################################################

    my (%hFiles, %hLArgs, $sRegistrationCode);

    $sRegistrationCode = "00000-00000-00000-00000-00000";
    $hSubject{'CN'} = $sClientId;
    %hLArgs =
    (
      'CertDays' => 1825,
      'ClientId' => $sClientId,
      'DsvToolExe' => "webjob-dsvtool",
      'Files' => \%hFiles,
      'ForceWrite' => $$phProperties{'ForceWrite'},
      'Hostname' => $$phProperties{'Hostname'},
      'KeyBits' => 1024,
      'KeyType' => "rsa",
      'OpenSslExe' => "openssl",
      'OsClass' => $$phProperties{'OsClass'},
      'RegistrationCode' => $sRegistrationCode,
      'Subject' => \%hSubject,
      'WebJobHome' => $sWebJobHome,
    );
    if (!DsvCreateIdentityFiles(\%hLArgs))
    {
      print STDERR "$$phProperties{'Program'}: Error='Unable to create identity files for $sClientId ($hLArgs{'Error'}).'\n";
    }

    ##################################################################
    #
    # Get the fingerprint from the newly created signing certificate.
    #
    ##################################################################

    my ($sDsvFingerprint);

    %hLArgs =
    (
      'File' => $$phProperties{'Config'}{'WebJobProfilesDirectory'} . "/" . $sClientId . "/etc/client-dsv-a.pem",
      'OpenSslExe' => "openssl",
      'Type' => "x509",
    );
    $sDsvFingerprint = DsvGetFingerprint(\%hLArgs);
    if (!defined($sDsvFingerprint))
    {
      print STDERR "$$phProperties{'Program'}: Error='Unable to get fingerprint for $sClientId ($hLArgs{'Error'}).'\n";
      $sDsvFingerprint = "";
    }

    ##################################################################
    #
    # Update MLDBM.
    #
    ##################################################################

    my ($sClientDb, $sCreated);

    $sClientDb = $$phProperties{'Config'}{'WebJobDbDirectory'} . "/mldbm/client.db";
    $sCreated = time();
    print "Updating $sClientDb\n";
    $sCommandLine = qq(webjob-mldbm-set-config-kvps -d $sClientDb -c $sClientId);
    $sCommandLine .= qq( "DsvFingerprint=$sDsvFingerprint") if (defined($sDsvFingerprint));
    $sCommandLine .= qq( "HostAccessList=$hProfiles{$sClientId}{'HostAccessList'}");
    $sCommandLine .= qq( "Ipv4Address=$hProfiles{$sClientId}{'Ipv4Address'}");
    $sCommandLine .= qq( "RegistrationCode=$sRegistrationCode") if (defined($sRegistrationCode));
    qx($sCommandLine);

    ##################################################################
    #
    # Copy the bundled CAs file into place.
    #
    ##################################################################

    foreach my $sExt ("a", "b")
    {
      my $sFile = $$phProperties{'Config'}{'WebJobProfilesDirectory'} . "/" . $sClientId . "/etc/bundled-cas-" . $sExt . ".pem";
      if (-f $sFile && !$$phProperties{'ForceWrite'})
      {
        print "Skipping $sFile (already exists)\n";
      }
      else
      {
        print "Creating $sFile\n";
        if (!copy($$phProperties{'Config'}{'ApacheSslBundledCAsFile'}, $sFile))
        {
          print STDERR "$$phProperties{'Program'}: Error='Unable to create \"$sFile\" ($!).'\n";
        }
        else
        {
          chmod(0640, $sFile);
        }
      }
    }

    ##################################################################
    #
    # Copy the master DSV file into place.
    #
    ##################################################################

    my ($sWebJobDsvFile, $sMasterDsvFile);

    $sWebJobDsvFile = $$phProperties{'Config'}{'WebJobConfigDirectory'} . "/dsv/webjob-dsv.pem";
    $sMasterDsvFile = $$phProperties{'Config'}{'WebJobProfilesDirectory'} . "/" . $sClientId . "/dsv/webjob-dsv.pem";
    if (-f $sMasterDsvFile && !$$phProperties{'ForceWrite'})
    {
      print "Skipping $sMasterDsvFile (already exists)\n";
    }
    else
    {
      print "Creating $sMasterDsvFile\n";
      if (!copy($sWebJobDsvFile, $sMasterDsvFile))
      {
        print STDERR "$$phProperties{'Program'}: Error='Unable to create \"$sMasterDsvFile\" ($!).'\n";
      }
    }

    ##################################################################
    #
    # Create a custom override for the webjob_config profile.
    #
    ##################################################################

    if ($sClientId eq "webjob_config")
    {
      my $sFile = $$phProperties{'Config'}{'WebJobConfigDirectory'} . "/nph-webjob/clients/webjob_config/nph-webjob.cfg";

      if (-f $sFile && !$$phProperties{'ForceWrite'})
      {
        print "Skipping $sFile (already exists)\n";
      }
      else
      {
        print "Creating $sFile\n";
        if (open(FH, "> $sFile"))
        {
          print FH <<EOF;
FolderList=
EOF
          close(FH);
          if (!chown($$phProperties{'Derived'}{'WebJobUid'}, $$phProperties{'Derived'}{'WebJobGid'}, $sFile))
          {
            print STDERR "$$phProperties{'Program'}: Error='Unable to set owner/group for $sFile ($!).'\n";
          }
        }
        else
        {
          print STDERR "$$phProperties{'Program'}: Error='Unable to create $sFile ($!).'\n";
        }
      }
    }
  }

  1;
}


######################################################################
#
# CreateSkelFiles
#
######################################################################

sub CreateSkelFiles
{
  my ($phProperties, $psError) = @_;

  ####################################################################
  #
  # Check required inputs.
  #
  ####################################################################

  if (!defined($phProperties))
  {
    $$psError = "Unable to proceed due to missing or undefined inputs.";
    return undef;
  }

  ####################################################################
  #
  # Create skeleton files.
  #
  ####################################################################

  my ($sFile);

  foreach my $sPlatform ("unix", "winx")
  {
    my $sRunDirectory = ($sPlatform eq "unix") ? "%sample_client_home/run" : "%sample_client_home\\run";
    my $sDsvDirectory = ($sPlatform eq "unix") ? "%sample_client_home/dsv" : "%sample_client_home\\dsv";
    my $sEtcDirectory = ($sPlatform eq "unix") ? "%sample_client_home/etc" : "%sample_client_home\\etc";
    my $sSeparator = ($sPlatform eq "unix") ? "/" : "\\";
    my $sExtension = ($sPlatform eq "unix") ? "" : ".exe";

    foreach my $sExt ("a", "b")
    {
      $sFile = $$phProperties{'Config'}{'WebJobConfigDirectory'} . "/skel/upload-$sPlatform-$sExt.cfg";

      my $sBundledCasFile = $sEtcDirectory . $sSeparator . "bundled-cas-" . $sExt . ".pem";
      my $sClientCrtFile = $sEtcDirectory . $sSeparator . "client-crt-" . $sExt . ".pem";
      my $sClientKeyFile = $sEtcDirectory . $sSeparator . "client-key-" . $sExt . ".pem";
      my $sCustomFile = $sEtcDirectory . $sSeparator . "custom-" . $sExt . ".cfg";

      if (-f $sFile && !$$phProperties{'ForceWrite'})
      {
        print "Skipping $sFile (already exists)\n";
      }
      else
      {
        print "Creating $sFile\n";
        if (open(FH, "> $sFile"))
        {
          print FH <<EOF;
ClientId=%sample_client
UrlAuthType=basic
UrlUsername=%sample_client
UrlPassword=%sample_password
UrlGetUrl=https://%sample_server/cgi-client/nph-webjob.cgi
UrlPutUrl=https://%sample_server/cgi-client/nph-webjob.cgi
SslUseCertificate=N
SslPublicCertFile=$sClientCrtFile
SslPrivateKeyFile=$sClientKeyFile
SslVerifyPeerCert=N
SslBundledCAsFile=$sBundledCasFile
SslExpectedPeerCN=%sample_server_cn
SslMaxChainLength=2
OverwriteExecutable=Y
TempDirectory=$sRunDirectory
UnlinkExecutable=Y
UnlinkOutput=Y
GetTimeLimit=1800
RunTimeLimit=86400
PutTimeLimit=28800
DsvCertificateTree=$sDsvDirectory
DsvVerifySignature=Y
Import=$sCustomFile
EOF
          close(FH);
          if (!chown($$phProperties{'Derived'}{'WebJobUid'}, $$phProperties{'Derived'}{'WebJobGid'}, $sFile))
          {
            print STDERR "$$phProperties{'Program'}: Error='Unable to set owner/group for $sFile ($!).'\n";
          }
        }
        else
        {
          print STDERR "$$phProperties{'Program'}: Error='Unable to create $sFile ($!).'\n";
        }
      }
    }

    foreach my $sExt ("a", "b")
    {
      foreach my $sConfig ("upload")
      {
        $sFile = $$phProperties{'Config'}{'WebJobConfigDirectory'} . "/skel/$sConfig-$sPlatform-$sExt.ptr";

        my $sImportFile = $sEtcDirectory . $sSeparator . "$sConfig-$sExt.cfg";

        if (-f $sFile && !$$phProperties{'ForceWrite'})
        {
          print "Skipping $sFile (already exists)\n";
        }
        else
        {
          print "Creating $sFile\n";
          if (open(FH, "> $sFile"))
          {
            print FH <<EOF;
Import=$sImportFile
EOF
            close(FH);
            if (!chown($$phProperties{'Derived'}{'WebJobUid'}, $$phProperties{'Derived'}{'WebJobGid'}, $sFile))
            {
              print STDERR "$$phProperties{'Program'}: Error='Unable to set owner/group for $sFile ($!).'\n";
            }
          }
          else
          {
            print STDERR "$$phProperties{'Program'}: Error='Unable to create $sFile ($!).'\n";
          }
        }
      }
    }

    foreach my $sConfig ("hourly", "daily")
    {
      $sFile = $$phProperties{'Config'}{'WebJobConfigDirectory'} . "/skel/worker-$sConfig-$sPlatform.cfg";
      my $sWebJobExe = "%wjcd_home" . $sSeparator . "bin" . $sSeparator . "webjob" . $sExtension;
      my $sUploadCfg = "%wjcd_home" . $sSeparator . "etc" . $sSeparator . "upload.cfg";

      if (-f $sFile && !$$phProperties{'ForceWrite'})
      {
        print "Skipping $sFile (already exists)\n";
      }
      else
      {
        print "Creating $sFile\n";
        if (open(FH, "> $sFile"))
        {
          print FH <<EOF;
Active=Y
Minute=%M
Hour=%H
DayOfWeek=%w
DayOfMonth=%d
Month=%m
Command=$sWebJobExe
CommandLine="$sWebJobExe" -e -f "$sUploadCfg" $sConfig
LogOutput=N
Sticky=Y
EOF
          close(FH);
          if (!chown($$phProperties{'Derived'}{'WebJobUid'}, $$phProperties{'Derived'}{'WebJobGid'}, $sFile))
          {
            print STDERR "$$phProperties{'Program'}: Error='Unable to set owner/group for $sFile ($!).'\n";
          }
        }
        else
        {
          print STDERR "$$phProperties{'Program'}: Error='Unable to create $sFile ($!).'\n";
        }
      }
    }
  }

  1;
}


######################################################################
#
# CreateWebJobConf
#
######################################################################

sub CreateWebJobConf
{
  my ($phProperties, $psError) = @_;

  ####################################################################
  #
  # Check required inputs.
  #
  ####################################################################

  if (!defined($phProperties))
  {
    $$psError = "Unable to proceed due to missing or undefined inputs.";
    return undef;
  }

  ####################################################################
  #
  # Create webjob.conf.
  #
  ####################################################################

  my ($sFile);

  $sFile = $$phProperties{'Config'}{'WebJobConfigDirectory'} . "/apache/webjob.conf";

  if (-f $sFile && !$$phProperties{'ForceWrite'})
  {
    print "Skipping $sFile (already exists)\n";
  }
  else
  {
    print "Creating $sFile\n";
    if (open(FH, "> $sFile"))
    {
      print FH <<EOF;
######################################################################
#
# Various configuration files are used to control runtime behavior.
# To make use of them, set the following environment variables as
# appropriate. Upon execution, each CGI script checks for its assigned
# variable and attempts to process the specified file.
#
######################################################################

SetEnv CONFIG_PROPERTIES_FILE $$phProperties{'Config'}{'WebJobConfigDirectory'}/nph-config/nph-config.cfg
SetEnv WEBJOB_PROPERTIES_FILE $$phProperties{'Config'}{'WebJobConfigDirectory'}/nph-webjob/nph-webjob.cfg

######################################################################
#
# The cgi-client directory is where client-based scripts live.
#
######################################################################

ScriptAlias /cgi-client/ "$$phProperties{'Config'}{'WebJobCgiDirectory'}/cgi-client/"

<Directory "$$phProperties{'Config'}{'WebJobCgiDirectory'}/cgi-client">
  AllowOverride AuthConfig
  Options None
  Order allow,deny
  Allow from all
</Directory>

######################################################################
#
# The cgi-public directory is where user-based scripts live.
#
######################################################################

ScriptAlias /cgi-public/ "$$phProperties{'Config'}{'WebJobCgiDirectory'}/cgi-public/"

<Directory "$$phProperties{'Config'}{'WebJobCgiDirectory'}/cgi-public">
  AllowOverride AuthConfig
  Options None
  Order allow,deny
  Allow from all
</Directory>
EOF
      close(FH);
      if (!chown($$phProperties{'Derived'}{'WebJobUid'}, $$phProperties{'Derived'}{'WebJobGid'}, $sFile))
      {
        print STDERR "$$phProperties{'Program'}: Error='Unable to set owner/group for $sFile ($!).'\n";
      }
    }
    else
    {
      print STDERR "$$phProperties{'Program'}: Error='Unable to create $sFile ($!).'\n";
    }
  }

  1;
}


######################################################################
#
# CreateWebJobDirectories
#
######################################################################

sub CreateWebJobDirectories
{
  my ($phProperties, $psError) = @_;

  ####################################################################
  #
  # Define and create server-side directory structure.
  #
  ####################################################################

  $$phProperties{'Pdmugs'} = # Prefix, Directory, Mode, UID, GID
  [
    [
      undef,
      $$phProperties{'Config'}{'WebJobServerDirectory'},
      $$phProperties{'Derived'}{'WebJobServerDirectoryMode'},
      $$phProperties{'Derived'}{'WebJobServerDirectoryUid'},
      $$phProperties{'Derived'}{'WebJobServerDirectoryGid'},
    ],
    [
      undef,
      $$phProperties{'Config'}{'WebJobArchivesDirectory'},
      $$phProperties{'Derived'}{'WebJobArchivesDirectoryMode'},
      $$phProperties{'Derived'}{'WebJobArchivesDirectoryUid'},
      $$phProperties{'Derived'}{'WebJobArchivesDirectoryGid'},
    ],
    [
      undef,
      $$phProperties{'Config'}{'WebJobBinDirectory'},
      $$phProperties{'Derived'}{'WebJobBinDirectoryMode'},
      $$phProperties{'Derived'}{'WebJobBinDirectoryUid'},
      $$phProperties{'Derived'}{'WebJobBinDirectoryGid'},
    ],
    [
      undef,
      $$phProperties{'Config'}{'WebJobCgiDirectory'},
      $$phProperties{'Derived'}{'WebJobCgiDirectoryMode'},
      $$phProperties{'Derived'}{'WebJobCgiDirectoryUid'},
      $$phProperties{'Derived'}{'WebJobCgiDirectoryGid'},
    ],
    [
      $$phProperties{'Config'}{'WebJobCgiDirectory'},
      "cgi-client",
      $$phProperties{'Derived'}{'WebJobCgiDirectoryMode'},
      $$phProperties{'Derived'}{'WebJobCgiDirectoryUid'},
      $$phProperties{'Derived'}{'WebJobCgiDirectoryGid'},
    ],
    [
      $$phProperties{'Config'}{'WebJobCgiDirectory'},
      "cgi-public",
      $$phProperties{'Derived'}{'WebJobCgiDirectoryMode'},
      $$phProperties{'Derived'}{'WebJobCgiDirectoryUid'},
      $$phProperties{'Derived'}{'WebJobCgiDirectoryGid'},
    ],
    [
      undef,
      $$phProperties{'Config'}{'WebJobConfigDirectory'},
      $$phProperties{'Derived'}{'WebJobConfigDirectoryMode'},
      $$phProperties{'Derived'}{'WebJobConfigDirectoryUid'},
      $$phProperties{'Derived'}{'WebJobConfigDirectoryGid'},
    ],
    [
      $$phProperties{'Config'}{'WebJobConfigDirectory'},
      "apache",
      $$phProperties{'Derived'}{'WebJobConfigDirectoryMode'},
      $$phProperties{'Derived'}{'WebJobConfigDirectoryUid'},
      $$phProperties{'Derived'}{'WebJobConfigDirectoryGid'},
    ],
    [
      $$phProperties{'Config'}{'WebJobConfigDirectory'},
      "dsv",
      $$phProperties{'Derived'}{'WebJobConfigDirectoryMode'},
      $$phProperties{'Derived'}{'WebJobConfigDirectoryUid'},
      $$phProperties{'Derived'}{'WebJobConfigDirectoryGid'},
    ],
    [
      $$phProperties{'Config'}{'WebJobConfigDirectory'},
      "jqd",
      $$phProperties{'Derived'}{'WebJobConfigDirectoryMode'},
      $$phProperties{'Derived'}{'WebJobConfigDirectoryUid'},
      $$phProperties{'Derived'}{'WebJobConfigDirectoryGid'},
    ],
    [
      $$phProperties{'Config'}{'WebJobConfigDirectory'},
      "nph-config",
      $$phProperties{'Derived'}{'WebJobConfigDirectoryMode'},
      $$phProperties{'Derived'}{'WebJobConfigDirectoryUid'},
      $$phProperties{'Derived'}{'WebJobConfigDirectoryGid'},
    ],
    [
      $$phProperties{'Config'}{'WebJobConfigDirectory'},
      "nph-config/clients",
      $$phProperties{'Derived'}{'WebJobConfigDirectoryMode'},
      $$phProperties{'Derived'}{'WebJobConfigDirectoryUid'},
      $$phProperties{'Derived'}{'WebJobConfigDirectoryGid'},
    ],
    [
      $$phProperties{'Config'}{'WebJobConfigDirectory'},
      "nph-config/configs",
      $$phProperties{'Derived'}{'WebJobConfigDirectoryMode'},
      $$phProperties{'Derived'}{'WebJobConfigDirectoryUid'},
      $$phProperties{'Derived'}{'WebJobConfigDirectoryGid'},
    ],
    [
      $$phProperties{'Config'}{'WebJobConfigDirectory'},
      "nph-webjob",
      $$phProperties{'Derived'}{'WebJobConfigDirectoryMode'},
      $$phProperties{'Derived'}{'WebJobConfigDirectoryUid'},
      $$phProperties{'Derived'}{'WebJobConfigDirectoryGid'},
    ],
    [
      $$phProperties{'Config'}{'WebJobConfigDirectory'},
      "nph-webjob/clients",
      $$phProperties{'Derived'}{'WebJobConfigDirectoryMode'},
      $$phProperties{'Derived'}{'WebJobConfigDirectoryUid'},
      $$phProperties{'Derived'}{'WebJobConfigDirectoryGid'},
    ],
    [
      $$phProperties{'Config'}{'WebJobConfigDirectory'},
      "nph-webjob/commands",
      $$phProperties{'Derived'}{'WebJobConfigDirectoryMode'},
      $$phProperties{'Derived'}{'WebJobConfigDirectoryUid'},
      $$phProperties{'Derived'}{'WebJobConfigDirectoryGid'},
    ],
    [
      $$phProperties{'Config'}{'WebJobConfigDirectory'},
      "nph-webjob/queues",
      $$phProperties{'Derived'}{'WebJobConfigDirectoryMode'},
      $$phProperties{'Derived'}{'WebJobConfigDirectoryUid'},
      $$phProperties{'Derived'}{'WebJobConfigDirectoryGid'},
    ],
    [
      $$phProperties{'Config'}{'WebJobConfigDirectory'},
      "skel",
      $$phProperties{'Derived'}{'WebJobConfigDirectoryMode'},
      $$phProperties{'Derived'}{'WebJobConfigDirectoryUid'},
      $$phProperties{'Derived'}{'WebJobConfigDirectoryGid'},
    ],
    [
      undef,
      $$phProperties{'Config'}{'WebJobDbDirectory'},
      $$phProperties{'Derived'}{'WebJobDbDirectoryMode'},
      $$phProperties{'Derived'}{'WebJobDbDirectoryUid'},
      $$phProperties{'Derived'}{'WebJobDbDirectoryGid'},
    ],
    [
      $$phProperties{'Config'}{'WebJobDbDirectory'},
      "mldbm",
      $$phProperties{'Derived'}{'WebJobDbDirectoryMode'},
      $$phProperties{'Derived'}{'WebJobDbDirectoryUid'},
      $$phProperties{'Derived'}{'WebJobDbDirectoryGid'},
    ],
    [
      $$phProperties{'Config'}{'WebJobDbDirectory'},
      "pound",
      $$phProperties{'Derived'}{'WebJobDbDirectoryMode'},
      $$phProperties{'Derived'}{'WebJobDbDirectoryUid'},
      $$phProperties{'Derived'}{'WebJobDbDirectoryGid'},
    ],
    [
      $$phProperties{'Config'}{'WebJobDbDirectory'},
      "pound/commands",
      $$phProperties{'Derived'}{'WebJobDbDirectoryMode'},
      $$phProperties{'Derived'}{'WebJobDbDirectoryUid'},
      $$phProperties{'Derived'}{'WebJobDbDirectoryGid'},
    ],
    [
      undef,
      $$phProperties{'Config'}{'WebJobDsvDirectory'},
      $$phProperties{'Derived'}{'WebJobDsvDirectoryMode'},
      $$phProperties{'Derived'}{'WebJobDsvDirectoryUid'},
      $$phProperties{'Derived'}{'WebJobDsvDirectoryGid'},
    ],
    [
      undef,
      $$phProperties{'Config'}{'WebJobDynamicDirectory'},
      $$phProperties{'Derived'}{'WebJobDynamicDirectoryMode'},
      $$phProperties{'Derived'}{'WebJobDynamicDirectoryUid'},
      $$phProperties{'Derived'}{'WebJobDynamicDirectoryGid'},
    ],
    [
      undef,
      $$phProperties{'Config'}{'WebJobEtcDirectory'},
      $$phProperties{'Derived'}{'WebJobEtcDirectoryMode'},
      $$phProperties{'Derived'}{'WebJobEtcDirectoryUid'},
      $$phProperties{'Derived'}{'WebJobEtcDirectoryGid'},
    ],
    [
      undef,
      $$phProperties{'Config'}{'WebJobIncomingDirectory'},
      $$phProperties{'Derived'}{'WebJobIncomingDirectoryMode'},
      $$phProperties{'Derived'}{'WebJobIncomingDirectoryUid'},
      $$phProperties{'Derived'}{'WebJobIncomingDirectoryGid'},
    ],
    [
      undef,
      $$phProperties{'Config'}{'WebJobLibDirectory'},
      $$phProperties{'Derived'}{'WebJobLibDirectoryMode'},
      $$phProperties{'Derived'}{'WebJobLibDirectoryUid'},
      $$phProperties{'Derived'}{'WebJobLibDirectoryGid'},
    ],
    [
      undef,
      $$phProperties{'Config'}{'WebJobLogfilesDirectory'},
      $$phProperties{'Derived'}{'WebJobLogfilesDirectoryMode'},
      $$phProperties{'Derived'}{'WebJobLogfilesDirectoryUid'},
      $$phProperties{'Derived'}{'WebJobLogfilesDirectoryGid'},
    ],
    [
      undef,
      $$phProperties{'Config'}{'WebJobProfilesDirectory'},
      $$phProperties{'Derived'}{'WebJobProfilesDirectoryMode'},
      $$phProperties{'Derived'}{'WebJobProfilesDirectoryUid'},
      $$phProperties{'Derived'}{'WebJobProfilesDirectoryGid'},
    ],
    [
      $$phProperties{'Config'}{'WebJobProfilesDirectory'},
      "common",
      $$phProperties{'Derived'}{'WebJobProfilesDirectoryMode'},
      $$phProperties{'Derived'}{'WebJobProfilesDirectoryUid'},
      $$phProperties{'Derived'}{'WebJobProfilesDirectoryGid'},
    ],
    [
      $$phProperties{'Config'}{'WebJobProfilesDirectory'},
      "common/commands",
      $$phProperties{'Derived'}{'WebJobProfilesDirectoryMode'},
      $$phProperties{'Derived'}{'WebJobProfilesDirectoryUid'},
      $$phProperties{'Derived'}{'WebJobProfilesDirectoryGid'},
    ],
    [
      $$phProperties{'Config'}{'WebJobProfilesDirectory'},
      "common/configs",
      $$phProperties{'Derived'}{'WebJobProfilesDirectoryMode'},
      $$phProperties{'Derived'}{'WebJobProfilesDirectoryUid'},
      $$phProperties{'Derived'}{'WebJobProfilesDirectoryGid'},
    ],
    [
      $$phProperties{'Config'}{'WebJobProfilesDirectory'},
      "common/content",
      $$phProperties{'Derived'}{'WebJobProfilesDirectoryMode'},
      $$phProperties{'Derived'}{'WebJobProfilesDirectoryUid'},
      $$phProperties{'Derived'}{'WebJobProfilesDirectoryGid'},
    ],
    [
      $$phProperties{'Config'}{'WebJobProfilesDirectory'},
      "common/hooks",
      $$phProperties{'Derived'}{'WebJobProfilesDirectoryMode'},
      $$phProperties{'Derived'}{'WebJobProfilesDirectoryUid'},
      $$phProperties{'Derived'}{'WebJobProfilesDirectoryGid'},
    ],
    [
      $$phProperties{'Config'}{'WebJobProfilesDirectory'},
      "common/triggers",
      $$phProperties{'Derived'}{'WebJobProfilesDirectoryMode'},
      $$phProperties{'Derived'}{'WebJobProfilesDirectoryUid'},
      $$phProperties{'Derived'}{'WebJobProfilesDirectoryGid'},
    ],
    [
      undef,
      $$phProperties{'Config'}{'WebJobRunDirectory'},
      $$phProperties{'Derived'}{'WebJobRunDirectoryMode'},
      $$phProperties{'Derived'}{'WebJobRunDirectoryUid'},
      $$phProperties{'Derived'}{'WebJobRunDirectoryGid'},
    ],
    [
      undef,
      $$phProperties{'Config'}{'WebJobWebDirectory'},
      $$phProperties{'Derived'}{'WebJobWebDirectoryMode'},
      $$phProperties{'Derived'}{'WebJobWebDirectoryUid'},
      $$phProperties{'Derived'}{'WebJobWebDirectoryGid'},
    ],
    [
      undef,
      $$phProperties{'Config'}{'WebJobSpoolDirectory'},
      $$phProperties{'Derived'}{'WebJobSpoolDirectoryMode'},
      $$phProperties{'Derived'}{'WebJobSpoolDirectoryUid'},
      $$phProperties{'Derived'}{'WebJobSpoolDirectoryGid'},
    ],
    [
      $$phProperties{'Config'}{'WebJobSpoolDirectory'},
      "jqd",
      $$phProperties{'Derived'}{'WebJobSpoolDirectoryMode'} | 0020,
      $$phProperties{'Derived'}{'WebJobSpoolDirectoryUid'},
      $$phProperties{'Derived'}{'ApacheGid'},
    ],
    [
      $$phProperties{'Config'}{'WebJobSpoolDirectory'},
      "rid",
      $$phProperties{'Derived'}{'WebJobSpoolDirectoryMode'} | 0020,
      $$phProperties{'Derived'}{'WebJobSpoolDirectoryUid'},
      $$phProperties{'Derived'}{'ApacheGid'},
    ],
    [
      undef,
      $$phProperties{'Config'}{'WebJobUsersDirectory'},
      $$phProperties{'Derived'}{'WebJobUsersDirectoryMode'},
      $$phProperties{'Derived'}{'WebJobUsersDirectoryUid'},
      $$phProperties{'Derived'}{'WebJobUsersDirectoryGid'},
    ],
  ];

  foreach my $paPdmug (@{$$phProperties{'Pdmugs'}})
  {
    my %hPdmugArgs =
    (
      'Prefix'    => $$paPdmug[0],
      'Directory' => $$paPdmug[1],
      'Mode'      => $$paPdmug[2],
      'Uid'       => $$paPdmug[3],
      'Gid'       => $$paPdmug[4],
    );
    if (!defined(FdaCreateDirectory(\%hPdmugArgs)))
    {
      $$psError = $hPdmugArgs{'Error'};
      return undef;
    }
  }

  1;
}


######################################################################
#
# ModifyApacheHttpdConf
#
######################################################################

sub ModifyApacheHttpdConf
{
  my ($phProperties, $psError) = @_;

  ####################################################################
  #
  # Check required inputs.
  #
  ####################################################################

  if (!defined($phProperties))
  {
    $$psError = "Unable to proceed due to missing or undefined inputs.";
    return undef;
  }

  ####################################################################
  #
  # Modify httpd.conf.
  #
  ####################################################################

  my (@aLines, $sFile, $sFound, $sTargetLine);

  $sFile = $$phProperties{'Config'}{'ApacheConfDirectory'} . "/" . "httpd.conf";

  if (!open(FH, "< $sFile"))
  {
    $$psError = "Unable to open $sFile for reading ($!).";
    return undef;
  }
  @aLines = <FH>;
  close(FH);

  $sTargetLine = "Include $$phProperties{'Config'}{'WebJobConfigDirectory'}/apache/webjob.conf";
  $sFound = 0;
  foreach my $sLine (@aLines)
  {
    if ($sLine =~ /^$sTargetLine$/)
    {
      $sFound = 1;
      last;
    }
  }

  if ($sFound == 0)
  {
    my $sBackupFile = $sFile . ".webjob.bak";
    print "Creating $sBackupFile\n";
    if (!open(FH, "> $sBackupFile"))
    {
      $$psError = "Unable to open $sBackupFile for writing ($!).";
      return undef;
    }
    print FH @aLines;
    close(FH);

    print "Altering $sFile\n";
    if (!open(FH, ">> $sFile"))
    {
      $$psError = "Unable to open $sFile in append mode ($!).";
      return undef;
    }
    print FH $sTargetLine, "\n";
    close(FH);
  }
  else
  {
    print "Skipping $sFile (already includes webjob.conf)\n";
  }

  1;
}


######################################################################
#
# PrintConfigProperties
#
######################################################################

sub PrintConfigProperties
{
  my ($phProperties, $sType) = @_;

  if ($sType =~ /^all$/i)
  {
    PrintConfigProperties($phProperties, "config");
    PrintConfigProperties($phProperties, "derived");
  }
  elsif ($sType =~ /^(config|derived)$/i)
  {
    my $sHash = lc($sType);
    $sHash =~ s/^(.)/uc($1)/e;
    print "$sHash properties ...\n";
    foreach my $sKey (sort(keys(%{$$phProperties{$sHash}})))
    {
      if ($sKey =~ /(Mode|Umask)/)
      {
        printf("%s=%04o\n", $sKey, $$phProperties{$sHash}{$sKey});
      }
      else
      {
        print $sKey, "=", $$phProperties{$sHash}{$sKey}, "\n";
      }
    }
  }

  1;
}


######################################################################
#
# Usage
#
######################################################################

sub Usage
{
  my ($sProgram) = @_;
  print STDERR "\n";
  print STDERR "Usage: $sProgram [-F] [-p {all|config|derived}] -f {file|-}\n";
  print STDERR "\n";
  exit(1);
}


=pod

=head1 NAME

webjob-setup-server - Install and configure WebJob server-side components.

=head1 SYNOPSIS

B<webjob-setup-server> B<[-F]> B<[-p {all|config|derived}]> B<-f {file|-}>

=head1 DESCRIPTION

This utility installs and configures WebJob server-side components.

The tree structure depicted here is used by the server to support
WebJob request processing.  Each top-level directory is explained in
the following paragraphs.

    webjob
       |
       + archives (reserved)
       |
       + config
       |   |
       |   + apache
       |   |   |
       |   |   - ht-client
       |   |   - ht-public
       |   |   - webjob.conf
       |   |
       |   + dsv
       |   |   |
       |   |   - webjob-dsv.pem
       |   |   - webjob-key.pem
       |   |
       |   + jqd
       |   |   |
       |   |   - groups
       |   |
       |   + nph-config
       |   |   |
       |   |   - nph-config.cfg
       |   |   |
       |   |   + clients
       |   |   |   |
       |   |   |   - nph-config.cfg
       |   |   |   |
       |   |   |   + <client-N>
       |   |   |       |
       |   |   |       - nph-config.cfg
       |   |   |       |
       |   |   |       + <command-N>
       |   |   |           |
       |   |   |           - nph-config.cfg
       |   |   |
       |   |   + configs
       |   |       |
       |   |       - nph-config.cfg
       |   |       |
       |   |       + <config-N>
       |   |           |
       |   |           - nph-config.cfg
       |   |           |
       |   |           + <client-N>
       |   |               |
       |   |               - nph-config.cfg
       |   |
       |   + nph-webjob
       |   |   |
       |   |   - nph-webjob.cfg
       |   |   |
       |   |   + clients
       |   |   |   |
       |   |   |   - nph-webjob.cfg
       |   |   |   |
       |   |   |   + <client-N>
       |   |   |       |
       |   |   |       - nph-webjob.cfg
       |   |   |       |
       |   |   |       + <command-N>
       |   |   |           |
       |   |   |           - nph-webjob.cfg
       |   |   |
       |   |   + commands
       |   |   |   |
       |   |   |   - nph-webjob.cfg
       |   |   |   |
       |   |   |   + <command-N>
       |   |   |       |
       |   |   |       - nph-webjob.cfg
       |   |   |       |
       |   |   |       + <client-N>
       |   |   |           |
       |   |   |           - nph-webjob.cfg
       |   |   |
       |   |   + queues
       |   |       |
       |   |       - nph-webjob.cfg
       |   |       |
       |   |       + <client-N>
       |   |           |
       |   |           - nph-webjob.cfg
       |   |
       |   + skel
       |       |
       |       - upload-unix-a.cfg
       |       - upload-unix-a.ptr
       |       - upload-unix-b.cfg
       |       - upload-unix-b.ptr
       |       - upload-winx-a.cfg
       |       - upload-winx-a.ptr
       |       - upload-winx-b.cfg
       |       - upload-winx-b.ptr
       |       - worker-daily-unix.cfg
       |       - worker-daily-winx.cfg
       |       - worker-hourly-unix.cfg
       |       - worker-hourly-winx.cfg
       |
       + db
       |   |
       |   + mldbm
       |   |   |
       |   |   - client.db
       |   |   - client.db.lock
       |   |
       |   + pound
       |       |
       |       + commands
       |
       + dynamic
       |   |
       |   + <client-N>
       |       |
       |       + jids
       |           |
       |           - <jid-N>.get
       |           - <jid-N>.put
       |
       + incoming
       |   |
       |   - <job-N>.out
       |   - <job-N>.err
       |   - <job-N>.env
       |   - <job-N>.rdy
       |
       + logfiles
       |   |
       |   - nph-config.log
       |   - nph-config-hook.err
       |   - nph-config-hook.log
       |   - nph-config-hook.out
       |   - nph-webjob.log
       |   - nph-webjob-hook.err
       |   - nph-webjob-hook.log
       |   - nph-webjob-hook.out
       |   - nph-webjob-trigger.log
       |
       + outcasts (reserved)
       |
       + pidfiles (reserved)
       |
       + pipeline (reserved)
       |
       + profiles
       |   |
       |   + <common-N>
       |   |   |
       |   |   + commands
       |   |   |   |
       |   |   |   - <command-N>
       |   |   |
       |   |   + configs
       |   |   |   |
       |   |   |   - <config-N>
       |   |   |
       |   |   + content
       |   |   |   |
       |   |   |   - <content-N>
       |   |   |
       |   |   + hooks
       |   |   |   |
       |   |   |   - <hook-N>
       |   |   |
       |   |   + triggers
       |   |       |
       |   |       - <trigger-N>
       |   |
       |   + <client-N>
       |       |
       |       + baseline
       |       |   |
       |       |   + <dir-1>
       |       |   |   |
       |       |   |   - <file-1>
       |       |   |
       |       |   + <dir-N>
       |       |       |
       |       |       - <file-N>
       |       |
       |       + etc
       |       |   |
       |       |   - upload-a.cfg
       |       |   - upload-a.ptr
       |       |   - upload-b.cfg
       |       |   - upload-b.ptr
       |       |   - upload.cfg
       |       |
       |       + commands
       |       |   |
       |       |   - <command-N>
       |       |
       |       + configs
       |       |   |
       |       |   - <config-N>
       |       |
       |       + content
       |       |   |
       |       |   - <command-N>
       |       |
       |       + hooks
       |       |   |
       |       |   - <hook-N>
       |       |
       |       + triggers
       |       |   |
       |       |   - <trigger-N>
       |       |
       |       + workers
       |           |
       |           - daily.cfg
       |           - hourly.cfg
       |
       + spool
       |   |
       |   + jqd
       |   |   |
       |   |   + <client-N>
       |   |   |
       |   |   + <public-N>
       |   |
       |   + rid
       |       |
       |       + <rid-N>
       |
       + users
           |
           + <user-N>

=over 4

=item B<archives>

The archives directory is an area where uploaded data may be placed
for long-term storage.  It is reserved for future use.

=item B<config>

The config directory contains sub-directories for major server-side
components (e.g., Apache, DSV, JQD, etc.) and applications.  For
example, the sub-directory called nph-webjob contains the main CGI
configuration file for nph-webjob.cgi.  The directories below this
level are optional, and they are used in conjunction with the
EnableConfigOverrides and ConfigSearchOrder controls.

=item B<db>

The db directory is an area where various databases may be placed and
maintained.

=item B<dynamic>

The dynamic directory is an area where content is stored temporarily
while it is being delivered to a particular client.

=item B<incoming>

The incoming directory is a holding tank.  This is where uploaded data
is stored prior to being processed.  Files in this directory are
created automatically by nph-webjob.cgi as clients post their data to
the server.  The names of these files may be customized (see
PutNameFormat property in nph-webjob.cfg).

=item B<logfiles>

The logfiles directory is self-explanatory.

=item B<pidfiles>

The pidfiles directory is reserved for future use.

=item B<pipeline>

The pipeline directory is reserved for future use.

=item B<outcasts>

The outcasts directory is reserved for future use.

=item B<profiles>

The profiles directory contains one or more shared directories
(e.g., common, shared, sysadmin, etc.) and one directory for each
client that is managed by the server.

Client directories may contain some or all of the following
sub-directories: baseline, commands, configs, content, dsv, etc,
hooks, log, run, triggers, and workers.  The baseline tree is intended
to be a mirror or backup (perhaps sparsely populated) of the client
file system.  Trusted files are placed in this tree just as they would
appear on the client system relative to the root directory.  The
configs tree is intended to contain various config files that the
client may request via nph-config.cgi while executing jobs.  The etc
tree holds client-specific config files.  The commands tree is
intended to contain programs or scripts that you want the client to
execute.  This directory may also be populated with symbolic links
that point to native files in the corresponding baseline tree.  WebJob
automatically looks in the commands tree for programs and scripts that
may be served to the client.  The other sub-directores are reserved
for local profiles and/or future use.

Shared directories (e.g., common, shared, webadmin, etc.) are
used to store programs common to multiple clients.  These directories
are searched when the requested program or script can't be found
in the client's commands directory.  Shared directories are enabled
by adding their name to the folder list (see FolderList property
in nph-webjob.cfg).

=item B<spool>

The spool directory is an area where various spools or queues may be
placed and maintained.

=item B<users>

The users directory is reserved for future use.

=back

=head1 OPTIONS

=over 4

=item B<-F>

Force existing files to be overwritten.  By default, existing files
are not modified.

=item B<-f {file|-}>

Specifies the name of the input file. A value of '-' will cause the
program to read from stdin.

=item B<-p {all|config|derived}>

Print configuration properties to stdout.  A value of 'config' will
print all specified properties from the file and any remaining
properties that have a default value.  A value of 'derived' will print
only those properties that are derived from base properties.  A value
of 'all' will print both sets of properties.

=back

=head1 AUTHOR

Klayton Monroe

=head1 SEE ALSO

nph-config.cgi(1), nph-webjob.cgi(1)

=head1 LICENSE

All documentation and code are distributed under same terms and
conditions as B<WebJob>.

=cut
