Annotation of loncom/CrGrant.pl, revision 1.4

1.1       foxr        1: #!/usr/bin/perl
                      2: # The LearningOnline Network
                      3: # CrGrant.pl  - Grant a loncapa SSL certificate.
                      4: #
1.4     ! foxr        5: # $Id: CrGrant.pl,v 1.3 2004/07/06 11:05:45 foxr Exp $
1.1       foxr        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
1.2       foxr       58: #      o Only one instance of this script will be run at a time in
                     59: #        this directory.
1.1       foxr       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;
1.3       foxr       69: use lib '/home/httpd/lib/perl';	# An assumption!!!
1.1       foxr       70: use MIME::Entity;
                     71: use LONCAPA::Configuration;
                     72: 
                     73: 
                     74: 
                     75: # Global variable declarations
                     76: 
1.3       foxr       77: 
1.2       foxr       78: my $ssl_dir       = "/usr/share/ssl";    # Where ssl config files etc. live
1.3       foxr       79: my $ca_cert_file  = $ssl_dir."/loncapaca/cacert.pem"; # CA's certificate file.
1.2       foxr       80: my $ca_config_file= $ssl_dir."/loncapaca.cnf";      # CA's config file. 
1.1       foxr       81: 
1.3       foxr       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)
1.4     ! foxr       91: my $return_address;		# Email return address.
1.3       foxr       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: 
1.1       foxr      100: 
                    101: # Debug/log support
                    102: 
1.4     ! foxr      103: my $DEBUG=0;
1.1       foxr      104: 
                    105: sub Debug {
                    106:     my $msg = shift;
                    107:     if($DEBUG) {
                    108: 	print STDERR "$msg\n";
                    109:     }
                    110: }
                    111: #  Support subs:
                    112: 
1.2       foxr      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
1.1       foxr      124: 
1.2       foxr      125: Where:
                    126:    requestfile.pem is a PEM formatted certificate extracted from an email 
                    127:                    to the LonCAPA certificate manager.
                    128: USAGE
                    129: 
                    130: }
1.3       foxr      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:     }
1.4     ! foxr      187:     #  Get the email address of the certificate manager:
        !           188:     #  this is the email return address:
1.3       foxr      189: 
1.4     ! foxr      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:     }
1.3       foxr      197: 
                    198: }
                    199: 
1.2       foxr      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: #
1.1       foxr      215: sub CreateCertificate {
1.2       foxr      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;
1.1       foxr      301: 
                    302: }
1.3       foxr      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
1.4     ! foxr      328: # Return
        !           329: #    Name of the file we created.
1.3       foxr      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: 
1.4     ! foxr      393: rm -f \$CACERT
        !           394: rm -f \$HOSTCERT
1.3       foxr      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: 
1.4     ! foxr      408: if [ "\${yesno:0:1}" = "Y" -o "\${yesno:0:1}"  = "y" ] 
1.3       foxr      409: then
                    410:    /etc/init.d/loncontrol restart
                    411: fi
                    412: BASH_TRAILER
                    413: 
                    414:     close INSTALLER;
1.4     ! foxr      415: 
        !           416:     return "CertInstall.sh";
1.3       foxr      417: }
1.4     ! foxr      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;
1.1       foxr      475: 
                    476: }
                    477: 
1.4     ! foxr      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.
1.1       foxr      486: sub SendEmail {
1.4     ! foxr      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");
1.1       foxr      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: 
1.3       foxr      521: &ReadConfig;
                    522: 
                    523: my $email_address = &CreateCertificate($CertificateRequest);
                    524: Debug("CreateCertificate returned: $email_address");
1.2       foxr      525: 
                    526: if(!defined $email_address) {
                    527:     print STDERR "Bad or missing certificate file!!";
                    528:     Usage;
                    529:     exit -1;
                    530: }
                    531: 
1.4     ! foxr      532: my $script_name = &CreateInstallScript;
        !           533: my $Message = &CreateEmail($script_name, $email_address);
        !           534: &SendEmail($Message);
        !           535: &Cleanup($script_name);
1.1       foxr      536: 
                    537: # POD documentation.

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