## OpenCA - Public Web-Gateway Command
## (c) 2002-2003 by Massimiliano Pala and OpenCA Group
## (c) Copyright 2004 The OpenCA Project
##
##   File Name: scepPKIOperation
##     Version: $Revision : 0.2 $
##       Brief: manage SCEP certificates/crl operations
## Description: supports handling of SCEP commands and PKI Operations
##              for enrollment.
##  Parameters: operation, message

use strict;

our ($plain_csr);

sub cmdScepPKIOperation {

  my ( $operation, $message, $cert );
  my ( $h, $i, $p7_file, $csr_file, $cert_file, $reccert_file, $response );
  my ( $scep_cmd, $scep_pwd, $scep_cert, $scep_key, $scep_crl, $scep_failinfo );
  my ( $scep_tid );
  my $chainDir;

  ##// Let's get parameters
  $operation = $query->param('operation');
  $message = $query->param('message');

  ## setup local vars
  $scep_cmd  = getRequired ("scepPath");
  $scep_pwd  = getRequired ("scepRAPasswd");
  $scep_cert = getRequired ("scepRACert");
  $scep_key  = getRequired ("scepRAKey");
  $scep_crl  = getRequired ("CRLDir") . "/cacrl.pem";

  $chainDir  = getRequired ( 'ChainDir' );

  $p7_file      = getRequired ( 'tempdir' ) . "/scep_pkiOp_$$.p7";
  $csr_file     = getRequired ( 'tempdir' ) . "/scep_pkiOp_$$.csr";
  $cert_file    = getRequired ( 'tempdir' ) . "/scep_pkiOp_$$.crt";
  $reccert_file = getRequired ( 'tempdir' ) . "/scep_client_$$.crt";

  ## insert newlines every 64 characters
  ## to avoid crashs because of too long lines
  my $h_message = $message;
  $message  = "";
  while ($h_message)
  {
    $message .= substr ($h_message, 0, 64)."\n";
    $h_message = substr ($h_message, 64, length ($h_message));
  }
  $message =~ s/[\n\r][\n\r]*/\n/g;
  $message =~ s/\n$//;

  $tools->saveFile( FILENAME=>$p7_file, DATA=>$message );

  $errno  = 0;
  $errval = "";
  $scep_failinfo = "";

  ## We have to:
  ##   1. parse the request
  ##   2. check the requirements
  ##   3. build the response
  ##   4. send back the reply

  ## Send the response to the SCEP client
  print "Content-type: application/x-pki-message\n\n";

  ##
  ## common actions per scep-msg
  ##

  ## get transid
  last SWITCH if not scepGetTID();

  ## get recipient cert
  last SWITCH if not scepGetReqClientCert();

  ## first we need the message type to know what to do
  open OUT, "-|", "$scep_cmd -in $p7_file -noout -print_msgtype";
  my $msgtype = join '', <OUT>;
  close OUT;

  ## FIXME: is this correct perl!?
  $_ = $msgtype;

  SWITCH: {
      if (/PKCSReq/i)
      {
	  ## get request
	  last SWITCH if not scepGetRequest();
          ## store request
          last SWITCH if not scepStoreRequest();
          ## send pending answer
	  last SWITCH if not scepAnswerPending();
      }

      if (/GetCertInitial/i)
      {

          ## search the request for issued
          my @reqs = $db->searchItems (DATATYPE => "ARCHIVED_REQUEST", SCEP_TID => $scep_tid);

          my $response;
          if (scalar @reqs)
          {
              ## SUCCESS

              ## search the highest CSR
              my $csr = undef;
              foreach my $req (@reqs)
              {
                  $csr = $req if (not $csr or $csr->getSerial() < $req->getSerial())
              }

              ## load certificate via CSR_SERIAL
              my @certs = $db->searchItems (DATATYPE => "CERTIFICATE", CSR_SERIAL => $csr->getSerial());
              my $cert  = $certs[0];
              $tools->saveFile (FILENAME => $cert_file,DATA=>$cert->getPEM());

              ## FIXME: check if Cert has been revoked, than send fail-msg instead
              ##        no openca-error!! just scep-failure-msg for client

              ## build response
              $ENV{pwd} = $scep_pwd;
              open OUT, "-|", "$scep_cmd -new -signcert $scep_cert -msgtype CertRep -status SUCCESS -keyfile $scep_key -passin env:pwd -in $p7_file -reccert $reccert_file -issuedcert $cert_file -outform DER";
              $response = join '', <OUT>;
              close OUT;
              delete $ENV{pwd};

	      print $response;
	      last SWITCH;
          }

	  ## search the request for deleted
          my @reqs = $db->searchItems (DATATYPE => "DELETED_REQUEST", SCEP_TID => $scep_tid);
	  if (scalar @reqs)
	  {
              # we found a deleted request - so send failur msg
	      $scep_failinfo = "badRequest";
              scepAnswerFailure();
              last SWITCH;

	  } else {

	      scepAnswerPending();
	      last SWITCH;

          }

      }

      if (/GetCert/i)
      {
          ## FIXME: to be implemented - so long we send an failure msg

          $scep_failinfo = "badRequest";
          scepAnswerFailure();
	  last SWITCH;

      	  ## only success or failure possible, no pending
	  ## same reply like sending out cert

      }

      if (/GetCRL/i)
      {
	  ## send crl message
	  ## build response
          $ENV{pwd} = $scep_pwd;
	  open OUT, "-|", "$scep_cmd -new -signcert $scep_cert -msgtype CertRep -status SUCCESS -crlfile $scep_crl -keyfile $scep_key -passin env:pwd -in $p7_file -reccert $reccert_file -outform DER";
          $response = join '', <OUT>;
          close OUT;
          delete $ENV{pwd};
	  print $response;

          last SWITCH;
      }

      if (//i)
      {
          last SWITCH;
      }
  }

if ($scep_failinfo)
  {
      ## build error response
      $ENV{pwd} = $scep_pwd;
      open OUT, "-|", "$scep_cmd -new -signcert $scep_cert -msgtype CertRep -status FAILURE -failinfo $scep_failinfo -keyfile $scep_key -passin env:pwd -in $p7_file -reccert $reccert_file -outform DER";
      $response = join '', <OUT>;
      close OUT;
      delete $ENV{pwd};
      print $response;
  }

  ## output error if necessary
  if ($errno)
  {
      ## do openca error stuff
      generalError ($errval, $errno);
  }

  ## cleanup tmp-files and passphrases
  delete $ENV{pwd};
  unlink $p7_file       if -e $p7_file;
  unlink $csr_file      if -e $csr_file;
  unlink $cert_file     if -e $cert_file;
  unlink $reccert_file  if -e $reccert_file;

  ## return success for storage commit
  return 1;

  ##
  ## Functions for MSG-Evaluation
  ##

  sub scepGetTID {
    open OUT, "-|", "$scep_cmd -in $p7_file -noout -print_transid";
    $scep_tid = join '', <OUT>;
    close OUT;
    $scep_tid =~ s/.*=//;
    $scep_tid =~ s/[\n\r]//g;
    if (not $scep_tid)
    {
      $errno  = 723705;
      $errval = gettext ("Cannot extract the transaction ID from the SCEP message!");
      ## FIXME: appropriate Error Reason?
      $scep_failinfo = "badMessageCheck";
      return -1;
    }

    return 1;
  } ## end of scepGetTID

  sub scepGetReqClientCert {
    $ENV{pwd} = $scep_pwd;
    `$scep_cmd -in $p7_file -keyfile $scep_key -passin env:pwd -noout -print_scert > $reccert_file`;
    delete $ENV{pwd};
    if ($@)
    {
      $errno  = 723709;
      $errval = gettext ("There is a problem with the scep program.").$@;
      print STDERR $errno.": ".$errval."\n";
      $scep_failinfo = "badMessageCheck";
      return -1;
    }

    return 1;
  } ## end of scepGetReqClientCert

  sub scepGetRequest {
    $ENV{pwd} = $scep_pwd;
    open OUT, "-|", "$scep_cmd -in $p7_file -keyfile $scep_key -passin env:pwd -noout -print_req";
    $plain_csr = join '', <OUT>;
    close OUT;
    delete $ENV{pwd};

    if (not $plain_csr)
    {
      $errno  = 723701;
      $errval = gettext ("Cannot extract the request from the SCEP message!");
      print STDERR $errno.": ".$errval."\n";
      $scep_failinfo = "badRequest";
      return -1;
    }

    return 1;
  } ## end of scepGetRequest

  ##
  ## Functions for SCEP-Processing
  ##

  sub scepStoreRequest {

    ## never store a request twice
    my @list = $db->searchItems (DATATYPE => "REQUEST", SCEP_TID => $scep_tid);

    if (not @list or not scalar @list)
    {
      ## build OpenCA request
      my $tmp;
      $tmp = "-----BEGIN HEADER-----\n";
      $tmp .= "TYPE = PKCS#10\n";
      my $last_req = libDBGetLastItem ("REQUEST");
      my $req_elements = 0;
      $req_elements    = $last_req->getSerial("REQUEST") if ($last_req);
      $req_elements  >>= getRequired ("ModuleShift");
      if ((not defined $req_elements) or ($req_elements < 0)) {
        $errno  = 723713;
        $errval = gettext ("The database fails during counting the already existing requests!");
        print STDERR $errno.": ".$errval."\n";
        $scep_failinfo = "badRequest";
        return -1;
      } else {
        $req_elements++;
      }
      my $new_serial = ($req_elements << getRequired ("ModuleShift")) | getRequired ("ModuleID");
      $tmp .= "SERIAL = $new_serial\n";
      $tmp .= "NOTBEFORE = " . $tools->getDate() . "\n";
      $tmp .= "LOA = 10\n" if ( getRequired('USE_LOAS') =~ m/yes/i);
      $tmp .= "ROLE = VPN Server\n";
      $tmp .= "SCEP_TID = ".$scep_tid."\n";
      $tmp .= "-----END HEADER-----\n";
      $tmp .= $plain_csr;

      ## create new object
      my $req;
      if( not $req = new OpenCA::REQ( SHELL   => $cryptoShell,
                                      GETTEXT => \&i18nGettext,
                                      DATA    => $tmp) )
      {
        $errno  = 723717;
        $errval = gettext ("Internal Request Error");
        print STDERR $errno.": ".$errval."\n";
        $scep_failinfo = "badRequest";
        return -1;
      }
      ## store request in database
      if( not $db->storeItem(
                DATATYPE=>"NEW_REQUEST",
                OBJECT=>$req,
                INFORM=>"PEM",
                MODE=>"INSERT" )) {
        $errno  = 723721;
        $errval = gettext ("Error while storing REQ in database!");
        print STDERR $errno.": ".$errval."\n";
        $scep_failinfo = "badRequest";
	return -1;
      }

    } ## end of creating new cert-req

    return 1;
  } ## end of scepStoreRequest

  ##
  ## Functions for generating SCEP-Answers
  ##

  sub scepAnswerPending {
        $ENV{pwd} = $scep_pwd;
        open OUT, "-|", "$scep_cmd -new -signcert $scep_cert -msgtype CertRep -status PENDING -keyfile $scep_key -passin env:pwd -in $p7_file -reccert $reccert_file -outform DER";
        $response = join '', <OUT>;
        close OUT;
        delete $ENV{pwd};

        print $response;

        ## FIXME: do errorchecking, message created without errors?
        ##        create openca-error

        return 1;
  } ## end of scepAnswerPending

  sub scepAnswerFailure {
      $ENV{pwd} = $scep_pwd;
      ## set standard error if no is specified so far
      $scep_failinfo = "badRequest" if not $scep_failinfo;
      open OUT, "-|", "$scep_cmd -new -signcert $scep_cert -msgtype CertRep -status FAILURE -failinfo $scep_failinfo -keyfile $scep_key -passin env:pwd -in $p7_file -reccert $reccert_file -outform DER";
      $response = join '', <OUT>;
      close OUT;
      delete $ENV{pwd};

      print $response;

      ## FIXME: do errorchecking, message created without errors?
      ##        create openca-error

      return 1;
  } ## end of scepAnswerFailure

}

1;
