--- loncom/CrGrant.pl 2004/07/02 10:51:18 1.1 +++ loncom/CrGrant.pl 2009/02/17 21:18:07 1.6 @@ -2,7 +2,7 @@ # The LearningOnline Network # CrGrant.pl - Grant a loncapa SSL certificate. # -# $Id: CrGrant.pl,v 1.1 2004/07/02 10:51:18 foxr Exp $ +# $Id: CrGrant.pl,v 1.6 2009/02/17 21:18:07 schafran Exp $ # # Copyright Michigan State University Board of Trustees # @@ -55,14 +55,8 @@ # o The certificate authority files are in $SSLDir/loncapaca # o The certificate authority certificate is in: # $SSLDir/loncapaca/cacert.pem -# o The certificate authority maintains a certificate index file -# $SSLDIR/loncapaca/index.txt -# o Only one instance of this script will be run at a time!!!!! -# (otherwise the last line of the index file may not be the -# index to our certificate. We'll do some rudimentary -# error checking, but have no idea how to recover in case -# of problems). -# o The generated certificates are stored in $SSLDIR/loncapaca/certs +# o Only one instance of this script will be run at a time in +# this directory. # o The person that runs this script knows the passphrase # for the loncapa certificate authority's private key # which remains encrypted for security reasons. @@ -72,7 +66,7 @@ # Import section: use strict; -use lib '/home/httpd/lib/perl'; +use lib '/home/httpd/lib/perl'; # An assumption!!! use MIME::Entity; use LONCAPA::Configuration; @@ -81,10 +75,32 @@ use LONCAPA::Configuration; # Global variable declarations +my $ssl_dir = "/usr/share/ssl"; # Where ssl config files etc. live +my $ca_cert_file = $ssl_dir."/loncapaca/cacert.pem"; # CA's certificate file. +my $ca_config_file= $ssl_dir."/loncapaca.cnf"; # CA's config file. + + +# LONCAPA Configuration global variables: + +# Items read from our configuration file. + +my $ssl_command = "/usr/bin/openssl "; # Command to run openssl. +my $loncapa_cert_dir; # Name of target cert dir (from config) +my $loncapa_hostcert_name; # Name of host's signed cert file (config) +my $loncapa_cacert_name; # Name of the CA's certificate file (config) +my $return_address; # Email return address. + +# Items I just need to know: + +my $loncapa_config = "loncapa.conf"; # User's override config file. +my $loncapa_apache_user = 'www'; # Name of apache daemon's user +my $loncapa_apache_group = 'www'; # Name of apache daemon's group + + # Debug/log support -my $DEBUG=1; +my $DEBUG=0; sub Debug { my $msg = shift; @@ -94,23 +110,400 @@ sub Debug { } # Support subs: -sub Usage {} +# +# Print out program usage. +# +# Side effects: +# Output goes to stderr. +# +sub Usage { + print STDERR << "USAGE"; + +Usage: + CrGrant.pl requestfile.pem + +Where: + requestfile.pem is a PEM formatted certificate extracted from an email + to the LonCAPA certificate manager. +USAGE +} +# +# Read the loncapa configuration file and pull out the items +# we need: +# +# Implicit inputs: +# $loncapa_config - The name of the auxilliary config file. +# Side effects: +# - On failure exits with an error message. +# - On success set the following variables: +# o loncapa_cert_dir - Path to certificates. +# o loncapa_hostcert_name - Name of host's cert file in that dir +# o loncapa_cacert_name - Name of CA's cert file in that dir. +# o ssl_command - Name of ssl utility command. +sub ReadConfig { + Debug("Reading the config files"); + my $perlvarref = LONCAPA::Configuration::read_conf($loncapa_config); + + # Pull out the individual variables or die: + + # SSL Command: + + if($perlvarref->{SSLProgram}) { + $ssl_command = $perlvarref->{SSLProgram}; + Debug("SSL utility program is $ssl_command"); + } + else { + die "LonCAPA configuration errror: Can't read SSLProgram variable"; + } + # Certificate directory: + + if($perlvarref->{lonCertificateDirectory}) { + $loncapa_cert_dir = $perlvarref->{lonCertificateDirectory}; + Debug("Certificates will be installed in $loncapa_cert_dir"); + } + else { + die "LonCAPA configuration error can't read lonCertificateDirectory variable"; + + } + # Get the name of the host's certificate: + + if($perlvarref->{lonnetCertificate}) { + $loncapa_hostcert_name = $perlvarref->{lonnetCertificate}; + Debug("Host's certificate will be $loncapa_hostcert_name"); + } + else { + die "LonCAPA configuration error: Can't read lonnetCertificate variable"; + } + # Get the name of the certificate authority's certificate. + + if($perlvarref->{lonnetCertificateAuthority}) { + $loncapa_cacert_name = $perlvarref->{lonnetCertificateAuthority}; + Debug("CA's certificate will be $loncapa_cacert_name"); + } + else { + die "LonCAPA configuration error: Can't read lonnetCertificateAuthority variable"; + } + # Get the email address of the certificate manager: + # this is the email return address: + + if($perlvarref->{SSLEmail}) { + $return_address = $perlvarref->{SSLEmail}; + Debug("Return address will be $return_address"); + } + else { + die "LonCAPA configuration error can't read SSLEmail configuration item"; + } + +} + +# Create a certificate from the request file. The certificate +# is used, in conjunction with the openssl command with the +# certificate authority configuration to produce a certificate +# file. +# +# The certificate is parsed to determine the email address +# of the requestor, which is returned to the caller. +# +#Parameters: +# request_file - Name of the file containing the certificate request. +#Returns: +# If the request file exists and is able to produce a certificate +# the email address of the requester is returned to the caller. +# If not, undef is returned. +# sub CreateCertificate { - my $RequestFile = shift; + my ($request_file) = @_; + + Debug("CreateCertificate"); + + if(!(-e $request_file)) { + Debug("Certificate file $request_file does not exist"); + return undef; + } + Debug("Certificate file $request_file exists"); + + # Create the certificate: The status of the openssl command + # is used to determine if the certificate succeeded: + + my $create_command = $ssl_command." ca -config ".$ca_config_file + ." -in ".$request_file + ." -out hostCertificate.pem"; + my $status = system($create_command); + if($status) { + Debug("openssl ca failed"); + print STDERR "Certificate generation failed... probably bad"; + print STDERR " request file!\n"; + return undef; + } + Debug("openssl ca succeeded"); + + # Now we have a shining new signed certificate in ./hostCertificate.pem + # we parse it to get the email address to which the certificate should + # be emailed. + # The certificate's return email address will be in the Subject line: + # + + Debug("Parsing certificate file for Subject:"); + open CERTIFICATE, ") { + Debug("Line = $line"); + if($line =~ /Subject:/) { + Debug("Found Subject: in $line"); + $subject_found =1; + last; + } + } + close CERTIFICATE; + + if(!$subject_found) { + Debug("Did not find Subject line in cert"); + print STDERR "Output certificate parse failed: no Subject:\n"; + return undef; + } + # The subject line contains an Email= string amidst the other stuff. + # First break in to comma separated stuff, then locate the piece that + # contains /Email= + + my @subject_fields = split(/,/, $line); + my $email_found = 0; + my $element; + my $email_element; + Debug("Parsing subject line for Email="); + foreach $element (@subject_fields) { + $email_element = $element; + Debug("Parsing $element"); + if($element =~ /\/Email=/) { + Debug("Found /Email="); + $email_found = 1; + last; + } + } + if(!$email_found) { + Debug("Failed to fine Email="); + print STDERR "Unable to find line with /Email= in cert. Subject\n"; + return undef; + } + + # The piece we found must first be split at the / + # to isolate the Email= part and then that part at the = to isolate + # the address: + + Debug("Splitting $email_element at /"); + my ($junk, $email) = split(/\//, $email_element); + Debug("Email part is $email"); + my ($junk, $address) = split(/=/, $email); + Debug("CreateCertificate Returning $address to caller"); + + return $address; - return 'fox@nscl.msu.edu'; # Stub.. } -sub CreateInstallScript {} +# +# Create the installation script. This will be bash script +# that will install the certifiate and the CA's certificate with ownership +# WebUser:WebGroup and permissions 0400. I thought about using a perl +# script in order to be able to get the certificate file/directory from +# the configuration files. Unfortunately this is not as easy as it looks. +# Root has a chicken and egg problem. In order to read the config file +# you need to have added the ..../lib/perl to the perl lib path. To do +# that correctly, you need to have read the config file to know where +# it is...What we will do is read our local configuration file and +# assume that our configuration is the same as the target's system in +# all respects we care about. +# Implicit Inputs: +# - Bash is in /bin/bash +# - $loncapa_cert_dir - install target directory. +# - $loncapa_hostcert_name - Name of installed host cert file. +# - $loncapa_cacert_name - Name of installed ca cert file. +# - $loncapa_apache_user - username under which httpd runs. +# - $loncapa_apache_group - group under which httpd runs. +# - 0400 - install permissions. +# - The host's certificate is now in ./hostCertificate.pem +# - The CA's certificate is now in $ca_cert_file +# +# Implicit Outputs: +# A file named CertInstall.sh +# Return +# Name of the file we created. +# +sub CreateInstallScript { + open INSTALLER,">CertInstall.sh"; + print INSTALLER <\$HOSTCERT +BASH_HEADER + + # Now copy the host certificate into the script: + + open HOSTCERT, ") { + print INSTALLER $line; # Line presumably has a \n. + } + close HOSTCERT; + + # Close the here doc, and start up the cat of the ca cert: + + print INSTALLER "HOSTCERTTEXT\n"; + print INSTALLER "echo unpacking CA certificate\n"; + print INSTALLER "cat <<-CACERTTEXT >\$CACERT\n"; + open CACERT, "<$ca_cert_file"; + while(my $line = ) { + print INSTALLER $line; + } + close CACERT; + print INSTALLER "CACERTTEXT\n"; + + # Ok, the script can create the two files, now it must install + # install them >and< clean up after itself. + + print INSTALLER <build(Type => "multipart/mixed", + From => $return_address, + To => $send_address, + Subject =>"LonCAPA certificates"); + if(!$mime_message) { + die "Unable to create top level MIME Message"; + } + + $mime_message->attach(Data =>[" This e-mail contains your lonCAPA SSL certificates. These\n", + "certificates allow your system to interact with the world wide\n", + "cluster of LonCAPA systems, and allow you to access and share\n", + "public resources for courses you host.\n\n", + " The certificates are shipped as a self installing shell script\n", + "To install these certificates:\n\n", + "1. Extract the attachment to this e-mail message\n", + "2. Save the attachment where it can be recovered in case you need\n", + " to re-install these certificates later on for some reason\n", + "3. As root execute the certificate request file: + . $installer_file\n", + " (Note: If you used a Windows based e-mail program to extract the\n", + " this file and then tranferred it to your unix lonCAPA system you \n", + " Will probably need to convert the file first e.g.: \n", + " dos2unix $installer_file\n", + " . $installer_file\n", + " The installer file will install the certificates and ask you\n", + " if you want to restart the LonCAPA system. You must restart the\n", + " LonCAPA system for it to use the new certificates.\n\n", + " Thank you for choosing LonCAPA for your course delivery needs,\n", + " The LonCAPA team.\n"]); + + Debug("Main message body created"); + + + # Attach the certificate intaller: + + $mime_message->attach(Type => "text/plain", + Path => $installer_file); + Debug("Installer attached"); + + return $mime_message; + } +# +# Sends a mime message to an email address. +# Parameters: +# message - A MIME::Entity containing the message. +# Implicit inputs: +# Mail is sent via /usr/lib/sendmail -t -oi -oem" +# This should work on all systems with a properly configured +# sendmail or compatible mail transfer agent. sub SendEmail { - my ($EmailAddress, $Message) = @_; + my ($message) = @_; + + Debug("Mailing"); + + open MAILPIPE, "| /usr/lib/sendmail -t -oi -oem" or + die "Failed to open pipe to sendmail: $!"; + + $message->print(\*MAILPIPE); + Debug("Submitted to sendmail"); + close MAILPIPE; +} +# +# Cleanup destroys the certificate file and its installer. +# +# +sub Cleanup { + my ($installer) = @_; + unlink($installer); + unlink("hostCertificate.pem"); } -sub Cleanup {} # Program entry point @@ -125,10 +518,20 @@ if($argc != 1) { } my $CertificateRequest = $ARGV[0]; -my $EmailAddress = CreateCertificate($CertificateRequest); -CreateInstallScript; -my $Message = CreateEmail; -SendEmail($EmailAddress, $Message); -Cleanup; +&ReadConfig; + +my $email_address = &CreateCertificate($CertificateRequest); +Debug("CreateCertificate returned: $email_address"); + +if(!defined $email_address) { + print STDERR "Bad or missing certificate file!!"; + Usage; + exit -1; +} + +my $script_name = &CreateInstallScript; +my $Message = &CreateEmail($script_name, $email_address); +&SendEmail($Message); +&Cleanup($script_name); # POD documentation.