#!/usr/bin/perl
#
# Copyright (c) 2008--2015 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;

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

use Mail::RFC822::Address ();
use File::Spec ();
use DBI ();
use Digest::SHA qw/sha256_hex/;
use DateTime ();
use Net::Domain;
use Spacewalk::Setup ();
use Fcntl qw(F_GETFD F_SETFD FD_CLOEXEC);
use IO::Socket ();
use RHN::DB ();
use File::Copy;

my $DEBUG;
$DEBUG = 0;

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

use constant COBBLER_20_COMMAND => 'cobbler20-setup';
use constant COBBLER_COMMAND => 'spacewalk-setup-cobbler';

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

my %answers = ();
my @skip = ();
push @skip, Spacewalk::Setup::EMBEDDED_DB_ANSWERS
    if not Spacewalk::Setup::is_embedded_db(\%opts);
Spacewalk::Setup::load_answer_file(\%opts, \%answers, \@skip);
my $product_name = $answers{'product_name'} || 'Spacewalk';

$answers{hostname} ||= Net::Domain::hostfqdn();

if (not defined $opts{"clear-db"} and defined $answers{"clear-db"} and
    $answers{"clear-db"} =~ /Y/i){
    $opts{'clear-db'} = 1;
}

$opts{"skip-db-install"} = 1 if $opts{"clear-db"};

# 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($product_name, @ARGV);
}

if (Spacewalk::Setup::have_selinux()) {
    print Spacewalk::Setup::loc("* Setting up SELinux..\n");
    Spacewalk::Setup::system_or_exit(['/usr/sbin/spacewalk-selinux-enable', ''], 42,
               'Could not enable selinux policy.');
}

if ($opts{"run-cobbler"}) {
    print Spacewalk::Setup::loc("* Setting up Cobbler..\n");
    setup_cobbler(\%opts, \%answers);
    exit 0;
}

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

my %up2dateOptions = ();
if (-e Spacewalk::Setup::DEFAULT_UP2DATE_LOCATION) {
    Spacewalk::Setup::read_config(Spacewalk::Setup::DEFAULT_UP2DATE_LOCATION,
        \%up2dateOptions);

    if ($up2dateOptions{'enableProxy'}) {
        $answers{'rhn-http-proxy'} = $up2dateOptions{'httpProxy'}
            if not defined $answers{'rhn-http-proxy'};
        if ($up2dateOptions{'enableProxyAuth'}) {
            $answers{'rhn-http-proxy-username'} = $up2dateOptions{'proxyUser'}
                if not defined $answers{'rhn-http-proxy-username'};
            $answers{'rhn-http-proxy-password'} = $up2dateOptions{'proxyPassword'}
                if not defined $answers{'rhn-http-proxy-password'};
        }
    }
}

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{'db-backend'} eq 'oracle') {
    Spacewalk::Setup::oracle_setup_db(\%opts, \%answers);
}
elsif ($answers{'db-backend'} eq 'postgresql') {
    Spacewalk::Setup::postgresql_setup_db(\%opts, \%answers);
}
else {
    die "No idea how to install to database: " . $answers{'db-backend'};
}

print Spacewalk::Setup::loc("* Configuring tomcat.\n");
setup_tomcat(\%opts, \%answers);

if ($opts{'db-only'}) {
    exit;
}

remove_old_jvm(\%opts, \%answers);
remove_tomcat_cache(\%opts);

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

setup_services();
setup_gpg(\%opts);

setup_admin_email(\%opts, \%answers, \%rhnOptions);

print Spacewalk::Setup::loc("* Performing initial configuration.\n");
my $config_opts = populate_initial_configs(\%opts, \%answers);
mkdir_mount_points($config_opts->{'mount_point'},
        $config_opts->{'mount_point'} . '/redhat',
        $config_opts->{'kickstart_mount_point'});
setup_sudoers();

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

print Spacewalk::Setup::loc("* Configuring apache SSL virtual host.\n");
setup_mod_ssl(\%opts, \%answers);

print Spacewalk::Setup::loc("* Configuring jabberd.\n");
setup_jabberd(\%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("* Setting up Cobbler..\n");
setup_cobbler(\%opts, \%answers);

if ($opts{'upgrade'}) {
  Spacewalk::Setup::postgresql_start() if (Spacewalk::Setup::is_embedded_db(\%opts));

  print Spacewalk::Setup::loc("This portion of the $product_name upgrade process has successfully completed.\n");
  if ($product_name =~ /Satellite/) {
    print Spacewalk::Setup::loc("Please refer to appropriate upgrade document in /etc/sysconfig/rhn/satellite-upgrade\n");
    print Spacewalk::Setup::loc("for any remaining steps in the process.\n");
  }
} else {
  print Spacewalk::Setup::loc("* Restarting services.\n");
  Spacewalk::Setup::system_or_exit(['/usr/sbin/spacewalk-service', 'restart'], 40,
               'Could not restart spacewalk services.');
  wait_for_tomcat($answers{hostname}) or exit 56;
  print Spacewalk::Setup::loc("Installation complete.\n");
  print Spacewalk::Setup::loc("Visit https://%s to create the $product_name administrator account.\n", $answers{hostname});
}

exit 0;



sub choose_database_schema {
    my $answers = shift;

    my %is_valid_schema = (
        'oracle' => 1,
        'postgresql' => 1,
        );

    my $question = "Choose your database backend ("
        . join(", ", sort(keys(%is_valid_schema)))
        . ")";

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

}

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

        return unless (($opts{"upgrade"}) and ($product_name =~ /Satellite/));

        my $jvm_list = Spacewalk::Setup::SHARED_DIR . "/old-jvm-list";

        local *F;
        open F, $jvm_list or die "Error opening [$jvm_list]: $!\n";
        my @jvms = <F>;
        close F;
        chomp @jvms;

        my $remove;

        foreach my $jvm (@jvms) {
                system("rpm -q $jvm >& /dev/null");
                $remove .= " $jvm" unless $? >> 8;
        }

        if ($remove) {
                print Spacewalk::Setup::loc("Setup found following old java packages:\n");
                foreach my $p (split (" ", $remove)) {
                        print Spacewalk::Setup::loc("\t$p\n");
                }
        } else {
                return;
        }

        Spacewalk::Setup::ask(
                -noninteractive => $opts{"non-interactive"},
                -question => "Should setup remove these packages",
                -test => sub { my $text = shift; return $text =~ /^[YyNn]/ },
                -answer => \$answers->{"remove-old-jvm"},
                -default => 'Y',
        );
        unless ( $answers->{"remove-old-jvm"} =~ /^[Yy]/ ) {
                print Spacewalk::Setup::loc("** Skipping removal of old java packages.\n");
                return;
        }

        my $result = `rpm -e $remove 2>&1`;
        if ($? >> 8) {
                print Spacewalk::Setup::loc("** Error occurred while removing the packages:\n");
                print Spacewalk::Setup::loc($result);
        }
}

sub remove_tomcat_cache {
        my $opts = shift;

        return unless ($opts->{'upgrade'});

        my @dirs = glob "/var/cache/tomcat?/work";
        if (scalar @dirs > 0) {
                system("rm -rf /var/cache/tomcat?/work/* > /dev/null 2>&1");
        }
}

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

  my $cobbler_version = `rpm -q --queryformat '%{version}' cobbler`;

  # newer versions of Cobbler don't ship the config templates
  if ($cobbler_version =~ /2\.0/) {
    # Cobbler 2.0 can use the old helper script
    system(COBBLER_20_COMMAND);
  } else {
    system(COBBLER_COMMAND, '--macros', "hostname:$answers->{'hostname'}");

    my $skip_rhnconf = 0;
    open(FILE, "<" . Spacewalk::Setup::DEFAULT_RHN_CONF_LOCATION);
    while (<FILE>) {
        if ($_ =~ /^cobbler\.host/) {
          $skip_rhnconf = 1;
          last;
        }
    }
    close(FILE);

    if (!$skip_rhnconf) {
      open(FILE, ">>" . Spacewalk::Setup::DEFAULT_RHN_CONF_LOCATION);
      print FILE "#cobbler host name\n";
      print FILE "cobbler.host = " . $answers->{'hostname'} . "\n";
      close(FILE);
    }
  }

  Spacewalk::Setup::ask(
    -noninteractive => $opts{"non-interactive"},
    -question => "Cobbler requires tftp and xinetd services be turned on for PXE provisioning functionality. Enable these services",
    -test => sub { my $text = shift; return $text =~ /^[YyNn]/ },
    -answer => \$answers->{"enable-tftp"},
    -default => 'Y',
  );

  if ($opts{'enable-tftp'}) {
    $answers{'enable-tftp'} = $opts{'enable-tftp'};
  }

  if (($answers{'enable-tftp'} and $answers{'enable-tftp'} =~ /^[Yy]/) || $opts{"non-interactive"}) {
      if (-e '/usr/lib/systemd/system/tftp.socket') {
        system("systemctl --quiet enable tftp.socket");
      } elsif (Spacewalk::Setup::isSUSE() && -e '/usr/sbin/atftpd') {
          system("systemctl --quiet enable atftpd.service");
      } else {
        system("chkconfig tftp on");
        system("chkconfig xinetd on");
      }
  }
}

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 {
  Spacewalk::Setup::system_or_exit(['/usr/bin/spacewalk-setup-sudoers', ''], 1,
    'Could not setup sudo for Spacewalk commands.');
  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::get_dbh($answers);
  my $sth = $dbh->prepare(<<EOQ);
SELECT  VC.code AS CODE,
          VC.short_name AS NAME
     FROM valid_countries VC
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_mod_ssl {
  my $opts = shift;
  my $answers = shift;

  if ($opts{"skip-ssl-vhost-setup"}) {
    print Spacewalk::Setup::loc("** Skipping SSL virtual host configuration.\n");
    return;
  }
  Spacewalk::Setup::ask(
    -noninteractive => $opts{"non-interactive"},
    -question => "Should setup configure apache's default ssl server for you (saves original ssl.conf)",
    -test => sub { my $text = shift; return $text =~ /^[YyNn]/ },
    -answer => \$answers->{"ssl-config-sslvhost"},
    -default => 'Y',
  );
  unless ( $answers->{"ssl-config-sslvhost"} =~ /^[Yy]/ ) {
    print Spacewalk::Setup::loc("** Skipping SSL virtual host configuration.\n");
    return;
  }

  system('/usr/bin/spacewalk-setup-httpd');

}

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

  Spacewalk::Setup::system_or_exit(['/usr/bin/spacewalk-setup-tomcat', ''], 43,
               'Could not setup tomcat.');
  return;
}

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

  system('rpm -q jabberd >& /dev/null');
  if ($? >> 8 == 0) {
    system("/usr/bin/spacewalk-setup-jabberd", "--macros", "hostname:$answers->{'hostname'}");
  }
}

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" => $Spacewalk::Setup::DEFAULT_DOC_ROOT."/pub",
                 "-trust-dir"  => $Spacewalk::Setup::CA_TRUST_DIR);

  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);

  Spacewalk::Setup::generate_server_pem(-ssl_dir => $answers->{'ssl-dir'},
                      -system => $system_name,
                      -out_file => '/etc/pki/spacewalk/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,
                              "trust-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 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 $product_name version",
                  20 => 'Remote activation failure',
                  30 => 'Local activation failure',
                  40 => 'Channel population failure',
                  80 => 'No management entitlements remaining',
                  82 => "$product_name channel not found",
                  83 => "No $product_name channel entitlements remaining",
                  84 => "Invalid $product_name certificate",
                  85 => 'Unknown activation error',
                  86 => "$product_name has no base channel on parent server",
                  87 => "No $product_name 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 $product_name 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-cert-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 $product_name.\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;

    # TODO: This may need to be addressed. Can query this for postgresql with
    # "show client_encoding;":
    my $charset = 'UTF8';
    if ($answers{'db-backend'} eq 'oracle') {
        my %nls_database_paramaters = Spacewalk::Setup::get_nls_database_parameters($answers);
        $charset = $nls_database_paramaters{NLS_CHARACTERSET};
    }

    # Define some db specific settings:
    Spacewalk::Setup::set_hibernate_conf($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 => ($opts->{'rhn-http-proxy'} ? $opts->{'rhn-http-proxy'} : $answers->{'rhn-http-proxy'}) || '',
     serverDOTsatelliteDOThttp_proxy_username => ($opts->{'rhn-http-proxy'} ? $opts->{'rhn-http-proxy-username'} : $answers->{'rhn-http-proxy-username'}) || '',
     serverDOTsatelliteDOThttp_proxy_password => ($opts->{'rhn-http-proxy'} ? $opts->{'rhn-http-proxy-password'} :$answers->{'rhn-http-proxy-password'}) || '',
     osadispatcherDOTosa_ssl_cert => $Spacewalk::Setup::DEFAULT_DOC_ROOT.'/pub/RHN-ORG-TRUSTED-SSL-CERT',
     encrypted_passwords => 1,
     db_backend => $answers->{'db-backend'},
     db_user => $answers->{'db-user'},
     db_password => $answers->{'db-password'},
     db_name => $answers->{'db-name'},
     db_host => $answers->{'db-host'},
     db_port => $answers->{'db-port'},
     db_ssl_enabled => $answers->{'db-ssl-enabled'},
     hibernate_dialect => $answers->{'hibernate.dialect'},
     hibernate_driver => $answers->{'hibernate.connection.driver_class'},
     hibernate_driver_proto => $answers->{'hibernate.connection.driver_proto'},
     traceback_mail => $answers->{'admin-email'},
     jabberDOThostname => $answers->{hostname},
     serverDOTsatelliteDOTca_chain => '/usr/share/rhn/RHNS-CA-CERT',
     serverDOTnls_lang => 'english.' . $charset,
     server_secret_key => generate_secret(),
     webDOTssl_available => $opts->{'skip-ssl-cert-generation'} ? 0 : 1,
     cobblerDOThost => $answers->{hostname}
    );

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

  my %rhnopt = ();
  if ($answers->{disconnected} || $opts->{disconnected}) {
    $rhnopt{'server.satellite.rhn_parent'} = '';
    $rhnopt{'disconnected'} = "1";
  }
  else {
    $rhnopt{'server.satellite.rhn_parent'} = $answers->{'rhn-parent'} || 'satellite.rhn.redhat.com';
  }
  for my $key (qw/product_name web.version enable_nvrea web.subscribe_proxy_channel force_package_upload
          web.l10n_resourcebundles web.default_mail_from/) {
    if (defined($answers->{$key})) {
      $rhnopt{$key} = $answers->{$key};
    }
  }
  Spacewalk::Setup::write_config( \%rhnopt,
                '/var/lib/rhn/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();
        }
    }

  Spacewalk::Setup::generate_satcon_dict();
  Spacewalk::Setup::write_config(\%config_opts, Spacewalk::Setup::DEFAULT_SATCON_DICT);

  Spacewalk::Setup::satcon_deploy();

    return \%config_opts;
}

# given a list of directories (by default, /var/satellite)
# check if they exist and if they do not exist, create them
# with apache as owner
sub mkdir_mount_points {
  system('/usr/bin/spacewalk-make-mount-points', @_);
}

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

  Spacewalk::Setup::satcon_deploy(-tree => '/var/lib/rhn/rhn-satellite-prep/etc/rhn',
                -dest => '/etc/rhn');

  return;
}


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

  my $dbh = Spacewalk::Setup::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
       (id, category_id, label, value, description)
VALUES (sequence_nextval('rhn_template_str_id_seq'),
        (SELECT TC.id FROM rhnTemplateCategory TC WHERE TC.label = 'org_strings'),
        'hostname',
        ?,
        'Host name for the Red Hat Satellite')
EOQ

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

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

  }

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

  return;
}

sub generate_secret {
  return sha256_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 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/RHN-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';

    } elsif ( -e '/etc/redhat-release' ) {

      # need to read the file to see if it's a Red Hat or CentOS system.
      # we only want to import the key if it's a Red Hat system because
      # the file doesn't exist on a CentOS system.
      open(RELEASE, '/etc/redhat-release') or die "Could not open '/etc/redhat-release': $OS_ERROR\n";
      my @release = <RELEASE>;
      close(RELEASE);

      my $rel_str = join('', @release);

      # this is a RHEL system... RHEL 5 path.
      if ( $rel_str =~ m/^Red Hat/ ) {
        $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 {
  Spacewalk::Setup::system_or_exit(["/usr/sbin/spacewalk-service", "--level", "35", "enable"], 11, 'Could not turn spacewalk services on.');

  return 1;
}

sub setup_users_and_groups {

    my $apache_user = 'apache';
    my $apache_group = 'apache';
    if(Spacewalk::Setup::isSUSE()) {
        $apache_user = 'wwwrun';
        $apache_group = 'www';
    }
    # Check to be sure the required users and groups exist.
    my @required_groups = qw/$apache_group/;
    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_user);
    if (not defined $apache_group_id) {
        print Spacewalk::Setup::loc("The group '$apache_user' should exist.\n");
    }
    if (not grep { $_ eq 'tomcat' } split /\s+/, $apache_group_members) {
        Spacewalk::Setup::system_or_exit(['/usr/sbin/usermod', '-G', $apache_group, '-a', 'tomcat'], 9,
            'Could not add tomcat to the apache group.');
        # If you have for example NIS before passwd in nsswitch.conf, the usermod
        # will not modify what the system uses. Let's check.
        (undef, undef, undef, my $test_apache_group_members) = getgrnam($apache_user);
        if (not grep { $_ eq 'tomcat' } split /\s+/, $test_apache_group_members) {
            print Spacewalk::Setup::loc("The usermod failed to add tomcat to apache group.\n");
            exit 87;
        }
    }

    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++) {
        IO::Socket::INET->new(
            PeerAddr => 'localhost',
            PeerPort => '8009',
            Proto    => 'tcp'
        ) and last;
        sleep 5;
    }

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

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 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 set_required_fields {
  my $self = shift;
  my @fields = @_;

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

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 version {
  my $self = shift;

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

__END__
