#!/usr/bin/perl

eval 'exec /usr/bin/perl  -S $0 ${1+"$@"}'
    if 0; # not running under some shell
#
# Copyright (c) 2008 Red Hat, Inc.
#
# This software is licensed to you under the GNU General Public License,
# version 2 (GPLv2). There is NO WARRANTY for this software, express or
# implied, including the implied warranties of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2
# along with this software; if not, see
# http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
#
# Red Hat trademarks are not licensed under GPLv2. No permission is
# granted to use or replicate Red Hat trademarks that are incorporated
# in this software or its documentation.

use strict;
use warnings;

use English;

# Store the command line args for calls to other scripts. These
# seem to be stripped as soon as we do the validation.
my $cmdargs = "";
foreach my $t (@ARGV) {
    $cmdargs = $cmdargs . " " . $t;
}

use Params::Validate;
Params::Validate::validation_options(strip_leading => "-");

use Term::ReadKey qw/ReadLine ReadMode/;
use Mail::RFC822::Address;
use File::Spec;
use DBI;
use Digest::MD5 qw/md5_hex/;
use DateTime;
use Sys::Hostname;
use Getopt::Long;
use Spacewalk::Setup;

my $DEBUG;
$DEBUG = 0;

use constant DEFAULT_RHN_SATCON_TREE =>
  '/etc/sysconfig/rhn-satellite-prep/etc';

use constant DEFAULT_SATCON_DICT =>
  '/etc/sysconfig/rhn-satellite-prep/satellite-local-rules.conf';

use constant DEFAULT_CA_CERT_NAME =>
  'RHN-ORG-TRUSTED-SSL-CERT';

use constant DEFAULT_BACKUP_DIR =>
   '/etc/sysconfig/rhn/backup-' . `date +%F-%R`;

use constant SCHEMAS_DIR => '/usr/share/spacewalk/schema/';



my %opts = Spacewalk::Setup::parse_options();

# Skip the logfile init, normally just used when called from install.pl,
# which already did this.
if (not $opts{"skip-logfile-init"}) {
    Spacewalk::Setup::init_log_files(@ARGV);
}

my %answers = ();
Spacewalk::Setup::load_answer_file(\%opts, \%answers);

$answers{hostname} ||= Sys::Hostname::hostname;

my %rhnOptions = ();
if (-e Spacewalk::Setup::DEFAULT_RHN_CONF_LOCATION) {
    Spacewalk::Setup::read_config(Spacewalk::Setup::DEFAULT_RHN_CONF_LOCATION,
        \%rhnOptions);
}

choose_database_schema(\%answers);

# If Oracle was selected, call the Oracle specific setup script. If it turns
# out we need a PostgreSQL specific setup script as well, refactor this to
# something generic.

if ($answers{'database'} eq 'oracle') {
    Spacewalk::Setup::oracle_setup_db(\%opts, \%answers);
}
else {
    die "No idea how to install to database: " . $answers{'database'};
}

print Spacewalk::Setup::loc("* Setting up users and groups.\n");
setup_users_and_groups();

setup_services();
setup_gpg(\%opts);





# TODO: Pickup removing Oracle code below:
setup_admin_email(\%opts, \%answers, \%rhnOptions);

print Spacewalk::Setup::loc("* Performing initial configuration.\n");
populate_initial_configs(\%opts, \%answers);
setup_sudoers(\%opts, \%answers);

print Spacewalk::Setup::loc("* Activating Satellite.\n");
load_satellite_certificate(\%opts, \%answers);

print Spacewalk::Setup::loc("* Enabling Monitoring.\n");
setup_monitoring(\%opts, \%answers);

print Spacewalk::Setup::loc("* Creating SSL certificates.\n");
setup_ssl_certs(\%opts, \%answers);

print Spacewalk::Setup::loc("* Deploying configuration files.\n");
populate_final_configs(\%opts, \%answers);

print Spacewalk::Setup::loc("* Update configuration in database.\n");
final_db_config(\%opts, \%answers);

print Spacewalk::Setup::loc("* Restarting services.\n");
Spacewalk::Setup::system_or_exit(['/sbin/rhn-satellite', 'restart'], 40,
	       'Could not restart the rhn-satellite service.');

wait_for_tomcat($answers{hostname});

Spacewalk::Setup::upgrade_stop_services(\%opts);
Spacewalk::Setup::oracle_upgrade_start_db(\%opts);

if ($opts{'upgrade'}) {
  print Spacewalk::Setup::loc("This portion of the Satellite upgrade process has successfully completed.
Please refer to appropriate upgrade document in /etc/sysconfig/rhn/satellite-upgrade
for any remaining steps in the process.\n");
} else {
  print Spacewalk::Setup::loc("Installation complete.\n");
  print Spacewalk::Setup::loc("Visit https://%s to create the satellite administrator account.\n", $answers{hostname});
}

exit 0;



sub choose_database_schema {
    my $answers = shift;

    my @valid_schemas = ();
    push(@valid_schemas, "oracle");
    # Uncomment this to enable installation of postgresql schema:
    # NOTE: useful for development ONLY at this time.
    #push(@valid_schemas, "postgresql");
    my %is_valid_schema = ();

    if (scalar(@valid_schemas) eq 1) {
        print "Only one database option available, assuming: " . $valid_schemas[0] . "\n";
        $answers->{'database'} = $valid_schemas[0];
    }
    else {
        print "Available database backends:\n";
        # Hash just used for checking if a schema is valid:
        while (my $i = shift @valid_schemas) {
            print "   $i\n";
            $is_valid_schema{$i} = 1;
        }

        Spacewalk::Setup::ask(
            -noninteractive => $opts{"non-interactive"},
            -question => 'Database',
            -test => sub {
                my $text = shift;
                $is_valid_schema{$text}
            },
            -answer => \$answers->{'database'});
    }

}


sub setup_admin_email {
  my $opts = shift;
  my $answers = shift;
  my $rhnoptions = shift;

  if ($rhnoptions->{'traceback_mail'}) {
    $answers->{'admin-email'} = $rhnoptions->{'traceback_mail'};
  } else {
    Spacewalk::Setup::ask(
        -noninteractive => $opts{"non-interactive"},
        -question => "Admin Email Address",
        -test => sub { my $text = shift;
                       valid_multiple_email($text) && length($text) <= 128 },
        -answer => \$answers{'admin-email'});
  }
}


sub setup_sudoers {
  my $opts = shift;
  my $answers = shift;

  #If sudoers isn't there, put it there.
  if (not -e '/etc/sudoers') {
    Spacewalk::Setup::system_debug('cp ' . Spacewalk::Setup::SHARED_DIR .
        '/sudoers.base /etc/sudoers');
    chmod(0440, '/etc/sudoers');
  }

  my $ret = Spacewalk::Setup::system_debug('grep INSTALL_RHN /etc/sudoers');

  if ($ret) {
    system('cat ' . Spacewalk::Setup::SHARED_DIR .
        '/sudoers.rhn >> /etc/sudoers');
  }

  # sudo: do not require a real tty for tomcat and apache
  $ret = Spacewalk::Setup::system_debug('grep -Eq ^Defaults:tomcat[[:space:]\\t]+\\!requiretty /etc/sudoers');
  if ($ret) {
    system("echo 'Defaults:tomcat !requiretty' >> /etc/sudoers");
  }

  $ret = Spacewalk::Setup::system_debug('grep -Eq ^Defaults:apache[[:space:]\\t]+\\!requiretty /etc/sudoers');
  if ($ret) {
    system("echo 'Defaults:apache !requiretty' >> /etc/sudoers");
  }

  #BZ 230780
  #This is temporary workaround for user who have only partial
  #content of sudoers.rhn
  $ret = Spacewalk::Setup::system_debug("grep 'tomcat.*CONFIG_RHN' /etc/sudoers");
  if ($ret) {
    system(" echo 'tomcat  ALL=(root)      NOPASSWD: CONFIG_RHN' >>/etc/sudoers");
    system(" echo 'tomcat  ALL=(root)      NOPASSWD: CONFIG_RHN' >>/etc/sudoers");
  }

  return;
}

sub passwords_match {
  my $password_1 = shift;
  my $password_2 = shift;

  if ($password_1 eq $password_2) {
    return 1;
  }

  print Spacewalk::Setup::loc("Passwords did not match, please try again.\n");

  return 0;
}

sub valid_ssl_cert_password {
  my $password = shift;

  my $ret;

  if (not $password) {
    print Spacewalk::Setup::loc("You must enter a password.\n");
    return 0;
  }

  if ($password =~ /([\t\r\n\f\013&+%\'\`\\\"=\#)])/) {
    $ret = $1;
  }

  if ($ret) {
    print Spacewalk::Setup::loc("Invalid character: '%s'.\n", $ret);
    return 0;
  }

  return 1;
}

sub valid_cert_countries {
  my $answers = shift;

  my $dbh = Spacewalk::Setup::oracle_get_dbh($answers);

  my $sth = $dbh->prepare(<<EOQ);
  SELECT VC.code AS CODE,
         nvl(TL.short_name_tl, VC.short_name) AS NAME
    FROM valid_countries VC,
         valid_countries_tl TL
   WHERE TL.lang (+) = 'en'
     AND TL.code (+)= VC.code
ORDER BY VC.short_name
EOQ

  $sth->execute;

  my ($by_code, $by_name);

  while (my ($code, $name) = $sth->fetchrow) {
    $by_code->{$code} = $name;
    $by_name->{$name} = $code;
  }

  $sth->finish();
  $dbh->disconnect();

  return ($by_code, $by_name);
}

sub default_cert_expiration {
  my $dt = DateTime->now;
  my $dt2 = new DateTime (year => 2038, month => 1, day => 18);
  my $diff = $dt2 - $dt;

  return $diff->years - 1;
}

sub setup_ssl_certs {
  my $opts = shift;
  my $answers = shift;

  if ($opts{"skip-ssl-cert-generation"} || $opts{"upgrade"}) {
    print Spacewalk::Setup::loc("** Skipping SSL certificate generation.\n");
    return;
  }

  my ($password_1, $password_2);

  unless ($answers->{"ssl-password"}) {
    do {
      ($password_1, $password_2) = (undef, undef); # clear previous passwords
      Spacewalk::Setup::ask(
        -noninteractive => $opts{"non-interactive"},
        -question => "CA certificate password",
	  -test => \&valid_ssl_cert_password,
	  -answer => \$password_1,
	  -password => 1,
         );

      Spacewalk::Setup::ask(
        -noninteractive => $opts{"non-interactive"},
          -question => "Re-enter CA certificate password",
	  -test => \&valid_ssl_cert_password,
	  -answer => \$password_2,
	  -password => 1,
         );
    } until (passwords_match($password_1, $password_2));

    $answers->{"ssl-password"} ||= $password_1;
  };

  Spacewalk::Setup::ask(
      -noninteractive => $opts{"non-interactive"},
      -question => "Organization",
      -test => sub { my $text = shift;
		     return $text =~ /\S/ && length($text) <= 128 },
      -answer => \$answers->{"ssl-set-org"},
     );

  Spacewalk::Setup::ask(
      -noninteractive => $opts{"non-interactive"},
      -question => "Organization Unit",
      -test => sub { my $text = shift;
                     return $text =~ /\S/ && length($text) <= 128 },
      -default => $answers->{'hostname'},
      -answer => \$answers->{"ssl-set-org-unit"},
     );

  $answers->{"ssl-set-common-name"} ||= $answers->{hostname};

  Spacewalk::Setup::ask(
      -noninteractive => $opts{"non-interactive"},
      -question => 'Email Address',
      -test => sub { my $text = shift;
		     valid_multiple_email($text) && length($text) <= 128 },
      -default => $answers->{'admin-email'},
      -answer => \$answers->{'ssl-set-email'},
     );

  Spacewalk::Setup::ask(
      -noninteractive => $opts{"non-interactive"},
      -question => 'City',
      -test => sub { my $text = shift;
		     $text =~ /\S+/ && length($text) < 128 },
      -answer => \$answers->{'ssl-set-city'},
     );

  Spacewalk::Setup::ask(
      -noninteractive => $opts{"non-interactive"},
      -question => 'State',
      -test => sub { my $text = shift;
		     length($text) > 0 && length($text) < 128 },
      -answer => \$answers->{'ssl-set-state'},
     );

  my ($by_code, $by_name) = valid_cert_countries($answers);

  while (not $answers->{'ssl-set-country'}
	 or not (exists $by_code->{$answers->{'ssl-set-country'}}
		 or exists $by_name->{$answers->{'ssl-set-country'}})) {
    Spacewalk::Setup::ask(
        -noninteractive => $opts{"non-interactive"},
        -question => 'Country code (Examples: "US", "JP", "IN", or type "?" to see a list)',
	-test => sub { my $text = shift;
		       exists $by_code->{$text} or exists $by_name->{$text} or $text eq '?' },
	-answer => \$answers->{'ssl-set-country'},
       );

    if ($answers->{'ssl-set-country'} eq '?') {
      print_country_list($by_name);
      $answers->{'ssl-set-country'} = "";
    }
  }

  if (my $code = $by_name->{$answers->{'ssl-set-country'}}) {
    $answers->{'ssl-set-country'} = $code;
  }

  $answers->{'ssl-ca-cert-expiration'} ||= default_cert_expiration();
  $answers->{'ssl-server-cert-expiration'} ||= default_cert_expiration();

  my @hostname_parts = split(/\./, $answers->{hostname});
  my $system_name;

  if (scalar @hostname_parts > 2) {
    $system_name = join('.', splice(@hostname_parts, 0, -2));
  }
  else {
    $system_name = join('.', @hostname_parts);
  }

  $answers->{'ssl-server-rpm'} ||= 'rhn-org-httpd-ssl-key-pair-' . $system_name;
  $answers->{'ssl-dir'} ||= '/root/ssl-build';

  print Spacewalk::Setup::loc("** SSL: Generating CA certificate.\n");

  generate_ca_cert(-dir => $answers->{'ssl-dir'},
		   -password => $answers->{'ssl-password'},
		   '-set-country' => $answers->{'ssl-set-country'},
		   '-set-state' => $answers->{'ssl-set-state'},
		   '-set-city' => $answers->{'ssl-set-city'},
		   '-set-org' => $answers->{'ssl-set-org'},
		   '-set-org-unit' => $answers->{'ssl-set-org-unit'},
		   '-set-common-name' => $answers->{'ssl-set-common-name'},
		   '-cert-expiration' => $answers->{'ssl-ca-cert-expiration'},
		  );

  print Spacewalk::Setup::loc("** SSL: Deploying CA certificate.\n");

  deploy_ca_cert("-source-dir" => $answers->{'ssl-dir'},
		 "-target-dir" => '/var/www/html/pub');

  print Spacewalk::Setup::loc("** SSL: Generating server certificate.\n");

  generate_server_cert(-dir => $answers->{'ssl-dir'},
		       -password => $answers->{'ssl-password'},
		       '-set-country' => $answers->{'ssl-set-country'},
		       '-set-state' => $answers->{'ssl-set-state'},
		       '-set-city' => $answers->{'ssl-set-city'},
		       '-set-org' => $answers->{'ssl-set-org'},
		       '-set-org-unit' => $answers->{'ssl-set-org-unit'},
		       '-cert-expiration' => $answers->{'ssl-server-cert-expiration'},
		       '-set-email' => $answers->{'ssl-set-email'},
		       '-set-hostname' => $answers->{'hostname'},
		      );

  print Spacewalk::Setup::loc("** SSL: Storing SSL certificates.\n");

  install_server_cert(-dir => $answers->{'ssl-dir'},
		      -system => $system_name);

  generate_server_pem(-ssl_dir => $answers->{'ssl-dir'},
		      -system => $system_name,
		      -out_file => '/etc/jabberd/server.pem');

  store_ssl_cert(-ssl_dir => $answers->{'ssl-dir'});
}

sub print_country_list {
  my $by_name = shift;

  foreach my $name (sort keys %{$by_name}) {
    printf("%s\t%s\n", $by_name->{$name}, $name);
  }

  return
}
sub generate_ca_cert {
  my %params = validate(@_, {
			     dir => 1,
			     password => 1,
			     'set-country' => 1,
			     'set-state' => 1,
			     'set-city' => 1,
			     'set-org' => 1,
			     'set-org-unit' => 1,
			     'set-common-name' => 0,
			     'cert-expiration' => 1, # In years
			    });

  $params{'cert-expiration'} *= 365;

  my @opts = ( "--gen-ca", "--force" );

  foreach my $name (keys %params) {
    next unless ($params{$name});

    push @opts, qq(--$name=$params{$name});
  }

  Spacewalk::Setup::system_or_exit(['/usr/bin/rhn-ssl-tool', @opts], 35, 'Could not generate CA certificate.');

  return;
}

sub generate_server_cert {
  my %params = validate(@_, {
			     dir => 1,
			     password => 1,
			     'set-country' => 1,
			     'set-state' => 1,
			     'set-city' => 1,
			     'set-org' => 1,
			     'set-org-unit' => 1,
			     'cert-expiration' => 1,
			     'set-email' => 1,
			     'set-hostname' => 1,
			    });

  $params{'cert-expiration'} *= 365;

  my @opts = "--gen-server";

  foreach my $name (keys %params) {
    next unless ($params{$name});

    push @opts, qq(--$name=$params{$name});
  }

  Spacewalk::Setup::system_or_exit(['/usr/bin/rhn-ssl-tool', @opts], 36, 'Could not generate server certificate.');

  return;
}

sub deploy_ca_cert {
  my %params = validate(@_, { "source-dir" => 1,
			      "target-dir" => 1 });

  my @opts;

  foreach my $key (keys %params) {
    push @opts, qq(--$key=$params{$key});
  }

  Spacewalk::Setup::system_or_exit(['/usr/bin/rhn-deploy-ca-cert.pl', @opts], 37, 'Could not deploy CA certificate.');

  return;
}

sub install_server_cert {
  my %params = validate(@_, { dir => 1,
			      system => 1 });

  my @opts;

  push @opts, '--dir=' . File::Spec->catfile($params{dir}, $params{system});

  Spacewalk::Setup::system_or_exit(['/usr/bin/rhn-install-ssl-cert.pl', @opts], 38, 'Could not deploy SSL certificate.');

  return;
}

sub generate_server_pem {
  my %params = validate(@_, { ssl_dir => 1,
			      system => 1,
			      out_file => 0 });

  my @opts;

  push @opts, '--ssl-dir=' . File::Spec->catfile($params{ssl_dir}, $params{system});

  if ($params{out_file}) {
    push @opts, '--out-file=' . $params{out_file};
  }
  my $opts = join(' ', @opts);

  my $content;

  open(FH, "/usr/bin/rhn-generate-pem.pl $opts |")
    or die "Could not generate server.pem file: $OS_ERROR";

  my @content = <FH>;

  close(FH);

  if (not $params{out_file}) {
    $content = join('', @content);
  }

  return $content;
}

sub store_ssl_cert {
  my %params = validate(@_, { ssl_dir => 1,
			      ca_cert => { default => DEFAULT_CA_CERT_NAME },
			    });


  my $cert_path = File::Spec->catfile($params{ssl_dir}, $params{ca_cert});
  my @opts = ("--ca-cert=${cert_path}");

  Spacewalk::Setup::system_or_exit(['/usr/bin/rhn-ssl-dbstore', @opts], 39,
		 "There was a problem storing the SSL certificate.");

  return;
}

sub satellite_activate {
  my %params = validate(@_, { filename => 1,
			      sanity_only => 0,
			      disconnected => 0,
			    });

  my @args = ('--rhn-cert', $params{filename});

  if ($params{sanity_only}) {
    push @args, '--sanity-only';
  }

  if ($params{disconnected}) {
    push @args, '--disconnected';
  }

  # Only need to run the local checks the first time.
  if ($params{sanity_only}) {
    local_sat_cert_checks($params{filename});
  }

  my $ret = Spacewalk::Setup::system_debug('/usr/bin/rhn-satellite-activate',
		   @args);

  my %retcodes = (
		  10 => 'Could not parse certificate file',
		  11 => 'Certificate expired',
		  12 => 'Unknown Satellite version',
		  20 => 'Remote activation failure',
		  30 => 'Local activation failure',
		  40 => 'Channel population failure',
		  80 => 'No management entitlements remaining',
		  82 => 'Satellite channel not found',
		  83 => 'No Satellite channel entitlements remaining',
		  84 => 'Invalid Satellite certificate',
		  85 => 'Unknown activation error',
		  86 => 'Satellite has no base channel on parent server',
		  87 => 'No Satellite channel available for this version',
		  127 => 'Unhandled error',
		 );

  if ($ret) {
    my $exit_value = $? >> 8;

    die "(satellite_activation_failed) $retcodes{$exit_value}\n\n"
      if exists $retcodes{$exit_value};

    die "There was a problem validating the satellite certificate: $exit_value\n\n";
  }

  return 1;
}

sub local_sat_cert_checks {
  my $filename = shift;

  open(CERT, $filename) or die "Could not open '$filename': $OS_ERROR\n";
  my @data = <CERT>;
  close(CERT);

  my $cert_str = join('', @data);
  my ($signature, $cert);

  eval {
    ($signature, $cert) = Sat::Cert->parse_cert($cert_str);
  };
  if ($@) {
    die "(parse_error) Error parsing satellite cert: $@\n";
  }

  return 1;

  my $sat_version = 0; #PXT::Config->get('version');
  my $cert_version = $cert->get_field('satellite-version');

  #The cert version should be less specific than the sat version.
  my $match_length = length($cert_version);
  $sat_version = substr($sat_version, 0, $match_length);
  unless ($sat_version eq $cert_version) {
    die "(satellite_activation_failed) The version of the supplied cert ($cert_version)"
      . " did not match the version of this satellite ($sat_version)\n";
  }

  return;
}

sub load_satellite_certificate {
  my $opts = shift;
  my $answers = shift;

  if ($opts->{'upgrade'}) {
    print Spacewalk::Setup::loc("** Certificate not activated.\n");
    print Spacewalk::Setup::loc("** Upgrade process requires the certificate to be activated after the schema is upgraded.\n");
    return;
  }

  Spacewalk::Setup::ask(
      -noninteractive => $opts{"non-interactive"},
      -question => "Where is your satellite certificate file",
      -test => sub { my $text = shift; return (-e File::Spec->canonpath($text) and not -d File::Spec->canonpath($text)) },
      -answer => \$answers->{'satellite-cert-file'});

  print Spacewalk::Setup::loc("** Loading Satellite Certificate.\n");

  open(FH, $answers->{'satellite-cert-file'})
    or die "Could not open '" . $answers->{'satellite-cert-file'} . "' - $OS_ERROR.\n";

  my $cert_contents = do { local $INPUT_RECORD_SEPARATOR; <FH> };

  if (not $cert_contents) {
    print Spacewalk::Setup::loc("No data found in '%s'.\n", $answers->{'satellite-certificate-file'});
    exit 34;
  }

  my %activate_options;

  if ($opts->{disconnected}) {
    $activate_options{"-disconnected"} = 1;
  }

  eval {
    print Spacewalk::Setup::loc("** Verifying certificate locally.\n");

    satellite_activate(-filename => $answers->{'satellite-cert-file'},
		       -sanity_only => 1,
		      );

    print Spacewalk::Setup::loc("** Activating Satellite.\n");

    satellite_activate(-filename => $answers->{'satellite-cert-file'},
		       %activate_options);
  };

  if ($@) {
    my $E = $@;

    my $msg;

    if ($E =~ /\(satellite_activation_failed\) (.*)/) {
      $msg = $1;
      print Spacewalk::Setup::loc("There was a problem activating the satellite: %s.\n", $msg);
      exit 31;
    }
    elsif ($E =~ /\(parse_error\) (.*)/) {
      $msg = $1;
      print Spacewalk::Setup::loc($msg);
      exit 32;
    }
    else {
      $msg = $E;
      print Spacewalk::Setup::loc($msg);
      exit 33;
    }
  }

  return 1;
}

sub populate_initial_configs {
  my $opts = shift;
  my $answers = shift;

  my %nls_database_paramaters = Spacewalk::Setup::get_nls_database_parameters($answers);

  my %config_opts =
    (
     mount_point => $answers->{'mount-point'} || '/var/satellite',
     kickstart_mount_point => $answers->{'kickstart-mount-point'} || $answers->{'mount-point'} || '/var/satellite',
     serverDOTsatelliteDOThttp_proxy => $answers->{'rhn-http-proxy'} || '',
     serverDOTsatelliteDOThttp_proxy_username => $answers->{'rhn-http-proxy-username'} || '',
     serverDOTsatelliteDOThttp_proxy_password => $answers->{'rhn-http-proxy-password'} || '',
     webDOTis_monitoring_backend => 0,
     webDOTis_monitoring_scout => 0,
     webDOTenable_solaris_support => 0,
     jabberDOThostname => $answers->{hostname},
     osadispatcherDOTosa_ssl_cert => '/var/www/html/pub/RHN-ORG-TRUSTED-SSL-CERT',
     encrypted_passwords => 1,
     default_db => sprintf('%s/%s@%s', @{$answers}{qw/db-user db-password db-sid/}),
     db_user => $answers->{'db-user'},
     db_password => $answers->{'db-password'},
     db_sid => $answers->{'db-sid'},
     db_host => $answers->{'db-host'},
     db_port => $answers->{'db-port'},
     traceback_mail => $answers->{'admin-email'},
     jabberDOTusername => 'rhn-dispatcher-sat',
     jabberDOTpassword => 'rhn-dispatcher-' . random_password(6),
     serverDOTsatelliteDOTca_chain => '/usr/share/rhn/RHNS-CA-CERT',
     serverDOTnls_lang => 'english.' . $nls_database_paramaters{NLS_CHARACTERSET},
     server_secret_key => generate_secret(),
     webDOTssl_available => $opts->{'skip-ssl-cert-generation'} ? 0 : 1,
     monitoringDOTdbname => $answers->{'db-sid'},
     monitoringDOTusername => $answers->{'db-user'},
     monitoringDOTpassword => $answers->{'db-password'},
     monitoringDOTsmonDOTaddr => '127.0.0.1',
     monitoringDOTsmonDOTfqdn => 'localhost',
     monitoringDOTsmonDOTtestaddr => '127.0.0.1',
     monitoringDOTsmonDOTtestfqdn => 'localhost',
     monitoringDOTorahome => '/opt/oracle',
     monitoringDOTdbd => 'Oracle',
     monitoringDOTscout_shared_key => '', # blank for now.
    );

  for ($config_opts{'db_password'}) {
    s/\\/\\\\/g if defined $_;
  }

  my $parent;
  if ($answers->{disconnected} || $opts->{disconnected}) {
    $parent = '';
    write_config( { 'disconnected' => "1" },
		'/etc/sysconfig/rhn-satellite-prep/etc/rhn/rhn.conf' );

  }
  else {
    $parent = $answers->{'rhn-parent'} || 'satellite.rhn.redhat.com';
  }

  write_config( { 'server.satellite.rhn_parent' => $parent },
		'/etc/sysconfig/rhn-satellite-prep/etc/rhn/rhn.conf' );

  foreach my $opt_name (qw/session_swap_secret session_secret/) {
    foreach my $i (1 .. 4) {
      $config_opts{"${opt_name}_${i}"} = generate_secret();
    }
  }

  generate_satcon_dict();
  write_config(\%config_opts,
	       '/etc/sysconfig/rhn-satellite-prep/satellite-local-rules.conf');

  satcon_deploy();
}

sub populate_final_configs {
  my $options = shift;
  my $answers = shift;

  my $final_config = {satellite_install => 0,
		     };

  write_config($final_config,
	       '/etc/sysconfig/rhn-satellite-prep/satellite-local-rules.conf');

  satcon_deploy(-tree => '/etc/sysconfig/rhn-satellite-prep/etc/rhn',
		-dest => '/etc/rhn');

  return;
}


sub final_db_config {
  my $options = shift;
  my $answers = shift;

  my $dbh = Spacewalk::Setup::oracle_get_dbh($answers);
  my $sth = $dbh->prepare(<<EOQ);
SELECT TS.value
  FROM rhnTemplateString TS
 WHERE TS.label = 'hostname'
EOQ

  $sth->execute();
  my ($current_hostname) = $sth->fetchrow();

  unless ($current_hostname) {
    $sth = $dbh->prepare(<<EOQ);
INSERT
  INTO rhnTemplateString TS
       (id, category_id, label, value, description)
VALUES (rhn_template_str_id_seq.nextval,
        (SELECT TC.id FROM rhnTemplateCategory TC WHERE TC.label = 'org_strings'),
        'hostname',
        ?,
        'Host name for the RHN Satellite')
EOQ

    $sth->execute($answers->{hostname});

    if ($DEBUG) {
      $dbh->rollback();
    }
    else {
      $dbh->commit();
    }

  }

  $sth->finish;
  $dbh->disconnect();

  return;
}

sub generate_satcon_dict {
  my %params = validate(@_, { conf_file => { default => DEFAULT_SATCON_DICT },
			      tree => { default => DEFAULT_RHN_SATCON_TREE },
			    });

  Spacewalk::Setup::system_or_exit([ "/usr/bin/satcon-build-dictionary.pl",
		   "--tree=" . $params{tree},
		   "--target=" . $params{conf_file} ],
		 28,
		 'There was a problem building the satcon dictionary.');

  return 1;
}

sub write_config {
  my $options = shift;
  my $target = shift || Spacewalk::Setup::DEFAULT_RHN_CONF_LOCATION;

  my @opt_strings = map { "--option=${_}=" . $options->{$_} } keys %{$options};

  Spacewalk::Setup::system_or_exit([ "/usr/bin/rhn-config-satellite.pl",
		   "--target=$target",
		   @opt_strings,
		 ],
		 29,
		 'There was a problem setting initial configuration.');

  return 1;
}

sub satcon_deploy {
  my %params = validate(@_, { conf_file => { default => DEFAULT_SATCON_DICT },
			      tree => { default => DEFAULT_RHN_SATCON_TREE },
			      dest => { default => '/etc' },
			      backup => { default => DEFAULT_BACKUP_DIR },
			    });

  $params{backup} =~ s/\s+$//;
  my @opts = ("--source=" . $params{tree}, "--dest=" . $params{dest},
	      "--conf=" . $params{conf_file}, "--backupdir=" . $params{backup});

  Spacewalk::Setup::system_or_exit([ "/usr/bin/satcon-deploy-tree.pl", @opts ],
		   30,
		   'There was a problem deploying the satellite configuration.');

  return 1;
}

sub setup_monitoring {
  my $opts = shift;
  my $answers = shift;

  # write monitoring config to DB.
  my %mon_config =
    (
     RHN_ADMIN_EMAIL => $answers->{'admin-email'},
     MAIL_MX => $answers->{'mail-mx'} || 'localhost',
     MDOM => $answers->{mdom} || $answers->{hostname},
     RHN_DB_NAME => $answers->{'db-sid'},
     RHN_DB_USERNAME => $answers->{'db-user'},
     RHN_DB_PASSWD => $answers->{'db-password'},
     RHN_DB_TABLE_OWNER => $answers->{'db-user'},
     RHN_SAT_HOSTNAME => $answers->{hostname},
     XPROTO => 'https',
     RHN_SAT_WEB_PORT => 443
    );

  update_monitoring_config($answers, \%mon_config);

  my $dbname = $mon_config{RHN_DB_NAME};

  update_monitoring_environment($answers, $dbname);

  return 1;
}

sub update_monitoring_environment {
  my $answers = shift;
  my $db_name = shift;

  my $dbh = Spacewalk::Setup::oracle_get_dbh($answers);
  my $sth = $dbh->prepare(<<EOQ);
UPDATE rhn_db_environment
   SET db_name = UPPER(?)
 WHERE environment = 'LICENSE'
EOQ

  $sth->execute($db_name);

  if ($DEBUG) {
    $dbh->rollback();
  }
  else {
    $dbh->commit();
  }

  $dbh->disconnect();

  return 1;
}

sub update_monitoring_config {
  my $answers = shift;
  my $mon_config = shift;

  my $dbh = Spacewalk::Setup::oracle_get_dbh($answers);
  my $sth = $dbh->prepare(<<EOQ);
    UPDATE rhn_config_macro
    SET    definition = ?,
           last_update_user = 'installer',
           last_update_date = sysdate
    WHERE  name = ?
EOQ

  foreach my $name (keys %{$mon_config}) {
    $sth->execute($name, $mon_config->{$name});
  }

  if ($DEBUG) {
    $dbh->rollback();
  }
  else {
    $dbh->commit();
  }

  $dbh->disconnect();

  return;
}

sub generate_secret {
  return md5_hex(random_bits(4096));
}

sub random_bits {
  my $n = shift;

  open(RANDOM, '/dev/urandom') or die "could not open /dev/urandom for reading!\n";
  binmode(RANDOM);
  my $rand_data;
  my $result = read(RANDOM, $rand_data, $n >> 3);
  close(RANDOM);

  unless (defined $result) {
    die "could not read from /dev/urandom!\n";
  }

  return $rand_data;
}



sub random_password {
  my $length = shift;

  die "random password too short\n"
    unless $length > 5;

  my $ret;
  my @chars = ('a'..'z');
  my @nums = ('0'..'9');

  $ret .= $chars[int rand @chars]
    while $length-- > 5;

  $ret .= $nums[int rand @nums]
    while $length-- >= 0;

  return $ret;
}

sub clear_db {
  my $answers = shift;

  my $dbh = Spacewalk::Setup::oracle_get_dbh($answers);

  print Spacewalk::Setup::loc("** Database: Shutting down services that may be using DB: [tomcat5, taskomatic, httpd, jabberd, osa-dispatcher, tsdb_local_queue].\n");

  Spacewalk::Setup::system_debug('/sbin/service tomcat5 stop');
  Spacewalk::Setup::system_debug('/sbin/service taskomatic stop');
  Spacewalk::Setup::system_debug('/sbin/service httpd stop');
  Spacewalk::Setup::system_debug('/sbin/service jabberd stop');
  Spacewalk::Setup::system_debug('/sbin/service osa-dispatcher stop');
  Spacewalk::Setup::system_debug('/sbin/service tsdb_local_queue stop');

  print Spacewalk::Setup::loc("** Database: Services stopped.  Clearing DB.\n");

  my $select_sth = $dbh->prepare(<<EOQ);
  SELECT 'drop ' || UO.object_type ||' '|| UO.object_name AS DROP_STMT
    FROM user_objects UO
   WHERE UO.object_type NOT IN ('TABLE', 'INDEX', 'TRIGGER', 'LOB')
UNION
  SELECT 'drop ' || UO.object_type ||' '|| UO.object_name
         || ' cascade constraints' AS DROP_STMT
    FROM user_objects UO
   WHERE UO.object_type = 'TABLE'
     AND UO.object_name NOT LIKE '%$%'
EOQ

  $select_sth->execute();

  while (my ($drop_stmt) = $select_sth->fetchrow()) {
    my $drop_sth = $dbh->prepare($drop_stmt);
    $drop_sth->execute();
  }

  if ($DEBUG) {
    $dbh->rollback();
  }
  else {
    $dbh->commit();
  }

  $dbh->disconnect();

  return;
}

sub need_oracle_9i_10g_upgrade {
      my $orahome = qx{dbhome embedded};
      chomp($orahome);
      my $spfile = $orahome. "/dbs/spfilerhnsat.ora";
      return (not -r $spfile);
}

sub setup_gpg {
  my $opts = shift;

  if ($opts->{"skip-gpg-key-import"}) {
    print Spacewalk::Setup::loc("** GPG: Skipping gpg key import\n");
    return 0;
  }

  print Spacewalk::Setup::loc("** GPG: Initializing GPG and importing key.\n");

  unless (-d '/root/.gnupg') {
    print Spacewalk::Setup::loc("** GPG: Creating /root/.gnupg directory\n");
    Spacewalk::Setup::system_or_exit(['mkdir', '-m', '700', '/root/.gnupg'], 12, 'Could not create /root/.gnupg');
  }

  Spacewalk::Setup::system_or_exit(['/usr/bin/gpg --list-keys'], 12, 'Could not run gpg.');

  my $key_path = '/usr/share/rhn/RPM-GPG-KEY';
  if ( ! (-e $key_path) ) {
    if ( -e '/etc/fedora-release' ) {
      # this is a fedora system
      $key_path = '/etc/pki/rpm-gpg/RPM-GPG-KEY-fedora';
    } else {
      # this is a RHEL system... RHEL 5 path.
      $key_path = '/etc/pki/rpm-gpg/RPM-GPG-KEY-redhat-release';
    }
  }

  Spacewalk::Setup::system_or_exit(['/usr/bin/gpg', '--import', $key_path], 12, 'Could not import public GPG key.');

  return 1;
}

# Satellite services are handled by chkconfig now.
sub setup_services {
  my @services = qw/jabberd rhn-database osa-dispatcher taskomatic tomcat5 satellite-httpd Monitoring MonitoringScout rhn-search/;

  foreach my $service (@services) {
    if (-e "/etc/init.d/$service") {
      Spacewalk::Setup::system_or_exit(["/sbin/chkconfig", "--level", "345", $service, "on"], 11, 'Could not turn the %s service on.', $service);
    }
  }

  return 1;
}

sub setup_users_and_groups {

    # Check to be sure the required users and groups exist.
    my @required_groups = qw/apache/;
    Spacewalk::Setup::check_groups_exist(@required_groups);

    # Need user tomcat in the apache group so the Java stack can read the same
    # configuration files as the rest of the application.
    (undef, undef, my $apache_group_id, my $apache_group_members) = getgrnam("apache");
    if (not defined $apache_group_id) {
        print Spacewalk::Setup::loc("The group 'apache' should exist.\n");
    }
    if (not grep { $_ eq 'tomcat' } split /\s+/, $apache_group_members) {
        Spacewalk::Setup::system_or_exit(['usermod', '-G', 'apache', '-a', 'tomcat'], 9,
            'Could not add tomcat to the apache group.');
    }

    return 1;
}

sub valid_multiple_email {
  my $text = shift || '';

  my @addys = grep { $_ } split(/[\s,]+/, $text);
  my $valid = 1;

  foreach my $addy (@addys) {
    if (not Mail::RFC822::Address::valid($text)) {
      print Spacewalk::Setup::loc("'%s' does not appear to be a valid email address.\n", $text);
      $valid = 0;
    }
  }

  unless (@addys) {
    print Spacewalk::Setup::loc("You must enter an email address.\n");

    $valid = 0;
  }

  return $valid;
}

sub wait_for_tomcat {
    my $hostname = shift;

    for (my $i = 0; $i < 20; $i++) {
        my $retval = system("HEAD http://$hostname/ 2>&1 > /dev/null");
        if ($retval) {
            sleep 5;
        }
        else {
            return;
        }
    }
    print "Tomcat failed to start properly or the installer ran out of tries.  Please check /var/log/tomcat5/catalina.out for errors.\n";
    return;
}

package Sat::Cert;

use XML::LibXML;
use IO::File;

sub new {
  my $class = shift;

  my $self = bless { }, $class;

  $self->_init();

  return $self;
}

sub parse_cert {
  my $class = shift;
  my $data = shift;

  $data =~ s/^\s+$//gm;

  my $p = new XML::LibXML;
  my $doc = $p->parse_string($data);
  my $root = $doc->getDocumentElement();

  die "invalid root\n" unless $root->getName eq 'rhn-cert';

  my ($signature_node) = $root->findnodes('rhn-cert-signature');
  my $signature = $signature_node ? $signature_node->getFirstChild->getData : undef;

  my @fields;
  my @extended_fields;

  foreach my $field_node ($root->findnodes('rhn-cert-field')) {
    my $name = $field_node->getAttribute('name');

    my @attributes = map { $_->getName } $field_node->getAttributes;

    # more than 1 means we had something besides name
    if (@attributes > 1) {
      push @extended_fields, [ $name, map { $_, $field_node->getAttribute($_) } grep { $_ ne 'name' } @attributes ];
    }
    else {
      if ($field_node->getFirstChild) {
	my $value = $field_node->getFirstChild->getData;

	push @fields, [ $name, $value ];
      }
      else {
	push @fields, [ $name, '' ];
      }
    }
  }

  my $cert = new $class;
  $cert->set_field($_->[0], $_->[1]) foreach @fields;

  $cert->push_field("name", @$_) foreach @extended_fields;

  return $signature, $cert;
}

sub add_field {
  my $self = shift;
  my @fields = shift;

  push @{$self->{fields}}, @fields;
}

sub clear_field {
  my $self = shift;
  my $field = shift;

  delete $self->{field_values}->{$field};
}

sub set_field {
  my $self = shift;
  my $field = shift;
  my $val = shift;

  die "Field '$field' not allowed\n" unless grep { $_ eq $field } @{$self->{fields}};
  $self->{field_values}->{$field} = $val;
}

sub push_field {
  my $self = shift;
  my %vals = @_;

  my $field = delete $vals{name};
  die "no field in @_\n" unless $field;

  push @{$self->{field_values}->{$field}}, \%vals;
}

sub get_field {
  my $self = shift;
  my $field = shift;

  return $self->{field_values}->{$field};
}

sub as_checksum_string {
  my $self = shift;

  my $data;
  foreach my $field (sort @{$self->{fields}}) {
    my $val = $self->get_field($field);

    next unless exists $self->{field_values}->{$field};

    if (defined $val) {
      if (ref $val) {
	foreach my $val (sort { join("", sort %$a) cmp join("", sort %$b) } @$val) {
	  $data .= "$field-" . join("-", map { $_, $val->{$_} } sort keys %$val) . "\n";
	}
      }
      else {
	$data .= $field . "-" . $self->get_field($field) . "\n";
      }
    }
  }

  return $data;
}

sub set_required_fields {
  my $self = shift;
  my @fields = @_;

  $self->{required_fields} = \@fields;
}

sub check_required_fields {
  my $self = shift;

  foreach my $f (@{$self->{required_fields}}) {
    die "Required field $f not found in $self\n"
      unless defined $self->{field_values}->{$f};
  }
}

# change this when signature criteria changes
sub current_generation {
  return 2;
}

sub _init {
  my $self = shift;

  my @fields = qw/product owner issued expires slots/;

  $self->add_field($_) foreach @fields;
  $self->set_required_fields(@fields);

  my @optional_fields = qw/virtualization_host virtualization_host_platform monitoring-slots provisioning-slots nonlinux-slots channel-families satellite-version generation/;
  $self->add_field($_) foreach @optional_fields;

  return;
}

sub clear_channel_families {
  my $self = shift;

  $self->clear_field("channel-families");
}

sub set_channel_family {
  my $self = shift;
  my $label = shift;
  my $quantity = shift;

  $self->push_field(name => 'channel-families', family => $label, quantity => $quantity);
}

sub get_channel_families {
  my $self = shift;

  my $a = $self->get_field('channel-families');

  return map { [ $_->{family}, $_->{quantity} ] } @$a;
}

sub version {
  my $self = shift;

  return $self->get_field('satellite-version');
}

__END__
