/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/*
 * Allow freebl and softoken to be loaded without util or NSPR.
 *
 * These symbols are overridden once real NSPR, and libutil are attached.
 */
#define _GNU_SOURCE 1
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <fcntl.h>
#include <time.h>
#include <unistd.h>
#include <sys/time.h>
#include <dlfcn.h>
#include <prio.h>
#include <prlink.h>
#include <prlog.h>
#include <prthread.h>
#include <plstr.h>
#include <prinit.h>
#include <prlock.h>
#include <prmem.h>
#include <prerror.h>
#include <prmon.h>
#include <pratom.h>
#include <prsystem.h>
#include <prinrval.h>
#include <prtime.h>
#include <prcvar.h>
#include <secasn1.h>
#include <secdig.h>
#include <secport.h>
#include <secitem.h>
#include <blapi.h>
#include <assert.h>
#include <private/pprio.h>

/* Android API < 21 doesn't define RTLD_NOLOAD */
#ifndef RTLD_NOLOAD
#define RTLD_NOLOAD 0
#endif

#define FREEBL_NO_WEAK 1

#define WEAK __attribute__((weak))

#ifdef FREEBL_NO_WEAK

/*
 * This uses function pointers.
 *
 * CONS:  A separate function is needed to
 * fill in the function pointers.
 *
 * PROS: it works on all platforms.
 *  it allows for dynamically finding nspr and libutil, even once
 *  softoken is loaded and running. (NOTE: this may be a problem if
 *  we switch between the stubs and real NSPR on the fly. NSPR will
 *  do bad things if passed an _FakeArena to free or allocate from).
 */
#define STUB_DECLARE(ret, fn, args) \
    typedef ret(*type_##fn) args;   \
    static type_##fn ptr_##fn = NULL

#define STUB_SAFE_CALL0(fn) \
    if (ptr_##fn) {         \
        return ptr_##fn();  \
    }
#define STUB_SAFE_CALL1(fn, a1) \
    if (ptr_##fn) {             \
        return ptr_##fn(a1);    \
    }
#define STUB_SAFE_CALL2(fn, a1, a2) \
    if (ptr_##fn) {                 \
        return ptr_##fn(a1, a2);    \
    }
#define STUB_SAFE_CALL3(fn, a1, a2, a3) \
    if (ptr_##fn) {                     \
        return ptr_##fn(a1, a2, a3);    \
    }
#define STUB_SAFE_CALL4(fn, a1, a2, a3, a4) \
    if (ptr_##fn) {                         \
        return ptr_##fn(a1, a2, a3, a4);    \
    }
#define STUB_SAFE_CALL5(fn, a1, a2, a3, a4, a5) \
    if (ptr_##fn) {                             \
        return ptr_##fn(a1, a2, a3, a4, a5);    \
    }
#define STUB_SAFE_CALL6(fn, a1, a2, a3, a4, a5, a6) \
    if (ptr_##fn) {                                 \
        return ptr_##fn(a1, a2, a3, a4, a5, a6);    \
    }

#define STUB_FETCH_FUNCTION(fn)            \
    ptr_##fn = (type_##fn)dlsym(lib, #fn); \
    if (ptr_##fn == NULL) {                \
        return SECFailure;                 \
    }

#else
/*
 * this uses the loader weak attribute. it works automatically, but once
 * freebl is loaded, the symbols are 'fixed' (later loading of NSPR or
 * libutil will not resolve these symbols).
 */

#define STUB_DECLARE(ret, fn, args) \
    WEAK extern ret fn args

#define STUB_SAFE_CALL0(fn) \
    if (fn) {               \
        return fn();        \
    }
#define STUB_SAFE_CALL1(fn, a1) \
    if (fn) {                   \
        return fn(a1);          \
    }
#define STUB_SAFE_CALL2(fn, a1, a2) \
    if (fn) {                       \
        return fn(a1, a2);          \
    }
#define STUB_SAFE_CALL3(fn, a1, a2, a3) \
    if (fn) {                           \
        return fn(a1, a2, a3);          \
    }
#define STUB_SAFE_CALL4(fn, a1, a2, a3, a4) \
    if (fn) {                               \
        return fn(a1, a2, a3, a4);          \
    }
#define STUB_SAFE_CALL6(fn, a1, a2, a3, a4, a5, a6) \
    if (fn) {                                       \
        return fn(a1, a2, a3, a4, a5, a6);          \
    }
#endif

STUB_DECLARE(void *, PORT_Alloc_Util, (size_t len));
STUB_DECLARE(void *, PORT_ArenaAlloc_Util, (PLArenaPool * arena, size_t size));
STUB_DECLARE(void *, PORT_ArenaZAlloc_Util, (PLArenaPool * arena, size_t size));
STUB_DECLARE(void, PORT_Free_Util, (void *ptr));
STUB_DECLARE(void, PORT_FreeArena_Util, (PLArenaPool * arena, PRBool zero));
STUB_DECLARE(int, PORT_GetError_Util, (void));
STUB_DECLARE(PLArenaPool *, PORT_NewArena_Util, (unsigned long chunksize));
STUB_DECLARE(void, PORT_SafeZero, (void *p, size_t n));
STUB_DECLARE(void, PORT_SetError_Util, (int value));
STUB_DECLARE(void *, PORT_ZAlloc_Util, (size_t len));
STUB_DECLARE(void *, PORT_ZAllocAligned_Util, (size_t bytes, size_t alignment, void **mem));
STUB_DECLARE(void *, PORT_ZAllocAlignedOffset_Util, (size_t bytes, size_t alignment, size_t offset));
STUB_DECLARE(void, PORT_ZFree_Util, (void *ptr, size_t len));

STUB_DECLARE(void, PR_Assert, (const char *s, const char *file, PRIntn ln));
STUB_DECLARE(PRStatus, PR_Access, (const char *name, PRAccessHow how));
STUB_DECLARE(PRStatus, PR_CallOnce, (PRCallOnceType * once, PRCallOnceFN func));
STUB_DECLARE(PRStatus, PR_Close, (PRFileDesc * fd));
STUB_DECLARE(void, PR_DestroyLock, (PRLock * lock));
STUB_DECLARE(void, PR_DestroyCondVar, (PRCondVar * cvar));
STUB_DECLARE(void, PR_Free, (void *ptr));
STUB_DECLARE(char *, PR_GetLibraryFilePathname, (const char *name, PRFuncPtr addr));
STUB_DECLARE(PRFileDesc *, PR_ImportPipe, (PROsfd osfd));
STUB_DECLARE(void, PR_Lock, (PRLock * lock));
STUB_DECLARE(PRCondVar *, PR_NewCondVar, (PRLock * lock));
STUB_DECLARE(PRLock *, PR_NewLock, (void));
STUB_DECLARE(PRStatus, PR_NotifyCondVar, (PRCondVar * cvar));
STUB_DECLARE(PRStatus, PR_NotifyAllCondVar, (PRCondVar * cvar));
STUB_DECLARE(PRFileDesc *, PR_Open, (const char *name, PRIntn flags, PRIntn mode));
STUB_DECLARE(PRInt32, PR_Read, (PRFileDesc * fd, void *buf, PRInt32 amount));
STUB_DECLARE(PROffset32, PR_Seek, (PRFileDesc * fd, PROffset32 offset, PRSeekWhence whence));
STUB_DECLARE(PRStatus, PR_Sleep, (PRIntervalTime ticks));
STUB_DECLARE(PRStatus, PR_Unlock, (PRLock * lock));
STUB_DECLARE(PRStatus, PR_WaitCondVar, (PRCondVar * cvar, PRIntervalTime timeout));
STUB_DECLARE(char *, PR_GetEnvSecure, (const char *));

STUB_DECLARE(SECItem *, SECITEM_AllocItem_Util, (PLArenaPool * arena, SECItem *item, unsigned int len));
STUB_DECLARE(SECComparison, SECITEM_CompareItem_Util, (const SECItem *a, const SECItem *b));
STUB_DECLARE(PRBool, SECITEM_ItemsAreEqual_Util, (const SECItem *a, const SECItem *b));
STUB_DECLARE(SECStatus, SECITEM_CopyItem_Util, (PLArenaPool * arena, SECItem *to, const SECItem *from));
STUB_DECLARE(void, SECITEM_FreeItem_Util, (SECItem * zap, PRBool freeit));
STUB_DECLARE(void, SECITEM_ZfreeItem_Util, (SECItem * zap, PRBool freeit));
STUB_DECLARE(SECOidTag, SECOID_FindOIDTag_Util, (const SECItem *oid));
STUB_DECLARE(int, NSS_SecureMemcmp, (const void *a, const void *b, size_t n));
STUB_DECLARE(unsigned int, NSS_SecureMemcmpZero, (const void *mem, size_t n));
STUB_DECLARE(void, NSS_SecureSelect, (void *dest, const void *src0, const void *src1, size_t n, unsigned char b));
#ifndef NSS_FIPS_DISABLED
STUB_DECLARE(PRBool, NSS_GetSystemFIPSEnabled, (void));
#endif

#define PORT_ZNew_stub(type) (type *)PORT_ZAlloc_stub(sizeof(type))
#define PORT_New_stub(type) (type *)PORT_Alloc_stub(sizeof(type))
#define PORT_ZNewArray_stub(type, num) \
    (type *)PORT_ZAlloc_stub(sizeof(type) * (num))
#define PORT_ZNewAligned_stub(type, alignment, mem) \
    (type *)PORT_ZAllocAlignedOffset_stub(sizeof(type), alignment, offsetof(type, mem))

/*
 * NOTE: in order to support hashing only the memory allocation stubs,
 * the get library name stubs, and the file io stubs are needed (the latter
 * two are for the library verification). The remaining stubs are simply to
 * compile. Attempts to use the library for other operations without NSPR
 * will most likely fail.
 */

/* memory */
extern void *
PORT_Alloc_stub(size_t len)
{
    STUB_SAFE_CALL1(PORT_Alloc_Util, len);
    return malloc(len);
}

extern void
PORT_Free_stub(void *ptr)
{
    STUB_SAFE_CALL1(PORT_Free_Util, ptr);
    return free(ptr);
}

extern void *
PORT_ZAlloc_stub(size_t len)
{
    STUB_SAFE_CALL1(PORT_ZAlloc_Util, len);
    void *ptr = malloc(len);
    if (ptr) {
        memset(ptr, 0, len);
    }
    return ptr;
}

/* aligned_alloc is C11. This is an alternative to get aligned memory. */
extern void *
PORT_ZAllocAligned_stub(size_t bytes, size_t alignment, void **mem)
{
    STUB_SAFE_CALL3(PORT_ZAllocAligned_Util, bytes, alignment, mem);

    /* This only works if alignement is a power of 2. */
    if ((alignment == 0) || (alignment & (alignment - 1))) {
        return NULL;
    }

    size_t x = alignment - 1;
    size_t len = (bytes ? bytes : 1) + x;

    if (!mem) {
        return NULL;
    }

    /* Always allocate a non-zero amount of bytes */
    *mem = malloc(len);
    if (!*mem) {
        return NULL;
    }

    memset(*mem, 0, len);

    /* We're pretty sure this is non-zero, but let's assure scan-build too. */
    void *ret = (void *)(((uintptr_t)*mem + x) & ~(uintptr_t)x);
    assert(ret);

    return ret;
}

extern void *
PORT_ZAllocAlignedOffset_stub(size_t size, size_t alignment, size_t offset)
{
    STUB_SAFE_CALL3(PORT_ZAllocAlignedOffset_Util, size, alignment, offset);
    if (offset > size) {
        return NULL;
    }

    void *mem = NULL;
    void *v = PORT_ZAllocAligned_stub(size, alignment, &mem);
    if (!v) {
        return NULL;
    }

    *((void **)((uintptr_t)v + offset)) = mem;
    return v;
}

extern void
PORT_ZFree_stub(void *ptr, size_t len)
{
    STUB_SAFE_CALL2(PORT_ZFree_Util, ptr, len);
    memset(ptr, 0, len);
    return free(ptr);
}

extern void
PR_Free_stub(void *ptr)
{
    STUB_SAFE_CALL1(PR_Free, ptr);
    return free(ptr);
}

/* we have defensive returns after abort(), which is marked noreturn on some
 * platforms, making the compiler legitimately complain. */
#ifdef __clang__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunreachable-code-return"
#endif

/*
 * arenas
 *
 */
extern PLArenaPool *
PORT_NewArena_stub(unsigned long chunksize)
{
    STUB_SAFE_CALL1(PORT_NewArena_Util, chunksize);
    abort();
    return NULL;
}

extern void *
PORT_ArenaAlloc_stub(PLArenaPool *arena, size_t size)
{

    STUB_SAFE_CALL2(PORT_ArenaZAlloc_Util, arena, size);
    abort();
    return NULL;
}

extern void *
PORT_ArenaZAlloc_stub(PLArenaPool *arena, size_t size)
{

    STUB_SAFE_CALL2(PORT_ArenaZAlloc_Util, arena, size);
    abort();
    return NULL;
}

extern void
PORT_FreeArena_stub(PLArenaPool *arena, PRBool zero)
{

    STUB_SAFE_CALL2(PORT_FreeArena_Util, arena, zero);
    abort();
}

/* io */
extern PRFileDesc *
PR_Open_stub(const char *name, PRIntn flags, PRIntn mode)
{
    int *lfd = NULL;
    int fd;
    int lflags = 0;

    STUB_SAFE_CALL3(PR_Open, name, flags, mode);

    if (flags & PR_RDWR) {
        lflags = O_RDWR;
    } else if (flags & PR_WRONLY) {
        lflags = O_WRONLY;
    } else {
        lflags = O_RDONLY;
    }

    if (flags & PR_EXCL)
        lflags |= O_EXCL;
    if (flags & PR_APPEND)
        lflags |= O_APPEND;
    if (flags & PR_TRUNCATE)
        lflags |= O_TRUNC;

    fd = open(name, lflags, mode);
    if (fd >= 0) {
        lfd = PORT_New_stub(int);
        if (lfd != NULL) {
            *lfd = fd;
        } else {
            close(fd);
        }
    }
    return (PRFileDesc *)lfd;
}

extern PRFileDesc *
PR_ImportPipe_stub(PROsfd fd)
{
    int *lfd = NULL;

    STUB_SAFE_CALL1(PR_ImportPipe, fd);

    lfd = PORT_New_stub(int);
    if (lfd != NULL) {
        *lfd = fd;
    }
    return (PRFileDesc *)lfd;
}

extern PRStatus
PR_Close_stub(PRFileDesc *fd)
{
    int *lfd;
    STUB_SAFE_CALL1(PR_Close, fd);

    lfd = (int *)fd;
    close(*lfd);
    PORT_Free_stub(lfd);

    return PR_SUCCESS;
}

extern PRInt32
PR_Read_stub(PRFileDesc *fd, void *buf, PRInt32 amount)
{
    int *lfd;
    STUB_SAFE_CALL3(PR_Read, fd, buf, amount);

    lfd = (int *)fd;
    return read(*lfd, buf, amount);
}

extern PROffset32
PR_Seek_stub(PRFileDesc *fd, PROffset32 offset, PRSeekWhence whence)
{
    int *lfd;
    int lwhence = SEEK_SET;
    STUB_SAFE_CALL3(PR_Seek, fd, offset, whence);
    lfd = (int *)fd;
    switch (whence) {
        case PR_SEEK_CUR:
            lwhence = SEEK_CUR;
            break;
        case PR_SEEK_END:
            lwhence = SEEK_END;
            break;
        case PR_SEEK_SET:
            break;
    }

    return lseek(*lfd, offset, lwhence);
}

PRStatus
PR_Access_stub(const char *name, PRAccessHow how)
{
    int mode = F_OK;
    int rv;
    STUB_SAFE_CALL2(PR_Access, name, how);
    switch (how) {
        case PR_ACCESS_WRITE_OK:
            mode = W_OK;
            break;
        case PR_ACCESS_READ_OK:
            mode = R_OK;
            break;
        /* assume F_OK for all others */
        default:
            break;
    }
    rv = access(name, mode);
    if (rv == 0) {
        return PR_SUCCESS;
    }
    return PR_FAILURE;
}

/*
 * library
 */
extern char *
PR_GetLibraryFilePathname_stub(const char *name, PRFuncPtr addr)
{
    Dl_info dli;
    char *result;

    STUB_SAFE_CALL2(PR_GetLibraryFilePathname, name, addr);

    if (dladdr((void *)addr, &dli) == 0) {
        return NULL;
    }
    result = PORT_Alloc_stub(strlen(dli.dli_fname) + 1);
    if (result != NULL) {
        strcpy(result, dli.dli_fname);
    }
    return result;
}

#include <errno.h>

/* errors */
extern int
PORT_GetError_stub(void)
{
    STUB_SAFE_CALL0(PORT_GetError_Util);
    return errno;
}

extern void
PORT_SafeZero_stub(void *p, size_t n)
{
    STUB_SAFE_CALL2(PORT_SafeZero, p, n);
    /* just use a generic call in the case where we are running
     * standalone freebl */
    if (p != NULL) {
        volatile unsigned char *__vl = (unsigned char *)p;
        size_t __nl = n;
        while (__nl--)
            *__vl++ = 0;
    }
}

extern void
PORT_SetError_stub(int value)
{
    STUB_SAFE_CALL1(PORT_SetError_Util, value);
    errno = value;
}

/* misc */
extern void
PR_Assert_stub(const char *s, const char *file, PRIntn ln)
{
    STUB_SAFE_CALL3(PR_Assert, s, file, ln);
    fprintf(stderr, "%s line %d: %s\n", file, ln, s);
    abort();
}

/* time */
extern PRStatus
PR_Sleep_stub(PRIntervalTime ticks)
{
    STUB_SAFE_CALL1(PR_Sleep, ticks);
    usleep(ticks * 1000);
    return PR_SUCCESS;
}

/* locking */
extern PRLock *
PR_NewLock_stub(void)
{
    STUB_SAFE_CALL0(PR_NewLock);
    abort();
    return NULL;
}

extern PRStatus
PR_Unlock_stub(PRLock *lock)
{
    STUB_SAFE_CALL1(PR_Unlock, lock);
    abort();
    return PR_FAILURE;
}

extern void
PR_Lock_stub(PRLock *lock)
{
    STUB_SAFE_CALL1(PR_Lock, lock);
    abort();
    return;
}

extern void
PR_DestroyLock_stub(PRLock *lock)
{
    STUB_SAFE_CALL1(PR_DestroyLock, lock);
    abort();
    return;
}

extern PRCondVar *
PR_NewCondVar_stub(PRLock *lock)
{
    STUB_SAFE_CALL1(PR_NewCondVar, lock);
    abort();
    return NULL;
}

extern PRStatus
PR_NotifyCondVar_stub(PRCondVar *cvar)
{
    STUB_SAFE_CALL1(PR_NotifyCondVar, cvar);
    abort();
    return PR_FAILURE;
}

extern PRStatus
PR_NotifyAllCondVar_stub(PRCondVar *cvar)
{
    STUB_SAFE_CALL1(PR_NotifyAllCondVar, cvar);
    abort();
    return PR_FAILURE;
}

extern PRStatus
PR_WaitCondVar_stub(PRCondVar *cvar, PRIntervalTime timeout)
{
    STUB_SAFE_CALL2(PR_WaitCondVar, cvar, timeout);
    abort();
    return PR_FAILURE;
}

extern char *
PR_GetEnvSecure_stub(const char *var)
{
    STUB_SAFE_CALL1(PR_GetEnvSecure, var);
#ifdef __USE_GNU
    return secure_getenv(var);
#else
    return getenv(var);
#endif
}

extern void
PR_DestroyCondVar_stub(PRCondVar *cvar)
{
    STUB_SAFE_CALL1(PR_DestroyCondVar, cvar);
    abort();
    return;
}

/*
 * NOTE: this presupposes GCC 4.1
 */
extern PRStatus
PR_CallOnce_stub(PRCallOnceType *once, PRCallOnceFN func)
{
    STUB_SAFE_CALL2(PR_CallOnce, once, func);
    abort();
    return PR_FAILURE;
}

/*
 * SECITEMS implement Item Utilities
 */
extern void
SECITEM_FreeItem_stub(SECItem *zap, PRBool freeit)
{
    STUB_SAFE_CALL2(SECITEM_FreeItem_Util, zap, freeit);
    abort();
}

extern SECItem *
SECITEM_AllocItem_stub(PLArenaPool *arena, SECItem *item, unsigned int len)
{
    STUB_SAFE_CALL3(SECITEM_AllocItem_Util, arena, item, len);
    abort();
    return NULL;
}

extern SECComparison
SECITEM_CompareItem_stub(const SECItem *a, const SECItem *b)
{
    STUB_SAFE_CALL2(SECITEM_CompareItem_Util, a, b);
    abort();
    return SECEqual;
}

extern PRBool
SECITEM_ItemsAreEqual_stub(const SECItem *a, const SECItem *b)
{
    STUB_SAFE_CALL2(SECITEM_ItemsAreEqual_Util, a, b);
    /* two nulls are equal */
    if (!a && !b) {
        return PR_TRUE;
    }
    /* only one NULL is not equal */
    if (!a || !b) {
        return PR_FALSE;
    }
    /* we know both secitems have been set, now make sure the lengths
     * are equal */
    if (a->len != b->len) {
        return PR_FALSE;
    }
    /* lengths are equal, safe to verify the data */
    if (PORT_Memcmp(a->data, b->data, b->len) != 0) {
        return PR_FALSE;
    }
    return PR_TRUE;
}

extern SECStatus
SECITEM_CopyItem_stub(PLArenaPool *arena, SECItem *to, const SECItem *from)
{
    STUB_SAFE_CALL3(SECITEM_CopyItem_Util, arena, to, from);
    abort();
    return SECFailure;
}

extern SECOidTag
SECOID_FindOIDTag_stub(const SECItem *oid)
{
    STUB_SAFE_CALL1(SECOID_FindOIDTag_Util, oid);
    abort();
    return SEC_OID_UNKNOWN;
}

#ifdef __clang__
#pragma clang diagnostic pop
#endif

extern void
SECITEM_ZfreeItem_stub(SECItem *zap, PRBool freeit)
{
    STUB_SAFE_CALL2(SECITEM_ZfreeItem_Util, zap, freeit);
    if (zap) {
        if (zap->data) {
            PORT_Memset(zap->data, 0, zap->len);
            PORT_Free_stub(zap->data);
        }
        PORT_Memset(zap, 0, sizeof(SECItem));
        if (freeit) {
            PORT_Free_stub(zap);
        }
    }
}

extern int
NSS_SecureMemcmp_stub(const void *a, const void *b, size_t n)
{
    STUB_SAFE_CALL3(NSS_SecureMemcmp, a, b, n);
    abort();
}

extern unsigned int
NSS_SecureMemcmpZero_stub(const void *mem, size_t n)
{
    STUB_SAFE_CALL2(NSS_SecureMemcmpZero, mem, n);
    abort();
}

extern void
NSS_SecureSelect_stub(void *dest, const void *src0, const void *src1, size_t n, unsigned char b)
{
    STUB_SAFE_CALL5(NSS_SecureSelect, dest, src0, src1, n, b);
    abort();
}

#ifndef NSS_FIPS_DISABLED
PRBool
NSS_GetSystemFIPSEnabled_stub(void)
{
    STUB_SAFE_CALL0(NSS_GetSystemFIPSEnabled);
    const char *env;

    /* The environment variable is active for all platforms */
    env = PR_GetEnvSecure_stub("NSS_FIPS");
    /* we generally accept y, Y, 1, FIPS, TRUE, and ON as turning on FIPS
     * mode. Anything else is considered 'off' */
    if (env && (*env == 'y' || *env == '1' || *env == 'Y' ||
                (strcasecmp(env, "fips") == 0) ||
                (strcasecmp(env, "true") == 0) ||
                (strcasecmp(env, "on") == 0))) {
        return PR_TRUE;
    }

/* currently only Linux has a system FIPS indicator. Add others here
 * as they become available/known */
#ifdef LINUX
    {
        FILE *f;
        char d;
        size_t size;
        f = fopen("/proc/sys/crypto/fips_enabled", "r");
        if (!f)
            return PR_FALSE;

        size = fread(&d, 1, 1, f);
        fclose(f);
        if (size != 1)
            return PR_FALSE;
        if (d == '1')
            return PR_TRUE;
    }
#endif /* LINUX */
    return PR_FALSE;
}
#endif /* NSS_FIPS_DISABLED = 0 */

#ifdef FREEBL_NO_WEAK

static const char *nsprLibName = SHLIB_PREFIX "nspr4." SHLIB_SUFFIX;
static const char *nssutilLibName = SHLIB_PREFIX "nssutil3." SHLIB_SUFFIX;

static SECStatus
freebl_InitNSPR(void *lib)
{
    STUB_FETCH_FUNCTION(PR_Free);
    STUB_FETCH_FUNCTION(PR_Open);
    STUB_FETCH_FUNCTION(PR_ImportPipe);
    STUB_FETCH_FUNCTION(PR_Close);
    STUB_FETCH_FUNCTION(PR_Read);
    STUB_FETCH_FUNCTION(PR_Seek);
    STUB_FETCH_FUNCTION(PR_GetLibraryFilePathname);
    STUB_FETCH_FUNCTION(PR_Assert);
    STUB_FETCH_FUNCTION(PR_Access);
    STUB_FETCH_FUNCTION(PR_Sleep);
    STUB_FETCH_FUNCTION(PR_CallOnce);
    STUB_FETCH_FUNCTION(PR_NewCondVar);
    STUB_FETCH_FUNCTION(PR_NotifyCondVar);
    STUB_FETCH_FUNCTION(PR_NotifyAllCondVar);
    STUB_FETCH_FUNCTION(PR_WaitCondVar);
    STUB_FETCH_FUNCTION(PR_DestroyCondVar);
    STUB_FETCH_FUNCTION(PR_NewLock);
    STUB_FETCH_FUNCTION(PR_Unlock);
    STUB_FETCH_FUNCTION(PR_Lock);
    STUB_FETCH_FUNCTION(PR_DestroyLock);
    STUB_FETCH_FUNCTION(PR_GetEnvSecure);
    return SECSuccess;
}

static SECStatus
freebl_InitNSSUtil(void *lib)
{
    STUB_FETCH_FUNCTION(PORT_Alloc_Util);
    STUB_FETCH_FUNCTION(PORT_Free_Util);
    STUB_FETCH_FUNCTION(PORT_ZAlloc_Util);
    STUB_FETCH_FUNCTION(PORT_ZFree_Util);
    STUB_FETCH_FUNCTION(PORT_NewArena_Util);
    STUB_FETCH_FUNCTION(PORT_ArenaAlloc_Util);
    STUB_FETCH_FUNCTION(PORT_ArenaZAlloc_Util);
    STUB_FETCH_FUNCTION(PORT_FreeArena_Util);
    STUB_FETCH_FUNCTION(PORT_GetError_Util);
    STUB_FETCH_FUNCTION(PORT_SetError_Util);
    STUB_FETCH_FUNCTION(SECITEM_FreeItem_Util);
    STUB_FETCH_FUNCTION(SECITEM_AllocItem_Util);
    STUB_FETCH_FUNCTION(SECITEM_CompareItem_Util);
    STUB_FETCH_FUNCTION(SECITEM_CopyItem_Util);
    STUB_FETCH_FUNCTION(SECITEM_ZfreeItem_Util);
    STUB_FETCH_FUNCTION(SECOID_FindOIDTag_Util);
    STUB_FETCH_FUNCTION(NSS_SecureMemcmp);
    STUB_FETCH_FUNCTION(NSS_SecureMemcmpZero);
    STUB_FETCH_FUNCTION(NSS_SecureSelect);
    return SECSuccess;
}

/*
 * fetch the library if it's loaded. For NSS it should already be loaded
 */
#define freebl_getLibrary(libName) \
    dlopen(libName, RTLD_LAZY | RTLD_NOLOAD)

#define freebl_releaseLibrary(lib) \
    if (lib)                       \
    dlclose(lib)

static void *FREEBLnsprGlobalLib = NULL;
static void *FREEBLnssutilGlobalLib = NULL;

void __attribute((destructor)) FREEBL_unload()
{
    freebl_releaseLibrary(FREEBLnsprGlobalLib);
    freebl_releaseLibrary(FREEBLnssutilGlobalLib);
}
#endif

/*
 * load the symbols from the real libraries if available.
 *
 * if force is set, explicitly load the libraries if they are not already
 * loaded. If we could not use the real libraries, return failure.
 */
extern SECStatus
FREEBL_InitStubs()
{
    SECStatus rv = SECSuccess;
#ifdef FREEBL_NO_WEAK
    void *nspr = NULL;
    void *nssutil = NULL;

    /* NSPR should be first */
    if (!FREEBLnsprGlobalLib) {
        nspr = freebl_getLibrary(nsprLibName);
        if (!nspr) {
            return SECFailure;
        }
        rv = freebl_InitNSPR(nspr);
        if (rv != SECSuccess) {
            freebl_releaseLibrary(nspr);
            return rv;
        }
        FREEBLnsprGlobalLib = nspr; /* adopt */
    }
    /* now load NSSUTIL */
    if (!FREEBLnssutilGlobalLib) {
        nssutil = freebl_getLibrary(nssutilLibName);
        if (!nssutil) {
            return SECFailure;
        }
        rv = freebl_InitNSSUtil(nssutil);
        if (rv != SECSuccess) {
            freebl_releaseLibrary(nssutil);
            return rv;
        }
        FREEBLnssutilGlobalLib = nssutil; /* adopt */
    }
#endif

    return rv;
}
