File:  [LON-CAPA] / loncom / CrGrant.pl
Revision 1.4: download - view: text, annotated - select for diffs
Fri Jul 9 09:11:48 2004 UTC (19 years, 8 months ago) by foxr
Branches: MAIN
CVS tags: HEAD
Complete debugging of certificate granting script.

    1: #!/usr/bin/perl
    2: # The LearningOnline Network
    3: # CrGrant.pl  - Grant a loncapa SSL certificate.
    4: #
    5: # $Id: CrGrant.pl,v 1.4 2004/07/09 09:11:48 foxr Exp $
    6: #
    7: # Copyright Michigan State University Board of Trustees
    8: #
    9: # This file is part of the LearningOnline Network with CAPA (LON-CAPA).
   10: #
   11: # LON-CAPA is free software; you can redistribute it and/or modify
   12: # it under the terms of the GNU General Public License as published by
   13: # the Free Software Foundation; either version 2 of the License, or 
   14: # (at your option) any later version.
   15: #
   16: # LON-CAPA is distributed in the hope that it will be useful,
   17: # but WITHOUT ANY WARRANTY; without even the implied warranty of
   18: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   19: # GNU General Public License for more details.
   20: #
   21: # You should have received a copy of the GNU General Public License
   22: # along with LON-CAPA; if not, write to the Free Software
   23: # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   24: #
   25: # /home/httpd/html/adm/gpl.txt
   26: #
   27: 
   28: 
   29: # http://www.lon-capa.org/
   30: #
   31: # This script operates on a certificate request that has been
   32: # extracted from the attachment sent to the loncapa certificate 
   33: # administrator and:
   34: #
   35: #  1. Creates an ssl certificate corresponding to the request.
   36: #  2. Constructs an installation script that will install
   37: #     the certificate along with the certificate authority's
   38: #     certificate in a loncapa system.
   39: #  3. Constructs an email which contains a cover letter 
   40: #     describing what to do with the attachment, and an
   41: #     attachment that consists of the installation script
   42: #     created in step 2.
   43: #  4. Emails the message to the email address in the certificate
   44: #     request.
   45: #
   46: #  There are some assumptions we need to make in order to
   47: #  get this all to work:
   48: #    - The certificate authority is installed on a 
   49: #      loncapa system with configuration files that specify
   50: #      the same certificate directory and certificate filenames
   51: #      as the target system (otherwise we can't generate the
   52: #      installation script).
   53: #    - The loncapa certificate authority configuration file is
   54: #      $SSLDir/loncapaca.cnf and that it specifies that:
   55: #      o The certificate authority files are in $SSLDir/loncapaca
   56: #      o The certificate authority certificate is in:
   57: #         $SSLDir/loncapaca/cacert.pem
   58: #      o Only one instance of this script will be run at a time in
   59: #        this directory.
   60: #      o The person that runs this script knows the passphrase
   61: #        for the loncapa certificate authority's private key
   62: #        which remains encrypted for security reasons.
   63: #
   64: #
   65: 
   66: # Import section:
   67: 
   68: use strict;
   69: use lib '/home/httpd/lib/perl';	# An assumption!!!
   70: use MIME::Entity;
   71: use LONCAPA::Configuration;
   72: 
   73: 
   74: 
   75: # Global variable declarations
   76: 
   77: 
   78: my $ssl_dir       = "/usr/share/ssl";    # Where ssl config files etc. live
   79: my $ca_cert_file  = $ssl_dir."/loncapaca/cacert.pem"; # CA's certificate file.
   80: my $ca_config_file= $ssl_dir."/loncapaca.cnf";      # CA's config file. 
   81: 
   82: 
   83: #   LONCAPA Configuration global variables:
   84: 
   85: # Items read from our configuration file.
   86: 
   87: my $ssl_command   = "/usr/bin/openssl "; # Command to run openssl.
   88: my $loncapa_cert_dir;		# Name of target cert dir (from config)
   89: my $loncapa_hostcert_name;	# Name of host's signed cert file (config)
   90: my $loncapa_cacert_name;        # Name of the CA's certificate file (config)
   91: my $return_address;		# Email return address.
   92: 
   93: #  Items I just need to know:
   94: 
   95: my $loncapa_config = "loncapa.conf";   # User's override config file.
   96: my $loncapa_apache_user = 'www';	# Name of apache daemon's user
   97: my $loncapa_apache_group = 'www';	# Name of apache daemon's group
   98: 
   99: 
  100: 
  101: # Debug/log support
  102: 
  103: my $DEBUG=0;
  104: 
  105: sub Debug {
  106:     my $msg = shift;
  107:     if($DEBUG) {
  108: 	print STDERR "$msg\n";
  109:     }
  110: }
  111: #  Support subs:
  112: 
  113: #
  114: #   Print out program usage.
  115: #
  116: # Side effects:
  117: #    Output goes to stderr.
  118: #
  119: sub Usage {
  120:     print STDERR << "USAGE";
  121: 
  122: Usage:
  123:    CrGrant.pl requestfile.pem
  124: 
  125: Where:
  126:    requestfile.pem is a PEM formatted certificate extracted from an email 
  127:                    to the LonCAPA certificate manager.
  128: USAGE
  129: 
  130: }
  131: #
  132: #  Read the loncapa configuration file and pull out the items
  133: #  we need:
  134: #
  135: # Implicit inputs:
  136: #   $loncapa_config   - The name of the auxilliary config file.
  137: # Side effects:
  138: #    - On failure exits with an error message.
  139: #    - On success set the following variables:
  140: #      o loncapa_cert_dir      - Path to certificates.
  141: #      o loncapa_hostcert_name - Name of host's cert file in that dir
  142: #      o loncapa_cacert_name   - Name of CA's cert file in that dir.
  143: #      o ssl_command           - Name of ssl utility command.
  144: sub ReadConfig {
  145:     Debug("Reading the config files");
  146:     my $perlvarref = LONCAPA::Configuration::read_conf($loncapa_config);
  147: 
  148:     #  Pull out the individual variables or die:
  149: 
  150:     # SSL Command:
  151: 
  152:     if($perlvarref->{SSLProgram}) {
  153: 	$ssl_command = $perlvarref->{SSLProgram};
  154: 	Debug("SSL utility program is $ssl_command");
  155:     } 
  156:     else {
  157: 	die "LonCAPA configuration errror: Can't read SSLProgram variable";
  158:     }
  159:     # Certificate directory:
  160:    
  161:     if($perlvarref->{lonCertificateDirectory}) {
  162: 	$loncapa_cert_dir = $perlvarref->{lonCertificateDirectory};
  163: 	Debug("Certificates will be installed in $loncapa_cert_dir");
  164:     } 
  165:     else {
  166: 	die "LonCAPA configuration error can't read lonCertificateDirectory variable";
  167: 
  168:     }
  169:     #  Get the name of the host's certificate:
  170: 
  171:     if($perlvarref->{lonnetCertificate}) {
  172: 	$loncapa_hostcert_name = $perlvarref->{lonnetCertificate};
  173: 	Debug("Host's certificate will be $loncapa_hostcert_name");
  174:     }
  175:     else {
  176: 	die "LonCAPA configuration error: Can't read lonnetCertificate variable";
  177:     }
  178:     #   Get the name of the certificate authority's certificate.
  179: 
  180:     if($perlvarref->{lonnetCertificateAuthority}) {
  181: 	$loncapa_cacert_name = $perlvarref->{lonnetCertificateAuthority};
  182: 	Debug("CA's certificate will be $loncapa_cacert_name");
  183:     }
  184:     else {
  185: 	die "LonCAPA configuration error: Can't read lonnetCertificateAuthority variable";
  186:     }
  187:     #  Get the email address of the certificate manager:
  188:     #  this is the email return address:
  189: 
  190:     if($perlvarref->{SSLEmail}) {
  191: 	$return_address = $perlvarref->{SSLEmail};
  192: 	Debug("Return address will be $return_address");
  193:     }
  194:     else {
  195: 	die "LonCAPA configuration error can't read SSLEmail configuration item";
  196:     }
  197: 
  198: }
  199: 
  200: #  Create a certificate from the request file.  The certificate
  201: #  is used, in conjunction with the openssl command with the 
  202: #  certificate authority configuration to produce a certificate
  203: #  file.
  204: #
  205: #  The certificate is parsed to determine the email address
  206: #  of the requestor, which is returned to the caller.
  207: #
  208: #Parameters:
  209: #     request_file   - Name of the file containing the certificate request.
  210: #Returns:
  211: #     If the request file exists and is able to produce a certificate
  212: #     the email address of the requester is returned to the caller.
  213: #     If not, undef is returned.
  214: #
  215: sub CreateCertificate {
  216:     my ($request_file) = @_;
  217: 
  218:     Debug("CreateCertificate");
  219: 
  220:     if(!(-e $request_file)) {
  221: 	Debug("Certificate file $request_file does not exist");
  222: 	return undef;
  223:     }
  224:     Debug("Certificate file $request_file exists");
  225: 
  226:     # Create the certificate:  The status of the openssl command
  227:     # is used to determine if the certificate succeeded:
  228: 
  229:     my $create_command = $ssl_command." ca -config ".$ca_config_file
  230: 	                             ." -in ".$request_file
  231: 				     ." -out hostCertificate.pem";
  232:     my $status = system($create_command);
  233:     if($status) {
  234: 	Debug("openssl ca failed");
  235: 	print STDERR "Certificate generation failed... probably bad";
  236: 	print STDERR " request file!\n";
  237: 	return undef;
  238:     }
  239:     Debug("openssl ca succeeded");
  240: 
  241:     #  Now we have a shining new signed certificate in ./hostCertificate.pem
  242:     #  we parse it to get the email address to which the certificate should
  243:     #  be emailed.
  244:     #   The certificate's return email address will be in the Subject line:
  245:     #
  246: 
  247:     Debug("Parsing certificate file for Subject:");
  248:     open CERTIFICATE, "<hostCertificate.pem";
  249:     my $line;
  250:     my $subject_found = 0;
  251:     while ($line = <CERTIFICATE>) {
  252: 	Debug("Line = $line");
  253: 	if($line =~ /Subject:/) {
  254: 	    Debug("Found Subject: in $line");
  255: 	    $subject_found =1;
  256: 	    last;
  257: 	}
  258:     }
  259:     close CERTIFICATE;
  260: 
  261:     if(!$subject_found) {
  262: 	Debug("Did not find Subject line in cert");
  263: 	print STDERR "Output certificate parse failed: no Subject:\n";
  264: 	return undef;
  265:     }
  266:     #  The subject line contains an Email= string amidst the other stuff.
  267:     #  First break in to comma separated stuff, then locate the piece that
  268:     #  contains /Email=
  269: 
  270:     my @subject_fields = split(/,/, $line);
  271:     my $email_found = 0;
  272:     my $element;
  273:     my $email_element;
  274:     Debug("Parsing subject line for Email=");
  275:     foreach $element (@subject_fields) {
  276: 	$email_element = $element;
  277: 	Debug("Parsing $element");
  278: 	if($element =~ /\/Email=/) {
  279: 	    Debug("Found /Email=");
  280: 	    $email_found = 1;
  281: 	    last;
  282: 	}
  283:     }
  284:     if(!$email_found) {
  285: 	Debug("Failed to fine Email=");
  286: 	print STDERR "Unable to find line with /Email= in cert. Subject\n";
  287: 	return undef;
  288:     }
  289: 
  290:     #  The piece we found must first be split at the /
  291:     #  to isolate the Email= part and then that part at the = to isolate
  292:     #  the address:
  293: 
  294:     Debug("Splitting $email_element at /");
  295:     my ($junk, $email) = split(/\//, $email_element);
  296:     Debug("Email part is $email");
  297:     my ($junk, $address) = split(/=/, $email);
  298:     Debug("CreateCertificate Returning $address to caller");
  299: 
  300:     return $address;
  301: 
  302: }
  303: #
  304: #   Create the installation script.  This will be  bash script
  305: #   that will install the certifiate and the CA's certificate with ownership
  306: #   WebUser:WebGroup and permissions 0400.  I thought about using a perl
  307: #   script in order to be able to get the certificate file/directory from
  308: #   the configuration files.  Unfortunately this is not as easy as it looks.
  309: #   Root has a chicken and egg problem.  In order to read the config file
  310: #   you need to have added the ..../lib/perl to the perl lib path. To do
  311: #   that correctly, you need to have read the config file to know where
  312: #   it is...What we will do is read our local configuration file and
  313: #   assume that our configuration is the same as the target's system in
  314: #   all respects we care about.
  315: # Implicit Inputs:
  316: #    - Bash is in /bin/bash
  317: #    - $loncapa_cert_dir             -  install target directory.
  318: #    - $loncapa_hostcert_name        -  Name of installed host cert file.
  319: #    - $loncapa_cacert_name          -  Name of installed ca cert file.
  320: #    - $loncapa_apache_user          -  username under which httpd runs.
  321: #    - $loncapa_apache_group         -  group under which httpd runs.
  322: #    - 0400                          -  install permissions.
  323: #    - The host's certificate is now in ./hostCertificate.pem
  324: #    - The CA's certificate is now in  $ca_cert_file
  325: #
  326: # Implicit Outputs:
  327: #    A file named CertInstall.sh
  328: # Return
  329: #    Name of the file we created.
  330: #
  331: sub CreateInstallScript {
  332:     open INSTALLER,">CertInstall.sh";
  333:     print INSTALLER <<BASH_HEADER;
  334: #!/bin/bash
  335: #
  336: #    Installer for your lonCAPA certificates.  Please check the
  337: #    configuration variables to be sure they match your installation.
  338: #    Then run this script under a root shell to complete the 
  339: #    installation of the certificates.
  340: #
  341: # Configuration Variables:
  342: CERTDIR="$loncapa_cert_dir"        # Directory with your host key.
  343: HOSTCERT="$loncapa_hostcert_name"   # Name of host's certificate file.
  344: CACERT="$loncapa_cacert_name"     # Name of certifiate authority file.
  345: HTTPDUID="$loncapa_apache_user"     # UID of httpd.
  346: HTTPDGID="$loncapa_apache_group"    # GID of httpd.
  347: 
  348: #   End of configuration variables.
  349: 
  350: MODE=0444                           # certificates get this mode.
  351: HOSTCERTPATH="\$CERTDIR/\$HOSTCERT"
  352: CACERTPATH="\$CERTDIR/\$CACERT"
  353: 
  354: #  Create the host certificate file to install:
  355: 
  356: echo unpacking host certificate
  357: 
  358: cat <<-HOSTCERTTEXT   >\$HOSTCERT
  359: BASH_HEADER
  360: 
  361:     #   Now copy the host certificate into the script:
  362: 
  363:     open HOSTCERT, "<hostCertificate.pem";
  364:     while(my $line = <HOSTCERT>) {
  365: 	print INSTALLER $line;	# Line presumably has a \n.
  366:     }
  367:     close HOSTCERT;
  368: 
  369:     #  Close the here doc, and start up the cat of the ca cert:
  370: 
  371:     print INSTALLER "HOSTCERTTEXT\n";
  372:     print INSTALLER "echo unpacking CA certificate\n";
  373:     print INSTALLER "cat <<-CACERTTEXT >\$CACERT\n";
  374:     open  CACERT, "<$ca_cert_file";
  375:     while(my $line = <CACERT>) {
  376: 	print INSTALLER $line;
  377:     }
  378:     close CACERT;
  379:     print INSTALLER "CACERTTEXT\n";
  380: 
  381:     #  Ok, the script can create the two files, now it must install
  382:     # install them >and< clean up after itself.
  383: 
  384:     print INSTALLER <<BASH_TRAILER;
  385: 
  386: echo Installing certificates
  387: 
  388: install -m \$MODE -o \$HTTPDUID -g \$HTTPDGID \$CACERT \$CACERTPATH
  389: install -m \$MODE -o \$HTTPDUID -g \$HTTPDGID \$HOSTCERT \$HOSTCERTPATH
  390: 
  391: echo done
  392: 
  393: rm -f \$CACERT
  394: rm -f \$HOSTCERT
  395: 
  396: #    Do they want to restart loncapa:
  397: #
  398: 
  399: echo In order to start running in secure mode you will need to start
  400: echo lonCAPA.  If you want I can do that now for you.  Otherwise,
  401: echo you will have to do it yourself later either by rebooting your
  402: echo system or by typing:
  403: echo
  404: echo /etc/init.d/loncontrol restart
  405: echo
  406: read -p "Restart loncapa now [yN]?"  yesno
  407: 
  408: if [ "\${yesno:0:1}" = "Y" -o "\${yesno:0:1}"  = "y" ] 
  409: then
  410:    /etc/init.d/loncontrol restart
  411: fi
  412: BASH_TRAILER
  413: 
  414:     close INSTALLER;
  415: 
  416:     return "CertInstall.sh";
  417: }
  418: #
  419: #    Create a mime Email that consists of a cover letter of installation
  420: #    instructions and an attachment that is the installation script.
  421: # Parameters:
  422: #     script    - The name of the script that will be attached
  423: #                 to the email.
  424: #     send_address - Where the mail will be sent.
  425: # Returns:
  426: #     The MIME::Entity handle of the script.
  427: #
  428: sub CreateEmail {
  429:     Debug("Creating Email");
  430:     my ($installer_file, $send_address) = @_;
  431: 
  432:     #  The top level mime entity is the mail headers and the
  433:     #  cover letter:
  434: 
  435:     my $mime_message = MIME::Entity->build(Type    => "multipart/mixed",
  436: 					   From    => $return_address,
  437: 					   To      => $send_address,
  438: 					   Subject =>"LonCAPA certificates");
  439:     if(!$mime_message) {
  440: 	die "Unable to create top level MIME Message";
  441:     }
  442: 
  443:      $mime_message->attach(Data =>["  This email contains your lonCAPA SSL certificates.  These\n",
  444:      "certificates allow your system to interact with the world wide\n",
  445:      "cluster of LonCAPA systems, and allow you to access and share\n",
  446:      "public resources for courses you host.\n\n",
  447:      "   The certificates are shipped as a self installing shell script\n",
  448:      "To install these certificates:\n\n",
  449:      "1. Extract the attachment to this email message\n",
  450:      "2. Save the attachment where it can be recovered in case you need\n",
  451:      "   to re-install these certificates later on for some reason\n",
  452:      "3. As root execute the certificate request file:
  453:            . $installer_file\n",
  454:      "   (Note: If you used a Windows based email program to extract the\n",
  455:      "   this file and then tranferred it to your unix lonCAPA system you \n",
  456:      "   Will probably need to convert the file first e.g.: \n",
  457:      "     dos2unix $installer_file\n",
  458:      "     . $installer_file\n",
  459:      "   The installer file will install the certificates and ask you\n",
  460:      "   if you want to restart the LonCAPA system.  You must restart the\n",
  461:      "   LonCAPA system for it to use the new certificates.\n\n",
  462:      "      Thank you for choosing LonCAPA for your course delivery needs,\n",
  463:      "      The LonCAPA team.\n"]);
  464: 
  465:     Debug("Main message body created");
  466: 
  467: 
  468:     #  Attach the certificate intaller:
  469: 
  470:     $mime_message->attach(Type    => "text/plain",
  471: 			  Path    => $installer_file);
  472:     Debug("Installer attached");
  473: 
  474:     return $mime_message;
  475: 
  476: }
  477: 
  478: #
  479: #   Sends a mime message to an email address.
  480: # Parameters:
  481: #    message   - A MIME::Entity containing the message.
  482: # Implicit inputs:
  483: #   Mail is sent via /usr/lib/sendmail -t -oi -oem"
  484: #   This should work on all systems with a properly configured
  485: #   sendmail or compatible mail transfer agent.
  486: sub SendEmail {
  487:     my ($message) =  @_;
  488: 
  489:     Debug("Mailing");
  490: 
  491:     open MAILPIPE, "| /usr/lib/sendmail -t -oi -oem" or 
  492: 	die "Failed to open pipe to sendmail: $!";
  493: 
  494:     $message->print(\*MAILPIPE);
  495:     Debug("Submitted to sendmail");
  496:     close MAILPIPE;
  497: }
  498: #
  499: #  Cleanup destroys the certificate file and its installer.
  500: #
  501: #
  502: sub Cleanup {
  503:     my ($installer) = @_;
  504:     unlink($installer);
  505:     unlink("hostcertificate.pem");
  506: }
  507: 
  508: 
  509: #  Program entry point
  510: #   The usage is:
  511: #     CrGrant.pl    {request_file}
  512: #
  513: 
  514: my $argc = @ARGV;		# Count number of command parameters.
  515: if($argc != 1) {
  516:     Usage;
  517:     exit -1;
  518: }
  519: my $CertificateRequest = $ARGV[0];
  520: 
  521: &ReadConfig;
  522: 
  523: my $email_address = &CreateCertificate($CertificateRequest);
  524: Debug("CreateCertificate returned: $email_address");
  525: 
  526: if(!defined $email_address) {
  527:     print STDERR "Bad or missing certificate file!!";
  528:     Usage;
  529:     exit -1;
  530: }
  531: 
  532: my $script_name = &CreateInstallScript;
  533: my $Message = &CreateEmail($script_name, $email_address);
  534: &SendEmail($Message);
  535: &Cleanup($script_name);
  536: 
  537: # POD documentation.

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