/* Copyright (c) 1995,1996,1997 NEC Corporation.  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: hostname.c,v 1.31.4.2 1998/03/20 18:00:53 steve Exp $
 */

#include "socks5p.h"
#include "protocol.h"
#include "addr.h"
#include "wrap.h"
#include "log.h"

#define S5_HOSTLIST_SIZE    256
#define S5_HOSTALIASES_SIZE 16
 
int lsInWrapHostname = 0;

/* table of the hostnames which could not be resolved, used for faked res.   */
static char hostnames[256][S5_HOSTNAME_SIZE];
static int inited = 0;

/* local array of host address list and aliases list                         */
static struct in_addr host_addr[S5_HOSTLIST_SIZE];
static char host_aliases[S5_HOSTALIASES_SIZE][S5_HOSTNAME_SIZE];

/* The address of unresolved host will be of the format "0.0.0.i", where i   */
/* ranges from 1 to 254, pretty simple...                                    */
#define	FAKEPREFIX	"0.0.0."

static void FakeHostInit(void) {
    int i;

    if (inited) return;
    for (i = 0; i < 255; i++) hostnames[i][0] = '\0';
    inited = 1;
}

static void HostentCopy(struct in_addr **addr_list, char **aliases, const struct hostent *h) {
    int i;

    for (i = 0; i < S5_HOSTALIASES_SIZE; i++) {
	if (h->h_aliases[i] == NULL) break;

	strncpy(host_aliases[i], h->h_aliases[i], MIN(strlen(h->h_aliases[i]), S5_HOSTNAME_SIZE-1));
	host_aliases[i][MIN(strlen(h->h_aliases[i]), S5_HOSTNAME_SIZE-1)] = '\0';
	aliases[i] = (char *)host_aliases[i];
    }
    aliases[i] = NULL;

    for (i = 0; i < S5_HOSTLIST_SIZE; i++) {
	if (h->h_addr_list[i] == NULL) break;

	memcpy((char *)&host_addr[i], h->h_addr_list[i], sizeof(struct in_addr));
	addr_list[i] = &host_addr[i];
    }
    addr_list[i] = NULL;
}

/* wrapper around the gethostbyname call.                                    */
/* similar to gethostbyname() except for:                                    */
/* *** if gethostbyname() fails, then it returns a pointer to a hostent      */
/*     structure filled with a special value, so that SOCKSxxxxxx() will     */
/*     realize that this host was unresolved and fill in the protocol        */
/*     accordingly...                                                        */
/*                                                                           */
/* returns a pointer to a gethostent structure on success; NULL on failure   */
struct hostent *LIBPREFIX(gethostbyname)(const char *name) {
    static struct in_addr special_addr, *my_addr_list[S5_HOSTLIST_SIZE+1];
    static char	my_name[MAXNAMELEN], *my_aliases[S5_HOSTALIASES_SIZE+1];
    static struct hostent h;
    struct hostent *hp;
    char *local, *fake;
    int i;

    if (lsInRLDFunctions || lsInWrapFunction || lsInWrapHostname) return REAL(gethostbyname)(name);

    lsInWrapFunction = 1;
    lsInWrapHostname = 1;
    LIBPREFIX2(init)("libsocks5");
    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "SOCKS gethostbyname: looking up %s", name);

    fake  = getenv("SOCKS5_FAKEALLHOSTS");
    local = getenv("SOCKS5_LOCALDNSONLY");

    if (!fake && (hp = REAL(gethostbyname)(name)) != NULL) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "SOCKS gethostbyname: REAL: %s", inet_ntoa(*(struct in_addr *)hp->h_addr));

        strcpy(my_name, hp->h_name);
	HostentCopy(my_addr_list, my_aliases, hp);

    	h.h_name      = my_name;
    	h.h_aliases   = my_aliases;
    	h.h_addrtype  = hp->h_addrtype;
    	h.h_length    = hp->h_length;
    	h.h_addr_list = (char **)my_addr_list;
	
        lsInWrapFunction = 0;
        lsInWrapHostname = 0;
	return &h;
    }

    /* If your DNS is the same as the socks server, don't fake a correct     */
    /* lookup when you know it won't work...                                 */
    if (local) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "SOCKS gethostbyname: REAL: Fake not configured");
        lsInWrapFunction = 0;
        lsInWrapHostname = 0;
	return NULL;
    }

    /* Fill in some UNRESOLVED values and let the daemon resolve it          */
    strcpy(my_name, name);
    my_aliases[0] = NULL;
    FakeHostInit();

    for (i = 1; i < 255; i++) {
	if (hostnames[i][0] != '\0') {
	    if (!strcmp(name, hostnames[i])) break;
	} else {
	    strncpy(hostnames[i], name, MIN(strlen(name), S5_HOSTNAME_SIZE-1));
	    hostnames[i][MIN(strlen(name), S5_HOSTNAME_SIZE-1)] = '\0';
	    break;
	}
    }
	
    if (i == 255) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(1), 0, "SOCKS gethostbyname: FAKE: Failed: No empty slots");
        lsInWrapFunction = 0;
        lsInWrapHostname = 0;
	return NULL;
    }
    
    special_addr.s_addr = htonl(i);
    
    my_addr_list[0] = &special_addr;
    my_addr_list[1] = NULL;
	
    h.h_name      = my_name;
    h.h_aliases   = my_aliases;
    h.h_addrtype  = AF_INET;
    h.h_length    = sizeof(struct in_addr);
    h.h_addr_list = (char **)my_addr_list;
    
    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "SOCKS gethostbyname: FAKE: %s",  inet_ntoa(*(struct in_addr *)h.h_addr_list[0]));
    lsInWrapFunction = 0;
    lsInWrapHostname = 0;
    return &h;
}

int lsGetCachedAddress(const char *name, S5NetAddr *na) {
    int i;

    if (inited) for (i = 1; i < 255; i++) {
	if (hostnames[i][0] == '\0') continue;
	if (strcmp(name, hostnames[i])) continue;

	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "lsGetCachedAddress: Faked host #%d, name is: %s", i, hostnames[i]);
	memset(&na->sin, 0, sizeof(ssi));
	na->sin.sin_family      = AF_INET;
	na->sin.sin_port        = 0;
	na->sin.sin_addr.s_addr = htonl(i);
	return 0;
    }

    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "lsGetCachedAddress: Not a fake a hostname: %s", name);
    return -1;
}

/* checks if the address is an unresolved-address and if so, returns the     */
/* unresolved hostname else returns NULL                                     */
char *lsGetCachedHostname(const S5NetAddr *na) {
    int i;

    FakeHostInit();
    
    if (na->sin.sin_family != AF_INET) {
    	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "lsGetCachedHostname: Not a fake address, wrong address family: %d", na->sin.sin_family);
	return NULL;
    }

    if ((i = (int)ntohl(na->sin.sin_addr.s_addr)) < 255 && i > 0 && hostnames[i][0] != '\0') {
    	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "lsGetCachedHostname: Faked host #%d, name is: %s", i, hostnames[i]);
	return hostnames[i];
    }
    
    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "lsGetCachedHostname: Not a fake hostname: %s", inet_ntoa(na->sin.sin_addr));
    return NULL;
}

