<package name="ssl">
<domain name="papp" lang="en">

<perl><![CDATA[
=head1 NAME

macro/ssl - mod_ssl-helpers for PApp

=head1 SYNOPSIS

	<import src="macro/ssl"/>

=head1 DESCRIPTION

Helper functions for apache/mod_ssl.
Some functions support three-state pages, the PApp application
should be mounted on three different virtual hosts in Apache.
The three states some functions assume are:

   http://host:80/foobar      - plain http without any encryption (url_prefix_nossl)
   https://host:443/foobar    - anonymous https (no client cert required) (url_prefix_ssl)
   https://host:444/foobar    - require full client authentification (url_prefix_sslauth)

Port 444 is nonstandard, so you can override these in your mountconfig.
It's a good idea to override all three values, because the fallback
uses $request->hostname, which may give unexpected results.
If you only define url_prefix_ssl (say to "https://secure.w3.org"),
the following can happen if the plain request goes to http://seppl.org:

normal sections      http://seppl.org
<:require_ssl:>      https://secure.w3.org
<:require_ssl_user:> https://secure.w3.org:444

if you switch directly from normal to auth:

normal sections      http://seppl.org
<:require_ssl_user:> https://seppl.org:444 

So PLEASE override all three settings (url_prefix_nossl, url_prefix_ssl
and url_prefix_sslauth) to get a consistent behavior...

If you want to kick out nonstandard port 444 you need a IP-virtual
Host configuration because the https protocol does not support
the Host request-header. You should also use different server-keys for
ssl and sslauth or you may get warnings in client browser about
mismatch of real-hostname and the hostname in the server certificate.

url_prefix_nossl   => http://seppl.org
url_prefix_ssl	   => https://seppl.org     (cert for "seppl.org")
url_prefix_sslauth => https://auth.seppl.org (cert for "auth.seppl.org")

for example, use:
SSLCertificateFile /share/www/conf/ssl.crt/server.crt.seppl.org
SSLCertificateKeyFile /share/www/conf/ssl.key/server.key.seppl.org

in the seppl.org https-section and the following for auth.seppl.org

SSLCertificateFile /share/www/conf/ssl.crt/server.crt.auth.seppl.org
SSLCertificateKeyFile /share/www/conf/ssl.key/server.auth.key.seppl.org

So the easier setup is to use 444 because you need only one server
certificate and you don't need to deal with url_prefix.

This is a convention used by this package, not standard mod_ssl setup.

It is always possible to detect the current SSL state like this:

   #if ssl_p
   <?"under SSL":>
   # if ssl_user
   <?" with user auth: " . ssl_user:>
   # else
   <?" without user auth":>
   # endif
   #else
   <?"no SSL":>
   #endif

The defined states are only needed to switch to a higher auth-level by
redirection.

To enforce SSL you can use:

<:require_ssl:>

and if you require strict client authentification you can use:

<:require_ssl_user:>

=head1 Apache Sample Configuration

Use the following directives for SSL (strong 128bit encryption)
on Port 443 (standard SSL without user auth).

    SSLVerifyDepth 1
    SSLCACertificateFile conf/ssl.crt/ca.crt
    SSLEngine on
    SSLCipherSuite HIGH:MEDIUM
    SSLCertificateFile /usr/www/conf/ssl.crt/server.crt
    SSLCertificateKeyFile /usr/www/conf/ssl.key/server.key
    SSLCARevocationPath /usr/www/conf/ssl.crl
    SSLOptions +StdEnvVars
    <Perl>
      search_path PApp "/usr/www/lib/papp";
      search_path PApp "/usr/www/lib/papp/macro";
      mount_appset PApp default;
    </Perl>

Use the exact same configuration on port 444 and in addition -
enable user authentification by adding:

    SSLVerifyClient require
    
=head1 FUNCTIONS

=over 4

=item ssl_p

Returns true if the current request is a https request.

=item ssl_user

Returns the user name of the client certificate or C<undef>
if there is no SSL-session at all or if the SSL-session is
anonymous (without a client certificate)

=item require_ssl

Redirects the current request to a secure non-user authentificated
SSL connection if insecure http protocol is used.

=item require_ssl_user

Like C<ssl_needed> but requires user authentification.

=item no_ssl

Redirects the current request to http if https is used.
This can be used to drop SSL security.
   
=back

=cut

]]></perl>


<perl><![CDATA[

use PApp qw(*state $userid getuid);
use PApp::UserObs;

sub _compress_cert($) {
 my ($ret) = '';
 ($ret = $_[0]) =~ s/^-----.*\n//mg;
 $ret =~ s/\n//mgs;
 $ret
}

sub _decompress_cert($) {
      my ($ret);
      ($ret .= $_[0]) =~ s/(.{1,64})/$1\n/sg;
      "-----BEGIN CERTIFICATE-----\n" . $ret . "-----END CERTIFICATE-----\n"
}

sub update_cert($;$)
{
   my ($ccrt, $id) = (_compress_cert($_[0]), $_[1]);
   $id ||= 0;
   # replace does not work with blob, because blob can't be unique...
   sql_exec $PApp::Config::DBH,
   	'delete from ssluser where cert = ?', $ccrt;
   sql_exec $PApp::Config::DBH,
   	'insert into ssluser (userid,cert) values (?,?)', $id, $ccrt;
}

# 0 = already in DB unassigned;banned , -1 = never seen before.
sub get_uid_by_cert($)
{
    my ($id) = sql_fetch $PApp::Config::DBH,
       'select userid from ssluser where cert = ?', _compress_cert($_[0]);
       $id || -1;
}

sub get_cert_by_uid($)
{
   return undef unless $_[0];
   my ($crt) = sql_fetch $PApp::Config::DBH,
      'select cert from ssluser where userid = ?', $_[0];
   $crt ? _decompress_cert($crt) : undef;
}

sub get_unassigned_certs()
{
   map(_decompress_cert $_, sql_fetchall $PApp::Config::DBH,
         'select cert from ssluser where userid = 0')
}

]]></perl>

<macro name="_sslautologin()"><perl><![CDATA[
 $xuid = get_uid_by_cert $ENV{SSL_CLIENT_CERT};
 if($xuid > 0) {
    user_login($xuid);
    return;
 }
 # huch.
 die "Banned user" unless $xuid;

 user_login(0);
 $username =  $ENV{SSL_CLIENT_S_DN_CN};
 $username =~ s/[ \t]/_/g;
 # Avoid overwriting existing users.
 while(! update_username $username)  {
 	$username = choose_username $username;
 }
 update_password undef; # no "normal" password login allowed, SSL only.
 update_comment "SSL Cert Account $ENV{SSL_CLIENT_S_DN}";
 update_cert $ENV{SSL_CLIENT_CERT}, getuid();
]]></perl></macro>

<perl><![CDATA[
sub _url_prefix_nossl_host() {
   return "http://" . $request->hostname unless $papp->{url_prefix_nossl};
   $papp->{url_prefix_nossl};
}
sub _url_prefix_ssl_host() {
   return "https://" . $request->hostname . ":443" unless $papp->{url_prefix_ssl};
   $papp->{url_prefix_ssl};
}
sub _url_prefix_sslauth_host() {
   return "https://" . $request->hostname . ":444" unless $papp->{url_prefix_sslauth};
   $papp->{url_prefix_sslauth};
}
]]></perl>

<macro name="ssl_p*()"><perl><![CDATA[
   defined $ENV{SSL_PROTOCOL};
]]></perl></macro>

<macro name="ssl_user*()"><perl><![CDATA[
   return () unless $ENV{SSL_CLIENT_VERIFY} eq 'SUCCESS';
   _sslautologin unless authen_p;
   $ENV{SSL_CLIENT_S_DN_CN};
]]></perl></macro>

<macro name="require_ssl*()"><perl><![CDATA[
   return if defined $ENV{SSL_PROTOCOL};
   redirect _url_prefix_ssl_host . surl map { -$_, $A{$_} } keys %A;
]]></perl></macro>

<macro name="require_ssl_user*()"><perl><![CDATA[
   if ($ENV{SSL_CLIENT_VERIFY} eq 'SUCCESS') {
   	_sslautologin unless authen_p;
   	return;
   }
   redirect _url_prefix_sslauth_host . surl map { -$_, $A{$_} } keys %A;
]]></perl></macro>

<macro name="no_ssl*()"><perl><![CDATA[
   return unless defined $ENV{SSL_PROTOCOL};
   redirect _url_prefix_nossl . surl map { -$_, $A{$_} } keys %A;
]]></perl></macro>

<!-- todo SSLClientEmail => ssl_clientinfo->{email} etc. -->

<macro name="SSLClientEmail*()"><perl><![CDATA[
 $ENV{SSL_CLIENT_S_DN_Email}
]]></perl></macro>

<macro name="SSLClientCountry*()"><perl><![CDATA[
 $ENV{SSL_CLIENT_S_DN_C}
]]></perl></macro>

<macro name="SSLClientState*()"><perl><![CDATA[
 $ENV{SSL_CLIENT_S_DN_ST}
]]></perl></macro>

<macro name="SSLClientLocation*()"><perl><![CDATA[
 $ENV{SSL_CLIENT_S_DN_L}
]]></perl></macro>

<macro name="SSLClientOrganization*()"><perl><![CDATA[
 $ENV{SSL_CLIENT_S_DN_O}
]]></perl></macro>

<macro name="SSLClientOrganizationalUnit*()"><perl><![CDATA[
 $ENV{SSL_CLIENT_S_DN_OU}
]]></perl></macro>

<macro name="SSLClientName*()"><perl><![CDATA[
 $ENV{SSL_CLIENT_S_DN_CN}
]]></perl></macro>

<perl><![CDATA[

=head1 SEE ALSO

L<PApp>

=head1 AUTHOR

This module was kindly contributed by
Stefan Traby <stefan@hello-penguin.com>.

=cut

]]></perl>

</domain>
</package>

