/* Copyright (c) 1995-1999 NEC USA, Inc.  All rights reserved.               */
/*                                                                           */
/* The redistribution, use and modification in source or binary forms of     */
/* this software is subject to the conditions set forth in the copyright     */
/* document ("Copyright") included with this distribution.                   */

/*
 * $Id: rld.c,v 1.33.4.12 1999/08/02 14:56:13 wlu Exp $
 */

/* This file has all the stubs to get the "real" functions out of libc,      */
/* libnsl or libsocket.  Sometimes we need the "real" function, because      */
/* we've replaced it (i.e. connect), sometimes we need it because we will    */
/* have mangled some static data that it keeps around (i.e. getenv).         */
#include "socks5p.h"
#include "addr.h"
#include "log.h"

#ifdef HAVE_DLOPEN

#include <dlfcn.h>

#ifndef RTLD_LAZY
#define RTLD_LAZY 1
#endif
#ifndef RTLD_GLOBAL
#define RTLD_GLOBAL 0
#endif
#ifndef LIBC_NAME
#define LIBC_NAME NULL
#endif
#ifndef LIBNSL_NAME
#define LIBNSL_NAME NULL
#endif
#ifndef LIBRESOLV_NAME
#define LIBRESOLV_NAME NULL
#endif
#ifndef LIBSOCKET_NAME
#define LIBSOCKET_NAME NULL
#endif
#ifndef LIBDGC_NAME
#define LIBDGC_NAME NULL
#endif

/* Lets hope we don't have to do this for too many OS's...Maybe configure    */
/* should figure it out...?                                                  */
#define TRY_LIBC        (1 << 0)
#define TRY_LIBNSL      (1 << 1)
#define TRY_LIBSOCKET   (1 << 2)
#define TRY_LIBRESOLV   (1 << 3)

#define NO_RTLD_NEXT    (1 << 4)
#define USE_RTLD_GLOBAL (1 << 5)

#ifdef FOR_SHARED_LIBRARY

#define GETSYM(handle, libname, flags)                                       \
     if (!(handle) && ((handle) = dlopen((libname), (flags))) == NULL) {     \
	return;								     \
     }                                                                       \
     if ((*fptr = dlsym((handle), name)) != NULL) {                          \
        return;                                                              \
     } 
 
#define DGETSYM(handle, libname, flags)                                      \
     if (!(handle) && ((handle) = dlopen((libname), (flags))) == NULL) {     \
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Unable to open shared library: %s", (libname)); \
        return;                                                              \
     }                                                                       \
     if ((*fptr = dlsym((handle), name)) != NULL) {                          \
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Found %s in %s%s", name, (libname), libmask&USE_RTLD_GLOBAL?"(g)":"");  \
        return;                                                              \
     } 
 
#define GETSYMHANDLE(mask, handle, libname, envname, flags)                  \
    if (libmask & (mask)) {                                                  \
	static void *(handle) = NULL;                                        \
        char *lname = (envname)?getenv((envname)):NULL;                      \
	if (lname == NULL) lname = (libname);                                \
        if (lname) { GETSYM((handle), lname, (flags)); }                     \
    } 

#define DGETSYMHANDLE(mask, handle, libname, envname, flags)                 \
    if (libmask & (mask)) {                                                  \
	static void *(handle) = NULL;                                        \
        char *lname = (envname)?getenv((envname)):NULL;                      \
	if (lname == NULL) lname = (libname);                                \
        if (lname) { DGETSYM((handle), lname, (flags)); }                    \
    } 

#define GETFUNC(name, flags, invalid, cast, args, rtype)                     \
    static void *func = NULL;                                                \
    static rtype rval;                                                       \
    GetOriginalFunc(&func, (name), (flags));                                 \
    if (!func || func == (void *)-1) return (invalid);                       \
    lsInRLDFunctions = 1;                                                      \
    rval = (cast func)args;                                                  \
    lsInRLDFunctions = 0;                                                      \
    return rval

#define DGETFUNC(name, flags, invalid, cast, args, rtype)                    \
    static void *func = NULL;                                                \
    static rtype rval;                                                       \
    DGetOriginalFunc(&func, (name), (flags));                                 \
    if (!func || func == (void *)-1) return (invalid);                       \
    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "RLD: Found %s at %p", (name)+1, func);     \
    lsInRLDFunctions = 1;                                                      \
    rval = (cast func)args;                                                  \
    lsInRLDFunctions = 0;                                                      \
    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "RLD: %s returned: %x", (name)+1, (rval));  \
    return rval

extern int lsInRLDFunctions;

/* Look up name in libc.so, libnsl.so, and libsocket.so, if its there return */
/* the symbol we found, otherwise return NULL...                             */
static void GetOriginalFunc(void **fptr, char *name, int libmask) {
    /* Synchronize access to func and lib opening functions if we can...     */

#if __FreeBSD__ != 2
    name++;
#endif

    if (*fptr) return;

    /* Still have to figure out what OSs USE_RTLD_NEXT is valid for.  For    */
    /* most that I've tried, it hasn't worked right...                       */
#if defined(RTLD_NEXT) && defined(USE_RTLD_NEXT)
    if (name && ~libmask & NO_RTLD_NEXT && (*fptr = dlsym(RTLD_NEXT, name)) != NULL) {
        return;
    }
#endif

    GETSYMHANDLE(TRY_LIBRESOLV, libresolv_handle, LIBRESOLV_NAME,  "LIBRESOLV_NAME", RTLD_LAZY);
    GETSYMHANDLE(TRY_LIBNSL,    libnsl_handle,    LIBNSL_NAME,     "LIBNSL_NAME",    RTLD_LAZY);
    GETSYMHANDLE(TRY_LIBSOCKET, libsocket_handle, LIBSOCKET_NAME,  "LIBSOCKET_NAME", RTLD_LAZY);
    GETSYMHANDLE(TRY_LIBSOCKET, libdgc_handle,    LIBDGC_NAME,     "LIBDGC_NAME",    RTLD_LAZY);
    GETSYMHANDLE(TRY_LIBC,      libc_handle,      LIBC_NAME,       "LIBC_NAME",      RTLD_LAZY);
}

/* Look up name in libc.so, libnsl.so, and libsocket.so, if its there return */
/* the symbol we found, otherwise return NULL...                             */
static void DGetOriginalFunc(void **fptr, char *name, int libmask) {
    /* Synchronize access to func and lib opening functions if we can...     */

#ifndef __FreeBSD__
    name++;
#endif

    if (*fptr) return;
    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "RLD: %s", name);

    /* Still have to figure out what OSs USE_RTLD_NEXT is valid for.  For    */
    /* most that I've tried, it hasn't worked right...                       */
#if defined(RTLD_NEXT) && defined(USE_RTLD_NEXT)
    if (name && ~libmask & NO_RTLD_NEXT && (*fptr = dlsym(RTLD_NEXT, name)) != NULL) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "RLD: Used RTLD_NEXT");
        return;
    }
#endif

    DGETSYMHANDLE(TRY_LIBRESOLV, libresolv_handle, LIBRESOLV_NAME,  "LIBRESOLV_NAME", RTLD_LAZY);
    DGETSYMHANDLE(TRY_LIBNSL,    libnsl_handle,    LIBNSL_NAME,     "LIBNSL_NAME",    RTLD_LAZY);
    DGETSYMHANDLE(TRY_LIBSOCKET, libsocket_handle, LIBSOCKET_NAME,  "LIBSOCKET_NAME", RTLD_LAZY);
    DGETSYMHANDLE(TRY_LIBSOCKET, libdgc_handle,    LIBDGC_NAME,     "LIBDGC_NAME",    RTLD_LAZY);
    DGETSYMHANDLE(TRY_LIBC,      libc_handle,      LIBC_NAME,       "LIBC_NAME",      RTLD_LAZY);
    
    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(0), 0, "RLD: Unable to find symbol %s in suggested places: %d", name, libmask);
}

struct hostent *REAL(gethostbyname)(const char *name) {
    struct hostent *hp;
    static void *func = NULL;

    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "RLD: gethostbyname: %s", name);
    GetOriginalFunc(&func, "_gethostbyname", TRY_LIBC | TRY_LIBNSL | TRY_LIBRESOLV);
    if (!func || func == (void *)-1) return NULL;

    lsInRLDFunctions = 1;
    hp = ((struct hostent *(*)P((const char *)))func)(name);
    lsInRLDFunctions = 0;
    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "RLD: gethostbyname results: %s %s", name, hp?hp->h_name:"???");
    return hp;
}

#ifdef HAVE_GETHOSTBYNAME2
struct hostent *REAL(gethostbyname2)(const char *name, int af) {
    struct hostent *hp;
    static void *func = NULL;

    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "RLD: gethostbyname2: %s:%d", name, af);
    GetOriginalFunc(&func, "_gethostbyname2", TRY_LIBC | TRY_LIBNSL | TRY_LIBRESOLV);
    if (!func || func == (void *)-1) return NULL;

    lsInRLDFunctions = 1;
    hp = ((struct hostent *(*)P((const char *, int)))func)(name, af);
    lsInRLDFunctions = 0;
    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "RLD: gethostbyname2 results: %s %s", name, hp?hp->h_name:"???");
    return hp;
}
#endif

int REAL(getpeername) (S5IOHandle sd, ss *sa, int *slen) {
    GETFUNC("_getpeername", TRY_LIBC | TRY_LIBSOCKET,  -1, (int (*)P((S5IOHandle, ss *, int *))),                            (sd, sa, slen), int);
}

int REAL(getsockname) (S5IOHandle sd, ss *sa, int *slen) {
    GETFUNC("_getsockname", TRY_LIBC | TRY_LIBSOCKET, -1,  (int (*)P((S5IOHandle, ss *, int *))),                            (sd, sa, slen), int);
}
    
int REAL(accept)      (S5IOHandle sd, ss *sa, int *slen) {
    GETFUNC("_accept",      TRY_LIBC | TRY_LIBSOCKET,  -1, (int (*)P((S5IOHandle, ss *, int *))),                            (sd, sa, slen), int);
}

int REAL(connect)     (S5IOHandle sd, const ss *sa, int slen) {
    GETFUNC("_connect",     TRY_LIBC | TRY_LIBSOCKET, -1,  (int (*)P((S5IOHandle, const ss *, int))),                        (sd, sa, slen), int);
}

int REAL(bind)        (S5IOHandle sd, const ss *sa, int slen) {
    GETFUNC("_bind",        TRY_LIBC | TRY_LIBSOCKET, -1,  (int (*)P((S5IOHandle, const ss *, int))),                        (sd, sa, slen), int);
}

int REAL(recvfrom)    (S5IOHandle sd, IOPTRTYPE buf, IOLENTYPE blen, int f, ss *sa, int *slen) {
    GETFUNC("_recvfrom",    TRY_LIBC | TRY_LIBSOCKET, -1, (int (*)P((S5IOHandle, char *, int, int, ss *, int *))),           (sd, buf, blen, f, sa, slen), int);
}

int REAL(sendto)      (S5IOHandle sd, const IOPTRTYPE buf, IOLENTYPE blen, int f, const ss *sa, int slen) {
    GETFUNC("_sendto",      TRY_LIBC | TRY_LIBSOCKET, -1, (int (*)P((S5IOHandle, const char *, int, int, const ss *, int))), (sd, buf, blen, f, sa, slen), int);
}

#ifdef HAVE_SENDMSG
int REAL(recvmsg)    (S5IOHandle sd, const ms *sa, int slen) {
    GETFUNC("_recvmsg",    TRY_LIBC | TRY_LIBSOCKET, -1, (int (*)P((S5IOHandle, const ms *, int ))),           (sd, sa, slen), int);
}

int REAL(sendmsg)      (S5IOHandle sd, const ms *sa, int slen) {
    GETFUNC("_sendmsg",      TRY_LIBC | TRY_LIBSOCKET, -1, (int (*)P((S5IOHandle, const ms *, int))), (sd, sa, slen), int);
}
#endif

int REAL(recv)        (S5IOHandle sd, IOPTRTYPE buf, IOLENTYPE blen, int f) {
    GETFUNC("_recv",        TRY_LIBC | TRY_LIBSOCKET, -1, (int (*)P((S5IOHandle, char *, int, int))),                        (sd, buf, blen, f), int);
}

int REAL(send)        (S5IOHandle sd, const IOPTRTYPE buf, IOLENTYPE blen, int f) {
    GETFUNC("_send",        TRY_LIBC | TRY_LIBSOCKET, -1, (int (*)P((S5IOHandle, const char *, int, int))),                  (sd, buf, blen, f), int);
}

int REAL(shutdown)    (S5IOHandle sd, int how) {
    GETFUNC("_shutdown",    TRY_LIBC | TRY_LIBSOCKET, -1, (int (*)P((S5IOHandle, int))),                                     (sd, how), int);
}

int REAL(listen)      (S5IOHandle sd, int n) {
    GETFUNC("_listen",      TRY_LIBC | TRY_LIBSOCKET, -1, (int (*)P((S5IOHandle, int))),                                     (sd, n), int);
}

S5IOHandle REAL(dup)  (S5IOHandle sd) {
    GETFUNC("_dup",          TRY_LIBC,                S5InvalidIOHandle, (S5IOHandle (*)P((S5IOHandle))),                    (sd), S5IOHandle);
}

S5IOHandle REAL(dup2) (S5IOHandle sd, S5IOHandle s2) {
    GETFUNC("_dup2",         TRY_LIBC,                S5InvalidIOHandle, (S5IOHandle (*)P((S5IOHandle, S5IOHandle))),        (sd, s2), S5IOHandle);
}

int REAL(close)       (S5IOHandle sd) {
    GETFUNC("_close",       TRY_LIBC,                 -1, (int (*)P((S5IOHandle))),                                          (sd), int);
}

IORETTYPE REAL(read)  (S5IOHandle sd, IOPTRTYPE buf, IOLENTYPE blen) {
    GETFUNC("_read",         TRY_LIBC,                -1, (IORETTYPE (*)P((S5IOHandle, IOPTRTYPE, IOLENTYPE))),              (sd, buf, blen), IORETTYPE);
}

IORETTYPE REAL(write) (S5IOHandle sd, const IOPTRTYPE buf, IOLENTYPE blen) {
    GETFUNC("_write",        TRY_LIBC,                -1, (IORETTYPE (*)P((S5IOHandle, const IOPTRTYPE, IOLENTYPE))),        (sd, buf, blen), IORETTYPE);
}

struct tm *REAL(localtime)(const time_t *clock) {
    static void *func = NULL;
    static struct tm * rval;

    GetOriginalFunc(&func, "_localtime", TRY_LIBC);
    if (!func || func == (void *)-1) return (NULL);
    lsInRLDFunctions = 1;
    rval = ((struct tm * (*)(const time_t *))func)(clock);
    lsInRLDFunctions = 0;
    return rval;
}

void REAL(longjmp) (jmp_buf env, int val) {
    static void *func = NULL;

    GetOriginalFunc(&func, "_longjmp", TRY_LIBC);
    if (!func || func == (void *)-1) return; 

    lsInRLDFunctions = 0;
    ((void (*)(jmp_buf, int))func)(env, val);
}

int REAL(select)      (S5IOHandle wd, fd_set *rs, fd_set *ws, fd_set *es, struct timeval *to) {
    GETFUNC("_select",       TRY_LIBC,                -1, (int (*)P((S5IOHandle, fd_set *, fd_set *, fd_set *, struct timeval *))), (wd, rs, ws, es, to), int);
}

#ifdef HAVE_RRESVPORT
S5IOHandle REAL(rresvport)   (int *port) {
    GETFUNC("_rresvport",   TRY_LIBC | TRY_LIBSOCKET, S5InvalidIOHandle, (S5IOHandle (*)P((int *))),                         (port), S5IOHandle);
}
#endif

#endif /* FOR_SHARED_LIBRARY                                                 */
#endif /* HAVE_DLOPEN        */
