## OpenCA - Public Web-Gateway Command
## (c) 1998-2001 by Massimiliano Pala and OpenCA Group
##
##   File Name: basic_csr
##       Brief: basic CSR
##     Version: $Revision: 1.10.2.6 $
## Description: this script creates a CSR
##  Parameters: 
 
sub cmdBasic_csr {

my $minPinLength = getRequired('minpinlength');
my $OPERATION    = $query->param ('operation');
our $type        = $query->param ('CSR_TYPE');

## check the submitted data to be consistent

if ( not checkBasic_csr($OPERATION) ) {

	my $formFile        = getRequired('basic_csrstartform');

	## build the data

	## load the normal DN and build the html-elements
	my @elements = getRequiredList ("DN_TYPE_".$type."_ELEMENTS");
	my @user_dn = (); # array with the user fields
	## scan every element of the DN
	for (my $i=1; $i <= scalar @elements; $i++) {
			my $html;
		my $input_check = "";
		if ( defined $config->getParam ("DN_TYPE_".$type."_ELEMENT_".$i."_SELECT") ) {
			my @h = getRequiredList ("DN_TYPE_".$type."_ELEMENT_".$i."_SELECT");
			$html = $query->newInput (
					-regx   => 'MIXED',
					-intype => 'popup_menu',
					-name   => 'DN_VALUE_'.$i,
					-value  => \@h);
		 } elsif ( $elements [$i-1] =~ /EMAIL/i ) {
			$html = $query->newInput (
					-regx   => 'EMAIL',
					-intype => 'textfield',
					-size   => 30,
					-name   => 'DN_VALUE_'.$i,
					-check  => 'fill',
					-minlen => 3,
					-value  => $query->param ('DN_VALUE_'.$i));
		} else {
			$html = $query->newInput (
					-regx   => 'LATIN1_LETTERS',
					-intype => 'textfield',
					-size   => 30,
					-name   => 'DN_VALUE_'.$i,
					-check  => 'fill',
					-minlen => 3,
					-value  => $query->param ('DN_VALUE_'.$i));
		}
		push ( @user_dn, [
				getRequired ("DN_TYPE_".$type."_ELEMENT_".$i),
				$html
				]);
	}

	my $html_ra = $query->newInput (
				-regx=>'LETTERS',
		    		-intype=>'popup_menu',
		    		-name=>'ra',
       		     		-values=>
				[getRequiredList ('RegistrationAuthority')]);

	my $html_role = $query->newInput (
				-regx=>'LETTERS',
		    		-intype=>'popup_menu',
		    		-name=>'role',
       		     		-values=>[loadRoles()]);

	my $html_passwd1 = $query->newInput (
				-regx=>'*',
		    		-intype=>'password_field',
		    		-name=>'passwd1',
				-check=>'all',
		    		-size=>16,
		    		-minlen=>$minPinLength,
				-value=>$query->param('passwd1'));

	my $html_passwd2 = $query->newInput (
				-regx=>'*',
		    		-intype=>'password_field',
		    		-name=>'passwd2',
				-check=>'fill',
		    		-size=>16,
		    		-minlen=>$minPinLength,
				-value=>$query->param('passwd2'));

	my $html_bits = $query->newInput (
				-regx=>'NUMERIC',
		    		-intype=>'popup_menu',
		    		-name=>'bits',
       		     		-values=>
				[getRequiredList('Basic_CSR_Keysizes')]);

	if( not $form = $tools->getFile( $formFile )) {
		configError(i18nGettext ("Cannot load file __FILE__!", "__FILE__", $formFile));
	}

	my $header = $query->start_html(-title=>'Certificate Request',
			  -author=>'Massimiliano Pala <madwolf@openca.org>',
			  -BGCOLOR=>'white',
			  -TEXT=>'#264A8D',
			  -LINK=>'#A80C00',
			  -VLINK=>'#730000',
			  -ALINK=>'#000088');

	$header .= $query->start_multipart_form(-method=>'POST',
				-action=>$query->self_url());

	##// Let's buid the page
	my $page = $header . $form;

	## add the configured RDNs
	my $table;
	foreach my $item (@user_dn) {
		my @h = @{$item};
		$table .= "<tr>\n".
			"<th align=right>".$h[0].":</th>\n".
			"<td align=left>".$h[1]."</td>\n".
			"</tr>\n"
                  ;
	}
	$page = $query->subVar($page,"\@TABLE\@",$table);

	## add the default-fields
	$page = $query->subVar($page,'@PASSWD1@',  $html_passwd1);
	$page = $query->subVar($page,'@PASSWD2@',  $html_passwd2);
	$page = $query->subVar($page,'@BITS@',     $html_bits);
	$page = $query->subVar($page,'@RA@',       $html_ra);
	$page = $query->subVar($page,'@ROLE@',     $html_role);
	$page = $query->subVar($page,'@CSR_TYPE@', $type);
	$page = $query->subVar($page,'@MISTAKE@',  $errstruct);
        ## for header-requests
	$page = $query->subVar($page, '@_KEY@',    $query->param ('key'));

	print "$page";
	return 1;
} elsif ( $OPERATION =~ /client-filled-form/i ) {

	my $formFile2       = getRequired("DN_TYPE_".$type."_KEYGEN_SHEET");

	## load the static data
	my $PASSWD	= $query->param('passwd1');
	my $BITS	= $query->param('bits');
	my $RA          = $query->param('ra');
	my $ROLE        = $query->param('role');

	## load the dynamic data
	## load the normal DN and build the html-elements
	my @elements = getRequiredList ("DN_TYPE_".$type."_ELEMENTS");
	my $dn       = basic_csr_buildDN();
	my $ms_dn    = basic_csr_buildDN_MS();
	
	my $form;
	if( not $form = $tools->getFile( $formFile2 )) {
		configError( i18nGettext ("Can't load __FILE__", "__FILE__", $formFile2)) if ( not $form );
	}

        if (getRequired("DN_TYPE_".$type."_KEYGEN_MODE") =~ /IE/i)
        {
            $page = $form;
        } else {
            $header = $query->start_html (-title   => 'Certificate Request',
                                          -author  => 'Massimiliano Pala <madwolf@openCA.org>',
                                          -BGCOLOR => 'white',
                                          -TEXT    => '#264A8D',
                                          -LINK    => '#A80C00',
                                          -VLINK   => '#730000',
                                          -ALINK   => '#000088');

            $header.= $query->start_form (-method=>'POST',
                                          -action=>$query->referer() );

            ##// Let's buid the page
            $page = $header . $form;
        }

	## Substitute the parameters with their true values
	my $hidden = "";
	my $table  = "";
	for (my $i=1; $i <= scalar @elements; $i++) {
		$hidden .=  "<INPUT TYPE=HIDDEN NAME=\"DN_VALUE_".$i."\" ".
				   "VALUE=\"" .$query->param("DN_VALUE_".$i)."\">\n";
		$table .= "<tr bgcolor=#ffffff>\n".
			"<th align=right>".getRequired ("DN_TYPE_".$type."_ELEMENT_".$i).":</th>\n".
			"<td align=left>".$query->param("DN_VALUE_".$i)."</td>\n".
			"</tr>\n"
                  ;
	}
	$page = $query->subVar($page,'@PASSWD@',   $PASSWD);
	$page = $query->subVar($page,'@BITS@',     $BITS);
	$page = $query->subVar($page,'@CSR_TYPE@', $type);
	$page = $query->subVar($page,'@TABLE@',    $table);
	$page = $query->subVar($page,'@HIDDEN@',   $hidden);
	$page = $query->subVar($page,'@RA@',       $RA);
	$page = $query->subVar($page,'@ROLE@',     $ROLE);
	$page = $query->subVar($page,'@DN@',       $dn);
	$page = $query->subVar($page,'@MS_DN@',    $ms_dn);
        ## for header-requests
	$page = $query->subVar($page, '@_KEY@',    $query->param ('key'));

	print $page;
	return 1;
} elsif ( $OPERATION =~ /client-confirmed-form/i) {

        my $successPage = getRequired('basic_csrsuccesspage');
        my $dn = basic_csr_buildDN();

        ## load the static data
        my $RA          = $query->param('ra');
        my $ROLE        = $query->param('role');

        ## Status of FORM, possible values (in this order):
        ##	'client-filled-form' or 'client-confirmed-form'
        my $PASSWD	= $query->param('passwd1');

        my $key = "";
        my $req = undef;;
        my $bits = $query->param('bits');
        my $alg	 = $query->param('alg');
        if (getRequired ("DN_TYPE_".$type."_BODY") =~ /(Y|YES)/i) {

            if (getRequired("DN_TYPE_".$type."_KEYGEN_MODE") =~ /SPKAC/i )
            {
                my $spkac = "";

                ## load the normal DN and build the html-elements
                my @elements = getRequiredList ("DN_TYPE_".$type."_ELEMENTS");
                ## scan every element of the DN
                for (my $i=1; $i <= scalar @elements; $i++)
                {
                    my $dn_element = $query->param ('DN_VALUE_'.$i);
                    $dn_element =~ s/\\/\\\\/g;
                    $dn_element =~ s/\//\\\//g;
                    $dn_element =~ s/,/\\,/g;
                    $dn_element =~ s/=/\\=/g;
                    $spkac .= $elements [$i-1]." = ".$dn_element."\n"; 
                }

                ## load the base dn
                my @base = getRequiredList ("DN_TYPE_".$type."_BASE");
                for (my $i=1; $i <= scalar @base; $i++) {
                    if (getRequired ("DN_TYPE_".$type."_BASE_".$i) ne "")
                    {
                        $spkac .= $base [$i-1]." = ".getRequired ("DN_TYPE_".$type."_BASE_".$i)."\n";
                    }
                }

                ## add the key
                ## Delete ^M
                my $NEWKEY = $query->param('newkey');
                $NEWKEY =~ s/\015|\n//g;
                $spkac .= "SPKAC = $NEWKEY\n";

                if( not $req = new OpenCA::REQ (SHELL  => $cryptoShell,
                                                DATA   => $spkac,
                                                INFORM => "SPKAC" )) {
                    generalError( gettext("Error while creating REQ object.").
                                  " ".$OpenCA::REQ::errval, $OpenCA::REQ::errno );
                }

            } elsif (getRequired("DN_TYPE_".$type."_KEYGEN_MODE") =~ /IE/i ) {
                my $ie = "";

                $ie .= "-----BEGIN CERTIFICATE REQUEST-----\n";
                $ie .= $query->param('request');
                $ie .= "-----END CERTIFICATE REQUEST-----";

                if( not $req = new OpenCA::REQ( SHELL  => $cryptoShell, 
                                                DATA   => $ie,
                                                INFORM => "PEM" )) {
                    generalError( gettext ("Error while creating REQ object.").
                                  " ".$OpenCA::REQ::errval, $OpenCA::REQ::errno );
                };

            } else { ## this is server mode
                ## generate keypair
                my $TempDir = getRequired( 'tempdir' );

                ## Get the parameters
                my $keyFile	= "$TempDir/key_${$}.pem";

                ## create the key
                if( not $cryptoShell->genKey(
                                             BITS      => $bits,
                                             OUTFILE   => $keyFile,
                                             ALGORITHM => $alg,
                                             PASSWD    => $PASSWD ) ) {
                    generalError (gettext ("Cannot create keypair!").
                                  "<br>".$OpenCA::OpenSSL::errno,
                                  $OpenCA::OpenSSL::errno);
                }

                ## change to pkcs#8
                $key = $cryptoShell->dataConvert (
                                                  DATATYPE  => "KEY",
                                                  INFORM    => "PEM",
                                                  OUTFORM   => "PKCS8",
                                                  INPASSWD  => $PASSWD,
                                                  OUTPASSWD => $PASSWD,
                                                  INFILE    => $keyFile );
                if ( not $key ) {
                    generalError ( gettext ("Cannot convert key to PKCS#8!"));
                }

                ## generate PKCS#10 request
                $req = new OpenCA::REQ (
                                        SHELL   => $cryptoShell,
                                        KEYFILE => $keyFile,
                                        SUBJECT => $dn,
                                        PASSWD  => $PASSWD,
                                        FORMAT  => "PEM");
                if (not $req) {
                    generalError ( i18nGettext ("Cannot create request! <br>\n(__ERRVAL__)",
                                                "__ERRVAL__",
                                                $OpenCA::REQ::errval),
                                   $OpenCA::REQ::errno);
                }
                unlink ($keyFile);
            }
        }

        ## compose request
        my $tmp = "-----BEGIN HEADER-----\n";
        if (getRequired ("DN_TYPE_".$type."_BODY") =~ /(Y|YES)/i) {
            if (getRequired ("DN_TYPE_".$type."_KEYGEN_MODE") =~ /SPKAC/i)
            {
                $tmp .= "TYPE = SPKAC\n";
            } elsif (getRequired ("DN_TYPE_".$type."_KEYGEN_MODE") =~ /IE/i) {
                $tmp .= "TYPE = IE\n";
            } else {
                $tmp .= "TYPE = PKCS#10\n";
            }
        } else {
            $tmp .= "TYPE = HEADER\n";
        }

        ## build serial
        if ( (getRequired ('CgiServerType') =~ /(RA|CA)/i) and $query->param ('key') ) {
            $tmp .= "SERIAL = ".$query->param ('key')."\n";
        } else {
            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)) {
                generalError ( gettext ("Database fails during counting the already existing requests!"), 669);
            } else {
                $req_elements++;
            }
            my $new_serial = ($req_elements << getRequired ("ModuleShift")) | getRequired ("ModuleID");
            $tmp .= "SERIAL = ".$new_serial."\n";
        }
        $tmp .= "NOTBEFORE = " . $tools->getDate() . "\n";
        my $PASSWD = $query->param('passwd1');
        if ($PASSWD) {
            my $pin_digest = $cryptoShell->getDigest (
                                                      DATA      => $PASSWD,
                                                      ALGORITHM => "sha1");
            if (not $pin_digest) {
                generalError ( gettext ("OpenSSL fails during the calculation of the hash from the passphrase!"), 670);
            }
            $tmp .= "PIN = $pin_digest\n";
        }
        $tmp .= "RA = " . $query->param('ra') . "\n";
        $tmp .= "ROLE = $ROLE\n";
        if (getRequired ("DN_TYPE_".$type."_BODY") !~ /(Y|YES)/i) {
            $tmp .= "SUBJECT = $dn\n";
            $tmp .= "KEY_ALGORITHM = ".$alg."\n";
            $tmp .= "KEY_BITS = ".$bits."\n";
        }
        $tmp .= "-----END HEADER-----\n";
        if (getRequired ("DN_TYPE_".$type."_BODY") =~ /(Y|YES)/i) {
            $tmp .= $req->getBody();
            $tmp .= $key if ($key);
        }

        my $new_req;
        if( not $new_req = new OpenCA::REQ( SHELL=>$cryptoShell, DATA=>$tmp) ) {
            generalError( i18nGettext ("Internal Request Error (__ERRVAL__)",
                                       "__ERRVAL__",
                                       $OpenCA::REQ::errval),
                          $OpenCA::REQ::errno );
        }

        if( not $db->storeItem( 
                               DATATYPE => PENDING_REQUEST,
                               OBJECT   => $new_req,
                               INFORM   => PEM,
                               MODE     => "INSERT" )) {
            generalError( gettext ("Error while storing REQ in database!").
                          " ".$db->errval(), $db->errno() );
        };

        ## remove temporary files
        $tools->deleteFiles (DIR => $TempDir, FILTER => "key_${$}.pem");

        if( not $ret = $tools->getFile( $successPage )) {
            configError( i18nGettext ("Can't load __FILE__", "__FILE__", $successPage)) if ( not $ret );
        }

        $ret = $query->subVar($ret,"\@CSR_SERIAL\@",$new_req->getSerial());

        print "$ret";
        return 1;

} 

die gettext ("ERROR: Status Unkown!");

}

## we support the following state:
##
## 1. ""
## 2. client-filled-form
## 3. client-confirmed-form

sub checkBasic_csr {

    my $status = $_[0];
    my $mistake = "";
    my $minPinLength = getRequired('minpinlength');

    ## general check
    ## which type of request do we have to handle?
    my @types = getRequiredList ('DN_TYPES');
    if ( not grep (/^${type}$/i, @types) ) {

        ## starting browser detection
        my $USER_AGENT = $query->user_agent();

        ## Get the Browser Name and the Version
        if ( $USER_AGENT =~ /Opera/i )
        {
            $type = "SPKAC";
        } elsif ( $USER_AGENT =~ /MSIE/i )
        {
            $type = "IE";
        } elsif ( $USER_AGENT =~ /Mozilla/i ) {
            $type = "SPKAC";
        } else {
            $type = "BASIC";
        }
    }

    ## second level check
    if ($status)
    {
        ## load the normal DN and build the html-elements
        my @elements = getRequiredList ("DN_TYPE_".$type."_ELEMENTS");
        ## scan every element of the DN
        for (my $i=1; $i <= scalar @elements; $i++)
        {
            if ( $elements [$i-1] =~ /EMAIL/i )
            {
                $mistake .= $query->newInputCheck({
                                -regx   => 'EMAIL',
                                -intype => 'textfield',
                                -size   => 30,
                                -name   => 'DN_VALUE_'.$i,
                                -check  => 'fill',
                                -minlen => 3});
            } else {
                $mistake .= $query->newInputCheck({ 
                                -regx   => 'LATIN1_LETTERS',
                                -intype => 'textfield',
                                -size   => 30,
                                -name   => 'DN_VALUE_'.$i,
                                -check  => 'fill',
                                -minlen => 3});
            }

        }
        if ($query->param ('passwd1'))
        {
            $mistake .= $query->newInputCheck ({
                            -regx=>'*',
                            -intype=>'password_field',
                            -name=>'passwd1',
                            -check=>'all',
                            -size=>16,
                            -minlen=>$minPinLength});
        }

        #quick hack, to get a nicer output...
        my $tmp = "<BR>|OK|[\ \(\)]";
        $mistake =~ s/$tmp//g;

        if (defined $query->param ('passwd1') and defined $query->param ('passwd2'))
        {
            if ($query->param ('passwd1') ne $query->param ('passwd2'))
            {
                $mistake .= "<BR>";
                $mistake .= gettext ("Two different pin inserted. Please go <B><I>back</I></B> and correct the error.");
            }
        }

        ## Checking for Boguous Systems ( POST method, REFERER doc, etc... )
        my $METHOD       = $query->request_method();
        if ($METHOD !~ /POST/i) {
            $mistake .= gettext ("This command can only be used with from which are using POST as METHOD!");
        };

    } else {
        return undef;
    }

    if ($mistake)
    {
        $errstruct = $mistake;
        return undef;
    } else {
        return 1;
    }
}

sub basic_csr_buildDN {
    my $dn = "";

    ## load the normal DN and build the html-elements
    my @elements = getRequiredList ("DN_TYPE_".$type."_ELEMENTS");
    ## scan every element of the DN
    for (my $i=1; $i <= scalar @elements; $i++)
    {
        my $dn_element = $query->param ('DN_VALUE_'.$i);
        $dn_element =~ s/\\/\\\\/g;
        $dn_element =~ s/\//\\\//g;
        $dn_element =~ s/,/\\,/g;
        $dn_element =~ s/=/\\=/g;
        $dn .= $elements [$i-1]."=".$dn_element.","; 
    }

    ## load the base dn
    my @base = getRequiredList ("DN_TYPE_".$type."_BASE");
    for (my $i=1; $i <= scalar @base; $i++) {
        if (getRequired ("DN_TYPE_".$type."_BASE_".$i) ne "")
        {
            $dn .= $base [$i-1]."=".getRequired ("DN_TYPE_".$type."_BASE_".$i).",";
        }
    }

    ## remove trailing ","
    $dn =~ s/,$//;

    return $dn;
}

sub basic_csr_buildDN_MS {
    my $dn = "";

    ## the DN must be build in the reverse order

    ## load the base dn
    my @base = getRequiredList ("DN_TYPE_".$type."_BASE");
    for (my $i=scalar @base; $i > 0; $i--) {
        if (getRequired ("DN_TYPE_".$type."_BASE_".$i) ne "")
        {
            $dn .= $base [$i-1]."=".getRequired ("DN_TYPE_".$type."_BASE_".$i).",";
        }
    }

    ## load the normal DN and build the html-elements
    my @elements = getRequiredList ("DN_TYPE_".$type."_ELEMENTS");
    ## scan every element of the DN
    for (my $i=scalar @elements; $i > 0; $i--)
    {
        my $dn_element = $query->param ('DN_VALUE_'.$i);
        $dn_element =~ s/\\/\\\\/g;
        $dn_element =~ s/\//\\\//g;
        $dn_element =~ s/,/\\,/g;
        $dn_element =~ s/=/\\=/g;
        $dn .= $elements [$i-1]."=".$dn_element.","; 
    }


    ## remove trailing ","
    $dn =~ s/,$//;

    ## microsoft uses E for emailaddress
    $dn =~ s/,\s*emailAddress\s*=/,E=/i;
    $dn =~ s/^\s*emailAddress\s*=/E=/i;

    return $dn;
}

1;
