File:  [LON-CAPA] / loncom / CrCA.pl
Revision 1.1: download - view: text, annotated - select for diffs
Tue Jan 1 04:43:47 2019 UTC (3 years, 7 months ago) by raeburn
Branches: MAIN
CVS tags: HEAD
- Script to create a Certificate Authority (CA) for a LON-CAPA cluster.

#!/usr/bin/perl
use strict;
use Sys::Hostname::FQDN();
use Term::ReadKey;
use Locale::Country;
use Crypt::OpenSSL::X509;
use DateTime::Format::x509;
use File::Slurp;
use Cwd;

#
# Expected structure
#
#  /lonca
#          opensslca.cnf
#          cacert.pem
#          index.txt
#          /certs
#          /crl
#          /private
#          /requests
#

  print(<<END);

****** SSL Certificate Authority *****

If you are running your own cluster of LON-CAPA nodes you will need to
create a Certificate Authority (CA) for your cluster. You will then use
the CA to sign LON-CAPA SSL certificate signing requests generated by
the nodes in your cluster.

LON-CAPA SSL Certificates can be used in two different contexts:
(a) if you configure LON-CAPA to use a secure channel for exchange of
the shared encryption key when establishing an "internal" LON-CAPA
connection between nodes in your cluster, and (b) if you configure
LON-CAPA to use client SSL certificate validation when one node replicates
content from library node(s) in your cluster.

Although a LON-CAPA cluster may contain multiple domains and/or multiple
library nodes, there will only be one LON-CAPA certificate authority (CA)
for the cluster.  The CA certificate signing infrastructure need not be 
housed on a LON-CAPA node; it can instead be installed on a separate
Linux instance.  The instance housing the CA infrastructure needs to
have the following Linux packages installed:

openssl
perl

and the following perl modules from CPAN installed:

Term::ReadKey
Sys::Hostname::FQDN
Locale::Country
Crypt::OpenSSL::X509
DateTime::Format::x509
File::Slurp

You need to decide on a directory you wish to use to hold the
CA infrastructure. If necessary you should create a new directory.
Then move this script (CrCA.pl) to that directory, and run it with
the command: perl CrCA.pl

The script will create any required subdirectories (and files) 
within that directory, if they do not already exist.

You will need to provide a password to be used for the openssl CA key 
which will be stored in the /private subdirectory, and will be used
when signing certificate signing requests to create LON-CAPA certificates 
for use in the cluster.

END

#Proceed?

  my ($dir,$hostname,%data);

# Check if required subdirectories exist in current directory.
  $dir = Cwd::getcwd();

  if (-e "$dir/lonca") {
      if ((!-d "$dir/lonca") && (-f "$dir/lonca")) {
          print "A lonca directory is required, but there is an existing file of that name.\n".
                "Please either delete the lonca file, or change to a different directory, and ".
                "create the CA infrastructure there.\n";
          exit;
      }
  } else {
      mkdir("$dir/lonca",0700);
      system('chown root:root '."$dir/lonca");
  }
  if (-d "$dir/lonca") {
      foreach my $subdir ('certs','crl','private','requests') {
          if (!-d "$dir/lonca/$subdir") {
              if (-f "$dir/lonca/$subdir") {
                  print "A $subdir sub-directory is required, but there is an existing file of that name.\n".
                        "Please either delete or move the $subdir file, then run this script again.\n";
                  exit;
              } else {
                  mkdir("$dir/lonca/$subdir",0700);
                  system('chown root:root '."$dir/lonca/$subdir");
              }
          }
      }
  } else {
      print "A lonca directory is required, but no directory exists\n";
      exit;
  }  
  if (-e "$dir/lonca/opensslca.conf") {
      # retrieve existing config file and verify that if contains the required fields.
      %data = &parse_config("$dir/lonca/opensslca.conf");
      my %update = &confirm_config(%data);
      my %changes;
      foreach my $field ('clustername','organization','email','country','state','city','days','crldays') {
          if ($data{$field} ne $update{$field}) {
              $changes{$field} = $update{$field};
          }
      }
      if (keys(%changes)) {
          &save_config_changes("$dir/lonca/opensslca.conf",\%changes);
      }
  } else {
      print(<<END);
****** Certificate Authority Configuration File *****

A configuration file: $dir/lonca/opensslca.conf will be created.

The following information will be included: 
Country, State/Province, City, Cluster Name, Organizational Name, E-mail address, CA certificate lifetime (days), Default certificate lifetime (days), CRL re-creation interval (days)

END
      $hostname = Sys::Hostname::FQDN::fqdn();
      if ($hostname eq '') {
          $hostname =&get_hostname();
      } else {
          print "Hostname detected: $hostname. Is that correct? [Y/n]";
          if (!&get_user_selection(1)) {
              $hostname =&get_hostname();
          }
      }

      my %fieldname = (
                        city => 'City',
                        state => 'State or Province',
                        clustername => 'Cluster name',
                        organization => 'Organization name',
                      );
      my ($clustername,$organization,$country,$state,$city,$email,$cadays,$clusterhostname,$days,$crldays);
      $clusterhostname =  $hostname;
      $country = &get_country($hostname);
      print "Enter state or province name\n";
      $state = &get_info($fieldname{'state'});
      print "Enter city name\n";
      $city = &get_info($fieldname{'city'});
      $email = &get_camail();
      print 'Enter a name for this LON-CAPA cluster, e.g., "Lon-CAPA learning network"'."\n".
            'This name will be included as the Common Name for the CA certificate.'."\n";
      $clustername = &get_info($fieldname{'clustername'});
      print 'Enter the organization name for this LON-CAPA cluster, e.g., "Lon CAPA certification authority"'."\n".
            'This name will be included as the Oraganization for the CA certificate.'."\n";    
      $organization = &get_info($fieldname{'organization'});
      print "Enter the lifetime (in days) for the CA root certificate distributed to all nodes, e.g., 3650\n";
      $cadays = &get_days();
      print "Enter the default lifetime (in days) for each certificate created/signed by the CA for individual nodes, e.g., 3650\n";
      $days = &get_days();
      print "Enter the re-creation interval (in days) for the CA's certificate revocation list (CRL), e.g., 180\n";
      $crldays = &get_days();

      if (open(my $fh,'>',"$dir/lonca/opensslca.conf")) {
          print $fh <<"END";
[ ca ]
default_ca       =  loncapa

[ loncapa ]
dir              = $dir/lonca
certificate      = $dir/lonca/cacert.pem
database         = $dir/lonca/index.txt
new_certs_dir    = $dir/lonca/certs
private_key      = $dir/lonca/private/cakey.pem
serial           = $dir/lonca/serial

default_crl_days = $crldays
default_days     = $days
default_md       = sha256

policy           = loncapa_policy
x509_extensions  = certificate_extensions

[ loncapa_policy ]

commonName           = supplied
stateOrProvinceName  = supplied
countryName          = supplied
emailAddress         = supplied
organizationName     = supplied
organizationalUnitName = optional

[ certificate_extensions ]

basicConstraints   = CA:false
crlDistributionPoints = URI:http://$clusterhostname/adm/dns/loncapaCAcrl

[ req ]

default_bits       = 2048
distinguished_name = loncapa_ca

x509_extensions    = loncapa_ca_extensions

[ loncapa_ca ]

commonName           = $clustername
localityName         = $city
stateOrProvinceName  = $state
countryName          = $country
emailAddress         = $email
organizationName     = $organization

[ loncapa_ca_extensions ]
basicConstraints  = CA:true

[ crl_ext ]

authorityKeyIdentifier=keyid:always,issuer:always


END

      } else {
          print 'Error: failed to wtite to '."$dir/lonca/opensslca.conf. Exiting.\n";
          exit;
      }
      %data = &parse_config("$dir/lonca/opensslca.conf");
      my %update = &confirm_config(%data);
      my %changes;
      foreach my $field ('clustername','organization','email','country','state','city','days','crldays') {
          if ($data{$field} ne $update{$field}) {
              $changes{$field} = $update{$field};
          }
      }
      if (keys(%changes)) {
          &save_config_changes("$dir/lonca/opensslca.conf",\%changes);
      }
  }

  my $sslkeypass;
  if (-e "$dir/lonca/private/cakey.pem") {
      my ($keyok,$try);
      print "CA key aleady exists\n";
      $try = 1;
      while (!$keyok && $try) {
          $sslkeypass = &get_password('Enter the password for the CA key');
          if ($sslkeypass ne '') {
              open(PIPE,"openssl rsa -noout -in lonca/private/cakey.pem -passin pass:$sslkeypass -check |");
              my $check = <PIPE>;
              close(PIPE);
              chomp($check);
              if ($check eq 'RSA key ok') {
                  $keyok = 1;
                  last;
              } else {
                  print "CA key check failed. Try again? [Y/n]";
                  if (!&get_user_selection(1)) {
                      $try = 0;
                  }
              }
          }
      }
      unless ($keyok) {
          print "CA key check failed. Create a new key? [Y/n]";
          if (&get_user_selection(1)) {
              $sslkeypass = &get_new_sslkeypass();
              # generate SSL key
              unless (&make_key("$dir/lonca/private",$sslkeypass)) {
                  print "Failed to create CA key\n";
                  exit;
              }
          } else {
              exit;
          }
      } 
  } else {
      $sslkeypass = &get_new_sslkeypass();
      # generate SSL key
      unless (&make_key("$dir/lonca/private",$sslkeypass)) {
          print "Failed to create CA key\n";
          exit;
      }
  }
  if (-e "$dir/lonca/cacert.pem") {
      print "A CA certificate exists\n";
      open(PIPE,"openssl pkey -in $dir/lonca/private/cakey.pem -passin pass:$sslkeypass -pubout -outform der | sha256sum |");
      my $hashfromkey = <PIPE>;
      close(PIPE);
      chomp($hashfromkey);
      open(PIPE,"openssl x509 -in $dir/lonca/cacert.pem -pubkey | openssl pkey -pubin -pubout -outform der | sha256sum |");
      my $hashfromcert = <PIPE>;
      close(PIPE);
      chomp($hashfromcert);
      if ($hashfromkey eq $hashfromcert) {
          my ($now,$starttime,$endtime,$status,%cert);
          my $x509 = Crypt::OpenSSL::X509->new_from_file("$dir/lonca/cacert.pem");
          my @items = split(/,\s+/,$x509->subject());
          foreach my $item (@items) {
              my ($name,$value) = split(/=/,$item);
              if ($name eq 'CN') {
                  $cert{'cn'} = $value;
              }
          }
          $cert{'start'} = $x509->notBefore();
          $cert{'end'} = $x509->notAfter();
          $cert{'alg'} = $x509->sig_alg_name();
          $cert{'size'} = $x509->bit_length();
          $cert{'email'} = $x509->email();
          my $dt = DateTime::Format::x509->parse_datetime($cert{'start'});
          if (ref($dt)) {
              $starttime = $dt->epoch;
          }
          $dt =  DateTime::Format::x509->parse_datetime($cert{'end'});
          if (ref($dt)) {
              $endtime = $dt->epoch;
          }
          $now = time;
          if (($starttime ne '') && ($endtime ne '')) {
              if ($endtime <= $now) {
                  $status = 'previous';
                  print "Current CA certificate expired $cert{'end'}\n"; 
              } elsif ($starttime > $now) {
                  $status = 'future';
                  print "Current CA certificate will be valid after $cert{'start'}\n"; 
              } else {
                  $status eq 'active';
                  print "Current CA certificate valid until $cert{'end'}".' '.
                        "Signature Algorithm: $cert{'alg'}; Public Key size: $cert{'size'}\n"; 
              }
              if ($status eq 'previous') {
                  print 'Create a new certificate? [Y/n]';
                  if (&get_user_selection(1)) {
                      unless (&make_ca_cert("$dir/lonca/private","$dir/lonca",$sslkeypass)) {
                          print "Failed to create CA cert\n";
                          exit;
                      }
                  }
              }
          } else {
              print "Could not determine validity of current CA certificate\n";
              exit;
          }
      }
  } else {
      unless (&make_ca_cert("$dir/lonca/private","$dir/lonca",$sslkeypass)) {
          print "Failed to create CA cert\n";
          exit;
      }
  }

  if (!-e "$dir/lonca/index.txt") {
      File::Slurp::write_file("$dir/lonca/index.txt");
  }
  if (-e "$dir/lonca/index.txt") {
      my $mode = 0600;
      chmod $mode, "$dir/lonca/index.txt";
  } else {
      print "lonca/index.txt file is missing\n";
      exit; 
  }    
# echo 1000 > serial


  unless (-e "$dir/lonca/crl/loncapaCAcrl.pem") {
      open(PIPE,"openssl ca -gencrl -keyfile $dir/lonca/private/cakey.pem -cert $dir/lonca/cacert.pem -out $dir".
                "/lonca/crl/loncapaCAcrl.pem -config $dir/lonca/opensslca.conf -passin pass:$sslkeypass |");
      close(PIPE);
      if (-e "$dir/lonca/crl/loncapaCAcrl.pem") {
          print "Certificate Revocation List created\n";
      }
  }
  if (-e "$dir/lonca/crl/loncapaCAcrl.pem") {
      open(PIPE,"openssl crl -in $dir/lonca/crl/loncapaCAcrl.pem -inform pem -CAfile $dir/lonca/cacert.pem  -noout 2>&1 |");
      my $revoked = <PIPE>;
      close(PIPE);
      chomp($revoked);
      print "Revocation certificate status: $revoked\n";
# Create a new one? 
  }

sub cafield_to_key {
    my %mapping = (
                    city         => 'localityName',
                    state        => 'stateOrProvinceName',
                    country      => 'countryName',
                    email        => 'emailAddress',
                    organization => 'organizationName',
                    clustername  => 'commonName',
                  );
    return %mapping;
}

sub field_to_key {
    my %mapping = (
                    days    => 'default_days',
                    crldays => 'default_crl_days',
                  );
}

sub parse_config {
    my ($filepath) = @_;
    my (%fields,%data);
    if (open(my $fh,'<',$filepath)) {
        my $currsection;
        while(<$fh>) {
            chomp();
            s/(^\s+|\s+$)//g;
            if (/^\[\s*([^\s]+)\s*\]/) {
                $currsection = $1;
            } elsif (/^([^=]+)=([^=]+)$/) {
                my ($key,$value) = ($1,$2);
                $key =~ s/\s+$//;
                $value =~ s/^\s+//;
                if ($currsection ne '') {
                    $fields{$currsection}{$key} = $value;
                }
            }
        }
        close($fh); 
    }
    if (ref($fields{'loncapa_ca'}) eq 'HASH') {
        my %ca_mapping = &cafield_to_key();
        foreach my $key (keys(%ca_mapping)) {
            $data{$key} = $fields{'loncapa_ca'}{$ca_mapping{$key}};
        }
    }
    if (ref($fields{'loncapa'}) eq 'HASH') {
        my %mapping = &field_to_key();
        foreach my $key (keys(%mapping)) {
            $data{$key} = $fields{'loncapa'}{$mapping{$key}};
        }
    }
    return %data; 
}

sub save_config_changes {
    my ($filepath,$updated) = @_;
    return unless (ref($updated) eq 'HASH');
    my %mapping = &field_to_key();
    my %ca_mapping = &cafield_to_key();
    my %revmapping = reverse(%mapping);
    my %rev_ca_mapping = reverse(%ca_mapping);
    my $lines;
    if (open(my $fh,'<',$filepath)) {
        my $currsection;
        while(<$fh>) {
            my $line = $_;
            chomp();
            s/(^\s+|\s+$)//g;
            my $newline;
            if (/^\[\s*([^\s]+)\s*\]/) {
                $currsection = $1;
            } elsif (/^([^=]+)=([^=]*)$/) {
                my ($origkey,$origvalue) = ($1,$2);
                my ($key,$value) = ($origkey,$origvalue);
                $key =~ s/\s+$//;
                $value =~ s/^\s+//;
                if ($currsection eq 'loncapa_ca') {
                    if ((exists($rev_ca_mapping{$key})) && (exists($updated->{$rev_ca_mapping{$key}}))) {
                        if ($value eq '') {
                            if ($origvalue eq '') {
                                $origvalue = ' ';
                            }
                            $origvalue .= $updated->{$rev_ca_mapping{$key}};
                        } else {
                            $origvalue =~ s/\Q$value\E/$updated->{$rev_ca_mapping{$key}}/;
                        }
                        $newline = $origkey.'='.$origvalue."\n";
                    }
                } elsif ($currsection eq 'loncapa') {
                    if ((exists($revmapping{$key})) && (exists($updated->{$revmapping{$key}}))) {
                        if ($value eq '') {
                            if ($origvalue eq '') {
                                $origvalue = ' ';
                            }
                            $origvalue .= $updated->{$revmapping{$key}};
                        } else {
                            $origvalue =~ s/\Q$value\E/$updated->{$revmapping{$key}}/;
                        }
                        $newline = $origkey.'='.$origvalue."\n";
                    }
                }
            }
            if ($newline) {
                $lines .= $newline;
            } else {
                $lines .= $line;
            }
        }
        close($fh);
        if (open(my $fout,'>',$filepath)) {
            print $fout $lines;
            close($fout);
        } else {
            print "Error: failed to open '$filepath' for writing\n"; 
        }
    }
    return;
}

#
# get_hostname() prompts the user to provide the server's hostname.
#
# If invalid input is provided, the routine is called recursively
# until, a valid hostname is provided.
#

sub get_hostname {
    my $hostname;
    print 'Enter the hostname of this server, e.g., loncapa.somewhere.edu'."\n";
    my $choice = <STDIN>;
    chomp($choice);
    $choice =~ s/(^\s+|\s+$)//g;
    if ($choice eq '') {
        print "Hostname you entered was either blank or contanied only white space.\n";
    } elsif ($choice =~ /^[\w\.\-]+$/) {
        $hostname = $choice;
    } else {
        print "Hostname you entered was invalid --  a hostname may only contain letters, numbers, - and .\n";
    }
    while ($hostname eq '') {
        $hostname = &get_hostname();
    }
    print "\n";
    return $hostname;
}

sub get_new_sslkeypass {
    my $sslkeypass;
    my $flag=0;
# get password for SSL key
    while (!$flag) {
        $sslkeypass = &make_passphrase();
        if ($sslkeypass) {
            $flag = 1;
        } else {
            print "Invalid input (a password is required for the CA key).\n";
        }
    }
    return $sslkeypass;
}

sub make_passphrase {
    my ($got_passwd,$firstpass,$secondpass,$passwd);
    my $maxtries = 10;
    my $trial = 0;
    while ((!$got_passwd) && ($trial < $maxtries)) {
        $firstpass = &get_password('Enter a password for the CA key (at least 6 characters long)');
        if (length($firstpass) < 6) {
            print('Password too short.'."\n".
              'Please choose a password with at least six characters.'."\n".
              'Please try again.'."\n");
        } elsif (length($firstpass) > 30) {
            print('Password too long.'."\n".
                  'Please choose a password with no more than thirty characters.'."\n".
                  'Please try again.'."\n");
        } else {
            my $pbad=0;
            foreach (split(//,$firstpass)) {if ((ord($_)<32)||(ord($_)>126)){$pbad=1;}}
            if ($pbad) {
                print('Password contains invalid characters.'."\n".
                      'Password must consist of standard ASCII characters.'."\n".
                      'Please try again.'."\n");
            } else {
                $secondpass = &get_password('Enter password a second time');
                if ($firstpass eq $secondpass) {
                    $got_passwd = 1;
                    $passwd = $firstpass;
                } else {
                    print('Passwords did not match.'."\n".
                          'Please try again.'."\n");
                }
            }
        }
        $trial ++;
    }
    return $passwd;
}

sub get_password {
    my ($prompt) = @_;
    local $| = 1;
    print $prompt.': ';
    my $newpasswd = '';
    ReadMode 'raw';
    my $key;
    while(ord($key = ReadKey(0)) != 10) {
        if(ord($key) == 127 || ord($key) == 8) {
            chop($newpasswd);
            print "\b \b";
        } elsif(!ord($key) < 32) {
            $newpasswd .= $key;
            print '*';
        }
    }
    ReadMode 'normal';
    print "\n";
    return $newpasswd;
}

#
# make_key() generates CA root key
#

sub make_key {
    my ($keydir,$sslkeypass) = @_;
# generate SSL key
    my $created;
    if (($keydir ne '') && ($sslkeypass ne '')) {
        if (-f "$keydir/cakey.pem") {
            my $mode = 0600;
            chmod $mode, "$keydir/cakey.pem";
        }
        open(PIPE,"openssl genrsa -aes256 -passout pass:$sslkeypass -out $keydir/cakey.pem 2048 2>&1 |");
        close(PIPE);
        if (-f "$keydir/cakey.pem") {
            my $mode = 0400;
            chmod $mode, "$keydir/cakey.pem";
            $created= 1;
        }
    } else {
        print "Key creation failed.  Missing one or more of: certificates directory, key name\n";
    }
    return $created;
}

#
# make_ca_cert() generates CA root certificate
#

sub make_ca_cert {
    my ($keydir,$certdir,$sslkeypass) = @_;
# generate SSL cert for CA
    my $created;
    if ((-d $keydir) && (-d $certdir) && ($sslkeypass ne ''))  {
        my $cmd = "openssl req -x509 -key $keydir/cakey.pem -passin pass:$sslkeypass -new -batch -config $certdir/opensslca.conf -out $certdir/cacert.pem";
        print "Calling ||$cmd||\n";
        open(PIPE,"openssl req -x509 -key $keydir/cakey.pem -passin pass:$sslkeypass -new -batch -config $certdir/opensslca.conf -out $certdir/cacert.pem |");
        close(PIPE);
        if (-f "$certdir/cacert.pem") {
            my $mode = 0600;
            chmod $mode, "$certdir/cacert.pem";
#            chmod $mode, "$certdir/careq.pem";
#            open(PIPE,"openssl ca -create_serial -out $certdir/cacert.pem -days 3650 -keyfile $keydir/cakey.pem -selfsign -config ./openssl.cnf -infiles $certdir/careq.pem |");
#            close(PIPE);
#            if (-f "$certdir/cacert.pem") {
#                my $mode = 0600;
#                chmod $mode, "$certdir/cacert.pem";
#            }
            $created= 1;
        }
    } else {
        print "Creation of CA root certificate failed.  Missing one or more of: CA directory, CA key directory, or CA passphrase.\n";
    }
    return $created;
}

sub get_camail {
    my $camail;
    my $flag=0;
# get Certificate Authority E-mail
    while (!$flag) {
        print(<<END);

Enter e-mail address of Certificate Authority. 
END

        my $choice=<>;
        chomp($choice);
        if (($choice ne '') && ($choice =~ /^[^\@]+\@[^\@]+$/)) {
            $camail=$choice;
            $flag=1;
        } else {
            print "Invalid input (a valid email address is required).\n";
        }
    }
    return $camail;
}

sub ssl_info {
    print(<<END);

****** Information about Country, State or Province and City *****

A two-letter country code, e.g., US, CA, DE etc. as defined by ISO 3166,
is required. A state or province, and a city are also required.
This locality information is included in two SSL certificates used internally
by LON-CAPA, unless you are running standalone.

If your server will be part of either the production or development
clusters, then the certificate will need to be signed by the official
LON-CAPA Certificate Authority (CA).  If you will be running your own
cluster then the cluster will need to create its own CA.

END
}

sub get_country {
    my ($desiredhostname) = @_;
# get Country
    my ($posscountry,$country);
    if ($desiredhostname =~ /\.(edu|com|org)$/) {
        $posscountry = 'us';
    } else {
        ($posscountry) = ($desiredhostname =~ /\.(a-z){2}$/);
    }
    if ($posscountry) {
        my $countrydesc = &Locale::Country::code2country($posscountry);
        if ($countrydesc eq '') {
            undef($posscountry);
        }
    }

    my $flag=0;
    while (!$flag) {
        if ($posscountry) {
            $posscountry = uc($posscountry);
            print "Enter Two-Letter Country Code [$posscountry]:\n";
        } else {
            print "Enter the Two-Letter Country Code:\n";
        }
        my $choice=<STDIN>;
        chomp($choice);
        if ($choice ne '') {
            if (&Locale::Country::code2country(lc($choice))) {
                $country=uc($choice);
                $flag=1;
            } else {
                print "Invalid input -- a valid two letter country code is required\n";
            }
        } elsif (($choice eq '') && ($posscountry ne '')) {
            $country = $posscountry;
            $flag = 1;
        } else {
            print "Invalid input -- a country code is required\n";
        }
    }
    return $country;
}

sub get_info {
    my ($typename) = @_;
    my $value;
    my $choice = <STDIN>;
    chomp($choice);
    $choice =~ s/(^\s+|\s+$)//g;
    if ($choice eq '') {
        print "$typename you entered was either blank or contained only white space.\n";
    } else {
        $value = $choice;
    }
    while ($value eq '') {
        $value = &get_info($typename);
    }
    print "\n";
    return $value;
}

sub get_days {
    my $value;
    my $choice = <STDIN>;
    chomp($choice);
    $choice =~ s/(^\s+|\s+$)//g;
    if ($choice eq '') {
        print "The value you entered was either blank or contained only white space.\n";
    } elsif ($choice !~ /^\d+$/) {
        print "The value you entered contained invalid characters -- you must enter just an integer.\n";
    } else {
        $value = $choice;
    }
    while ($value eq '') {
        $value = &get_days();
    }
    print "\n";
    return $value;
}

sub confirm_config {
    my (%data) = @_;
    my $flag = 0;
    while (!$flag) {
        print(<<END);

The cluster name, organization name, country, state and city will be 
included in the CA certificate

1) Cluster Name: $data{'clustername'}
2) Organization Name: $data{'organization'}
3) Country: $data{'country'}
4) State or Province: $data{'state'}
5) City: $data{'city'}
6) E-mail: $data{'email'}
7) CA certificate lifetime (days): $data{'cadays'}
8) Default certificate lifetime for issued certs (days): $data{'days'}
9) CRL recreation interval (days): $data{'crldays'}
10) Everything is correct up above

Enter a choice of 1-9 to change, otherwise enter 10: 
END
        my $choice=<STDIN>;
        chomp($choice);
        if ($choice == 1) {
            print(<<END);
1) Cluster Name: $data{'clustername'}
Enter new value:
END
            my $choice2=<STDIN>;
            chomp($choice2);
            $data{'clustername'}=$choice2;
            chomp($choice2);
            $data{'organization'}=$choice2;
        } elsif ($choice == 3) {
            print(<<END);
3) Country: $data{'country'}
Enter new value (this should be a two-character code, e,g, US, CA, DE):
END
            my $choice2=<STDIN>;
            chomp($choice2);
            $data{'country'} = uc($choice2);
        } elsif ($choice == 4) {
            print(<<END);
4) State or Province: $data{'state'}
Enter new value:
END
            my $choice2=<>;
            chomp($choice2);
            $data{'state'}=$choice2;
        } elsif ($choice == 5) {
            print(<<END);
5) City: $data{'city'}
Enter new value:
END
            my $choice2=<>;
            chomp($choice2);
            $data{'city'}=$choice2;
        } elsif ($choice == 6) {
            print(<<END);
6) E-mail: $data{'email'}
Enter new value:
END
            my $choice2=<>;
            chomp($choice2);
            $data{'email'}=$choice2;
        } elsif ($choice == 7) {
print(<<END);
7) CA Root Certificate lifetime: $data{'cadays'}
Enter new value:
END
            my $choice2=<>;
            chomp($choice2);
            $choice2 =~ s/\D//g;
            $data{'cadays'}=$choice2;
        } elsif ($choice == 8) {
print(<<END);
8) Default certificate lifetime: $data{'days'}
Enter new value:
END
            my $choice2=<>;
            chomp($choice2);
            $choice2 =~ s/\D//g;
            $data{'days'}=$choice2;
        } elsif ($choice == 9) {
print(<<END);
9) CRL re-creation interval: $data{'crldays'}
Enter new value:
END
            my $choice2=<>;
            chomp($choice2);
            $choice2 =~ s/\D//g;
            $data{'crldays'}=$choice2;
        } elsif ($choice == 10) {
            $flag=1;
            foreach my $key (keys(%data)) { 
                $data{$key} =~ s{/}{ }g;
            }  
        } else {
            print "Invalid input.\n";
        }
    }
    return %data; 
}

sub get_user_selection {
    my ($defaultrun) = @_;
    my $do_action = 0;
    my $choice = <STDIN>;
    chomp($choice);
    $choice =~ s/(^\s+|\s+$)//g;
    my $yes = 'y';
    if ($defaultrun) {
        if (($choice eq '') || ($choice =~ /^\Q$yes\E/i)) {
            $do_action = 1;
        }
    } else {
        if ($choice =~ /^\Q$yes\E/i) {
            $do_action = 1;
        }
    }
    return $do_action;
}


FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>