#!/usr/bin/perl 

# generates a keypair and CSR and submits it to the user enrollement interface
#  on an iPlanet CA server.
# Optionally, if you specify a pregenerated csr with the -request
# cmdline option, it will submit that to be signed

# any parameters (except debug, help and request) that are omitted
# will be promted for.
#
# -name "John Doe" - the user's fullname, used as basis for CN
# -email           - the user's email address
# -phone           - the user's 10 digit phone number
# -sponsor         - the sponsor for this request, one of:
#                     Doug McCune, Dave Schissel or Martin Greenwald
# -password pass   - optional file containing password to for the private key
#                      if omitted, openssl will prompt for a passphrase
# -request         - an existing certificate signing request, if specified
#                    then we don't bother generating a cert, and ignore the
#                    -name argument
# -dir             - the directory  in which to store the private key
#                     defaults to $HOME/.globus
# -debug           - sets the debug flag, causing lots of debugging info
#                    to be spewed to the console
# -help            - will print a usage message

#  version 1.0 - July 27, 2004 mrthompson@lbl.gov
# based on Dhiva's UserGridEnrollment and Steve Chan's makecert.pl scripts
#
use Getopt::Long;


# url for the CA server's user certificate request interface

#use the following two lines for the fusionGrid CA
#my $baseURL = "https://jade.es.net:9002/enrollment";  # fusion CA
#my $dc2 = "FusionGrid";

#use the following two lines for the DOEGrids CA
my $baseURL = "https://pki1.doegrids.org/enrollment"; # doegrids CA
my $dc2 = "doegrids";


# files used for openssl certificate request

my $credDir =  $ENV{HOME}."/.globus";
my $reqfile = "/tmp/".$$."req.pem";
my $keyfile = $credDir."/".$$."key.pem";
my $currentReqFile = $credDir."/currentCertReq";
my $randfile = "/tmp/random.$$";
my $sslconf = "/tmp/sslconf.$$";
my $verbose = "-s";
my $insecure ="-k";

# file for output from the certificate request
my $output_file = "/tmp/response.$$.html";

my $curlVersion = `curl --version | awk \'{ print \$2 }\'`;

###########################################################
# SUBSCRIBER PERSONAL INFORMATION
###########################################################
my $csrRequestorName="";
my $csrRequestorEmail="";
my $csrRequestorPhone="";
my $csrRequestorAffiliation="FusionGrid";
my %SponsorEmail= (
  "doug",'dmccune@pppl.gov',
  "dave",'schissel@fusion.gat.com',
  "martin",'g@psfc.mit.edu');

my %SponsorPhone=(
   "doug","609 243-2731",
   "dave","858 455-3387",
   "martin","617 253-6053");

my %SponsorName=(
    "doug","Doug McCune",
    "dave", "David Schissel",
    "martin", "Martin Greenwald");

#############OPTIONAL-FIELDS###############################
###########################################################
my $COMMENT="csrRequestorComments=None";
my $PWDFLDS="challengePassword=''&confirmChallengePassword=''";

$PWDFLDS =~ s/ //g;
$COMMENT =~ s/ /+/g;

$OPTFIELDS="-d \"$COMMENT&$PWDFLDS\"";

###########################################################
# Hidden form fields - These are all defaults
# Standard Parameters required by the Service 
#  ********** DO NOT CHANGE THESE VALUES ******************
###########################################################
my $HIDDEN = "requestFormat=clientAuth&
certType=client&
certauthEnrollType=single&
ssl_client=true&
ssl_server=true&
digital_signature=true&
non_repudiation=true&
key_encipherment=true&
data_encipherment=true&
AuthServerRequest=false&
email=true";

###########################################################
# replace the spaces with + symbol
###########################################################

$HIDDEN =~ s/\s//g;
$HIDDEN = "-d \"$HIDDEN\"";

# hash for the command line args initialized to null strings
my @keys = qw(name email phone sponsor password request dir);
my %args;
@args{@keys} = ("") x @keys;

GetOptions( \%args,"name=s","email=s","phone=s","sponsor=s","debug+","help+","password=s","request=s","dir=s");

if ( $args{help}) {
  usage();
  exit 1;
}
if ($args{debug}) {
    $verbose = "-v";
}

if ($args{dir}) {
  $credDir = $args{dir};
  $keyfile = $credDir."/".$$."key.pem";
}
if ( ! -d $credDir) {
  mkdir $credDir;
}
# curl started requiring the server CA cert in verion 7.10, the -k switch overides the check
@vers = split /\./, $curlVersion;
if ($vers[0] <= 7) {
  if ($vers[1] <= 9 ) {
    $insecure = "";
  }
}

# if a request file was input use it
if ($args{request}) {
  if ( -e $args{request}) {
    $reqfile = $args{request};
    $keyfile = "/dev/null";
  } else {
    die "Could not find request file $args{request}";
  }
}
# if we don't have a request file specified, generate the keypair and csr
else {
  if (! $args{name}) {
    print "input your full name: ";
    $args{name} = <STDIN>;
    $args{name} =~ s/^\s*(.*?)\s*$/$1/;
  }

  # tack a random number onto the end of the name given
  srand();
  $num = int(rand() * 1000000);

  $args{name} .= " $num";

  # setup config file for the openssl stuff
  umask 0x600;

  open SSLCONF, ">$sslconf" || die "Could not setup openssl config file $sslconf";
    
  print SSLCONF <<EOT1;
#
# SSLeay configuration file.
# Dynamically generated for certificate issuing script
#

RANDFILE         = $randfile
####################################################################
[ req ]
default_bits            = 2048
default_keyfile         = $keyfile
distinguished_name      = req_distinguished_name
prompt                  = no
encrypt_key             = yes
[ req_distinguished_name ]
# BEGIN CONFIG
1.DC = org
2.DC = $dc2
OU = People
CN = $args{name}

# END CONFIG
EOT1

  close SSLCONF;
  
  if ( $args{password}){
    $passarg = "-passout file:$args{password}";
  }
  else {
    $passarg ="";
    print "you will be  prompted for a passphrase to encyrpt the private key that is being generated.\n";
  }

  # get some entropy for the random seed

  `date > $randfile`;
  `head -n1000 /dev/urandom >> $randfile` if ( -e "/dev/urandom" );
  `ps -aux >> $randfile`;
  `ls -ln /tmp >> $randfile`;

  # Generate a keypair and the CSR
  my ($cmd) = sprintf("openssl req -new $passarg -out %s -config %s",$reqfile,$sslconf);

  if ($args{debug}) {
    print $cmd."\n";
  }
  $res = system( $cmd );

  if ($res) {
    die "Error while executing $cmd";
  }
`chmod 600 $keyfile`;
}

unlink $randfile, $sslconf;

# Get the rest of the input arguments

$csrRequestorName = $args{name};

while  (! grep /@/, $args{email} ) {
  print "input your email address: ";
  $args{email} = <STDIN>;
  $args{email} =~ s/^\s*(.*?)\s*$/$1/;
}
$csrRequestorEmail = $args{email};
while ( length( $args{phone}) < 10 ) {
  print "input your 10 digit phone number: ";
  $args{phone} = <STDIN>;
  $args{phone} =~ s/^\s*(.*?)\s*$/$1/;
}
$csrRequestorPhone = $args{phone};

my $sponsor = "";
if ( $args{sponsor}) {
  if (grep /^[dD]oug/, $args{sponsor}) {
    $sponsor = "doug";
  } elsif (grep /^[dD]av/, $args{sponsor}) {
    $sponsor = "dave";
  } elsif (grep /^[mM]art/, $args{sponsor}){
    $sponsor ="martin";
  } else {
    print "unrecognized sponsor input\n";
}
} else { 
  while ( $sponsor ne "doug" & $sponsor ne "dave" & $sponsor ne "martin"){
    print "input your sponsor name- one of: \n" .
        "\t Doug McCune PPPL (doug)\n" .
        "\t David Schissel GAT (dave)\n" .
        "\t Martin Greenwald MIT (martin): ";
    $sponsor = <STDIN>;
    $sponsor =~ s/^\s*(.*?)\s*$/$1/;
  }
}
$SponsorEmail = $SponsorEmail{$sponsor};
$SponsorPhone = $SponsorPhone{$sponsor};
$SponsorName = $SponsorName{$sponsor};

my $FORMFIELDS="csrRequestorName=$csrRequestorName&
csrRequestorEmail=$csrRequestorEmail&
csrRequestorPhone=$csrRequestorPhone&
csrRequestorAffiliation=$csrRequestorAffiliation&
SponsorEmail=$SponsorEmail&
SponsorPhone=$SponsorPhone&
SponsorName=$SponsorName";


$FORMFIELDS =~ s/\&\s/\&/g;
$FORMFIELDS =~ s/\s/+/g;
$FORMFIELDS = "-d \"$FORMFIELDS\"";

# Build a command line for curl

# XXX Solaris grep does not take -A switch
$CR=`grep -A100 BEG $reqfile | grep -B100 END`;

$CR =~ s/\n/%0d%0a/g;
$CR =~ s/\+/%2B/g;
$CR =~ s/\//%2F/g;
$CR =~ s/=/%3D/g;
$CR =~ s/ /+/g;

$CR = "-d pkcs10Request=$CR";

# the following does not work with openssl 0.9.6
#$SUBJECT = `openssl req -in $reqfile -subject -noout `;
#$SUBJECT =~ s/^subject=\/(.*)\s$/$1/;
#$SUBJECT =~ s/\//,/g;
$SUBJECT ="DC=org,DC=$dc2,OU=People,CN=$args{name}";
$SUBJECT =~ s/ /+/g;
$SUBJECT = "-d \"subject=$SUBJECT\"";
$USERID  =  $args{name};
$USERID  =~ s/ //g;
$USERID = "-d \"uid=$USERID\"";

# the -k (insecure) switch turns off curl's check for a trusted CA on the server
# certificate when using https. curl looks in .../share/curl/cur-ca-bundle.crt 
# by default.
# you can give it a different ca bundle by using the -cacert option.
# if it can't find a bundle at all, it will skip the check.

$curlcmd = "curl $verbose $insecure -S $HIDDEN $OPTFIELDS $CR $FORMFIELDS $USERID $SUBJECT $baseURL > $output_file";

if ($args{debug}) {
    print $curlcmd."\n";
}
$res = system($curlcmd);

if ($res) {
    die "Error executing $curlcmd";
}

open OF, $output_file or die "can't open $output_file $!\n"; 
@output = <OF>;

if ($args{debug}) {
    print @output;
}

# only delete the request file if it wasn't originally specified on the
# commandline
unlink $reqfile unless $args{request};


###########################################################
# Print out request ID if its successful
# otherwise print the error message
###########################################################
$result=grep /Request Successfully Submitted/, @output;
@retError=grep /fixed\.unexpectedError/, @output;
if (@retError == 1 ) {
  $CMS_Error = $retError[0];
  $CMS_Error =~ s/fixed\.unexpectedError.*?=//;
  $CMS_Error =~  s/;//g ;
}
@CMS_Result_Info=grep /^fixed\.requestId/, @output;
if ( @CMS_Result_Info == 1 ) {
  $CMS_Request_id =  $CMS_Result_Info[0];
  $CMS_Request_id =~ s/\D//g;
}
@CMS_Policy_Error_Info = grep /^record\.policyMessage/, @output;
if ( @CMS_Policy_Error_Info == 1){
  $CMS_Policy_Error = $CMS_Policy_Error_Info[0];
  $CMS_Policy_Error =~  s/record.policyMessage.*?=//;
  $CMS_Policy_Error =~ s/<.*?>//g;    # get rid of html tags
  $CMS_Policy_Error =~ s/;//g ;
}

if ( $result > 0) {
  print "\nYour Certificate Request has been successfully submitted\n";
  print "Your Certificate Request id: $CMS_Request_id \n";
  print "\n\tYou will receive a notification email from the CA when your certificate
\thas been issued. Please disregard the instructions to download your
\tcertificate though a web browser and use the retrieveCert script instead.\n";
  print "\nNOTE: Your private key has been stored in $keyfile. retrieveCert will ask for this file\n";

  open FD , ">$currentReqFile";
  if ( ! $_) {
    print FD "$keyfile\n";
  } else {
    print "Could  not open $currentReqFile. Your key file is in $keyfile.
         Please input this value to retrieveCert.pl\n";
  }
}
else {
  print "Your Certificate Request has been rejected by the server\n";
  if ( @retError  == 1) {
    print "Unexpected error $CMS_Error\n";
  } else {
    print "$CMS_Policy_Error\n";
  }
}

unlink $output_file;


################### Usage ###############################
sub usage {
  print "usage is : -name <user's full name>
       -email <user's email>
       -phone <user's phone>
       -sponsor one of: Doug McCune, David Schissel, Martin Greenwald
       -password <optional file containing passphrase for private key>
       -request  <optional certificate request>
       -dir      <optional directory in which to store the private key>
                    defaults to $ENV{HOME}/.globus
       -debug spews lots of output to the terminal
       -help  prints this message
       any omitted argruments will be prompted for\n";
}