File:  [LON-CAPA] / loncom / manage_ssl_certs.pl
Revision 1.1: download - view: text, annotated - select for diffs
Sat Aug 18 23:33:30 2018 UTC (5 years, 7 months ago) by raeburn
Branches: MAIN
CVS tags: version_2_12_X, HEAD
- LON-CAPA SSL PKI for connections to other nodes, and replication of
  published content from other nodes.
  - script to create new SSL key and/or SSL certs (host cert and/or
    hostname certs

    1: #!/usr/bin/perl
    2: $|=1;
    3: # Displays status of LON-CAPA SSL certs, and allows new certificate
    4: # signing requests to be created and e-mailed to CA for cluster to
    5: # which server/VM belongs. 
    6: # $Id: manage_ssl_certs.pl,v 1.1 2018/08/18 23:33:30 raeburn Exp $
    7: #
    8: # Copyright Michigan State University Board of Trustees
    9: #
   10: # This file is part of the LearningOnline Network with CAPA (LON-CAPA).
   11: #
   12: # LON-CAPA is free software; you can redistribute it and/or modify
   13: # it under the terms of the GNU General Public License as published by
   14: # the Free Software Foundation; either version 2 of the License, or
   15: # (at your option) any later version.
   16: #
   17: # LON-CAPA is distributed in the hope that it will be useful,
   18: # but WITHOUT ANY WARRANTY; without even the implied warranty of
   19: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   20: # GNU General Public License for more details.
   21: #
   22: # You should have received a copy of the GNU General Public License
   23: # along with LON-CAPA; if not, write to the Free Software
   24: # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   25: #
   26: # /home/httpd/html/adm/gpl.txt
   27: #
   28: # http://www.lon-capa.org/
   29: #
   30: 
   31: use strict;
   32: use lib '/home/httpd/lib/perl/';
   33: use LONCAPA::Configuration;
   34: use LONCAPA::Lond;
   35: use LONCAPA::SSL;
   36: use LONCAPA;
   37: use Term::ReadKey;
   38: use Locale::Country;
   39: 
   40: my ($lonCluster,$domainDescription,$hostname,$certmail,
   41:     $certsdir,$privkey,$connectcsr,$replicatecsr);
   42: 
   43: print(<<END);
   44: 
   45: ===============================================================================
   46: 
   47: Which type of LON-CAPA cluster does this server/VM belong to?
   48: IMPORTANT: to take advantage of the cluster options 1) and 3),
   49: you must have been or expect to be accepted into the cluster.
   50: Contact loncapa\@loncapa.org for access.
   51: 
   52: 1) PRODUCTION - you have (or will) connect this machine to the
   53:                 LON-CAPA content sharing network. This setting is for
   54:                 schools, colleges, and universities, that currently
   55:                 are running - or in the future will run - courses.
   56: 2) STAND-ALONE - you want this machine to run in 'stand-alone' mode and
   57:                  not be connected to other LON-CAPA machines.
   58: 3) DEVELOPMENT - you want to do software (not content!) development with
   59:                  this workstation and eventually link it with 
   60:                  workstations of other LON-CAPA software developers.
   61: 4) RUNNING YOUR OWN CLUSTER - this machine is not in the standard LON-CAPA
   62:                  cluster and won't be in the future.
   63:                  (This choice is unlikely what you want to select.)
   64: END
   65: 
   66: my $flag=0;
   67: while (!$flag) {
   68:     print "ENTER 1, 2, 3, or 4:\n";
   69:     my $choice=<>;
   70:     chomp($choice);
   71:     if ($choice==1) {
   72:         $lonCluster='production'; $flag=1;
   73:     } elsif ($choice==2) {
   74:         $lonCluster='standalone'; $flag=1;
   75:     } elsif ($choice==3) {
   76:         $lonCluster='development'; $flag=1;
   77:     } elsif ($choice==4) {
   78:         $lonCluster='existing'; $flag=1;
   79:         my $earlyout;
   80:         foreach my $file ('hosts.tab','dns_hosts.tab',
   81:                           'domain.tab','dns_domain.tab') {
   82:             unless (-e '/home/httpd/lonTabs/'.$file) {
   83:                 print <<END;
   84: There is no existing /home/httpd/lonTabs/$file
   85: END
   86:                 $earlyout = 1;
   87:             }
   88:         }
   89:         if ($earlyout) {
   90:             exit;
   91:         }
   92:     }
   93: }
   94: 
   95: $flag = 0;
   96: my $dist=`/home/httpd/perl/distprobe`;
   97: my $confdir = '/etc/httpd/conf/';
   98: if ($dist =~ /^(sles|suse|ubuntu|debian)/) {
   99:     $confdir = '/etc/apache2/';
  100: }
  101: 
  102: my $filename='loncapa.conf';
  103: my %perlvar;
  104: if (-e "$confdir$filename") {
  105:     if (open(CONFIG,'<',$confdir.$filename) or die("Can't read $confdir$filename")) {
  106:         while (my $configline=<CONFIG>) {
  107:             if ($configline =~ /^[^\#]*PerlSetVar/) {
  108:                 my ($unused,$varname,$varvalue)=split(/\s+/,$configline);
  109:                 chomp($varvalue);
  110:                 $perlvar{$varname}=$varvalue if $varvalue!~/^\{\[\[\[\[/;
  111:             }
  112:         }
  113:         close(CONFIG);
  114:     }
  115: }
  116: 
  117: my $perlstaticref = &get_static_config($confdir);
  118: if (ref($perlstaticref) ne 'HASH') {
  119:     exit;
  120: }
  121: 
  122: if (open(IN,'<','/home/httpd/lonTabs/domain.tab')) {
  123:     while(my $line = <IN>) {
  124:        if ($line =~ /^\Q$perlvar{'lonDefDomain'}\E\:/) {
  125:            (undef,$domainDescription)=split(/:/,$line);
  126:            chomp($domainDescription);
  127:            last;
  128:         }
  129:     }
  130:     close(IN);
  131: }
  132: 
  133: if (open(IN,'<','/home/httpd/lonTabs/hosts.tab')) {
  134:     while(my $line = <IN>) {
  135:        if ($line =~ /^\Q$perlvar{'lonHostID'}\E\:/) {
  136:            (undef,undef,undef,$hostname)=split(/:/,$line);
  137:            last;
  138:         }
  139:     }
  140:     close(IN);
  141: }
  142: 
  143: $certsdir = $perlstaticref->{'lonCertificateDirectory'};
  144: $privkey = $perlstaticref->{'lonnetPrivateKey'};
  145: $connectcsr = $perlstaticref->{'lonnetCertificate'};
  146: $connectcsr =~ s/\.pem$/.csr/;
  147: $replicatecsr = $perlstaticref->{'lonnetHostnameCertificate'};
  148: $replicatecsr =~ s/\.pem$/.csr/;
  149: 
  150: 
  151: $certmail = &get_mail();
  152: 
  153: print "\nRetrieving status information for SSL key and certificates ...\n\n";
  154: my ($certinfo,$lonkeystatus,$lonhostcertstatus,$lonhostnamecertstatus,$sslref) =
  155:     &get_cert_status($perlvar{'lonHostID'},$hostname,$perlstaticref);
  156: print $certinfo;
  157: my %sslstatus;
  158: if (ref($sslref) eq 'HASH') {
  159:     %sslstatus = %{$sslref};
  160: }
  161: while (!$flag) {
  162:     print(<<END);
  163: 
  164: ===============================================================================
  165: 
  166: This is now the current configuration of your machine.
  167: 1) Private Key for SSL: $lonkeystatus
  168: 2) SSL Certificate for LON-CAPA server connections: $lonhostcertstatus
  169: 3) SSL Certificate for Content Replication: $lonhostnamecertstatus
  170: 4) Everything is correct up above
  171: 
  172: ENTER A CHOICE OF 1 TO 3 TO CHANGE, OTHERWISE ENTER 4:
  173: END
  174: 
  175:     my $choice=<>;
  176:     chomp($choice);
  177:     if ($choice==1) {
  178:         if ($sslstatus{'key'} == 1) {
  179:             print(<<END);
  180: 1) Private Key for SSL: $lonkeystatus
  181: 
  182: POSSIBLE CHOICES:
  183: 1) overwrite existing key
  184: 2) make no change
  185: ENTER NEW VALUE
  186: END
  187:             my $choice2=<>;
  188:             chomp($choice2);
  189:             if ($choice2 eq '1') {
  190:                 my $sslkeypass = &get_new_sslkeypass();
  191:                 &make_key($certsdir,$privkey,$sslkeypass);
  192:             }
  193:         } elsif ($sslstatus{'key'} == 0) {
  194:             print(<<END);
  195: 1) Private Key for SSL: $lonkeystatus
  196: END
  197:             my $sslkeypass = &get_new_sslkeypass();
  198:             &make_key($certsdir,$privkey,$sslkeypass);
  199:             print "\nRetrieving status information for SSL key and certificates ...\n\n";
  200:             ($certinfo,$lonkeystatus,$lonhostcertstatus,$lonhostnamecertstatus,$sslref) =
  201:                 &get_cert_status($perlvar{'lonHostID'},$hostname,$perlstaticref);
  202:             if (ref($sslref) eq 'HASH') {
  203:                 %sslstatus = %{$sslref};
  204:             }
  205:         }
  206:     } elsif ($choice==2) {
  207:         if (($sslstatus{'host'} == 1) || ($sslstatus{'host'} == 2) || ($sslstatus{'host'} == 3)) {
  208:             print(<<END);
  209: 2) SSL Certificate for LON-CAPA server connections: $lonhostcertstatus
  210: 
  211: POSSIBLE CHOICES:
  212: 1) create new certificate signing request with new key
  213: 2) create new certificate signing request with existing key
  214: 3) resend current certificate signing request
  215: 4) make no change
  216: ENTER NEW VALUE
  217: END
  218: 
  219:             my $choice2=<>;
  220:             chomp($choice2);
  221:             if (($choice2 eq '1') || ($choice2 eq '2')) {
  222:                 &ssl_info();
  223:                 my $country = &get_country($hostname);
  224:                 my $state = &get_state();
  225:                 my $city = &get_city();
  226:                 my $connectsubj = "/C=$country/ST=$state/O=$domainDescription/L=$city/CN=$perlvar{'lonHostID'}/OU=LONCAPA/emailAddress=$certmail";
  227:                 ($domainDescription,$country,$state,$city) = &confirm_locality($domainDescription,$country,$state,$city);
  228:                 my $sslkeypass;
  229:                 if ($choice2 eq '1') {
  230:                     $sslkeypass = &get_new_sslkeypass();
  231:                     &make_key($certsdir,$privkey,$sslkeypass);
  232:                 } elsif ($choice2 eq '2') {
  233:                     $sslkeypass = &get_password('Enter existing password for SSL key');
  234:                     &encrypt_key($certsdir,$privkey,$sslkeypass);
  235:                 }
  236:                 &make_host_csr($certsdir,$sslkeypass,$connectcsr,$connectsubj);
  237:                 &mail_csr('host',$lonCluster,$perlvar{'lonHostID'},$hostname,$certsdir,$connectcsr,$replicatecsr,$perlstaticref);
  238:                 print "\nRetrieving status information for SSL key and certificates ...\n\n";
  239:                 ($certinfo,$lonkeystatus,$lonhostcertstatus,$lonhostnamecertstatus,$sslref) =
  240:                     &get_cert_status($perlvar{'lonHostID'},$hostname,$perlstaticref);
  241:                 if (ref($sslref) eq 'HASH') {
  242:                     %sslstatus = %{$sslref};
  243:                 }
  244:             } elsif ($choice2 eq '3') {
  245:                 if (-e "$certsdir/$connectcsr") {
  246:                     &mail_csr('host',$lonCluster,$perlvar{'lonHostID'},$hostname,$certsdir,$connectcsr,$replicatecsr,$perlstaticref);
  247:                 }
  248:             }
  249:         } elsif (($sslstatus{'host'} == 0) || ($sslstatus{'host'} == 4) || ($sslstatus{'host'} == 5)) {
  250:             my $sslkeypass;
  251:             if ($sslstatus{'key'} == 1) {
  252:                 print(<<END);
  253: 2) SSL Certificate for LON-CAPA server connections: $lonhostcertstatus
  254: 
  255: POSSIBLE CHOICES:
  256: 1) create new certificate signing request with new key
  257: 2) create new certificate signing request with existing key
  258: 3) make no change
  259: ENTER NEW VALUE
  260: END
  261:                 my $choice2=<>;
  262:                 chomp($choice2);
  263:                 if ($choice2 eq '1') {
  264:                     $sslkeypass = &get_new_sslkeypass();
  265:                     &make_key($certsdir,$privkey,$sslkeypass);
  266:                 } elsif ($choice2 eq '2') {
  267:                     $sslkeypass = &get_password('Enter existing password for SSL key');
  268:                     &encrypt_key($certsdir,$privkey,$sslkeypass);
  269:                 }
  270:             } else {
  271:                 print(<<END);
  272: 2) SSL Certificate for LON-CAPA server connections: $lonhostcertstatus
  273: END
  274:                 $sslkeypass = &get_new_sslkeypass();
  275:             }
  276:             &ssl_info();
  277:             my $country = &get_country($hostname);
  278:             my $state = &get_state();
  279:             my $city = &get_city();
  280:             my $connectsubj = "/C=$country/ST=$state/O=$domainDescription/L=$city/CN=$perlvar{'lonHostID'}/OU=LONCAPA/emailAddress=$certmail";
  281:             &make_host_csr($certsdir,$sslkeypass,$connectcsr,$connectsubj);
  282:             &mail_csr('host',$lonCluster,$perlvar{'lonHostID'},$hostname,$certsdir,$connectcsr,$replicatecsr,$perlstaticref);
  283:             print "\nRetrieving status information for SSL key and certificates ...\n\n";
  284:             ($certinfo,$lonkeystatus,$lonhostcertstatus,$lonhostnamecertstatus,$sslref) =
  285:                 &get_cert_status($perlvar{'lonHostID'},$hostname,$perlstaticref);
  286:             if (ref($sslref) eq 'HASH') {
  287:                 %sslstatus = %{$sslref};
  288:             }
  289:         }
  290:     } elsif ($choice==3) {
  291:         if (($sslstatus{'hostname'} == 1) || ($sslstatus{'hostname'} == 2) || ($sslstatus{'hostname'} == 3)) {
  292:             print(<<END);
  293: 3) SSL Certificate for Content Replication: $lonhostnamecertstatus
  294: 
  295: POSSIBLE CHOICES:
  296: 1) create new certificate signing request with new key
  297: 2) create new certificate signing request with existing key
  298: 3) resend current certificate signing request
  299: 4) make no change
  300: ENTER NEW VALUE
  301: END
  302:             my $choice2=<>;
  303:             chomp($choice2);
  304:             if (($choice2 eq '1') || ($choice2 eq '2')) {
  305:                 &ssl_info();
  306:                 my $country = &get_country($hostname);
  307:                 my $state = &get_state();
  308:                 my $city = &get_city();
  309:                 my $replicatesubj = "/C=$country/ST=$state/O=$domainDescription/L=$city/CN=internal-$hostname/OU=LONCAPA/emailAddress=$certmail";
  310:                 my $sslkeypass;
  311:                 if ($choice2 eq '1') {
  312:                     $sslkeypass = &get_new_sslkeypass();
  313:                     &make_key($certsdir,$privkey,$sslkeypass);
  314:                 } elsif ($choice2 eq '2') {
  315:                     $sslkeypass = &get_password('Enter existing password for SSL key');
  316:                     &encrypt_key($certsdir,$privkey,$sslkeypass);
  317:                 }
  318:                 &make_hostname_csr($certsdir,$sslkeypass,$replicatecsr,$replicatesubj);
  319:                 &mail_csr('hostname',$lonCluster,$perlvar{'lonHostID'},$hostname,$certsdir,$connectcsr,$replicatecsr,$perlstaticref);
  320:                 print "\nRetrieving status information for SSL key and certificates ...\n\n";
  321:                 ($certinfo,$lonkeystatus,$lonhostcertstatus,$lonhostnamecertstatus,$sslref) =
  322:                     &get_cert_status($perlvar{'lonHostID'},$hostname,$perlstaticref);
  323:                 if (ref($sslref) eq 'HASH') {
  324:                     %sslstatus = %{$sslref};
  325:                 }
  326:             } elsif ($choice2 eq '3') {
  327:                 if (-e "$certsdir/$replicatecsr") {
  328:                     &mail_csr('hostname',$lonCluster,$perlvar{'lonHostID'},$hostname,$certsdir,$connectcsr,$replicatecsr,$perlstaticref);
  329:                 }
  330:             }
  331:         } elsif (($sslstatus{'hostname'} == 0) || ($sslstatus{'hostname'} == 4) || ($sslstatus{'hostname'} == 5)) {
  332:             my $sslkeypass;
  333:             if ($sslstatus{'key'} == 1) {
  334:                 print(<<END);
  335: 3) SSL Certificate for Content Replication: $lonhostnamecertstatus
  336: 
  337: POSSIBLE CHOICES:
  338: 1) create new certificate signing request with new key
  339: 2) create new certificate signing request with existing key
  340: 3) make no change
  341: ENTER NEW VALUE
  342: END
  343:                 my $choice2=<>;
  344:                 chomp($choice2);
  345:                 if ($choice2 eq '1') {
  346:                     $sslkeypass = &get_new_sslkeypass();
  347:                     &make_key($certsdir,$privkey,$sslkeypass);
  348:                 } elsif ($choice2 eq '2') {
  349:                     $sslkeypass = &get_password('Enter existing password for SSL key');
  350:                     &encrypt_key($certsdir,$privkey,$sslkeypass);
  351:                 }
  352:             } else {
  353:                 print(<<END);
  354: 3) SSL Certificate for Content Replication: $lonhostnamecertstatus
  355: END
  356:                 $sslkeypass = &get_new_sslkeypass();
  357:             }
  358:             &ssl_info();
  359:             my $country = &get_country($hostname);
  360:             my $state = &get_state();
  361:             my $city = &get_city();
  362:             my $replicatesubj = "/C=$country/ST=$state/O=$domainDescription/L=$city/CN=internal-$hostname/OU=LONCAPA/emailAddress=$certmail";
  363:             &make_hostname_csr($certsdir,$sslkeypass,$replicatecsr,$replicatesubj);
  364:             &mail_csr('hostname',$lonCluster,$perlvar{'lonHostID'},$hostname,$certsdir,$connectcsr,$replicatecsr,$perlstaticref);
  365:             print "\nRetrieving status information for SSL key and certificates ...\n\n";
  366:             ($certinfo,$lonkeystatus,$lonhostcertstatus,$lonhostnamecertstatus,$sslref) =
  367:                 &get_cert_status($perlvar{'lonHostID'},$hostname,$perlstaticref);
  368:             if (ref($sslref) eq 'HASH') {
  369:                 %sslstatus = %{$sslref};
  370:             }
  371:         }
  372:     } elsif ($choice==4) {
  373:         $flag=1;
  374:     } else {
  375:        print "Invalid input.\n";
  376:     }
  377: }
  378: 
  379: exit;
  380: 
  381: sub get_static_config {
  382:     my ($confdir) = @_;
  383:     my $filename='loncapa_apache.conf';
  384:     my %LCperlvar;
  385:     if (-e "$confdir$filename") {
  386:         open(CONFIG,'<',$confdir.$filename) or die("Can't read $confdir$filename");
  387:         while (my $configline=<CONFIG>) {
  388:             if ($configline =~ /^[^\#]?PerlSetVar/) {
  389:                 my ($unused,$varname,$varvalue)=split(/\s+/,$configline);
  390:                 chomp($varvalue);
  391:                 $LCperlvar{$varname}=$varvalue;
  392:             }
  393:         }
  394:         close(CONFIG);
  395:     }
  396:     return \%LCperlvar;
  397: }
  398: 
  399: sub get_sslnames {
  400:     my %sslnames = (
  401:                       key      => 'lonnetPrivateKey',
  402:                       host     => 'lonnetCertificate',
  403:                       hostname => 'lonnetHostnameCertificate',
  404:                       ca       => 'lonnetCertificateAuthority',
  405:                    );
  406:     return %sslnames;
  407: }
  408: 
  409: sub get_ssldesc {
  410:     my %ssldesc = (
  411:                     key      => 'Private Key',
  412:                     host     => 'Connections Certificate',
  413:                     hostname => 'Replication Certificate',
  414:                     ca       => 'LON-CAPA CA Certificate',
  415:                   );
  416:     return %ssldesc;
  417: }
  418: 
  419: sub get_cert_status {
  420:     my ($lonHostID,$hostname,$perlvarstatic) = @_;
  421:     my $currcerts = &LONCAPA::SSL::print_certstatus({$lonHostID => $hostname,},'text','cgi');
  422:     my ($lonkeystatus,$lonhostcertstatus,$lonhostnamecertstatus,%sslstatus);
  423:     my $output = '';
  424:     if ($currcerts eq "$lonHostID:error") {
  425:         $output .= "No information available for SSL certificates\n";
  426:         $sslstatus{'key'} = -1;
  427:         $sslstatus{'host'} = -1;
  428:         $sslstatus{'hostname'} = -1;
  429:         $sslstatus{'ca'} = -1;
  430:         $lonkeystatus = 'unknown status';
  431:         $lonhostcertstatus = 'unknown status';
  432:         $lonhostnamecertstatus = 'unknown status';
  433:     } else {
  434:         my %sslnames = &get_sslnames();
  435:         my %ssldesc = &get_ssldesc();
  436:         my %csr;
  437:         my ($lonhost,$info) = split(/\:/,$currcerts,2);
  438:         if ($lonhost eq $lonHostID) {
  439:             my @items = split(/\&/,$info);
  440:             foreach my $item (@items) {
  441:                 my ($key,$value) = split(/=/,$item,2);
  442:                 if ($key =~ /^(host(?:|name))\-csr$/) {
  443:                     $csr{$1} = $value;
  444:                 }
  445:                 my @data = split(/,/,$value);
  446:                 if (grep(/^\Q$key\E$/,keys(%sslnames))) {
  447:                     my ($checkcsr,$comparecsr);
  448:                     if (lc($data[0]) eq 'yes') {
  449:                         $output .= "$ssldesc{$key} ".$perlvarstatic->{$sslnames{$key}}." available with status = $data[1]\n";
  450:                         if ($key eq 'key') {
  451:                             $lonkeystatus = "status: $data[1]";
  452:                             if ($data[1] =~ /ok$/) {
  453:                                 $sslstatus{$key} = 1;
  454:                             }
  455:                         } else {
  456:                             my $setstatus;
  457:                             if (($key eq 'host') || ($key eq 'hostname')) {
  458:                                 if ($data[1] eq 'otherkey') {
  459:                                     $sslstatus{$key} = 4;
  460:                                     $setstatus = 1;
  461:                                     if ($key eq 'host') {
  462:                                         $lonhostcertstatus = "status: created with different key";
  463:                                     } elsif ($key eq 'hostname') {
  464:                                         $lonhostnamecertstatus = "status: created with different key";
  465:                                     }
  466:                                 } elsif ($data[1] eq 'nokey') {
  467:                                     $sslstatus{$key} = 5;
  468:                                     $setstatus = 1;
  469:                                     if ($key eq 'host') {
  470:                                         $lonhostcertstatus = "status: created with missing key";
  471:                                     } elsif ($key eq 'hostname') {
  472:                                         $lonhostnamecertstatus = "status: created with missing key";
  473:                                     }
  474:                                 }
  475:                                 if ($setstatus) {
  476:                                     $comparecsr = 1;
  477:                                 }
  478:                             }
  479:                             unless ($setstatus) {
  480:                                 if ($data[1] eq 'expired') {
  481:                                     $sslstatus{$key} = 2;
  482:                                     if (($key eq 'host') || ($key eq 'hostname')) {
  483:                                         $comparecsr = 1;
  484:                                     }
  485:                                 } elsif ($data[1] eq 'future') {
  486:                                     $sslstatus{$key} = 3;
  487:                                     $sslstatus{$key} = 3;
  488:                                 } else {
  489:                                     $sslstatus{$key} = 1;
  490:                                 }
  491:                                 if ($key eq 'host') {
  492:                                     $lonhostcertstatus = "status: $data[1]";
  493:                                 } elsif ($key eq 'hostname') {
  494:                                     $lonhostnamecertstatus = "status: $data[1]";
  495:                                 }
  496:                             }
  497:                         }
  498:                     } else {
  499:                         $sslstatus{$key} = 0;
  500:                         $output .= "$ssldesc{$key} ".$perlvarstatic->{$sslnames{$key}}." not available\n";
  501:                         if ($key eq 'key') {
  502:                             $lonkeystatus = 'still needed';
  503:                         } elsif (($key eq 'host') || ($key eq 'hostname')) {
  504:                             $checkcsr = 1;
  505:                         }
  506:                     }
  507:                     if (($checkcsr) || ($comparecsr)) {
  508:                         my $csrfile = $perlvarstatic->{$sslnames{$key}};
  509:                         $csrfile =~s /\.pem$/.csr/;
  510:                         my $csrstatus;
  511:                         if (-e $perlvarstatic->{'lonCertificateDirectory'}."/$csrfile") {
  512:                             if (open(PIPE,"openssl req -text -noout -verify -in ".$perlvarstatic->{'lonCertificateDirectory'}."/$csrfile 2>&1 |")) {
  513:                                 while(<PIPE>) {
  514:                                     chomp();
  515:                                     $csrstatus = $_;
  516:                                     last;
  517:                                 }
  518:                                 close(PIPE);
  519:                                 if ($comparecsr) {
  520:                                     my $csrhash;
  521:                                     if (open(PIPE,"openssl x509 -in certificate.crt -pubkey -noout -outform pem | sha256sum")) {
  522:                                         $csrhash = <PIPE>;
  523:                                         close(PIPE);
  524:                                     }
  525:                                 }
  526:                             }
  527:                             $output .= "Certificate signing request for $ssldesc{$key} available with status = $csrstatus\n\n";
  528:                             if ($key eq 'host') {
  529:                                 $lonhostcertstatus = 'awaiting signature';
  530:                             } else {
  531:                                 $lonhostnamecertstatus = 'awaiting signature';
  532:                             }
  533:                             $sslstatus{$key} = 3;
  534:                         } elsif ($checkcsr) {
  535:                             $output .= "No certificate signing request available for $ssldesc{$key}\n\n";
  536:                             if ($key eq 'host') {
  537:                                 $lonhostcertstatus = 'still needed';
  538:                             } else {
  539:                                 $lonhostnamecertstatus = 'still needed';
  540:                             }
  541:                         }
  542:                     }
  543:                 }
  544:             }
  545: # FIXME If different key, or missing key, or expired, check if there is a csr that does not match the cert, and that may be awaiting signature.
  546:         }
  547:     }
  548:     return ($output,$lonkeystatus,$lonhostcertstatus,$lonhostnamecertstatus,\%sslstatus);
  549: }
  550: 
  551: sub get_new_sslkeypass {
  552:    my $sslkeypass;
  553:     my $flag=0;
  554: # get Password for SSL key
  555:     while (!$flag) {
  556:         $sslkeypass = &make_passphrase();
  557:         if ($sslkeypass) {
  558:             $flag = 1;
  559:         } else {
  560:             print "Invalid input (a password is required for the SSL key).\n";
  561:         }
  562:     }
  563:     return $sslkeypass;
  564: }
  565: 
  566: sub make_passphrase {
  567:     my ($got_passwd,$firstpass,$secondpass,$passwd);
  568:     my $maxtries = 10;
  569:     my $trial = 0;
  570:     while ((!$got_passwd) && ($trial < $maxtries)) {
  571:         $firstpass = &get_password('Enter a password for the SSL key (at least 6 characters long)');
  572:         if (length($firstpass) < 6) {
  573:             print('Password too short.'."\n".
  574:               'Please choose a password with at least six characters.'."\n".
  575:               'Please try again.'."\n");
  576:         } elsif (length($firstpass) > 30) {
  577:             print('Password too long.'."\n".
  578:                   'Please choose a password with no more than thirty characters.'."\n".
  579:                   'Please try again.'."\n");
  580:         } else {
  581:             my $pbad=0;
  582:             foreach (split(//,$firstpass)) {if ((ord($_)<32)||(ord($_)>126)){$pbad=1;}}
  583:             if ($pbad) {
  584:                 print('Password contains invalid characters.'."\n".
  585:                       'Password must consist of standard ASCII characters.'."\n".
  586:                       'Please try again.'."\n");
  587:             } else {
  588:                 $secondpass = &get_password('Enter password a second time');
  589:                 if ($firstpass eq $secondpass) {
  590:                     $got_passwd = 1;
  591:                     $passwd = $firstpass;
  592:                 } else {
  593:                     print('Passwords did not match.'."\n".
  594:                           'Please try again.'."\n");
  595:                 }
  596:             }
  597:         }
  598:         $trial ++;
  599:     }
  600:     return $passwd;
  601: }
  602: 
  603: sub get_password {
  604:     my ($prompt) = @_;
  605:     local $| = 1;
  606:     print $prompt.': ';
  607:     my $newpasswd = '';
  608:     ReadMode 'raw';
  609:     my $key;
  610:     while(ord($key = ReadKey(0)) != 10) {
  611:         if(ord($key) == 127 || ord($key) == 8) {
  612:             chop($newpasswd);
  613:             print "\b \b";
  614:         } elsif(!ord($key) < 32) {
  615:             $newpasswd .= $key;
  616:             print '*';
  617:         }
  618:     }
  619:     ReadMode 'normal';
  620:     print "\n";
  621:     return $newpasswd;
  622: }
  623: 
  624: sub send_mail {
  625:     my ($hostname,$recipient,$subj,$file) = @_;
  626:     my $from = 'www@'.$hostname;
  627:     my $certmail = "To: $recipient\n".
  628:                    "From: $from\n".
  629:                    "Subject: ".$subj."\n".
  630:                    "Content-type: text/plain\; charset=UTF-8\n".
  631:                    "MIME-Version: 1.0\n\n";
  632:     if (open(my $fh,"<$file")) {
  633:         while (<$fh>) {
  634:             $certmail .= $_;
  635:         }
  636:         close($fh);
  637:         $certmail .= "\n\n";
  638:         if (open(my $mailh, "|/usr/lib/sendmail -oi -t -odb")) {
  639:             print $mailh $certmail;
  640:             close($mailh);
  641:             print "Mail sent ($subj) to $recipient\n";
  642:         } else {
  643:             print "Sending mail ($subj) to $recipient failed.\n";
  644:         }
  645:     }
  646:     return;
  647: }
  648: 
  649: sub mail_csr {
  650:     my ($types,$lonCluster,$lonHostID,$hostname,$certsdir,$connectcsr,$replicatecsr,$perlvarref) = @_;
  651:     my ($camail,$flag);
  652:     if ($lonCluster eq 'production' || $lonCluster eq 'development') {
  653:         $camail = $perlvarref->{'SSLEmail'};
  654:     } else {
  655:         $flag=0;
  656: # get Certificate Authority E-mail
  657:         while (!$flag) {
  658:             print(<<END);
  659: 
  660: ENTER EMAIL ADDRESS TO SEND CERTIFICATE SIGNING REQUESTS
  661: END
  662: 
  663:             my $choice=<>;
  664:             chomp($choice);
  665:             if ($choice ne '') {
  666:                 open(OUT,'>>/tmp/loncapa_updatequery.out');
  667:                 print(OUT 'Certificate Authority Email Address'."\t".$choice."\n");
  668:                 close(OUT);
  669:                 $camail=$choice;
  670:                 $flag=1;
  671:             } else {
  672:                 print "Invalid input (an email address is required).\n";
  673:             }
  674:         }
  675:     }
  676:     if ($camail) {
  677:         my $subj;
  678:         if (($types eq 'both') || ($types = 'host')) {
  679:             if (-e "$certsdir/$connectcsr") {
  680:                 $subj = "Certificate Request ($lonHostID)";
  681:                 print(&send_mail($hostname,$camail,$subj,"$certsdir/$connectcsr"));
  682:             }
  683:         }
  684:         if (($types eq 'both') || ($types = 'hostname')) {
  685:             if (-e "$certsdir/$replicatecsr") {
  686:                 $subj = "Certificate Request (internal-$hostname)";
  687:                 print(&send_mail($hostname,$camail,$subj,"$certsdir/$replicatecsr"));
  688:             }
  689:         }
  690:     }
  691: }
  692: 
  693: sub ssl_info {
  694:     print(<<END);
  695: 
  696: ****** Information about Country, State or Province and City *****
  697: 
  698: A two-letter country code, e.g., US, CA, DE etc. as defined by ISO 3166,
  699: is required. A state or province, and a city are also required.
  700: This locality information is included in two SSL certificates used internally
  701: by LON-CAPA, unless you are running standalone.
  702: 
  703: If your server will be part of either the production or development
  704: clusters, then the certificate will need to be signed by the official
  705: LON-CAPA Certificate Authority (CA).  If you will be running your own
  706: cluster then the cluster will need to create its own CA.
  707: 
  708: END
  709: }
  710: 
  711: sub get_country {
  712:     my ($hostname) = @_;
  713: # get Country
  714:     my ($posscountry,$country);
  715:     if ($hostname =~ /\.(edu|com|org)$/) {
  716:         $posscountry = 'us';
  717:     } else {
  718:         ($posscountry) = ($hostname =~ /\.(a-z){2}$/);
  719:     }
  720:     if ($posscountry) {
  721:         my $countrydesc = &Locale::Country::code2country($posscountry);
  722:         if ($countrydesc eq '') {
  723:             undef($posscountry);
  724:         }
  725:     }
  726: 
  727:     my $flag=0;
  728:     while (!$flag) {
  729:         if ($posscountry) {
  730:             $posscountry = uc($posscountry);
  731:             print "ENTER TWO-LETTER COUNTRY CODE [$posscountry]:\n";
  732:         } else {
  733:             print "ENTER TWO-LETTER COUNTRY CODE:\n";
  734:         }
  735:         my $choice=<>;
  736:         chomp($choice);
  737:         if ($choice ne '') {
  738:             if (&Locale::Country::code2country(lc($choice))) {
  739:                 open(OUT,'>>/tmp/loncapa_updatequery.out');
  740:                 print(OUT 'country'."\t".uc($choice)."\n");
  741:                 close(OUT);
  742:                 $country=uc($choice);
  743:                 $flag=1;
  744:             } else {
  745:                 print "Invalid input -- a valid two letter country code is required\n";
  746:             }
  747:         } elsif (($choice eq '') && ($posscountry ne '')) {
  748:             open(OUT,'>>/tmp/loncapa_updatequery.out');
  749:             print(OUT 'country'."\t".$posscountry."\n");
  750:             close(OUT);
  751:             $country = $posscountry;
  752:             $flag = 1;
  753:         } else {
  754:             print "Invalid input -- a country code is required\n";
  755:         }
  756:     }
  757:     return $country;
  758: }
  759: 
  760: sub get_state {
  761: # get State or Province
  762:     my $flag=0;
  763:     my $state = '';
  764:     while (!$flag) {
  765:         print(<<END);
  766: 
  767: ENTER STATE OR PROVINCE NAME:
  768: END
  769: 
  770:         my $choice=<>;
  771:         chomp($choice);
  772:         if ($choice ne '') {
  773:             open(OUT,'>>/tmp/loncapa_updatequery.out');
  774:             print(OUT 'state'."\t".$choice."\n");
  775:             close(OUT);
  776:             $state=$choice;
  777:             $flag=1;
  778:         } else {
  779:             print "Invalid input (a state or province name is required).\n";
  780:         }
  781:     }
  782:     return $state;
  783: }
  784: 
  785: sub get_city {
  786: # get City
  787:     my $flag=0;
  788:     my $city = '';
  789:     while (!$flag) {
  790:         print(<<END);
  791: 
  792: ENTER CITY NAME:
  793: END
  794: 
  795:         my $choice=<>;
  796:         chomp($choice);
  797:         if ($choice ne '') {
  798:             open(OUT,'>>/tmp/loncapa_updatequery.out');
  799:             print(OUT 'city'."\t".$choice."\n");
  800:             close(OUT);
  801:             $city=$choice;
  802:             $flag=1;
  803:         } else {
  804:             print "Invalid input (a city is required).\n";
  805:         }
  806:     }
  807:     return $city;
  808: }
  809: 
  810: sub confirm_locality {
  811:     my ($domainDescription,$country,$state,$city) = @_;
  812:     my $flag = 0;
  813:     while (!$flag) {
  814:         print(<<END);
  815: 
  816: The domain description, country, state and city will be
  817: used in the SSL certificates
  818: 
  819: 1) Domain Description: $domainDescription
  820: 2) Country: $country
  821: 3) State or Province: $state
  822: 4) City: $city
  823: 5) Everything is correct up above
  824: 
  825: ENTER A CHOICE OF 1-4 TO CHANGE, otherwise ENTER 5:
  826: END
  827:         my $choice=<>;
  828:         chomp($choice);
  829:         if ($choice == 1) {
  830:             print(<<END);
  831: 1) Domain Description: $domainDescription
  832: ENTER NEW VALUE
  833: END
  834:             my $choice2=<>;
  835:             chomp($choice2);
  836:             $domainDescription=$choice2;
  837:         } elsif ($choice == 2) {
  838:             print(<<END);
  839: 2) Country: $country
  840: ENTER NEW VALUE (this should be a two-character code, e,g, US, CA, DE)
  841: END
  842:             my $choice2=<>;
  843:             chomp($choice2);
  844:             $country = uc($choice2);
  845:         } elsif ($choice == 3) {
  846:             print(<<END);
  847: 3) State or Province: $state
  848: ENTER NEW VALUE:
  849: END
  850:             my $choice2=<>;
  851:             chomp($choice2);
  852:             $state=$choice2;
  853:         } elsif ($choice == 4) {
  854:             print(<<END);
  855: 4) City: $city
  856: ENTER NEW VALUE:
  857: END
  858:             my $choice2=<>;
  859:             chomp($choice2);
  860:             $city=$choice2;
  861:         } elsif ($choice == 5) {
  862:             $flag=1;
  863:             $state =~ s{/}{ }g;
  864:             $city =~ s{/}{ }g;
  865:             $domainDescription =~ s{/}{ }g;
  866:         } else {
  867:             print "Invalid input.\n";
  868:         }
  869:     }
  870:     return ($domainDescription,$country,$state,$city);
  871: }
  872: 
  873: sub make_key {
  874:     my ($certsdir,$privkey,$sslkeypass) = @_;
  875: # generate SSL key
  876:     if ($certsdir && $privkey) {
  877:         if (-f "$certsdir/lonKey.enc") {
  878:             my $mode = 0600;
  879:             chmod $mode, "$certsdir/lonKey.enc";
  880:         }
  881:         open(PIPE,"openssl genrsa -des3 -passout pass:$sslkeypass -out $certsdir/lonKey.enc 2048 2>&1 |");
  882:         close(PIPE);
  883:         if (-f "$certsdir/$privkey") {
  884:             my $mode = 0600;
  885:             chmod $mode, "$certsdir/$privkey";
  886:         }
  887:         open(PIPE,"openssl rsa -in $certsdir/lonKey.enc -passin pass:$sslkeypass -out $certsdir/$privkey -outform PEM |");
  888:         close(PIPE);
  889:         if (-f "$certsdir/lonKey.enc") {
  890:             my $mode = 0400;
  891:             chmod $mode, "$certsdir/lonKey.enc";
  892:         }
  893:         if (-f "$certsdir/$privkey") {
  894:             my $mode = 0400;
  895:             chmod $mode, "$certsdir/$privkey";
  896:         }
  897:     } else {
  898:         print "Key creation failed.  Missing one or more of: certificates directory, key name\n";
  899:     }
  900: }
  901: 
  902: sub encrypt_key {
  903:     my ($certsdir,$privkey,$sslkeypass) = @_;
  904:     if ($certsdir && $privkey) {
  905:         if ((-f "$certsdir/$privkey") && (!-f "$certsdir/lonKey.enc")) {
  906:             open(PIPE,"openssl rsa -des3 -in $certsdir/$privkey -out $certsdir/lonKey.enc |");
  907:         }
  908:     }
  909:     return;
  910: }
  911: 
  912: sub make_host_csr {
  913:     my ($certsdir,$sslkeypass,$connectcsr,$connectsubj) = @_;
  914: # generate SSL csr for hostID
  915:     if ($certsdir && $connectcsr && $connectsubj) {
  916:         open(PIPE,"openssl req -key $certsdir/lonKey.enc -passin pass:$sslkeypass -new -batch -subj \"$connectsubj\" -out $certsdir/$connectcsr |");
  917:         close(PIPE);
  918:     } else {
  919:         print "Creation of certificate signing request failed.  Missing one or more of: certificates directory, CSR name, or locality information.\n";
  920:     }
  921: }
  922: 
  923: sub make_hostname_csr {
  924:     my ($certsdir,$sslkeypass,$replicatecsr,$replicatesubj) = @_;
  925: # generate SSL csr for internal hostname
  926:     if ($certsdir && $replicatecsr && $replicatesubj) {
  927:         open(PIPE,"openssl req -key $certsdir/lonKey.enc -passin pass:$sslkeypass -new -batch -subj \"$replicatesubj\" -out $certsdir/$replicatecsr |");
  928:         close(PIPE);
  929:     } else {
  930:         print "Creation of certificate signing request failed.  Missing one or more of: certificates directory, CSR name, or locality information.\n";
  931:     }
  932: }
  933: 
  934: sub get_mail {
  935:     my $email;
  936:     my $flag=0;
  937: # get E-mail Address 
  938:     while (!$flag) {
  939:         print(<<END);
  940: 
  941: An e-mail address to be included with certificate signing requests is needed.
  942: After signing by the Certificate Authority, the signed certificate(s) will
  943: be returned to this e-mail address.
  944: ENTER E-MAIL ADDRESS
  945: END
  946:         my $choice=<>;
  947:         chomp($choice);
  948:         if (($choice ne '') && ($choice =~ /^[^\@]+\@[^\@]+$/)) {
  949:             $email=$choice;
  950:             $flag=1;
  951:         } else {
  952:             print "Invalid input (a valid email address is required).\n";
  953:         }
  954:     }
  955:     return $email;
  956: }
  957: 
  958: 

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