Annotation of loncom/CrGenerate.pl, revision 1.9

1.1       foxr        1: #!/usr/bin/perl
                      2: # The LearningOnline Network
                      3: # CrGenerate - Generate a loncapa certificate request.
                      4: #
1.9     ! schafran    5: # $Id: CrGenerate.pl,v 1.8 2004/07/05 11:36:52 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: #
                     32: #  This script:
                     33: #  1. Generates a private host key and certificate request/
                     34: #  2. Decodes the private host key
                     35: #  3. Installs the private host key with appropriate permissions
                     36: #     in the  appropriate directory (sorry to be vague about this, but
                     37: #     the installation directory is determined by external configuration
                     38: #     info).
                     39: # 4. Constructs an email to the loncapa cluster administrator
                     40: #    consisting of a generic heading and the certificate request as a MIME
                     41: #    attachment.
                     42: # 5. Sends the email and
                     43: # 6. Cleans up after itself by removing any temp files generated.
                     44: #
                     45: #
                     46: 
                     47: 
                     48: # Import section:
                     49: 
                     50: use strict;
1.6       foxr       51: use lib '/home/httpd/lib/perl';
1.1       foxr       52: use MIME::Entity;
                     53: use LONCAPA::Configuration;
1.4       foxr       54: use File::Copy;
1.1       foxr       55: 
1.4       foxr       56: #  Global variable declarations:4
1.1       foxr       57: 
1.2       foxr       58: my $SSLCommand;			  # Full path to openssl command.
                     59: my $CertificateDirectory;	  # LONCAPA Certificate directory.
                     60: my $KeyFilename;	          # Key filename (within CertificateDirectory).
                     61: my $RequestEmail;		  # Email address of loncapa cert admin.
1.4       foxr       62: my $WebUID;			# UID of web user.
                     63: my $WebGID;			# GID of web user.
1.1       foxr       64: 
1.4       foxr       65: my $Passphrase="loncapawhatever";      # Initial passphrase for keyfile
                     66: my $RequestFile="loncapaRequest.pem";  # Name of Certificate request file.
                     67: my $EncodedKey="hostkey.pem";	       # Name of encoded key file.
1.3       foxr       68: 
1.4       foxr       69: my $WebUser="www";		# Username running the web server.
                     70: my $WebGroup="www";		# Group name running the web server.
1.3       foxr       71: 
1.1       foxr       72: #   Debug/log support:
                     73: #
1.7       foxr       74: my $DEBUG = 0;			# 1 for on, 0 for off.
1.1       foxr       75: 
                     76: # Send debugging to stderr.
                     77: # Parameters:
                     78: #     msg   - Message to send to stderr.
                     79: # Implicit Inputs:
                     80: #    $DEBUG - message is only written if this is true.
                     81: #
                     82: sub Debug {
1.8       foxr       83:     my ($msg)  = @_;
1.1       foxr       84:     if($DEBUG) {
                     85: 	print STDERR "$msg\n";
                     86:     }
                     87: }
                     88: 
1.3       foxr       89: #
1.5       foxr       90: #  Decodes the email address from a textual certificate request
                     91: #  file:
                     92: # Parameters:
                     93: #    $RequestFile   - Name of the file containing the textual
                     94: #                     version of the certificate request.
                     95: # Returns:
                     96: #   Email address contained in the request.
                     97: # Failure:
                     98: #   If unable to open or unable to fine an email address in the file,
                     99: #   dies with a message.
                    100: #
                    101: sub DecodeEmailFromRequest {
                    102:     Debug("DecodeEmailFromRequest");
                    103: 
1.8       foxr      104:     my ($RequestFile) = @_;
1.5       foxr      105:     Debug("Request file is called $RequestFile");
                    106: 
                    107:     # We need to look for the line that has a "/Email=" in it.
                    108: 
                    109:     Debug("opening $RequestFile");
                    110:     open REQUEST, "< $RequestFile" or
1.9     ! schafran  111: 	die "Unable to open $RequestFile to parse return e-mail address";
1.5       foxr      112: 
                    113:     Debug("Parsing request file");
                    114:     my $line;
                    115:     my $found = 0;
                    116:     while($line = <REQUEST>) {
                    117: 	chomp($line);		# Never a bad idea.
                    118: 	if($line =~ /\/Email=/) {
                    119: 	    $found = 1;
                    120: 	    last;
                    121: 	}
                    122:     }
                    123:     if(!$found) {
1.9     ! schafran  124: 	die "There does not appear to be an e-mail address in $RequestFile";
1.5       foxr      125:     }
                    126: 
                    127:     close REQUEST;
                    128: 
                    129:     Debug("Found /Email in $line");
                    130:     
                    131:     # $line contains a bunch of comma separated key=value pairs.
                    132:     # The problem is that after these is a /Email=<what-we-want>
                    133:     # first we'll split the line up at the commas.
                    134:     # Then we'll look for the entity with the /Email in it.
                    135:     # That line will get split at the / and then the Email=<what-we-want>
                    136:     # gets split at the =.  I'm sure there's some clever regular expression
                    137:     # substitution that will get it all in a single line, but I think 
                    138:     # this approach is gonna be much easier to understand than punctuation
                    139:     # sneezed all over the page:
                    140:    
                    141:     my @commalist = split(/,/, $line);
                    142:     my $item;
                    143:     my $emailequals = "";
                    144:     foreach $item  (@commalist) {
                    145: 	if($item =~ /\/Email=/) { # gotcha...
                    146: 	    $emailequals = $item;
                    147: 	    last;
                    148: 	}
                    149:     }
                    150: 
                    151:     Debug("Pulled out $emailequals from $line");
                    152:     my ($trash, $addressequals) = split(/\//, $emailequals);
                    153:     Debug("Futher pulled out $addressequals");
                    154: 
                    155:     my ($junk, $address) = split(/=/, $addressequals);
1.9     ! schafran  156:     Debug("Parsed final e-mail address as $address");
1.5       foxr      157:     
                    158: 
                    159: 
                    160:     return $address;
                    161: }
                    162: 
                    163: #
1.3       foxr      164: #   Read the LonCAPA web config files to get the values of the 
                    165: #   configuration global variables we need:
                    166: # Implicit inputs:
                    167: #   loncapa.conf   - configuration file to read (user specific).
                    168: # Implicit outputs (see global variables section):
                    169: #   SSLCommand,
                    170: #   CertificateDirectory
                    171: #   KeyfileName
                    172: #   RequestEmail
                    173: # Side-Effects:
                    174: #   Exit with error if cannot complete.
                    175: #
                    176: sub ReadConfig {
                    177: 
                    178:     Debug("Reading configuration");
                    179:     my $perlvarref = LONCAPA::Configuration::read_conf('loncapa.conf');
                    180:     
                    181:     # Name of the SSL Program
                    182: 
                    183:     if($perlvarref->{SSLProgram}) {
                    184: 	$SSLCommand = $perlvarref->{SSLProgram};
                    185: 	Debug("SSL Command: $SSLCommand");
                    186:     }
                    187:     else {
                    188: 	die "Unable to read the SSLCommand configuration option\n";
                    189:     }
                    190: 
                    191:     # Where the certificates, and host key are installed:
1.1       foxr      192: 
1.3       foxr      193:     if($perlvarref->{lonCertificateDirectory}) {
                    194: 	$CertificateDirectory = $perlvarref->{lonCertificateDirectory};
                    195: 	Debug("Local certificate Directory: $CertificateDirectory");
                    196:     }
                    197:     else {
                    198: 	die "Unable to read SSLDirectory configuration option\n";
                    199:     }
                    200:     # The name of the host key file (to be installed in SSLDirectory).
                    201:     #
                    202:     if($perlvarref->{lonnetPrivateKey}) {
                    203: 	$KeyFilename  = $perlvarref->{lonnetPrivateKey};
                    204: 	Debug("Private key will be installed as $KeyFilename");
                    205:     } 
                    206:     else {
                    207: 	die "Unable to read lonnetPrivateKey conrig paraemter\n";
                    208:     }
                    209:     #  The email address to which the certificate request is sent:
                    210: 
                    211:     if($perlvarref->{SSLEmail}) {
                    212: 	$RequestEmail = $perlvarref->{SSLEmail};
                    213: 	Debug("Certificate request will be sent to $RequestEmail");
                    214:     }
                    215:     else {
                    216: 	die "Could not read SSLEmail coniguration key";
                    217:     }
1.4       foxr      218:     #  The UID/GID of the web user: It's possible the web user's
                    219:     #  GID is not its primary, so we'll translate that form the
                    220:     #  group file separately.
                    221: 
                    222:     my ($login, $pass, $uid, $gid) = getpwnam($WebUser);
                    223:     if($uid) {
                    224: 	$WebUID = $uid;
                    225: 	Debug("Web user: $WebUser -> UID: $WebUID");
                    226:     }
                    227:     else {
                    228: 	die "Could not translate web user: $WebUser to a uid.";
                    229:     }
                    230:     my $gid = getgrnam($WebGroup);
                    231:     if($gid) {
                    232: 	$WebGID = $gid;
                    233: 	Debug("Web group: $WebGroup -> GID $WebGID");
                    234:     }
                    235:     else {
                    236: 	die "Unable to translate web group $WebGroup to a gid.";
                    237:     }
                    238: }
                    239: #
                    240: #   Generate a certificate request.
                    241: #   The openssl command is issued to create a local host key and
                    242: #   a certificate request.  The key is initially encoded.
                    243: #   We will eventually decode this, however, since the key
                    244: #   passphrase is open source we'll protect even the initial 
                    245: #   encoded key file too.  We'll need to decode the keyfile since
                    246: #   otherwise, openssl will need a passphrase everytime an ssl connection
                    247: #   is created (ouch).
                    248: # Implicit Inputs:
                    249: #    Passphrase   - Initial passphrase for the encoded key.
                    250: #    RequestFile  - Filename of the certificate request.
                    251: #    EncodedKey   - Filename of the encoded key file.
                    252: #
                    253: # Side-Effects:
                    254: #
                    255: sub GenerateRequest {
                    256:     Debug("Generating the request and key");
                    257: 
                    258:     print "We are now going to generate the certificate request\n";
                    259:     print "You will be prompted by openssl for several pieces of \n";
                    260:     print "information.  Most of this information is for documentation\n";
                    261:     print "purposes only, so it's not critical if you make a mistake.\n";
                    262:     print "However:  The generated certificate will be sent to the \n";
1.9     ! schafran  263:     print "E-mail address you provide, and you should leave the optional\n";
1.4       foxr      264:     print "Challenge password blank.\n";
                    265: 
                    266:     my $requestcmd = $SSLCommand." req -newkey rsa:1024 "
                    267:                                 ." -keyout hostkey.pem "
                    268:                                 ." -keyform PEM "
                    269:                                 ." -out request.pem "
                    270:                                 ." -outform PEM "
                    271:                                 ." -passout pass:$Passphrase";
                    272:     my $status = system($requestcmd);
                    273:     if($status) {
                    274: 	die "Certificate request generation failed: $status";
                    275:     }
                    276: 
                    277:     chmod(0600, "hostkey.pem");	# Protect key since passphrase is opensrc.
                    278: 
                    279:     Debug("Decoding the key");
                    280:     my $decodecmd = $SSLCommand." rsa -in  hostkey.pem"
                    281:                                ."     -out hostkey.dec"
                    282:                                ."     -passin pass:$Passphrase";
1.5       foxr      283:     $status = system($decodecmd);
1.4       foxr      284:     if($status) {
                    285: 	die "Host key decode failed";
                    286:     }
                    287: 
                    288:     chmod(0600, "hostkey.dec");	# Protect the decoded hostkey.
1.5       foxr      289: 
                    290:     #  Create the textual version of the request too:
                    291: 
                    292:     Debug("Creating textual version of the request for users.");
                    293:     my $textcmd = $SSLCommand." req -in request.pem -text "
                    294: 	                     ." -out request.txt";
                    295:     $status = system($textcmd);
                    296:     if($status) {
                    297: 	die "Textualization of the certificate request failed";
                    298:     }
                    299: 	                     
                    300: 
1.4       foxr      301:     Debug("Done");
                    302: }
                    303: #
                    304: #  Installs the decoded host key (hostkey.dec) in the 
                    305: #  certificate directory with the correct permissions.
                    306: #
                    307: # Implicit Inputs:
                    308: #    hostkey.dec           - the name of the host key file.
                    309: #    $CertificateDirectory - where the key file gets installed
                    310: #    $KeyFilename          - Final name of the key file.
                    311: #    $WebUser              - User who should own the key file.
                    312: #    $WebGroup             - Group who should own the key file.
                    313: #    0400                  - Permissions to give to the installed key
                    314: #                            file.
                    315: #    0700                  - Permissions given to the certificate
                    316: #                            directory if created.
                    317: # Side-Effects:
                    318: #    If necessary, $CertificateDirectory is created.
                    319: #    $CertificateDirectory/$KeyFilename is ovewritten with the
                    320: #          contents of hostkey.dec in the cwd.
                    321: #
                    322: sub InstallKey {
                    323:     Debug("InstallKey");
                    324: 
                    325:     Debug("Need to create certificate directory?");
                    326:     if(!(-d $CertificateDirectory)) {
                    327: 	
                    328: 	Debug("Creating");
                    329: 	mkdir($CertificateDirectory, 0700);
                    330: 	chown($WebUID, $WebGID, $CertificateDirectory);
                    331:     }
                    332:     else {
                    333: 	Debug("Exists");
                    334:     }
                    335: 
                    336:     Debug("Installing the key file:");
                    337:     my $FullKeyPath = $CertificateDirectory."/".$KeyFilename;
                    338:     copy("hostkey.dec", $FullKeyPath);
                    339: 
                    340:     Debug("Setting ownership and permissions");
                    341:     chmod(0400, $FullKeyPath);
                    342:     chown($WebUID, $WebGID, $FullKeyPath);
                    343: 
                    344:     Debug("Done");
1.3       foxr      345: }
1.5       foxr      346: #
                    347: #  Package up a certificate request and email it to the loncapa
                    348: #  admin.  The email sent:
                    349: #   - Has the subject: "LonCAPA certificate request for hostname
                    350: #   - Has, as the body, the text version of the certificate.
                    351: #     This can be inspected by the human issuing the certificate
                    352: #     to decide if they want to really grant it... it will
                    353: #     have the return email and all the documentation fields.
                    354: #   - Has a text attachment that consists of the .pem version of the
                    355: #     request.  This is extracted by the human granting the 
                    356: #     certificate and used as input to the CrGrant.pl script.
                    357: #
                    358: #
                    359: # Implicit inputs:
                    360: #    request.pem    - The certificate request file.
                    361: #    request.txt    - Textual version of the request file.
                    362: #    $RequestEmail  - Email address to which the key is sent.
                    363: #  
                    364: sub MailRequest {
                    365:     Debug("Mailing request");
                    366: 
                    367:     # First we need to pull out the return address from the textual
                    368:     # form of the certificate request:
                    369: 
                    370:     my $FromEmail = DecodeEmailFromRequest("request.txt");
                    371:     if(!$FromEmail) {
1.9     ! schafran  372: 	die "From e-mail address cannot be decoded from certificate request";
1.5       foxr      373:     }
                    374:     Debug("Certificate will be sent back to $FromEmail");
                    375: 
                    376:     # Create the email message headers and all:
                    377:     #
                    378:     Debug("Creating top...level...");
                    379:     my $top = MIME::Entity->build(Type     => "multipart/mixed",
                    380: 				  From     => $FromEmail,
                    381: 				  To       => $RequestEmail,
                    382: 				  Subject  => "LonCAPA certificate request");
                    383:     if(!$top) {
                    384: 	die "Unable to create top level mime document";
                    385:     }
                    386:     Debug("Attaching Text formatted certificate request");
                    387:     $top->attach(Path     => "request.txt");
                    388: 
                    389: 
                    390:     Debug("Attaching PEM formatted certificate request...");
                    391:     $top->attach(Type       => "text/plain",
                    392: 		 Path      => "request.pem");
                    393: 
                    394:     #  Now send the email via sendmail this should work as long as
                    395:     #  sendmail or postfix are configured properly.  Most other mailers
                    396:     #  define the sendmail command too for compatibility with what
                    397:     #  we're trying to do.  I decided to use sendmail directly because
                    398:     #  otherwise I'm not sure the mail headers I created in $top
                    399:     #  will get properly passed as headers to other mailer thingies.
                    400:     #
                    401: 
                    402:     Debug("Mailing..");
                    403: 
                    404:     open MAILPIPE, "| /usr/lib/sendmail -t -oi -oem" or 
                    405: 	die "Failed to open pipe to sendmail: $!";
                    406:     $top->print(\*MAILPIPE);
                    407:     close MAILPIPE;
                    408: 
                    409: 
                    410: 
                    411:     Debug("Done");
                    412: } 
1.6       foxr      413: 
                    414: #
                    415: #   Cleans up the detritus that's been created by this 
                    416: #   script (see Implicit inputs below).
                    417: # Implicit inputs:
                    418: #    request.pem       - Name of certificate request file in PEM format
                    419: #                        which will be deleted.
                    420: #    request.txt       - Name of textual equivalent of request file
                    421: #                        which will also be deleted.
                    422: #    hostkey.pem       - Encrypted host key which will be deleted.
                    423: #    hostkey.dec       - Decoded host key, which will be deleted.
                    424: #
                    425: sub Cleanup {
                    426:     Debug("Cleaning up generated, temporary files");
                    427:     unlink("request.pem", "request.txt", "hostkey.pem", "hostkey.dec");
                    428:     Debug("done!");
                    429: }
1.1       foxr      430: 
                    431: 
                    432: 
                    433: #  Entry point:
                    434: 
                    435: Debug("Starting program");
                    436: ReadConfig;			# Read loncapa apache config file.
                    437: GenerateRequest;		# Generate certificate request.
                    438: InstallKey;			# Install the user's key.
                    439: MailRequest;			# Mail certificate request to loncapa 
                    440: Cleanup;			# Cleanup temp files created.
                    441: 
                    442: Debug("Done");
1.7       foxr      443: 
                    444: #---------------------- POD documentatio --------------------
                    445: 
                    446: =head1 NAME
                    447:  
                    448:     CrGenerate - Generate a loncapa certificate request.
                    449: 
                    450: =head1 SYNOPSIS
                    451: 
                    452: Usage: B<CrGenerate>
                    453: 
                    454: This should probably be run automatically at system
                    455: installation time.  Root must run this as write access is 
                    456: required to /home/httpd.
                    457: 
                    458: This is a command line script that:
                    459: 
                    460:    - Generates a hostkey and certificate request.
                    461:    - Installs the protected/decoded host key where
                    462:      secure lond/lonc can find it.
                    463:    - Emails the certificate request to the loncapa certificate
                    464:      manager.
                    465: 
                    466: In due course if all is legitimate, the loncapa certificate
                    467: manager will email a certificate installation script to 
                    468: the local loncapa system administrator.
                    469: 
                    470: =head1 DESCRIPTION
                    471: 
                    472: Using the default openssl configuration file, a certificate
                    473: request and local hostkey are created in the current working
                    474: directory.  The local host key is decoded and installed in the 
                    475: loncapa certificate directory.  This allows the secure versions 
                    476: of lonc and lond to locate them when attempting to form 
                    477: external connections.  The key file is given mode
                    478: 0400 to secure it from prying eyes.
                    479: 
                    480: The certificate request in PEM form is attached to an email that
                    481: contains the textual equivalent of the certificate request 
                    482: and sent to the loncapa certificate manager.  All temporary
                    483: files (certificate request, keys etc.) are removed from the
                    484: current working directory.
                    485: 
                    486: It is recommended that the directory this script is run in have 
                    487: permission mask 0700 to ensure that there are no timing holes
                    488: during which the decoded host key file can be stolen.
                    489: 
                    490: During certificate generation, the user will receive several 
                    491: prompts.  For the default LonCAPA  openssl configuration, 
                    492: these prompts, and documentation and sample responses
                    493: in angle brackets (<>)  are shown below:
                    494: 
                    495:     Country Name (2 letter code) [GB]: <your country e.g. US>
                    496:     State or Province Name (full name) [Berkshire]: <State, province prefecture etc. e.g. Michigan>
                    497:     Locality Name (eg, city) [Newbury]: <City township or  municipality e.g. East Lansing>
                    498:     Organization Name (eg, company) [My Company Ltd]: <corporate entity e.g. Michigan State University>
                    499:     Organizational Unit Name (eg, section) []: <unit within Organization e.g. LITE lab>
                    500:     Common Name (eg, your name or your server's host name) [] <server's hostname e.g. myhost.university.edu>
                    501:     Email Address []: <Address to which the granted certificate should be sent e.g. me@university.edu>
                    502:     
                    503:     Please enter the following 'extra' attributes
                    504:     to be sent with your certificate request
                    505:     A challenge password []: <leave this blank!!!!!>
                    506:     An optional company name []: <Put whatever you want or leave blank>
                    507: 
                    508: 
                    509: =head1  DEPENDENCIES
                    510: 
                    511:  - MIME::Entity           Used to create the email message.
                    512:  - LONCAPA::Configuration Used to parse the loncapa configuration files.
                    513:  - File::Copy             Used to install the key file.
                    514:  - /usr/lib/sendmail      Properly configured sendmail, used to send the
                    515:                           certificate request email to the loncapa
                    516:                           certificate administrator.
                    517:  - /etc/httpd/conf/*      Loncapa configuration files read to locate
                    518:                           the certificate directory etc.
                    519: 
                    520: =head1 FILES
                    521: 
                    522:   The following temporary files are created in the cwd
                    523: 
                    524:   hostkey.pem         - PEM formatted version of the encrypted host key.
                    525:   hostkey.dec         - PEM formatted decrypted version of the host key.
                    526:   request.pem         - PEM formatted certificate request.
                    527:   request.txt         - Textual rendering of the certificate request.
                    528: 
                    529:   The following permanent file is created:
                    530: 
                    531:   $CertDir/$Keyfile   - The installed decoded host key file. $CertDir
                    532:                         is defined by the Perl variable lonCertificateDirectory
                    533:                         in /etc/loncapa_apache.conf while $Keyfile is 
                    534:                         defined by the perl variable lonnetPrivateKey in the
                    535:                         same configuration file.
                    536:   
                    537: =head1 COPYRIGHT:
                    538: 
                    539:  Copyright Michigan State University Board of Trustees
                    540: 
                    541:  This file is part of the LearningOnline Network with CAPA (LON-CAPA).
                    542: 
                    543:  LON-CAPA is free software; you can redistribute it and/or modify
                    544:  it under the terms of the GNU General Public License as published by
                    545:  the Free Software Foundation; either version 2 of the License, or 
                    546:  (at your option) any later version.
                    547: 
                    548:  LON-CAPA is distributed in the hope that it will be useful,
                    549:  but WITHOUT ANY WARRANTY; without even the implied warranty of
                    550:  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
                    551:  GNU General Public License for more details.
                    552: 
                    553:  You should have received a copy of the GNU General Public License
                    554:  along with LON-CAPA; if not, write to the Free Software
                    555:  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
                    556: 
                    557:  /home/httpd/html/adm/gpl.txt
                    558: 
                    559: 
                    560: =cut
                    561: 
                    562: 
                    563:     

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>
500 Internal Server Error

Internal Server Error

The server encountered an internal error or misconfiguration and was unable to complete your request.

Please contact the server administrator at root@localhost to inform them of the time this error occurred, and the actions you performed just before this error.

More information about this error may be available in the server error log.