#!/usr/bin/perl
# The LearningOnline Network
# CrGrant.pl - Grant a loncapa SSL certificate.
#
# $Id: CrGrant.pl,v 1.3 2004/07/06 11:05:45 foxr Exp $
#
# Copyright Michigan State University Board of Trustees
#
# This file is part of the LearningOnline Network with CAPA (LON-CAPA).
#
# LON-CAPA is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# LON-CAPA is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with LON-CAPA; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# /home/httpd/html/adm/gpl.txt
#
# http://www.lon-capa.org/
#
# This script operates on a certificate request that has been
# extracted from the attachment sent to the loncapa certificate
# administrator and:
#
# 1. Creates an ssl certificate corresponding to the request.
# 2. Constructs an installation script that will install
# the certificate along with the certificate authority's
# certificate in a loncapa system.
# 3. Constructs an email which contains a cover letter
# describing what to do with the attachment, and an
# attachment that consists of the installation script
# created in step 2.
# 4. Emails the message to the email address in the certificate
# request.
#
# There are some assumptions we need to make in order to
# get this all to work:
# - The certificate authority is installed on a
# loncapa system with configuration files that specify
# the same certificate directory and certificate filenames
# as the target system (otherwise we can't generate the
# installation script).
# - The loncapa certificate authority configuration file is
# $SSLDir/loncapaca.cnf and that it specifies that:
# o The certificate authority files are in $SSLDir/loncapaca
# o The certificate authority certificate is in:
# $SSLDir/loncapaca/cacert.pem
# 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.
#
#
# Import section:
use strict;
use lib '/home/httpd/lib/perl'; # An assumption!!!
use MIME::Entity;
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)
# 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;
sub Debug {
my $msg = shift;
if($DEBUG) {
print STDERR "$msg\n";
}
}
# Support subs:
#
# 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";
}
}
# 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 ($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, "<hostCertificate.pem";
my $line;
my $subject_found = 0;
while ($line = <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;
}
#
# 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
#
sub CreateInstallScript {
open INSTALLER,">CertInstall.sh";
print INSTALLER <<BASH_HEADER;
#!/bin/bash
#
# Installer for your lonCAPA certificates. Please check the
# configuration variables to be sure they match your installation.
# Then run this script under a root shell to complete the
# installation of the certificates.
#
# Configuration Variables:
CERTDIR="$loncapa_cert_dir" # Directory with your host key.
HOSTCERT="$loncapa_hostcert_name" # Name of host's certificate file.
CACERT="$loncapa_cacert_name" # Name of certifiate authority file.
HTTPDUID="$loncapa_apache_user" # UID of httpd.
HTTPDGID="$loncapa_apache_group" # GID of httpd.
# End of configuration variables.
MODE=0444 # certificates get this mode.
HOSTCERTPATH="\$CERTDIR/\$HOSTCERT"
CACERTPATH="\$CERTDIR/\$CACERT"
# Create the host certificate file to install:
echo unpacking host certificate
cat <<-HOSTCERTTEXT >\$HOSTCERT
BASH_HEADER
# Now copy the host certificate into the script:
open HOSTCERT, "<hostCertificate.pem";
while(my $line = <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 = <CACERT>) {
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 <<BASH_TRAILER;
echo Installing certificates
install -m \$MODE -o \$HTTPDUID -g \$HTTPDGID \$CACERT \$CACERTPATH
install -m \$MODE -o \$HTTPDUID -g \$HTTPDGID \$HOSTCERT \$HOSTCERTPATH
echo done
# rm -f \$CACERT
# rm -f \$HOSTCERT
# Do they want to restart loncapa:
#
echo In order to start running in secure mode you will need to start
echo lonCAPA. If you want I can do that now for you. Otherwise,
echo you will have to do it yourself later either by rebooting your
echo system or by typing:
echo
echo /etc/init.d/loncontrol restart
echo
read -p "Restart loncapa now [yN]?" yesno
if [ "{\$yesno:0:1}" = "Y" ]
then
/etc/init.d/loncontrol restart
fi
BASH_TRAILER
close INSTALLER;
}
sub CreateEmail {
return "Dummy message"; # Stub.
}
sub SendEmail {
my ($EmailAddress, $Message) = @_;
}
sub Cleanup {}
# Program entry point
# The usage is:
# CrGrant.pl {request_file}
#
my $argc = @ARGV; # Count number of command parameters.
if($argc != 1) {
Usage;
exit -1;
}
my $CertificateRequest = $ARGV[0];
&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;
}
&CreateInstallScript;
my $Message = &CreateEmail;
&SendEmail($email_address, $Message);
&Cleanup;
# POD documentation.
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>