## OpenCA - Public Web-Gateway Command
## (c) 1999-2009 by Massimiliano Pala and OpenCA Group
##
##   File Name: startAutoCA
##       Brief: start Auto (On-Line) CA Daemon
##     Version: $Revision: 1.18 $
## Description: this script starts the On-Line CA Daemon
##  Parameters: 

use strict;

sub cmdStartAutoCA
{
	my $client 	= shift;
	my $pidfile = undef;
	my $ret 		= undef;

	## Parameters used by the issuing certificate process
	our ($DEBUG, %AUTOCONF);

	## Uncomment this to get detailed DEBUG information
	## $DEBUG = 1;

	# print SDTDERR "cmdStartAutoCA()->Started in BATCH mode. ($batch)\n" if ($batch);

	$pidfile = $AUTOCONF{"var_prefix"}."/tmp/openca_autoca.pid";
	$ret = start_process(	PIDFILE => $pidfile, 
												FUNCTION => \&AutoCaDaemon,
												GET_CA_TOKEN => 1,
												HUP_FUNCTION => \&closeAutoCA,
												CLIENT_SOCK => $client );

	## If an error is detected, report it!
	if ($ret < 0)
	{
		generalError( "ERROR, can not spawn new processes!\n" ) 
	}
	else
	{
		print STDERR "cmdStartAutoCA()->Auto Certificate Issuing Daemon Started Successfully (pid $ret).\n";
	}

	1;
}

sub closeAutoCA
{
	my $reason = shift;
	my $statusfile = undef;

	our (%AUTOCONF, $caDB);

	$statusfile = $AUTOCONF{"var_prefix"}."/tmp/openca_autoca_status.txt";
	close_process(STATUSFILE => $statusfile, DB => $caDB);

	## Print out some info
	print STDERR "closeAutoCA()::Terminating Auto Certificate Issuing Daemon process.";

	## Exit the process
	exit(1);
}

sub AutoCaDaemon
{
	my $func_params = { @_ };
	my $ca_token = undef;
	my $crypto_shell = undef;
	my $locDB = undef;
	my $params = undef;
	my $loas = undef;
	my $sleepSecs = 30;

	## Get the CA Token
	$ca_token = $func_params->{CA_TOKEN};
	if ((not defined ($func_params->{CA_TOKEN})) or ($ca_token eq ""))
	{
		print STDERR "[$$] On-Line CA::Error::No CA Token passed!\n";
		exit (1);
	}

	print STDERR "[$$] On-Line CA::Loading LOAConfiguration..." if ($DEBUG);
	$loas = libGetLoaLevels();
	print STDERR " Ok ($loas)\n" if ($DEBUG);

	# Load the parameters
	$params = startAutoCA_loadParams();

	if (not ($locDB = newConnectDB()))
	{
		print STDERR "[$$] On-Line CA::ERROR::Can not connect to DB! Exiting AutoCA Process!\n";
		exit(1);
	}
	else
	{
		print STDERR "[$$] On-Line CA::Start()->DB connection Ok.\n" if ($DEBUG);
	}
	$params->{DB} = $locDB;

	if ((defined $params->{SLEEP}) and ($params->{SLEEP} > 0))
	{
		$sleepSecs = $params->{SLEEP};
	}

	if ($params->{debug})
	{
		print STDERR "[$$] On-Line CA::Enabling Extended Logging (DEBUG).\n";
		$DEBUG = 1;
	}

	while (1) 
	{
		# Only way I have found to get rid of perl modules memory leaks is to fork
		my $subpid = fork();
		if (not defined $subpid) {
			print STDERR "Can not fork. Resources not available\n";
		} elsif ($subpid == 0) {
			# Child

			my $retVal = 0;

			if ($DEBUG)
			{
			   print STDERR "[$$] On-Line CA::Cycle Start!\n";
			   print STDERR "[$$] On-Line CA::DB=>$locDB\n";
			   print STDERR "[$$] On-Line CA::CRYPTO=>$ca_token\n";
			};

			if (not $locDB->connect())
			{
				print STDERR "[$$] On-Line CA::DB CONNECT ERROR::" . 
					$locDB->{errno} . " - " .
					$locDB->{errval} . "\n";

				## Connection to the DB was not successful - we sleep and re-try later!
				print STDERR "[$$] On-Line CA::DB CONNECT ERROR::Sleeping for $sleepSecs secs.\n" if ($DEBUG);
				sleep($sleepSecs);
				print STDERR "[$$] On-Line CA::DB CONNECT ERROR::Retrying...\n" if ($DEBUG);
				next;
			}
			elsif ($DEBUG)
			{
				print STDERR "[$$] On-Line CA::DB CONNECT Ok\n";
			}

			## Set the token parameter
			$params->{DB} = $locDB;
			$params->{CA_TOKEN} = $ca_token;
			$params->{LOALIST} = $loas;

			## Execute the autoCAProcess
			$retVal = autoCAProcess(%{$params});

			print STDERR "[$$] On-Line CA::Disconnecting from DB.\n" if ($DEBUG);
			if (not $locDB->disconnect())
			{
				print STDERR "[$$] On-Line CA::DB DISCONNECT ERR::". 
						$locDB->{errno} . " - " .
						$locDB->{errval} . "\n";
			}
			elsif ($DEBUG)
			{
				print STDERR "[$$] On-Line CA::DB DISCONNECT Ok\n";
			}; 

			exit(0);
		} else {
			# Parent
			waitpid($subpid,0);
		}

		## Sleep
		print STDERR "[$$] On-Line CA::Sleeping for $sleepSecs secs.\n" if ($DEBUG);
		sleep($sleepSecs);
	}
}

sub autoCAProcess
{

	## get the parameters
	## Get the parameters
	my $params	 = { @_ };

	my ($request, $operator_cert, $operator_serial, $signature, $role_sig, $cert);
	our ($query, $errno, $errval, $ca_token, $caDB, $DEBUG);

	if (not defined ($params))
	{
		print STDERR "[$$] On-Line CA::autoCAProcess()::Unrecoverable Error. Exiting!\n";
		return(-1);
	};

	if ($DEBUG)
	{
		print STDERR "[$$] On-Line CA::autoCAProcess() started\n";
		print STDERR "[$$] On-Line CA::Params::CA_TOKEN=>" . $params->{CA_TOKEN} ."\n";
	};

	# my $chainDir = getRequired('ChainDir');
	# my $tempDir = getRequired ('TempDir');

	## there can never be a request 0
	my $key = 0;
	my $dataType = "APPROVED_REQUEST";
	my $maxItems = 100;

	print STDERR "[$$] On-Line CA::autoCAProcess()::Start Request Listing\n" if ($DEBUG);

	$caDB = $params->{DB};
	my @list = $caDB->searchItems( DATATYPE => $dataType,
                               ITEMS    => $maxItems );

	my $loas = $params->{LOALIST};

	print STDERR "[$$] On-Line CA::autoCAProcess()::Retrieved $#list requests\n" if ($DEBUG);

	my $reqNumber = 0;
	while ($request = pop(@list))
	{
		## Provide some debugging info
		print STDERR "[$$] On-Line CA::autoCAProcess()::Processing request => $reqNumber\n" if ($DEBUG);

		## Gets the Request's Key
		$key = $request->getSerial();

		print STDERR "[$$] On-Line CA::autoCAProcess()::Current Request Key => $key\n" if ($DEBUG);

		## is there a signature?
		$signature = libGetSignatureObject (OBJECT => $request);
		print STDERR "[$$] On-Line CA::autoCAProcess()::got signature ($signature)\n" if ($DEBUG);

		if(ValueIsInArray("1", $params->{reqsig}))
		{
			if (not $signature)
			{
				print STDERR "[$$] On-Line CA::autoCAProcess()::" .
						i18nGettext ( "CSR __CSR_SERIAL__ ignored " . 
												"because the signature is required.\n",
												"__CSR_SERIAL__", $request->getSerial());

				## Skip the request - no signature but sig is required
				next;
			}

			## Let's get the cert from the signature and validate it!
			$operator_cert = libGetSignerCertificateDB(SIGNATURE => $signature, DB => $caDB);
			if (not $operator_cert)
			{
				if ($DEBUG)
				{
					print STDERR "[$$] ". i18nGettext("CSR __CSR_SERIAL__ ignored because the " .
					  "signer's certificate is not loadable.\n",
						"__CSR_SERIAL__", $request->getSerial()) ;
				}

				## Let's skip the request - we can not find the cert that signed
				## it (approved), so we do not want to automatically issue the cert!
				next;
			}

			## check role of signer
			if ((not ValueIsInArray("Any",$params->{ra})) and 
					(not ValueIsInArray($operator_cert->getParsed()->{HEADER}->{ROLE}, $params->{ra})))
			{
				if ($DEBUG)
				{
					print STDERR "[$$] " .i18nGettext("CSR __CSR_SERIAL__ ignored because the ".
						"signer's role is __ROLE__.\n",
						"__CSR_SERIAL__", $request->getSerial(),
						"__ROLE__", $operator_cert->getParsed()->{HEADER}->{ROLE});
				};

				## Skip the request - the signer's did not have the right role!
				next;
			}
		} 
		elsif ($DEBUG)
		{
			print STDERR "On-Line CA:: No Signature is required\n";
		}

		## check the requsted role
		if ( (not ValueIsInArray("Any", $params->{role})) and
			( not ValueIsInArray($request->getParsed()->{HEADER}->{ROLE}, $params->{role})))
		{
			if ($DEBUG)
			{
		    print STDERR "[$$] On-Line CA::" .
                	i18nGettext ( "CSR __CSR_SERIAL__ ignored because the".
					" requested role is __ROLE__.\n",
					"__CSR_SERIAL__", $request->getSerial(),
					"__ROLE__", $request->getParsed()->{HEADER}->{ROLE});
			};
			next;

		}

		## Check the request for the RA
		if ( (not ValueIsInArray("Any", $params->{ra})) and
			(not ValueIsInArray( $request->getParsed()->{HEADER}->{RA}, $params->{ra})))
		{
			if ($DEBUG)
			{
				print STDERR "[$$] On-Line CA::" .
          i18nGettext ( "CSR __CSR_SERIAL__ ignored because the".
												" requested RA is __RA__.\n",
												"__CSR_SERIAL__", $request->getSerial(),
												"__RA__", $request->getParsed()->{HEADER}->{RA});
			};
			next;
		}

		## Check the request LOA
		my $loaName = undef;
		if ((defined $request->getParsed()->{HEADER}->{LOA}) and 
			($request->getParsed()->{HEADER}->{LOA} ne ""))
		{
			$loaName = $loas->{$request->getParsed()->{HEADER}->{LOA}};
		}

		if ((not ValueIsInArray("Any", $params->{loa})) and
			(not ValueIsInArray($loaName, $params->{loa})))
		{
			if ($DEBUG)
			{
				print STDERR "[$$] On-Line CA::" .
          i18nGettext ( "CSR __CSR_SERIAL__ ignored because the".
												" requested LOA is __LOA__ ($loaName).\n",
												"__CSR_SERIAL__", $request->getSerial(),
												"__LOA__", $request->getParsed()->{HEADER}->{LOA});
				print STDERR "[$$] On-Line CA::autoCAProcess():: Accepted LOAS => ";
				my @arr = @{$params->{loa}};
				foreach my $ll (@arr)
				{
					print STDERR "$ll ";
				}
				print STDERR "\n";
			};
			next;
		}

		## Now let's check the key size
		if ((not ValueIsInArray("Any", $params->{keysize})) and
				(not ValueIsInArray ($request->getParsed()->{HEADER}->{KEY_BITS}, $params->{keysize})))
		{
			if ($DEBUG)
			{
				print STDERR "[$$] On-Line CA::" . i18nGettext ("CSR __CSR_SERIAL__ ignored because the " .
					"keysize is __SIZE__.\n", "__CSR_SERIAL__", $request->getSerial(),
					"__SIZE__", $request->getParsed()->{HEADER}->{KEY_BITS});
			};

			## Wrong Keysize, skip!
			next;
		}

		## Now let's check the algorithm
		if ((not ValueIsInArray("Any", $params->{algor})) and
				(not ValueIsInArray ($request->getParsed()->{HEADER}->{KEY_ALGORITHM}, $params->{algor})))
		{
			if ($DEBUG)
			{
				print STDERR "[$$] On-Line CA::" . i18nGettext ("CSR __CSR_SERIAL__ ignored because the " .
					"algorithm is __ALGOR__.\n", "__CSR_SERIAL__", $request->getSerial(),
					"__ALGOR__", $request->getParsed()->{HEADER}->{KEY_ALGORITHM});
			};

			## Not among the approved algorithms, skip!
			next;
		}

		## issue certificate
		print STDERR "[$$] On-Line CA::Issuing the certificate\n" if ($DEBUG);

		$cert = libIssueCertificate (
				KEY      => $request->getSerial(),
				DATATYPE => "APPROVED_REQUEST",
				CA_TOKEN => $params->{CA_TOKEN},
				DB	 		 => $caDB );

		if (not $cert)
		{
			our ($errno, $errval);

			## There's been an error: what shall we do ?
			## Options to implement:
			## - Send an email to the administrator and put the
			##   request back onto the queue (pending reqs ?)
			print STDERR "[$$] On-Line CA::ERROR in issuing the certificate\n";
			print STDERR "[$$] On-Line CA::ERROR::$errno::$errval\n";
		}
		else
		{
			if ($DEBUG)
			{
				print STDERR "[$$] On-Line CA::certificate issued correctly\n";
				print STDERR "[$$] On-Line CA::certificate data::$cert\n";
				print STDERR "[$$] On-Line CA::certificate data::" . $cert->getPEM() . "\n";
			};
	
			if (not $caDB->commit())
			{
				print STDERR "[$$] On-Line CA::DBI " .
					"commit error (" . $caDB->{errno} . " - " .  $caDB->{errval} . ")!\n";
			}
			else
			{
				print STDERR "[$$] On-Line CA::DBI commit Ok\n" if ($DEBUG);
			}
		}
	}

	# Here we finished scanning the current list of requests and we
	# just return OK
	print STDERR "[$$] On-Line CA::finished cycling through reqs\n" if ($DEBUG);

	return(1);
}

sub getParamsStartAutoCA
{
	our ($query, $DEBUG, $self);
	my $result = undef;
	my $pidfile = $AUTOCONF{"var_prefix"}."/tmp/openca_autoca.pid";
	my $status = libGetPidProcessStatus ( $pidfile );

	if( $status gt 0 )
	{
		return undef;
	};

	if (not $_[0])
	{
		my %labels = undef;

		my $params = startAutoCA_loadParams();

		my $html_startup = $query->newInput (
					-regx=>'NUMBERS',
					-intype=>'checkbox',
					-name=>'startup',
					-value=> '1',
					-class=>'checkbox',
					-label=> '',
					-disabled=>'1',
					-checked=>$params->{'startup'} );

		my $html_debug = $query->newInput (
					-regx=>'NUMBERS',
					-intype=>'checkbox',
					-name=>'debug',
					-value=> '1',
					-class=>'checkbox',
					-label=> '',
					-checked=>$params->{'debug'} );

		my $html_operator = $query->newInput (
				-intype => 'scrolling_list',
				-name => 'operator',
				-regx=>'*',
				-default=> $params->{'operator'},
				-size=>5,
				-multiple=>'true',
				-values=>[ gettext('Any'), loadRoles()],
				-attributes => undef );

		my $html_reqsig = $query->newInput (
					-regx=>'LETTERS',
					-intype=>'checkbox',
					-name=>'reqsig',
					-value=> '1',
					-class=>'checkbox',
					-label=> '',
					-checked=>$params->{'reqsig'} );

		my $html_role = $query->newInput (
					-regx=>'*',
					-intype=>'scrolling_list',
					-name=>'role',
					-size=>5,
					-multiple=>'true',
					-default=> $params->{'role'},
					-values=>[gettext('Any'), loadRoles()]);

		my $html_loa = $query->newInput (
					-regx=>'*',
					-intype=>'scrolling_list',
					-name=>'loa',
					-size=>5,
					-multiple=>'true',
					-default=> $params->{'loa'},
					-values=>[gettext('Any'), loadLoa()]);

		my $html_ra = $query->newInput (
					-regx=>'LETTERS',
					-intype=>'scrolling_list',
					-name=>'ra',
					-size=>5,
					-multiple=>'true',
					-default=>$params->{'ra'},
					-values=>[gettext('Any'), 
						getRequiredList("RegistrationAuthority")]);

		%labels = ( 'Any' => gettext('Any'),
		    "224" => "224 (ecdsa)", "256" => "256 (ecdsa)",
		    "521" => "521 (ecdsa)", "1024" => "1024 (rsa/dsa)",
		    "2048" => "2048 (rsa/dsa)", "4096" => "4096 (rsa/dsa)",
		    "8192" => "8192 (rsa/dsa)" );

		my $html_keysize = $query->newInput (
					-regx=>'LETTERS',
					-intype=>'scrolling_list',
					-name=>'keysize',
					-size=>3,
					-multiple=>'true',
					-default=> $params->{'keysize'},
					-labels=>\%labels,
					-values=>['Any', 
						"224", "256", "521", "1024",
						"2048", "4096", "8192" ]);

		my $html_algor = $query->newInput (
					-regx=>'LETTERS',
					-intype=>'scrolling_list',
					-name=>'algor',
					-size=>3,
					-multiple=>'true',
					-default=> $params->{'algor'},
					-values=>[gettext('Any'), 
						'rsa', 'dsa', 'ecdsa' ]);

		$result = "<table class=\"getParams\">\n";
		$result .= "<tr><td colspan=\"2\">\n";
		$result .= "<center><div style='font-size: 120%;'><h3>" . 
		       gettext("Auto Certificate Issuing System")."</h3>" .
		   "</div></center>";

		$result .=  "<div class=\"description\" style='margin: 10px;'>" .
		    gettext (
                    "You can filter requests based on which Registration " .
		    "Authority has approved the request. The following " .
		    "information will be used by the Auto CA system to " .
		    "process only the requests that match all of the " .
		    "requirements you set here." .
		    "Remeber that although the configuration options are ".
		    "stored on your system, if the OpenCA server is " .
		    "rebooted you will need to activate the system again."
		    ) .
		    "</div>";
		$result .= "</td></tr>\n";

		$result .= "  <tr>\n".
                   "    <td class='desclabel'>".gettext ("Activate Automatically at Startup")."</td>\n".
                   "    <td>".$html_startup."</td>\n".
                   "  </tr>\n";

		$result .= "<tr><td colspan=\"2\">\n";
		$result .= "<center><h3>" . 
		       gettext("Request Signature Requirements")."</h3></div>" .
		   "</center>";
		$result .= "</td></tr>\n";

		$result .= "<tr><td colspan=\"2\">";
		$result .=  "<div class=\"description\" style='margin: 10px;'>" .
		    gettext (
                    "Please provide the information about the required ".
		    "approval process (e.g., if the request has to be " .
		    "signed, by which Registration Authority and by which " .
		    "RA Operator's role). " .
		    "Keep in mind that requests from the Authenticated " .
		    "request form, as all the new requests, are not signed, ".
		    "therefore you need to disable the Process (Signed) " .
		    "Requests Only check button." 
		     ) .
		    "</div><br />"; 
		$result .= "</td></tr>\n";

		$result .= "  <tr>\n".
                   "    <td class='desclabel'>".gettext ("Registration Authoritie(s)")."</td>\n".
                   "    <td>".$html_ra."</td>\n".
                   "  </tr>\n";

		$result .= "  <tr>\n".
                   "    <td class='desclabel'>".gettext ("Operator's role(s)")."</td>\n".
                   "    <td>".$html_operator."</td>\n".
                   "  </tr>\n";

		$result .= "  <tr>\n".
                   "    <td class='desclabel'>".gettext ("Processed (Signed) Requests Only")."</td>\n".
                   "    <td>" . $html_reqsig . "</td>\n".
                   "  </tr>\n";

		$result .= "<tr><td colspan=\"2\">";
		$result .= "<br /><center><h3>".
			gettext ("Requests Details") . "</h3>" .
		   "</div></center></td></tr>";

		$result .= "<tr><td colspan=\"2\">";
		$result .=  "<div class=\"description\" style='margin: 10px;'>" .
		    i18nGettext (
                    "The following information regard the technical details " .
		    "of the requests that will be processed. Keep in mind " .
		    "that all of the requirements must be met, therefore " .
		    "__A_HREF__" .
		    "please review the Level of Assurance (LOA) configuration ".
		    "__A_HREF_CLOSE__" .
		    "of your system." .
		    "To further restrict the requirements from the LOA you " .
		    "can select to automatically issue certificates only for ".
		    "a subset of the supported algorithms by selecting them " .
		    "from the menus." , 
			"__A_HREF__", "<a href=\"$self?cmd=viewLoas\" >",
			"__A_HREF_CLOSE__", "</a>"
		    ) .
		    "</div><br />";
		$result .= "</td></tr>";

		$result .= "  <tr>\n".
                   "    <td class='desclabel'>".gettext ("Requested role(s)")."</td>\n".
                   "    <td>".$html_role."</td>\n".
                   "  </tr>\n";

		$result .= "  <tr>\n".
                   "    <td class='desclabel'>".gettext ("Level of Assurance(s)")."</td>\n".
                   "    <td>".$html_loa."</td>\n".
                   "  </tr>\n";

		$result .= "<tr><td colspan=\"2\">";
		$result .= "<br /><center><h3>".
			gettext("Accepted Algorithms and Key Sizes") . "</h3>" .
		   "</div></center></td></tr>";

		$result .= "<tr><td colspan=\"2\">";
		$result .=  "<div class=\"description\" style='margin: 10px;'>" .
		    gettext (
                    "You can further restrict the accepted algorithms and " .
		    "key sizes here. Keep in mind that keysizes bigger or " .
		    "equal to 1024 bits are for RSA and DSA algorithms, " .
		    "while keysizes ranging from 112 to 521 bits are for " .
		    "ECDSA (Elliptic Curves) only." 
		    ) .
		    "</div><br />";
		$result .= "  <tr>\n".
                   "    <td class='desclabel'>".gettext ("Accepted Algorithms(s)")."</td>\n".
                   "    <td>".$html_algor."</td>\n".
                   "  </tr>\n";

		$result .= "  <tr>\n".
                   "    <td class='desclabel'>".gettext ("Accepted Key Size(s)")."</td>\n".
                   "    <td>".$html_keysize."</td>\n".
                   "  </tr>\n";

		## DEBUG Checkbox
		$result .= "<tr><td colspan=\"2\">";
		$result .= "<br /><center><h3>".
			gettext("Debugging Information") . "</h3>" .
		   "</div></center></td></tr>";
		$result .= "<tr><td colspan=\"2\">";
		$result .=  "<div class=\"description\" style='margin: 10px;'>" .
		    gettext ( "You can enable extra logging by enabling the DEBUG " .
		    "logging here. Keep in mind that enabling this option is only " .
		    "for testing or debugging issues with the system as it produces lots of information. " .
		    "Disable this option in production systems.") .
		    "</div><br />";
		$result .= "  <tr>\n".
                   "    <td class='desclabel'>".gettext ("Activate Extended Logging (DEBUG)")."</td>\n".
                   "    <td>".$html_debug."</td>\n".
                   "  </tr>\n";

		$result .= "</table>\n";
	}
	else
	{
		startAutoCA_saveParams();
	};

	return $result;
}

sub startAutoCA_saveParams
{
	my $ret = undef;
	our ( $query, %AUTOCONF );

	my $conf = $AUTOCONF{"var_prefix"}."/db/openca_autoca.cnf";

	return libSaveCgiParams ( $conf );
}

sub startAutoCA_loadParams
{
	my $ret = undef
	my $savedQuery = undef;
	my $defaults = undef;

	# $defaults->{'operator'} = [ gettext('Any') ];
	# $defaults->{'ra'} = [ gettext('Any') ];
	# $defaults->{'reqsig'} = '1';
	# $defaults->{'role'} = [ gettext('Any') ];
	# $defaults->{'loa'} = [ gettext('Any') ];
	# $defaults->{'keysize'} = [ gettext('Any') ];
	# $defaults->{'algor'} = [ gettext('Any') ];
	# $defaults->{'debug'} = '0';
	# $defaults->{'startup'} = '0';

	$defaults->{'operator'} = [ gettext('Any') ];
	$defaults->{'ra'} = [ gettext('Any') ];
	$defaults->{'reqsig'} = '1';
	$defaults->{'role'} = [ gettext('Any') ];
	$defaults->{'loa'} = [ gettext('Any') ];
	$defaults->{'keysize'} = [ gettext('Any') ];
	$defaults->{'algor'} = [ gettext('Any') ];
	$defaults->{'debug'} = '0';
	$defaults->{'startup'} = '0';

	our ( $query, %AUTOCONF );

	my $conf = $AUTOCONF{"var_prefix"}."/db/openca_autoca.cnf";

	return libLoadCgiParams ( $conf, $defaults );
}

1;
