The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"

#include "ppport.h"

#include "NSS.h"

#define BUFFER_SIZE 8192

static const char * config_dir = NULL;

static SV * PasswordHook = NULL;

char *
pkcs11_password_hook(PK11SlotInfo *info, PRBool retry, void *arg) {
    dSP;
    char * password = NULL, * tmp;
    SV * rv;
    I32 rcount;
    
    ENTER;
    SAVETMPS;
    PUSHMARK(SP);

    XPUSHs(sv_2mortal(boolSV(retry)));
    XPUSHs(sv_2mortal(newSVsv((SV *) arg)));

    PUTBACK;

    rcount = call_sv(PasswordHook, G_SCALAR);

    SPAGAIN;

    if (rcount == 1) {
        rv = POPs;
        if (SvTRUE(rv) && SvPOK(rv)) {
            tmp = SvPV_nolen(rv);
            password = PORT_Strdup((char *) tmp);
        }
    }
    
    PUTBACK;
    FREETMPS;
    LEAVE;

    return password;
}

/* This is out ignore verification hook */
SECStatus
verify_certificate_ignore_hook(void * arg, PRFileDesc * fd, PRBool check_sig, PRBool is_server) {
    return SECSuccess;
}

/* This is called when we need to verify an incoming certificate */
SECStatus
verify_certificate_hook(void * arg, PRFileDesc * fd, PRBool check_sig, PRBool is_server) {    
    dSP;
    Net__NSS__SSL socket = (Net__NSS__SSL) arg;
    SV * rv, * self;
    IV tmp, rcount;
    
    SECStatus status = SECSuccess;
    
    ENTER;
    SAVETMPS;
    PUSHMARK(SP);
    
    /* Hack to prevent FREETMPS from closing socket */
    socket->do_not_free = TRUE;
    
    self = sv_newmortal();
	sv_setref_pv(self, "Net::NSS::SSL", (void *) socket);
	
    XPUSHs(self);
    XPUSHs(sv_2mortal(newSVsv(socket->client_certificate_hook_arg)));
    
    PUTBACK;

    rcount = call_sv(socket->client_certificate_hook, G_SCALAR);

    SPAGAIN;
        
    return status;
}

SECStatus
bad_certificate_hook(void * arg, PRFileDesc * fd) { 
    fprintf(stderr, "In bad_certificate_hook\n");
}

SECStatus
client_certificate_hook(void * arg, PRFileDesc * fd, CERTDistNames * ca_names, CERTCertificate ** target_cert, SECKEYPrivateKey ** target_key) {
    dSP;
    Net__NSS__SSL socket = (Net__NSS__SSL) arg;
    SV * rv, * self;
    IV tmp, rcount;
    
    SECStatus status = SECSuccess;
        
    ENTER;
    SAVETMPS;
    PUSHMARK(SP);

    /* Hack to prevent FREETMPS from closing socket */
    socket->do_not_free = TRUE;
    
    self = sv_newmortal();
	sv_setref_pv(self, "Net::NSS::SSL", (void *) socket);
	
    XPUSHs(self);
    XPUSHs(sv_2mortal(newSVsv(socket->client_certificate_hook_arg)));

    PUTBACK;

    rcount = call_sv(socket->client_certificate_hook, G_ARRAY);

    SPAGAIN;

    *target_cert = NULL;
    *target_key = NULL;

    if (rcount == 2) {
        rv = POPs;
        if (SvTRUE(rv) && sv_derived_from(rv, "Crypt::NSS::PrivateKey")) {
            tmp = SvIV((SV *) SvRV(rv));
            *target_key = SECKEY_CopyPrivateKey(INT2PTR(Crypt__NSS__PrivateKey, tmp));
    	}
    	else {
            status = SECFailure;
    	}
    	
        rv = POPs;
        if (SvTRUE(rv) && sv_derived_from(rv, "Crypt::NSS::Certificate")) {
            tmp = SvIV((SV *) SvRV(rv));
            *target_cert = CERT_DupCertificate(INT2PTR(Crypt__NSS__Certificate, tmp));
    	}
    	else {
            status = SECFailure;
    	}
    }
    else {
        status = SECFailure;
    }

    PUTBACK;
    FREETMPS;
    LEAVE;

    /* Hack to prevent FREETMPS from closing socket */
    socket->do_not_free = FALSE;
        
    return status;
}

static void
throw_exception_from_nspr_error(const char * desc) {
    PRInt32 len;
    char * tmpbuff;
    
    if ((len = PR_GetErrorTextLength())) {
        Newz(1, tmpbuff, len + 1, char);
        PR_GetErrorText(tmpbuff);
        croak("%s: %s (%d)", desc, tmpbuff, PR_GetError());
    }
    else {
        croak("%s: %d", desc, PR_GetError());
    }
}

static IV
get_argument_as_IV(HV *args, const char *key, IV default_value) {
    SV **value;

    value = hv_fetch(args, key, strlen(key), 0);
    
    if (value == NULL) {
        /* Failed to get key, return default */
        return default_value;
    }
    
    return SvIV(*value);
}

static PRBool
get_argument_as_PRBool(HV *args, const char *key, PRBool default_value) {
    SV **value;

    value = hv_fetch(args, key, strlen(key), 0);
    
    if (value == NULL) {
        /* Failed to get key, return default */
        return default_value;
    }
    
    return (PRBool) SvTRUE(*value);
}

MODULE = Crypt::NSS     PACKAGE = Crypt::NSS::PrivateKey

void
DESTROY(self)
    Crypt::NSS::PrivateKey self;
    CODE:
        if (self != NULL) {
            SECKEY_DestroyPrivateKey(self);
        }

MODULE = Crypt::NSS        PACKAGE = Net::NSS::SSL

Net::NSS::SSL
create_socket(pkg, type)
    const char * pkg;
    const char * type;
    PREINIT:
        NSS_SSL_Socket * socket;
    CODE:
        Newz(1, socket, 1, NSS_SSL_Socket);
        if (strEQ(type, "tcp")) {
            socket->fd = PR_NewTCPSocket();
        }
        else if (strEQ(type, "udp")) {
            socket->fd = PR_NewUDPSocket();
        }
        else {
            croak("Unknown socket type '%s'. Valid types are are 'tcp' and 'udp'", type);
        }

        if (socket->fd == NULL) {
            throw_exception_from_nspr_error("Failed to create new TCP socket");
        }        

        socket->is_connected = FALSE;
        RETVAL = socket;
    OUTPUT:
        RETVAL

void 
set_option(self, option, value)
    Net::NSS::SSL self;
    SV * option;
    I32 value;
    PREINIT:
        char * name;
        I32 num;
        PRSocketOptionData sock_opt;
    CODE:
        if (SvPOK(option)) {
            name = SvPV_nolen(option);
            if (strEQ(name, "KeepAlive")) {
                sock_opt.option = PR_SockOpt_Keepalive;
                sock_opt.value.keep_alive = value ? PR_TRUE : PR_FALSE;
            }
            else if (strEQ(name, "NoDelay")) {
                sock_opt.option = PR_SockOpt_NoDelay;
                sock_opt.value.no_delay = value ? PR_TRUE : PR_FALSE;
            }
            else if (strEQ(name, "Blocking")) {
                sock_opt.option = PR_SockOpt_Nonblocking;
                sock_opt.value.non_blocking = value ? PR_FALSE : PR_TRUE;
            }
            else {
                croak("Unknown option '%s'", option);
            }
            
            SET_SOCKET_OPTION(self->fd, sock_opt, form("Failed to set option '%s' on socket", option));
        }
        else if (SvIOK(option)) {
            EVALUATE_SEC_CALL(SSL_OptionSet(self->ssl_fd, SvIV(option), value), "Failed to set option")
        }

SV *
get_option(self, option)
    Net::NSS::SSL self;
    SV * option;
    PREINIT:
        char * name;
        I32 num;
        PRSocketOptionData sock_opt;
        PRBool on;
    CODE:
        if (SvPOK(option)) {
            name = SvPV_nolen(option);
            if (strEQ(name, "KeepAlive")) {
                sock_opt.option = PR_SockOpt_Keepalive;
                GET_SOCKET_OPTION(self->fd, sock_opt, option);
                RETVAL = boolSV(sock_opt.value.keep_alive ? TRUE : FALSE);
            }
            else if (strEQ(name, "NoDelay")) {
                sock_opt.option = PR_SockOpt_NoDelay;
                GET_SOCKET_OPTION(self->fd, sock_opt, option);
                RETVAL = boolSV(sock_opt.value.no_delay ? TRUE : FALSE);
            }
            else if (strEQ(name, "Blocking")) {
                sock_opt.option = PR_SockOpt_Nonblocking;
                GET_SOCKET_OPTION(self->fd, sock_opt, option);
                RETVAL = boolSV(sock_opt.value.non_blocking ? FALSE : TRUE);
            }
            else {
                croak("Unknown option '%s'", option);            
            }
        }
        else if (SvIOK(option)) {
            EVALUATE_SEC_CALL(SSL_OptionGet(self->ssl_fd, SvIV(option), &on), "Failed to get option")
            RETVAL = newSViv(on);
        }
    OUTPUT:
        RETVAL
        
void
set_verify_certificate_hook(self, hook)
    Net::NSS::SSL self;
    SV * hook;
    CODE:
        if (self->verify_certificate_hook != NULL) {
            SvREFCNT_dec(self->verify_certificate_hook);
        }
        if (SvPOK(hook) && strEQ(SvPV_nolen(hook), "built-in-ignore")) {
            EVALUATE_SEC_CALL(SSL_AuthCertificateHook(self->ssl_fd, verify_certificate_ignore_hook, NULL), "Failed to set auth certificate hook to ignore");
        }
        else if (SvTRUE(hook)) {
            if (self->verify_certificate_hook != NULL) {
                EVALUATE_SEC_CALL(SSL_AuthCertificateHook(self->ssl_fd, verify_certificate_hook, self), "Failed to set auth certificate hook");
            }
            self->verify_certificate_hook = SvREFCNT_inc(hook);
        }
        else {
            EVALUATE_SEC_CALL(SSL_AuthCertificateHook(self->ssl_fd, SSL_AuthCertificate, NULL), "Failed to set default auth certificate hook");
        }

void
set_bad_certificate_hook(self, hook)
    Net::NSS::SSL self;
    SV * hook;
    CODE:
        if (self->bad_certificate_hook != NULL) {
            SvREFCNT_dec(self->bad_certificate_hook);
        }
        if (SvTRUE(hook)) {
            if (self->bad_certificate_hook != NULL) {
                EVALUATE_SEC_CALL(SSL_BadCertHook(self->ssl_fd, bad_certificate_hook, self), "Failed to set bad certificate hook");
            }
            if (self->bad_certificate_hook == NULL) {
                self->bad_certificate_hook = newSVsv(hook);
            }
            else {
                sv_setsv(self->bad_certificate_hook, hook);
            }
        }
        else {
            EVALUATE_SEC_CALL(SSL_BadCertHook(self->ssl_fd, NULL, NULL), "Failed to set default bad certificate hook");
        }

void
set_client_certificate_hook(self, hook, data=NULL)
    Net::NSS::SSL self;
    SV * hook;
    SV * data;
    PREINIT:
        char * tmp;
        char * nickname = NULL;
    CODE:        
        if (self->client_certificate_hook_arg) {
            SvREFCNT_dec(self->client_certificate_hook_arg);
            self->client_certificate_hook_arg = NULL;
        }
        if (self->client_certificate_hook != NULL) {
            SvREFCNT_dec(self->client_certificate_hook);
        }
        if (SvPOK(hook) && strEQ(SvPV_nolen(hook), "built-in")) {
            if (data != NULL) {
                tmp = SvPV_nolen(data);
                nickname = PL_strdup(tmp);
            }
            SSL_GetClientAuthDataHook(self->ssl_fd, NSS_GetClientAuthData, NULL);
        }
        else if (SvTRUE(hook)) {
            SSL_GetClientAuthDataHook(self->ssl_fd, client_certificate_hook, self);
            self->client_certificate_hook = SvREFCNT_inc(hook);    
            if (data) {   
                self->client_certificate_hook_arg = SvREFCNT_inc(data);
            }
        }
        else {
            SSL_GetClientAuthDataHook(self->ssl_fd, NULL, NULL);
        }

void
set_pkcs11_pin_arg(self, arg)
    Net::NSS::SSL self;
    SV * arg;
    PREINIT:
        SV * pre_arg;
    CODE:
        pre_arg = (SV *) SSL_RevealPinArg(self->ssl_fd);
        if (pre_arg != NULL) {
            SvREFCNT_dec(pre_arg);
        }
        SSL_SetPKCS11PinArg(self->ssl_fd, (void *) SvREFCNT_inc(arg));

SV *
get_pkcs11_pin_arg(self)
    Net::NSS::SSL self;
    PREINIT:
        SV * arg;
    CODE:
        arg = (SV *) SSL_RevealPinArg(self->ssl_fd);
        if (arg == NULL) {
            XSRETURN_UNDEF;
        }
        RETVAL = SvREFCNT_inc(arg);
    OUTPUT:
        RETVAL

void
set_URL(self, hostname)
    Net::NSS::SSL self;
    const char * hostname;
    CODE:
        EVALUATE_SEC_CALL(SSL_SetURL(self->ssl_fd, hostname), "Failed to set url")

const char *
get_URL(self)
    Net::NSS::SSL self;
    PREINIT:
        char * domain;
    CODE:
        domain = SSL_RevealURL(self->ssl_fd);
        RETVAL = savepv(domain);
        PR_Free(domain);
    OUTPUT:
        RETVAL

void
remove_from_session_cache(self)
    Net::NSS::SSL self;
    CODE:
        if (SSL_InvalidateSession(self->ssl_fd) < 0) {
            throw_exception_from_nspr_error("Failed to invalidate session for socket");
        }


void
connect(self, hostname, port, timeout = PR_INTERVAL_NO_TIMEOUT)
    Net::NSS::SSL self;
    const char * hostname;
    I32 port;
    I32 timeout;
    PREINIT:
        PRNetAddr addr;
        PRHostEnt hostentry;
        char buffer[PR_NETDB_BUF_SIZE];
    CODE:
        EVALUATE_PR_CALL(PR_GetHostByName(hostname, buffer, sizeof(buffer), &hostentry), "Can't lookup host")
        if (PR_EnumerateHostEnt(0, &hostentry, port, &addr) < 0) {
            throw_exception_from_nspr_error("Failed to get IP from host entry");
        }
        EVALUATE_PR_CALL(PR_Connect(self->ssl_fd, &addr, PR_INTERVAL_NO_TIMEOUT), "Connection failed")        
        self->is_connected = TRUE;

void
bind(self, hostname, port)
    Net::NSS::SSL self;
    const char * hostname;
    I32 port;
    PREINIT:
        PRNetAddr addr;
        PRHostEnt hostentry;
        char buffer[PR_NETDB_BUF_SIZE];
    CODE:
        if (hostname != NULL) {
            EVALUATE_PR_CALL(PR_GetHostByName(hostname, buffer, sizeof(buffer), &hostentry), "Can't lookup host")
            if (PR_EnumerateHostEnt(0, &hostentry, port, &addr) < 0) {
                throw_exception_from_nspr_error("Failed to get IP from host entry");
            }
        }
        else {
            addr.inet.family = PR_AF_INET;
            addr.inet.ip = PR_htonl(PR_INADDR_ANY);
            addr.inet.port = port;
        }
        EVALUATE_PR_CALL(PR_Bind(self->fd, &addr), "Connection failed")

Net::NSS::SSL
accept(self, timeout = PR_INTERVAL_NO_TIMEOUT)
    Net::NSS::SSL self
    I32 timeout;
    PREINIT:
        NSS_SSL_Socket * new_socket;
        PRNetAddr addr;
        PRFileDesc * remote_fd = NULL;
    CODE:
        remote_fd = PR_Accept(self->fd, &addr, timeout);
        if (remote_fd == NULL) {
            throw_exception_from_nspr_error("Accept failed");
        }
        Newz(1, new_socket, 1, NSS_SSL_Socket);
        new_socket->fd = remote_fd;
        new_socket->ssl_fd = SSL_ImportFD(NULL, new_socket->fd);
        new_socket->is_connected = TRUE;
        new_socket->does_ssl = TRUE;
        RETVAL = new_socket;
    OUTPUT:
        RETVAL

void
listen(self, queue_length=10)
    Net::NSS::SSL self;
    I32 queue_length;
    CODE:
        EVALUATE_PR_CALL(PR_Listen(self->fd, queue_length), "Listen failed")

void
import_into_ssl_layer(self, proto=NULL)
    Net::NSS::SSL self;
    Net::NSS::SSL proto;
    PREINIT:
        PRFileDesc * proto_sock = NULL;
    CODE:
        if (proto != NULL) {
            proto_sock = proto->ssl_fd;
        }
        self->ssl_fd = (PRFileDesc *) SSL_ImportFD(proto_sock, self->fd);
        self->does_ssl = TRUE;

void
reset_handshake(self, as_server)
    Net::NSS::SSL self;
    bool as_server;
    CODE:
        EVALUATE_SEC_CALL(SSL_ResetHandshake(self->ssl_fd, as_server ? PR_TRUE : PR_FALSE), "Failed to reset handshake");
    
void
configure_as_server(self, cert, key)
    Net::NSS::SSL self;
    Crypt::NSS::Certificate cert;
    Crypt::NSS::PrivateKey key;
    PREINIT:
        SSLKEAType certKEA;
    CODE:
        certKEA = NSS_FindCertKEAType(cert);
        EVALUATE_SEC_CALL(SSL_ConfigSecureServer(self->ssl_fd, cert, key, certKEA), "Failed to configure server socket");
    
I32
available(self)
    Net::NSS::SSL self;
    CODE:
        RETVAL = PR_Available(self->ssl_fd);
    OUTPUT:
        RETVAL

void
_peeraddr(self)
    Net::NSS::SSL self;
    PREINIT:
        char * hostname;
        PRNetAddr addr;
    PPCODE:
        if (self->ssl_fd == NULL || !self->is_connected) {
            croak("Can't get peeraddr because we're not connected");
        }
        EVALUATE_PR_CALL(PR_GetPeerName(self->ssl_fd, &addr), "Failed to get peer addr")
        Newz(1, hostname, 16, char);
        if (PR_NetAddrToString(&addr, hostname, 16) != PR_SUCCESS) {
            Safefree(hostname);
            throw_exception_from_nspr_error("Failed to convert PRNetAddr to string");
        }
        EXTEND(SP, 2);
        PUSHs(sv_2mortal(newSVpv(hostname, 0)));
        PUSHs(sv_2mortal(newSViv(PR_ntohs(addr.inet.port))));
        Safefree(hostname);

void
_sockaddr(self)
    Net::NSS::SSL self;
    PREINIT:
        char * hostname;
        PRNetAddr addr;
    PPCODE:
        EVALUATE_PR_CALL(PR_GetSockName(self->ssl_fd, &addr), "Failed to get peer addr")
        Newz(1, hostname, 16, char);
        if (PR_NetAddrToString(&addr, hostname, 16) != PR_SUCCESS) {
            Safefree(hostname);
            throw_exception_from_nspr_error("Failed to convert PRNetAddr to string");
        }
        EXTEND(SP, 2);
        PUSHs(sv_2mortal(newSVpv(hostname, 0)));
        PUSHs(sv_2mortal(newSViv(PR_ntohs(addr.inet.port))));
        Safefree(hostname);
                
I32
keysize(self)
    Net::NSS::SSL self;
    PREINIT:
        int keysize;
    CODE:
        EVALUATE_SEC_CALL(SSL_SecurityStatus(self->ssl_fd, NULL, NULL, &keysize, NULL, NULL, NULL), 
                          "Failed to get session key length")
        RETVAL = keysize;
    OUTPUT:
        RETVAL

I32
secret_keysize(self)
    Net::NSS::SSL self;
    PREINIT:
        int secret_keysize;
    CODE:
        EVALUATE_SEC_CALL(SSL_SecurityStatus(self->ssl_fd, NULL, NULL, NULL, &secret_keysize, NULL, NULL), 
                         "Failed to get session secret key length")
        RETVAL = secret_keysize;
    OUTPUT:
        RETVAL

const char *
cipher(self)
    Net::NSS::SSL self;
    PREINIT:
        char *cipher;
    CODE:
        EVALUATE_SEC_CALL(SSL_SecurityStatus(self->ssl_fd, NULL, &cipher, NULL, NULL, NULL, NULL),
                                             "Failed to get session cipher")
        RETVAL = savepv(cipher);
        PR_Free(cipher);
    OUTPUT:
        RETVAL

const char *
issuer(self)
    Net::NSS::SSL self;
    PREINIT:
        char *issuer;
    CODE:
        EVALUATE_SEC_CALL(SSL_SecurityStatus(self->ssl_fd, NULL, NULL, NULL, NULL, &issuer, NULL),
                                             "Failed to get session issuer")
        RETVAL = savepv(issuer);
        PR_Free(issuer);
    OUTPUT:
        RETVAL

const char *
subject(self)
    Net::NSS::SSL self;
    PREINIT:
        char *subject;
    CODE:
        EVALUATE_SEC_CALL(SSL_SecurityStatus(self->ssl_fd, NULL, NULL, NULL, NULL, NULL, &subject),
                                             "Failed to get session subject")
        RETVAL = savepv(subject);
        PR_Free(subject);
    OUTPUT:
        RETVAL
    
I32
write(self, data, length = 0, offset = 0)
    Net::NSS::SSL self;
    SV * data;
    I32 length;
    I32 offset;
    PREINIT:
        char *buf;
        PRInt32 bytes_written = 0;
        STRLEN blen;
    CODE:
        buf = SvPVx(data, blen);
        length = length > 0 ? length : blen;
        if (offset < 0) {
            if (-offset > blen) {
                croak("Offset outside string");
            }
        }
        else if (offset >= blen && blen > 0) {
            croak("Offset outside string");
        }
        if (length > blen - offset) {
            length = blen - offset;
        }
        bytes_written = PR_Write(self->ssl_fd, buf + offset, length);
        if (bytes_written < 0) {
            throw_exception_from_nspr_error("Failed to write to socket");
        }
        RETVAL = bytes_written;
    OUTPUT:
        RETVAL
        
I32
read(self, buf, length = BUFFER_SIZE, offset = 0)
    Net::NSS::SSL self;
    I32 length;
    I32 offset;
    PREINIT:
        char *buf;
        PRInt32 bytes_read;
        STRLEN len;
    INPUT:
        SV * buf_sv = ST(1);
    CODE:
        if (!SvPOK(buf_sv)) {
            sv_setpv(buf_sv, "");
        }
        buf = SvPV_force(buf_sv, len);
        if (offset < 0) {
            if (-offset > len) {
                croak("Offset outside string");
            }
            offset += len;
        }
        if (offset > len) {
            Newz(1, buf, offset - len, char);
            sv_catpvn(buf_sv, buf, offset - len);
        }
        buf = SvGROW(buf_sv, offset + length + 1);
        bytes_read = PR_Read(self->ssl_fd, buf + offset, length);
        if (bytes_read >= 0) {
            SvCUR_set(buf_sv, offset + bytes_read);
            buf[offset + bytes_read] = '\0';
        }
        else {
            throw_exception_from_nspr_error("Failed to read from socket");
        }
        RETVAL = bytes_read;
    OUTPUT:
        RETVAL
        
void
close(self)
    Net::NSS::SSL self;
    CODE:
        if (self->ssl_fd != NULL) {
            EVALUATE_PR_CALL(PR_Close(self->ssl_fd), "Failed to close socket")
            self->ssl_fd = NULL;
            self->is_connected = PR_FALSE;
        }        
    
void
DESTROY(self)
    Net::NSS::SSL self;
    PREINIT:
        SV * pin_arg;
    CODE:
        /* Hack that prevents tmp stuff to close this */
        if (self->do_not_free)
            return;
            
        if (self->ssl_fd) {
            pin_arg = (SV *) SSL_RevealPinArg(self->ssl_fd);
            if (pin_arg != NULL) {
                SvREFCNT_dec(pin_arg);
            }
        }
        if (self->ssl_fd != NULL) {
            PR_Close(self->ssl_fd);
        }        
        if (self->client_certificate_hook_arg) {
            SvREFCNT_dec(self->client_certificate_hook_arg);
        }
        if (self->client_certificate_hook) {
            SvREFCNT_dec(self->client_certificate_hook);
        }
        Safefree(self);
        
Crypt::NSS::Certificate
peer_certificate(self)
    Net::NSS::SSL self;
    PREINIT:
        CERTCertificate * cert;
    CODE:
        cert = SSL_PeerCertificate(self->ssl_fd);
        if (cert == NULL) {
            XSRETURN_UNDEF;
        }
        RETVAL = cert;
    OUTPUT:
        RETVAL

bool
is_connected(self)
    Net::NSS::SSL self;
    CODE:
        RETVAL = self->is_connected;
    OUTPUT:
        RETVAL

bool
does_ssl(self)
    Net::NSS::SSL self;
    CODE:
        RETVAL = self->ssl_fd != 0 ? TRUE : FALSE;
    OUTPUT:
        RETVAL

MODULE = Crypt::NSS     PACKAGE = Crypt::NSS::Certificate

Crypt::NSS::Certificate
from_base64_DER(pkg, data)
    const char * pkg;
    const char * data;
    CODE:
        RETVAL = CERT_ConvertAndDecodeCertificate((char *) data);
        if (RETVAL == NULL) {
            XSRETURN_UNDEF;
        }
    OUTPUT:
        RETVAL

bool
verify_hostname(self, hostname)
    Crypt::NSS::Certificate self;
    const char * hostname;
    CODE:
        RETVAL = TRUE;
        if (CERT_VerifyCertName(self, hostname) != SECSuccess) {
            if (PR_GetError() == SSL_ERROR_BAD_CERT_DOMAIN) {
                RETVAL = FALSE;
            }
            else {
                throw_exception_from_nspr_error("Failed to verify hostname");
            }
        }
    OUTPUT:
        RETVAL

I32
get_validity_for_datetime(self, year, month, mday, hour = 0, min = 0, sec = 0, usec = 0)
    Crypt::NSS::Certificate self;
    I32 year;
    I32 month;
    I32 mday;
    I32 hour;
    I32 min;
    I32 sec;
    I32 usec;
    PREINIT:
        PRExplodedTime ts; 
        PRTime t;
        SECCertTimeValidity v;
    CODE:
        ts.tm_usec = usec;
        ts.tm_sec = sec;
        ts.tm_min = min;
        ts.tm_hour = hour;
        ts.tm_mday = mday;
        ts.tm_month = month;
        ts.tm_year = year;
        ts.tm_params.tp_gmt_offset = 0;
        ts.tm_params.tp_dst_offset = 0;
        t = PR_ImplodeTime(&ts);
        v = CERT_CheckCertValidTimes(self, t, PR_TRUE);
        RETVAL = v == secCertTimeValid ? 0 :
                 v == secCertTimeExpired ? -1 :
                 v == secCertTimeNotValidYet ? 1 : v;
    OUTPUT:
        RETVAL

Crypt::NSS::PublicKey
public_key(self)
    Crypt::NSS::Certificate self;
    CODE:
        RETVAL = CERT_ExtractPublicKey(self);
        if (RETVAL == NULL) {
            XSRETURN_UNDEF;
        }
    OUTPUT:
        RETVAL

const char *
subject(self)
    Crypt::NSS::Certificate self;
    CODE:
        RETVAL = savepv(self->subjectName);
    OUTPUT: 
        RETVAL

const char *
issuer(self)
    Crypt::NSS::Certificate self;
    CODE:
        RETVAL = savepv(self->issuerName);
    OUTPUT: 
        RETVAL
    
const char *
email_address(self)
    Crypt::NSS::Certificate self;
    CODE:
        RETVAL = savepv(self->emailAddr);
    OUTPUT: 
        RETVAL

Crypt::NSS::Certificate
clone(self)
    Crypt::NSS::Certificate self;
    CODE:
        RETVAL = CERT_DupCertificate(self);
    OUTPUT:
        RETVAL

void
DESTROY(self)
    Crypt::NSS::Certificate self;
    CODE:
        if (self != NULL) {
            CERT_DestroyCertificate(self);
        }


MODULE = Crypt::NSS     PACKAGE = Crypt::NSS::PKCS11

void
set_password_hook(pkg, hook)
    const char * pkg;
    SV * hook;
    CODE:
        if (PasswordHook != NULL) {
            SvREFCNT_dec(PasswordHook);
        }
        PasswordHook = SvREFCNT_inc(hook);

Crypt::NSS::Certificate
find_cert_by_nickname(pkg, nickname, pin_arg=&PL_sv_undef)
    const char * pkg;
    const char * nickname;
    SV * pin_arg;
    PREINIT:
        CERTCertificate * cert;
    CODE:
        cert = PK11_FindCertFromNickname(nickname, pin_arg);
        if (cert == NULL) {
            XSRETURN_UNDEF;
        }
        RETVAL = cert;
    OUTPUT:
        RETVAL

Crypt::NSS::PrivateKey
find_key_by_any_cert(pkg, cert, pin_arg)
    const char * pkg;
    Crypt::NSS::Certificate cert;
    SV * pin_arg;
    PREINIT:
        SECKEYPrivateKey * key;
    CODE:
        key = PK11_FindKeyByAnyCert(cert, pin_arg);
        if (key == NULL) {
            XSRETURN_UNDEF;
        }
        RETVAL = key;
    OUTPUT:
        RETVAL
      
MODULE = Crypt::NSS     PACKAGE = Crypt::NSS::PKCS11::Slot

const char *
slot_name(self)
    Crypt::NSS::PKCS11::Slot self;
    PREINIT:
        char *name;
    CODE:
        name = PK11_GetSlotName(self);
        RETVAL = savepv(name);
        PR_Free(name);
    OUTPUT:
        RETVAL
        
const char *
token_name(self)
    Crypt::NSS::PKCS11::Slot self;
    PREINIT:
        char *name;
    CODE:
        name = PK11_GetTokenName(self);
        RETVAL = savepv(name);
        PR_Free(name);
    OUTPUT:
        RETVAL

bool
is_hardware(self)
    Crypt::NSS::PKCS11::Slot self;
    CODE:
        RETVAL = PK11_IsHW(self) ? TRUE : FALSE;
    OUTPUT:
        RETVAL

bool
is_present(self)
    Crypt::NSS::PKCS11::Slot self;
    CODE:
        RETVAL = PK11_IsPresent(self) ? TRUE : FALSE;
    OUTPUT:
        RETVAL

bool
is_readonly(self)
    Crypt::NSS::PKCS11::Slot self;
    CODE:
        RETVAL = PK11_IsReadOnly(self) ? TRUE : FALSE;
    OUTPUT:
        RETVAL

MODULE = Crypt::NSS        PACKAGE = Crypt::NSS::SSL

void
set_option(pkg, option, on)
    const char * pkg;
    PRInt32 option;
    PRBool  on;
    CODE:
        EVALUATE_SEC_CALL(SSL_OptionSetDefault(option, on), "Failed to set option default")

PRBool
get_option(pkg, option)
    const char * pkg;
    PRInt32 option;
    PREINIT:
        PRBool on;
    CODE:
        EVALUATE_SEC_CALL(SSL_OptionGetDefault(option, &on), "Failed to get option default")
        RETVAL = on;
    OUTPUT:
        RETVAL

void
set_cipher(pkg, cipher, on)
    const char * pkg;
    PRInt32 cipher;
    PRBool  on;
    CODE:
        EVALUATE_SEC_CALL(SSL_CipherPrefSetDefault(cipher, on), "Failed to set cipher default")

PRBool
get_cipher(pkg, cipher)
    const char * pkg;
    PRInt32 cipher;
    PREINIT:
        PRBool on;
    CODE:
        EVALUATE_SEC_CALL(SSL_CipherPrefGetDefault(cipher, &on), "Failed to get cipher default")
        RETVAL = on;
    OUTPUT:
        RETVAL

AV *
_get_implemented_cipher_ids(pkg)
    const char * pkg;
    PREINIT:
        AV *ciphers = newAV();
        I32 i;
    CODE:
        for (i = 0; i < SSL_NumImplementedCiphers; i++) {
            av_push(ciphers, newSViv(SSL_ImplementedCiphers[i]));
        }
        RETVAL = ciphers;
    OUTPUT:
        RETVAL
        
void
set_cipher_suite(pkg, suite)
    const char * pkg;
    const char * suite;
    PREINIT:
        SECStatus status;
    CODE:
        if (strEQ(suite, "US") || strEQ(suite, "Domestic")) {
            status = NSS_SetDomesticPolicy();
        }
        else if (strEQ(suite, "France")) {
            status = NSS_SetFrancePolicy();
        }
        else if (strEQ(suite, "International") || strEQ(suite, "Export")) {
            status = NSS_SetExportPolicy();
        }
        else {
            croak("No cipher suite for '%s' exists", suite);
        }
        
        if (status != SECSuccess) {
            throw_exception_from_nspr_error("Failed to set cipher suite");
        }
        
void
clear_session_cache(pkg)
    const char * pkg;
    CODE:
        SSL_ClearSessionCache();

void
config_server_session_cache(pkg, args)
    const char * pkg;
    HV * args;
    PREINIT:
        int maxCacheEntries = 0;
        PRInt32 ssl2_timeout = 100;
        PRInt32 ssl3_timeout = 86400;
        const char *data_dir = NULL;
        bool shared = FALSE;
        SV **value;
    CODE:
        if ((value = hv_fetch(args, "max_cache_entries", 17, 0)) != NULL) {
            maxCacheEntries = SvIV(*value);
        }
        if ((value = hv_fetch(args, "ssl2_timeout", 12, 0)) != NULL) {
            ssl2_timeout = SvIV(*value);
        }
        if ((value = hv_fetch(args, "ssl3_timeout", 12, 0)) != NULL) {
            ssl3_timeout = SvIV(*value);
        }
        if ((value = hv_fetch(args, "data_dir", 8, 0)) != NULL) {
            data_dir = SvPV_nolen(*value);
        }
        if ((value = hv_fetch(args, "shared", 6, 0)) != NULL) {
            shared = SvTRUE(*value);
        }
        if (!shared) {
            EVALUATE_SEC_CALL(SSL_ConfigServerSessionIDCache(maxCacheEntries, ssl2_timeout, ssl3_timeout, data_dir),
                                                             "Failed to config server session cache")
        }
        else {
            EVALUATE_SEC_CALL(SSL_ConfigMPServerSIDCache(maxCacheEntries, ssl2_timeout, ssl3_timeout, data_dir),
                                                         "Failed to config shared server session cache")
        }

MODULE = Crypt::NSS		PACKAGE = Crypt::NSS		
PROTOTYPES: DISABLE

const char *
config_dir(pkg)
    const char * pkg;
    CODE:
        RETVAL = savepv(config_dir);
    OUTPUT:
        RETVAL

bool
set_config_dir(pkg, dir)
    const char * pkg;
    const char * dir;
    CODE:
        if (!NSS_IsInitialized()) {
            config_dir = dir;
            RETVAL = TRUE;
        }
        else
            RETVAL = FALSE;
    OUTPUT:
        RETVAL

SECStatus
initialize(pkg)
    const char * pkg;
    CODE:
        if (!NSS_IsInitialized()) {
            RETVAL = NSS_Init(config_dir);
            PK11_SetPasswordFunc(pkcs11_password_hook);
        }
        else
            RETVAL = SECFailure;
    OUTPUT:
        RETVAL

bool
is_initialized(pkg)
    const char * pkg;
    CODE:
        RETVAL = (bool) NSS_IsInitialized();
    OUTPUT:
        RETVAL

void
shutdown(pkg)
    const char * pkg;
    CODE:
        NSS_Shutdown();
        
BOOT:
    config_dir = ".";