/* 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: conf.c,v 1.37.2.1.2.7 1999/03/04 23:30:51 wlu Exp $
 */

#include "socks5p.h"
#include "buffer.h"
#include "addr.h"
#include "confutil.h"
#include "wrap.h"
#include "hostname.h"
#include "log.h"

#ifndef SOCKS_DEFAULT_VERSION
#define SOCKS_DEFAULT_VERSION (strcmp(LIBCONF_FILE, "no"))?0:SOCKS5_VERSION
#endif

struct tagProxyTuple {
    u_char version;

    list *command;
    list *userlist;

    struct host dest;
    struct port dport;

    S5NetAddr netaddr[S5_SERVER_NUM];
    int nnetaddr;
};

typedef struct tagProxyTuple ProxyTuple;

static void ProxyHandler P((void **, int, int, char *));
static ProxyTuple *pts = NULL;
static int nplines = 0, np = 0;

static struct intfc *intfcs = NULL;
static int ifcnt = 0;


static confid confids[] = {
    { "noproxy", "n",    ProxyHandler, (void **)&pts, &nplines, &np, sizeof(ProxyTuple) },
#define NOPROXYIND 0
    { "socks4",  "s4",   ProxyHandler, (void **)&pts, &nplines, &np, 0 },
#define SOCKS4IND  1
    { "socks5",  "s5",   ProxyHandler, (void **)&pts, &nplines, &np, 0 }
#define SOCKS5IND  2
};

static u_char  DefaultProto = (u_char)-1; /* default protocol to use...   */
static u_short DefaultPort = INVALIDPORT;
static S5NetAddr Socks4Addr;
static S5NetAddr Socks5Addr;

static void ProxyHandler(void **array, int indx, int i, char *tmp) {
    ProxyTuple *pa = (*(ProxyTuple **)array);
    int j;

    if (indx >= nplines) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_ERROR, 0, "Config file error: Not enough lines allocated");
	return;
    }
    
    SKIPNONSPACE(tmp);

    pa[indx].version                     = 0;
    pa[indx].nnetaddr                    = 0;
    memset((char *)pa[indx].netaddr, 0, sizeof(pa[indx].netaddr));

    if (lsGetPermCommand (&tmp, &pa[indx].command))  {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_ERROR, 0, "Bad line in configuration (%s) file: %d", "Command", lsLineNo);
	return;
    }

    if (lsGetHostAndMask  (&tmp, &pa[indx].dest)) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_ERROR, 0, "Bad line in configuration (%s) file: %d", "Host", lsLineNo);
	return;
    }

    if (lsGetPortOrService(&tmp, &pa[indx].dport)) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_ERROR, 0, "Bad line in configuration (%s) file: %d", "Services", lsLineNo);
	return;
    }

    if (lsGetPermUsers    (&tmp, &pa[indx].userlist)) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_ERROR, 0, "Bad line in configuration (%s) file: %d", "Users", lsLineNo);
	return;
    }

    switch (i) {
	case NOPROXYIND: return;
	case SOCKS4IND:  pa[indx].version = SOCKS4_VERSION; break;
    	case SOCKS5IND:  pa[indx].version = SOCKS5_VERSION; break;
    }

    for (j = 0; j < S5_SERVER_NUM && *tmp && *tmp != '\n'; j++, tmp++) {
	if (lsGetHostAddressAndPort(&tmp, &pa[indx].netaddr[j])) {
	    S5LogUpdate(S5LogDefaultHandle, S5_LOG_ERROR, 0, "Bad line in configuration (%s) file: %d", "Servers", lsLineNo);
	    return;
	}

	SKIPSPACE(tmp);
	
	if (*tmp != ',') {
	    j++;
	    break;
	}
    }

    pa[indx].nnetaddr = j;
}

void SetUpDefaults() {
    u_short tmpport = INVALIDPORT;
    char *tmp, *tmp2;

    DefaultProto =
	    getenv("SOCKS5_SERVER")?SOCKS5_VERSION:
	    getenv("SOCKS4_SERVER")?SOCKS4_VERSION:
	    getenv("SOCKS_SERVER")?SOCKS5_VERSION:
	    SOCKS_DEFAULT_VERSION;

/*
    lsName2Port("socks", "tcp", &DefaultPort);
*/
    if (DefaultPort == INVALIDPORT) DefaultPort = ntohs(SOCKS_DEFAULT_PORT);
    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Socks default port is: %d", ntohs(DefaultPort));
    
    if (!(tmp = getenv("SOCKS4_SERVER")) && !(tmp = getenv("SOCKS_SERVER"))) {
	tmp = SOCKS_DEFAULT_SERVER;
    }
    
    if ((tmp2 = strchr(tmp, ':'))) {
	*tmp2 = '\0';
	lsName2Port(tmp2+1, "tcp", &tmpport);
    } else {
	tmpport = DefaultPort;
    }
	
    lsName2Addr(tmp, &Socks4Addr);
    lsAddrSetPort(&Socks4Addr, tmpport);
    
    if (tmp2) *tmp2 = ':';
    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Default socks4 server is: %s %s:%d", tmp, ADDRANDPORT(&Socks4Addr));
    
    if (!(tmp = getenv("SOCKS5_SERVER")) && !(tmp = getenv("SOCKS_SERVER")))
	tmp = SOCKS_DEFAULT_SERVER;
    
    Socks5Addr.sin.sin_family = AF_INET;
    
    if ((tmp2 = strchr(tmp, ':'))) {
	*tmp2 = '\0';
	lsName2Port(tmp2+1, "tcp", &tmpport);
    } else {
	tmpport = DefaultPort;
    }
    
    lsName2Addr(tmp, &Socks5Addr);
    lsAddrSetPort(&Socks5Addr, tmpport);
    
    if (tmp2) *tmp2 = ':';
    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Default socks5 server is: %s %s:%d", tmp, ADDRANDPORT(&Socks5Addr));

    if (getenv("SOCKS5_NONETMASKCHECK") == NULL) lsSetupIntfcs(&intfcs, &ifcnt);
}

u_char lsHowToConnect(const S5NetAddr *dest, u_char command, S5NetAddr **proxy, int *nproxies, char *user, S5NetAddr *ret) {
    u_char proto = (u_char)-1;
    static int read = 0;
    static S5NetAddr defaddr;
    char hostname[S5_HOSTNAME_SIZE];
    int i, j, rval;

    /* The "no" check here is so you can disable the config file reading     */
    /* entirely by configuring --without-libconffile, this should also make  */
    /* the default version 5 unless otherwise configured...                  */
    if (!read) {
	if (strcmp(LIBCONF_FILE, "no")) {
	    char *tmp = getenv("SOCKS5_LIBCONF");

	    tmp = tmp?strdup(tmp):strdup(LIBCONF_FILE);

	    if (tmp) {
                lsReadConfig(tmp, confids, sizeof(confids)/sizeof(confid));
	        S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "lsHowToConnect: Config file (%s) read", tmp);
	        free(tmp);
	    } else {
		S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "lsHowToConnect: Config file not defined");
	    }
	}

	read = 1;

	memset(&defaddr, 0, sizeof(S5NetAddr));
	SetUpDefaults();
    }

    lsAddrCopy(ret, dest, lsAddrSize(dest));
    *proxy = NULL;
    *nproxies = 0;

    memset(hostname, 0, sizeof(hostname));
    rval = lsGetCachedHostname((S5NetAddr *)dest, hostname, sizeof(hostname));

    /* If the destination is localhost or the host itself return DIRECT.     */
    if (rval && dest->sin.sin_family == AF_INET) {
	if (dest->sin.sin_addr.s_addr == htonl(INADDR_LOOPBACK)) return DIRECT;

	if (getenv("SOCKS5_NONETMASKCHECK") == NULL) {
	    for (i = 0; i < ifcnt; i++) {
	        for (j = 0; j < intfcs[i].addrcnt; j++) {
            	    /* null address and null mask doesn't count ...          */
            	    if (!(intfcs[i].addrlist[j].ip.s_addr & intfcs[i].addrlist[j].net.s_addr)) continue;

	    	    if (checkifc(intfcs[i].addrlist[j], dest->sin.sin_addr)) {
		        S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "lsHowToConnect: dest(%08x) matches if (%s:%08x)",
			        dest->sin.sin_addr.s_addr, intfcs[i].name,
			        intfcs[i].addrlist[j].ip.s_addr);
		        return DIRECT;
		    }
	        }
	    }
	}
    }

    for (i = 0; i < nplines; i++) {
	if (pts[i].version == SOCKS4_VERSION) {
	    if ((command != SOCKS_BIND && command != SOCKS_CONNECT) || !rval) continue;
	}

	if (!lsCheckByte(pts[i].command, command, "commands"))       continue;
	if (!lsCheckHost(&pts[i].dest, (S5NetAddr *)dest, rval?NULL:hostname)) continue;
	if (command != SOCKS_PING && command != SOCKS_TRACER && !lsCheckPort(&pts[i].dport, (S5NetAddr *)dest, NULL, (command == SOCKS_UDP)?"udp":"tcp")) continue;
	if (!lsCheckUser(pts[i].userlist, user))                     continue;

	*proxy    = pts[i].netaddr;
	*nproxies = pts[i].nnetaddr;
	proto     = pts[i].version;
	break;
    }

    if (i != nplines) { 
        S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Config file line #%d matched", i);
        if (proto == DIRECT) return proto;
    } else *proxy = &defaddr;

    if (*nproxies == 0) *nproxies = 1;

    if (proto == (u_char)-1) proto = DefaultProto;

    /* Fill in the default ports and addresses if any are still invalid...   */
    for (i = 0; i < *nproxies; i++) {
	(*proxy)[i].sa.sa_family = AF_INET;

	if ((*proxy)[i].sin.sin_addr.s_addr == 0L || (*proxy)[i].sin.sin_addr.s_addr == INVALIDADDR) {
	    lsAddrCopy(&(*proxy)[i], (proto == SOCKS4_VERSION)?&Socks4Addr:&Socks5Addr, sizeof((*proxy)[i]));
	}

	if (lsAddr2Port(&(*proxy)[i]) == 0 || lsAddr2Port(&(*proxy)[i]) == INVALIDPORT) {
	    lsAddrSetPort(&(*proxy)[i], lsAddr2Port((proto == SOCKS4_VERSION)?&Socks4Addr:&Socks5Addr));
	}

        /* If the destination matches the proxy server, client must be able  */
        /* to reach it DIRECTly. Return DIRECT...                            */
	if (!lsAddrComp(&(*proxy)[i], dest)) {
	    *proxy = NULL;
	    *nproxies = 0;
	    return DIRECT;
	}
    }
    
    if (!rval && proto != SOCKS5_VERSION) {
	*nproxies = 0;
	*proxy    = NULL;
	proto = (u_char)-1;
    } else if (!rval) {
        ret->sa.sa_family = AF_S5NAME;
        ret->sn.sn_port   = lsAddr2Port(dest);
        strcpy(ret->sn.sn_name, hostname);
    }

    return proto;
}


