File:
[LON-CAPA] /
loncom /
CrGenerate.pl
Revision
1.5:
download - view:
text,
annotated -
select for diffs
Thu Jul 1 10:58:29 2004 UTC (20 years, 2 months ago) by
foxr
Branches:
MAIN
CVS tags:
HEAD
Added code to package up the certificate and mail it to a certificate manager.
Remaining work:
- Add Cleanup
- Add Pod documentation.
- Change debug value to 0 to make this less verbose.
#!/usr/bin/perl
# The LearningOnline Network
# CrGenerate - Generate a loncapa certificate request.
#
# $Id: CrGenerate.pl,v 1.5 2004/07/01 10:58:29 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:
# 1. Generates a private host key and certificate request/
# 2. Decodes the private host key
# 3. Installs the private host key with appropriate permissions
# in the appropriate directory (sorry to be vague about this, but
# the installation directory is determined by external configuration
# info).
# 4. Constructs an email to the loncapa cluster administrator
# consisting of a generic heading and the certificate request as a MIME
# attachment.
# 5. Sends the email and
# 6. Cleans up after itself by removing any temp files generated.
#
#
# Import section:
use strict;
use MIME::Entity;
use LONCAPA::Configuration;
use File::Copy;
# Global variable declarations:4
my $SSLCommand; # Full path to openssl command.
my $CertificateDirectory; # LONCAPA Certificate directory.
my $KeyFilename; # Key filename (within CertificateDirectory).
my $RequestEmail; # Email address of loncapa cert admin.
my $WebUID; # UID of web user.
my $WebGID; # GID of web user.
my $Passphrase="loncapawhatever"; # Initial passphrase for keyfile
my $RequestFile="loncapaRequest.pem"; # Name of Certificate request file.
my $EncodedKey="hostkey.pem"; # Name of encoded key file.
my $WebUser="www"; # Username running the web server.
my $WebGroup="www"; # Group name running the web server.
# Debug/log support:
#
my $DEBUG = 1; # 1 for on, 0 for off.
# Send debugging to stderr.
# Parameters:
# msg - Message to send to stderr.
# Implicit Inputs:
# $DEBUG - message is only written if this is true.
#
sub Debug {
my $msg = shift;
if($DEBUG) {
print STDERR "$msg\n";
}
}
#
# Decodes the email address from a textual certificate request
# file:
# Parameters:
# $RequestFile - Name of the file containing the textual
# version of the certificate request.
# Returns:
# Email address contained in the request.
# Failure:
# If unable to open or unable to fine an email address in the file,
# dies with a message.
#
sub DecodeEmailFromRequest {
Debug("DecodeEmailFromRequest");
my $RequestFile = shift;
Debug("Request file is called $RequestFile");
# We need to look for the line that has a "/Email=" in it.
Debug("opening $RequestFile");
open REQUEST, "< $RequestFile" or
die "Unable to open $RequestFile to parse return email address";
Debug("Parsing request file");
my $line;
my $found = 0;
while($line = <REQUEST>) {
chomp($line); # Never a bad idea.
if($line =~ /\/Email=/) {
$found = 1;
last;
}
}
if(!$found) {
die "There does not appear to be an email address in $RequestFile";
}
close REQUEST;
Debug("Found /Email in $line");
# $line contains a bunch of comma separated key=value pairs.
# The problem is that after these is a /Email=<what-we-want>
# first we'll split the line up at the commas.
# Then we'll look for the entity with the /Email in it.
# That line will get split at the / and then the Email=<what-we-want>
# gets split at the =. I'm sure there's some clever regular expression
# substitution that will get it all in a single line, but I think
# this approach is gonna be much easier to understand than punctuation
# sneezed all over the page:
my @commalist = split(/,/, $line);
my $item;
my $emailequals = "";
foreach $item (@commalist) {
if($item =~ /\/Email=/) { # gotcha...
$emailequals = $item;
last;
}
}
Debug("Pulled out $emailequals from $line");
my ($trash, $addressequals) = split(/\//, $emailequals);
Debug("Futher pulled out $addressequals");
my ($junk, $address) = split(/=/, $addressequals);
Debug("Parsed final email addresss as $address");
return $address;
}
#
# Read the LonCAPA web config files to get the values of the
# configuration global variables we need:
# Implicit inputs:
# loncapa.conf - configuration file to read (user specific).
# Implicit outputs (see global variables section):
# SSLCommand,
# CertificateDirectory
# KeyfileName
# RequestEmail
# Side-Effects:
# Exit with error if cannot complete.
#
sub ReadConfig {
Debug("Reading configuration");
my $perlvarref = LONCAPA::Configuration::read_conf('loncapa.conf');
# Name of the SSL Program
if($perlvarref->{SSLProgram}) {
$SSLCommand = $perlvarref->{SSLProgram};
Debug("SSL Command: $SSLCommand");
}
else {
die "Unable to read the SSLCommand configuration option\n";
}
# Where the certificates, and host key are installed:
if($perlvarref->{lonCertificateDirectory}) {
$CertificateDirectory = $perlvarref->{lonCertificateDirectory};
Debug("Local certificate Directory: $CertificateDirectory");
}
else {
die "Unable to read SSLDirectory configuration option\n";
}
# The name of the host key file (to be installed in SSLDirectory).
#
if($perlvarref->{lonnetPrivateKey}) {
$KeyFilename = $perlvarref->{lonnetPrivateKey};
Debug("Private key will be installed as $KeyFilename");
}
else {
die "Unable to read lonnetPrivateKey conrig paraemter\n";
}
# The email address to which the certificate request is sent:
if($perlvarref->{SSLEmail}) {
$RequestEmail = $perlvarref->{SSLEmail};
Debug("Certificate request will be sent to $RequestEmail");
}
else {
die "Could not read SSLEmail coniguration key";
}
# The UID/GID of the web user: It's possible the web user's
# GID is not its primary, so we'll translate that form the
# group file separately.
my ($login, $pass, $uid, $gid) = getpwnam($WebUser);
if($uid) {
$WebUID = $uid;
Debug("Web user: $WebUser -> UID: $WebUID");
}
else {
die "Could not translate web user: $WebUser to a uid.";
}
my $gid = getgrnam($WebGroup);
if($gid) {
$WebGID = $gid;
Debug("Web group: $WebGroup -> GID $WebGID");
}
else {
die "Unable to translate web group $WebGroup to a gid.";
}
}
#
# Generate a certificate request.
# The openssl command is issued to create a local host key and
# a certificate request. The key is initially encoded.
# We will eventually decode this, however, since the key
# passphrase is open source we'll protect even the initial
# encoded key file too. We'll need to decode the keyfile since
# otherwise, openssl will need a passphrase everytime an ssl connection
# is created (ouch).
# Implicit Inputs:
# Passphrase - Initial passphrase for the encoded key.
# RequestFile - Filename of the certificate request.
# EncodedKey - Filename of the encoded key file.
#
# Side-Effects:
#
sub GenerateRequest {
Debug("Generating the request and key");
print "We are now going to generate the certificate request\n";
print "You will be prompted by openssl for several pieces of \n";
print "information. Most of this information is for documentation\n";
print "purposes only, so it's not critical if you make a mistake.\n";
print "However: The generated certificate will be sent to the \n";
print "Email address you provide, and you should leave the optional\n";
print "Challenge password blank.\n";
my $requestcmd = $SSLCommand." req -newkey rsa:1024 "
." -keyout hostkey.pem "
." -keyform PEM "
." -out request.pem "
." -outform PEM "
." -passout pass:$Passphrase";
my $status = system($requestcmd);
if($status) {
die "Certificate request generation failed: $status";
}
chmod(0600, "hostkey.pem"); # Protect key since passphrase is opensrc.
Debug("Decoding the key");
my $decodecmd = $SSLCommand." rsa -in hostkey.pem"
." -out hostkey.dec"
." -passin pass:$Passphrase";
$status = system($decodecmd);
if($status) {
die "Host key decode failed";
}
chmod(0600, "hostkey.dec"); # Protect the decoded hostkey.
# Create the textual version of the request too:
Debug("Creating textual version of the request for users.");
my $textcmd = $SSLCommand." req -in request.pem -text "
." -out request.txt";
$status = system($textcmd);
if($status) {
die "Textualization of the certificate request failed";
}
Debug("Done");
}
#
# Installs the decoded host key (hostkey.dec) in the
# certificate directory with the correct permissions.
#
# Implicit Inputs:
# hostkey.dec - the name of the host key file.
# $CertificateDirectory - where the key file gets installed
# $KeyFilename - Final name of the key file.
# $WebUser - User who should own the key file.
# $WebGroup - Group who should own the key file.
# 0400 - Permissions to give to the installed key
# file.
# 0700 - Permissions given to the certificate
# directory if created.
# Side-Effects:
# If necessary, $CertificateDirectory is created.
# $CertificateDirectory/$KeyFilename is ovewritten with the
# contents of hostkey.dec in the cwd.
#
sub InstallKey {
Debug("InstallKey");
Debug("Need to create certificate directory?");
if(!(-d $CertificateDirectory)) {
Debug("Creating");
mkdir($CertificateDirectory, 0700);
chown($WebUID, $WebGID, $CertificateDirectory);
}
else {
Debug("Exists");
}
Debug("Installing the key file:");
my $FullKeyPath = $CertificateDirectory."/".$KeyFilename;
copy("hostkey.dec", $FullKeyPath);
Debug("Setting ownership and permissions");
chmod(0400, $FullKeyPath);
chown($WebUID, $WebGID, $FullKeyPath);
Debug("Done");
}
#
# Package up a certificate request and email it to the loncapa
# admin. The email sent:
# - Has the subject: "LonCAPA certificate request for hostname
# - Has, as the body, the text version of the certificate.
# This can be inspected by the human issuing the certificate
# to decide if they want to really grant it... it will
# have the return email and all the documentation fields.
# - Has a text attachment that consists of the .pem version of the
# request. This is extracted by the human granting the
# certificate and used as input to the CrGrant.pl script.
#
#
# Implicit inputs:
# request.pem - The certificate request file.
# request.txt - Textual version of the request file.
# $RequestEmail - Email address to which the key is sent.
#
sub MailRequest {
Debug("Mailing request");
# First we need to pull out the return address from the textual
# form of the certificate request:
my $FromEmail = DecodeEmailFromRequest("request.txt");
if(!$FromEmail) {
die "From email address cannot be decoded from certificate request";
}
Debug("Certificate will be sent back to $FromEmail");
# Create the email message headers and all:
#
Debug("Creating top...level...");
my $top = MIME::Entity->build(Type => "multipart/mixed",
From => $FromEmail,
To => $RequestEmail,
Subject => "LonCAPA certificate request");
if(!$top) {
die "Unable to create top level mime document";
}
Debug("Attaching Text formatted certificate request");
$top->attach(Path => "request.txt");
Debug("Attaching PEM formatted certificate request...");
$top->attach(Type => "text/plain",
Path => "request.pem");
# Now send the email via sendmail this should work as long as
# sendmail or postfix are configured properly. Most other mailers
# define the sendmail command too for compatibility with what
# we're trying to do. I decided to use sendmail directly because
# otherwise I'm not sure the mail headers I created in $top
# will get properly passed as headers to other mailer thingies.
#
Debug("Mailing..");
open MAILPIPE, "| /usr/lib/sendmail -t -oi -oem" or
die "Failed to open pipe to sendmail: $!";
$top->print(\*MAILPIPE);
close MAILPIPE;
Debug("Done");
}
sub Cleanup {}
# Entry point:
Debug("Starting program");
ReadConfig; # Read loncapa apache config file.
GenerateRequest; # Generate certificate request.
InstallKey; # Install the user's key.
MailRequest; # Mail certificate request to loncapa
Cleanup; # Cleanup temp files created.
Debug("Done");
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>