/* 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: validate.c,v 1.74.2.1.2.9 1999/03/05 21:52:50 steve Exp $
 */

/* This file contains all of the stuff associated with parsing the config    */
/* file, except for some utility routines which appear in valutil.c.  The    */
/* routines for checking the parsed config file appear in check.c.           */
#include "socks5p.h"
#include "threads.h"
#include "daemon.h"
#include "confutil.h"
#include "validate.h"
#include "protocol.h"
#include "msgids.h"
#include "info.h"
#include "log.h"

#define INVALID_TYPE (char)-1   /* some kind of config error...               */

IFTHREADED(static MUTEX_T confid_mutex = MUTEX_INITIALIZER;)
static struct intfc *intfcs = NULL;
static int ifcnt = 0;

struct sroute {
    char type;
    int calloced;
    struct in_addr ifaddr;
    struct intfc *ifp;
};

struct authtuple {
    int realline;
    struct host source;
    struct port sport;
    list *authlist;
    u_char banned;
};
typedef struct authtuple AuthTuple;
static AuthTuple *authList = NULL; /* An array of auth list entries          */
static int nalines = 0, na = 0;    /* how many auth lines, and current index */

/* A configuration tuple, used in the access control portion of the config   */
/* file...It tells us the permission as well as matching information...      */
struct conftuple {
    int realline;
    char permit;
    list *command;
    list *userlist;
    list *authlist;

    struct host source, dest;
    struct port sport, dport;
};
typedef struct conftuple ConfTuple;
static ConfTuple *accessList  = NULL; /* An array of access list entries     */
static int nplines = 0, np = 0;	      /* how many access lines, & index      */

/* A routing tuple...used in the routing portion of the config file to tell  */
/* the server how it should be connecting (and being connected) to hosts...  */
struct routetuple {
    int realline;
    struct host source;
    struct port sport;
    struct sroute nexthop;
};
typedef struct routetuple RouteTuple;
static RouteTuple *routeList = NULL; /* An array of route list entries       */
static int nrlines = 0, nr = 0;	     /* how many route lines, & index        */

/* A proxy tuple...used in the proxy portion of the config file to tell the  */
/* server how it should get to a destination (what the next step is...)      */
struct proxytuple {
    int realline;
    struct host source;
    struct port sport;
    
    u_char  nextver;
    S5NetAddr nextaddr[S5_SERVER_NUM];
    int nnextaddr;
};
typedef struct proxytuple ProxyTuple;
static ProxyTuple *proxyList = NULL; /* An array of socks list entries       */
static int nslines = 0, ns = 0;	     /* how many proxy lines & index         */

/* A filter tuple, used in the filter portion of the config file...It tells  */
/* us the what filter to use for a given connection...                       */
struct filtertuple {
    int realline;
    char name[S5_NAME_SIZE];

    list *command;
    list *userlist;
    list *authlist;

    struct host source, dest;
    struct port sport, dport;
};
typedef struct filtertuple FilterTuple;
static FilterTuple *filterList = NULL; /* An array of socks list entries     */
static int nflines = 0, nf = 0;	       /* how many filter lines & index      */

static char **varList = NULL;    /* A list of variables allocated            */
static int nvlines = 0, nv = 0;	 /* how many variable lines and cur index    */

extern void AuthsHandler  P((void **, int, int, char *)); /* Auth line       */
extern void PermsHandler  P((void **, int, int, char *)); /* permission line */
extern void HowtoHandler  P((void **, int, int, char *)); /* proxy line...   */
extern void RouteHandler  P((void **, int, int, char *)); /* route line...   */
extern void EvarsHandler  P((void **, int, int, char *)); /* evar line..     */
extern void FilterHandler P((void **, int, int, char *)); /* filter line...  */

confid confids[] = {
    { "permit",  "p",   PermsHandler, (void **)&accessList, &nplines, &np, sizeof(ConfTuple)    },
#define PERMIT_IND  0 /* An indentifier that this line is "permit"            */
    { "deny",    "d",   PermsHandler, (void **)&accessList, &nplines, &np, 0                    },
#define DENY_IND    1 /* An indentifier that this line is "deny"              */
    { "route",   "r",   RouteHandler, (void **)&routeList,  &nrlines, &nr, sizeof(RouteTuple)   },
#define ROUTE_IND   2 /* An indentifier that this line is "route"             */
    { "interface",   "i",   RouteHandler, (void **)&routeList,  &nrlines, &nr, 0		},
#define INTFC_IND   3 /* An indentifier that this line is "interface"         */
    { "socks4",  "4",   HowtoHandler, (void **)&proxyList,  &nslines, &ns, 0                    },
#define SOCKS4_IND  4 /* An indentifier that this line is "socks"             */
    { "socks5",  "5",   HowtoHandler, (void **)&proxyList,  &nslines, &ns, sizeof(ProxyTuple)   },
#define SOCKS5_IND  5 /* An indentifier that this line is "socks5"            */
    { "noproxy", "n",   HowtoHandler, (void **)&proxyList,  &nslines, &ns, 0                    },
#define DIRECT_IND  6 /* An indentifier that this line is "direct"            */
    { "auth",    "a",   AuthsHandler, (void **)&authList,   &nalines, &na, sizeof(AuthTuple)    },
#define AUTH_IND    7 /* An indentifier that this line is "auth"              */
    { "ban",     "b",   AuthsHandler, (void **)&authList,   &nalines, &na, 0                    },
#define BAN_IND     8 /* An indentifier that this line is "ban"               */
    { "set",     "s",   EvarsHandler, (void **)&varList,     &nvlines, &nv, sizeof(char *)      },
#define SET_IND     9 /* An indentifier that this line is "set"               */
    { "filter",  "f",   FilterHandler,(void **)&filterList,  &nflines, &nf, sizeof(FilterTuple) },
#define FILTER_IND  10 /* An indentifier that this line is "filter"            */
};
#define NCONFIDS (sizeof(confids)/sizeof(struct confid))

/* Look at the buffer that ptr points to, and read either a net address of a */
/* valid interface or the name (e.g. ef0, ef1, le0) of a valid int address.  */
/*                                                                           */
/* Arguments: ptr  -- a ptr to the buffer we are working with...(in/out)     */
/*            val  -- a ptr to the address we are looking up...(out)         */
static int lsGetHostOrIntfc(char **ptr, struct sroute *val) {
    struct in_addr guess;
    char *tmp, tc;
    int i, j;

    /* XXX ipv6 support here may get messy...                                */
    memset((char *)val, 0, sizeof(struct sroute));
    val->type = INVALID_TYPE;

    SKIPSPACE(*ptr);
    tmp = *ptr;

    SKIPNONSPACE(tmp);
    tc = *tmp; *tmp = '\0';

    if ((guess.s_addr = inet_addr(*ptr)) == INVALIDADDR) {
	val->type = NAME;
	val->ifp = NULL;

	for (i = 0; i < ifcnt; i++) {
	    if (strcmp(*ptr, intfcs[i].name)) continue;

	    val->ifp  = &intfcs[i];
	    break;
	}

	if (val->ifp == NULL && (val->ifp = (struct intfc *)calloc(1, sizeof(struct intfc)))) {
	    strncpy(val->ifp->name, *ptr, 16);
	    val->ifp->name[15] = '\0';
	    val->calloced = 1;
	}
    } else {
	val->type = IN_ADDR;
	val->ifaddr.s_addr = guess.s_addr;
	val->ifp = NULL;

	for (i = 0; i < ifcnt; i++) {
	    for (j = 0; j < intfcs[i].addrcnt; j++) {
	        if (intfcs[i].addrlist[j].ip.s_addr == guess.s_addr) break;
	    }

	    if (j < intfcs[i].addrcnt) {
		val->ifp = &intfcs[i];
	    	break;
	    }
	}
    }

    *(*ptr = tmp) = tc;
    return (val->type == INVALID_TYPE)?-1:0;
}

/* myunsetenv goes through and finds a variable=value pair which is the same */
/* as (char *)n.  Once it finds it, it switches it with the last variable in */
/* the environment, and returns...                                           */
/*                                                                           */
/* Arguments: n -- the name=value pair we are looking for (just name)        */
/*                                                                           */
/* Globals affected: environ -- no longer contians n.                        */
static void myunsetenv(void *n) {
#ifdef HAVE_UNSETENV
    unsetenv((char *)n);
    free(n);
#else
    char *name = (char *)n, **tmp, **end;
    extern char **environ;

    for (tmp = environ; *tmp; tmp++) 
	if (!strncmp(name, *tmp, strlen(name))) break; /* find a match...    */

    if (!*tmp) return;                /* no match, so give up...             */
    for (end = tmp; *(end+1); end++); /* find last set variable...           */

    *tmp = *end;                      /* switch with the last set var...     */
    *end = NULL;                      /* zero out the last set var...        */
    free(name);
#endif
}

#ifndef HAVE_SETENV
static int myputenv(char *name) {
#ifdef HAVE_PUTENV
    return putenv(name);
#else
    char **tmp, *end = strchr(name, '='), c;
    extern char **environ;
    static int done;
    int nenv;
    
    if (end == NULL) return 0;

    /* Find a match or count the number of variables in the environment...   */
    for (nenv = 0, tmp = environ; *tmp; tmp++, nenv++) {
	if (strncmp(name, *tmp, end-name)) continue;
	if ((*tmp)[end - name] != '=') continue;
	*tmp = name;
	return 1;
    }

    if (!done) {
	if ((tmp = (char **)malloc((nenv + 2) * sizeof(char *))) == NULL) {
	    return 0;
	}

	memcpy(environ, tmp, nenv+1 * sizeof(char *));
	done = 1;
    } else {
	if ((tmp = (char **)realloc(environ, (nenv + 2) * sizeof(char *))) == NULL) {
	    return 0;
	}
    }

    tmp[nenv+1] = name;
    tmp[nenv+2] = NULL;
    environ = tmp;

    return 1;
#endif
}
#endif

/* A function that clears up a given EntryList, for reading or rereading     */
/* the configuration file.   It deallocates any memory used in allocating    */
/* the list to begin with, and memset's the array of entries to be 0...      */
/*                                                                           */
/* Arguments: index -- which index in the confids array to clear up...       */
static void ClearEntryList(confid *confids, int nconfids) {
    int i;

#define CLEANUPIND(ind) do { if(*confids[(ind)].array) free(*confids[(ind)].array); *confids[(ind)].array = NULL, *confids[(ind)].number = 0; *confids[(ind)].cnum = 0; } while (0);

    for (i = 0; i < *confids[AUTH_IND].number; i++) {
	lsDeleteLinkedList(&((AuthTuple *)*confids[AUTH_IND].array)[i].authlist);
    }

    for (i = 0; i < *confids[ROUTE_IND].number; i++) {
        if (((RouteTuple *)*confids[ROUTE_IND].array)[i].nexthop.calloced)
		free(((RouteTuple *)*confids[ROUTE_IND].array)[i].nexthop.ifp);
    }

    for (i = 0; i < *confids[PERMIT_IND].number; i++) {
	lsDeleteLinkedList(&((ConfTuple *)*confids[PERMIT_IND].array)[i].command);
	lsDeleteLinkedList(&((ConfTuple *)*confids[PERMIT_IND].array)[i].userlist);
	lsDeleteLinkedList(&((ConfTuple *)*confids[PERMIT_IND].array)[i].authlist);
    }

    for (i = 0; i < *confids[SET_IND].number; i++) {
	if (((char **)*confids[SET_IND].array)[i]) {
	    MUTEX_LOCK(env_mutex);
	    myunsetenv(((char **)*confids[SET_IND].array)[i]);
	    MUTEX_UNLOCK(env_mutex);
	}
    }

    CLEANUPIND(AUTH_IND);
    CLEANUPIND(PERMIT_IND);
    CLEANUPIND(SOCKS5_IND);
    CLEANUPIND(ROUTE_IND);
    CLEANUPIND(SET_IND);
}

static void badline(int ln, char *msg) {
    S5LogUpdate(S5LogDefaultHandle, S5_LOG_WARNING, MSGID_SERVER_CONF_BADLINE, "Conf: Bad line (line number %d) in configuration file: %s", ln, msg);
}
	    
/* Examine the current line for an entry setting an environment variable...  */
/*                                                                           */
/* Arguments: indx -- index into the array we are dealing with...(in)        */
/*            i    -- index of the array we came from...(in)                 */
/*            tmp  -- postion in the buffer...(in)                           */
void EvarsHandler(void **array, int indx, int i, char *ptr) {
    /* set up the "environment variables", only from file... */  
    char *tmp, *end1, *new, **vlist = (*(char ***)array), buf[1024];
    int len;

    if (indx >= nvlines) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(1), 0, "Config: Not enough environment variable lines allocated");
	return;
    }

    SKIPNONSPACE(ptr);
    SKIPSPACE(ptr);

    if ((end1 = strstr(ptr, "\n"))) len = MIN(sizeof(buf)-1, end1-ptr);
    else len = MIN(sizeof(buf)-1, strlen(ptr));

    memset(buf, 0, sizeof(buf));
    strncpy(buf, ptr, len);
    buf[len] = '\0';

    for (tmp = buf ; !isspace((unsigned char)*tmp) && *tmp != '\0'; tmp++)
	if (islower((unsigned char)*tmp)) *tmp = toupper(*tmp); 

    end1 = tmp;
    SKIPSPACE(tmp);
	    
#ifndef HAVE_SETENV
    /* mush "variable    value" into "variable=value", and find the end     */
    if (end1 != tmp) for (*end1++ = '='; !isspace((unsigned char)*tmp) && *tmp != '\0'; tmp++, end1++) *end1 = *tmp;
    else *end1++ = '=';
    *end1 = '\0';
#endif

    new = strdup(buf);
    MUTEX_LOCK(env_mutex);

#ifdef HAVE_SETENV
    new[end1 - buf] = '\0';
    setenv(new, new + (tmp-buf), 1);
#else
    myputenv(new);        /* replace it in actual environ                    */
#endif

    MUTEX_UNLOCK(env_mutex);
    vlist[indx] = new;    /* put it in varList for future frees...           */
}

/* Examine the current line for an entry setting a filter to use...          */
/*                                                                           */
/* Arguments: indx -- index into the array we are dealing with...(in)        */
/*            i    -- index of the array we came from...(in)                 */
/*            tmp  -- postion in the buffer...(in)                           */
void FilterHandler(void **array, int indx, int i, char *tmp) {
    FilterTuple *entry = &(*(FilterTuple **)array)[indx];
    char *start;
    
    if (indx >= nflines) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(1), 0, "Config: Not enough filter entries allocated");
	return;
    }

    SKIPNONSPACE(tmp);
    entry->realline = lsLineNo;

    SKIPSPACE(tmp);
    start = tmp;
    SKIPNONSPACE(tmp);

    strncpy(entry->name, start, MIN(tmp-start, S5_NAME_SIZE-1));
    entry->name[MIN(tmp-start, S5_NAME_SIZE-1)] = '\0';

    if (lsGetAuthMethods   (&tmp, &entry->authlist) < 0) badline(entry->realline, "auth methods");
    if (lsGetPermCommand   (&tmp, &entry->command)  < 0) badline(entry->realline, "command");
    if (lsGetHostAndMask   (&tmp, &entry->source)   < 0) badline(entry->realline, "source host");
    if (lsGetHostAndMask   (&tmp, &entry->dest)     < 0) badline(entry->realline, "destination host");
    if (lsGetPortOrService (&tmp, &entry->sport)    < 0) badline(entry->realline, "source port");
    if (lsGetPortOrService (&tmp, &entry->dport)    < 0) badline(entry->realline, "destination port");
    if (lsGetPermUsers     (&tmp, &entry->userlist) < 0) badline(entry->realline, "userlist");
}
/* Examine the current line for an entry setting a route to use...           */
/*                                                                           */
/* Arguments: indx -- index into the array we are dealing with...(in)        */
/*            i    -- index of the array we came from...(in)                 */
/*            tmp  -- postion in the buffer...(in)                           */
void RouteHandler(void **array, int indx, int i, char *tmp) {
    RouteTuple *entry = &(*(RouteTuple **)array)[indx];
    
    if (indx >= nrlines) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(1), 0, "Config: Not enough route entries allocated");
	return;
    }

    SKIPNONSPACE(tmp);
    entry->realline = lsLineNo;
    if (lsGetHostAndMask   (&tmp, &entry->source)  < 0) badline(entry->realline, "source host");
    if (lsGetPortOrService (&tmp, &entry->sport)   < 0) badline(entry->realline, "source port");
    if (lsGetHostOrIntfc   (&tmp, &entry->nexthop) < 0) badline(entry->realline, "interface");
}

/* Examine the current line for an entry telling us how to get someplace...  */
/*                                                                           */
/* Arguments: indx -- index into the array we are dealing with...(in)        */
/*            i    -- index of the array we came from...(in)                 */
/*            tmp  -- postion in the buffer...(in)                           */
void HowtoHandler(void **array, int indx, int i, char *tmp) {
    static u_short socksport;
    u_short port;
    
    ProxyTuple *entry = &(*(ProxyTuple **)array)[indx];
    SKIPNONSPACE(tmp);
    
    if (indx >= nslines) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(1), 0, "Config: Not enough proxy entries allocated");
	return;
    }

    entry->realline = lsLineNo;
    if (lsGetHostAndMask   (&tmp, &entry->source) < 0) badline(entry->realline, "source host");
    if (lsGetPortOrService (&tmp, &entry->sport)  < 0) badline(entry->realline, "source port");

    if (i == DIRECT_IND) {
	entry->nextver   = 0;
	entry->nnextaddr = 0;
	memset((char *)entry->nextaddr, 0, sizeof(S5NetAddr));
	return;
    } 

    entry->nextver = (i == SOCKS5_IND)?SOCKS5_VERSION:SOCKS4_VERSION;

    for (i = 0; i < S5_SERVER_NUM && *tmp && *tmp != '\n'; i++, tmp++) {
	if (lsGetHostAddressAndPort(&tmp, &entry->nextaddr[i]) < 0) badline(entry->realline, "server address");

	if ((port = lsAddr2Port(&entry->nextaddr[i])) == INVALIDPORT || port == (u_short)0) {
	    if (!socksport && lsName2Port("socks", "tcp", &socksport) < 0) socksport = htons(SOCKS_DEFAULT_PORT);
	    lsAddrSetPort(&entry->nextaddr[i], socksport);
	}

	SKIPSPACE(tmp);

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

    entry->nnextaddr = i;
}

/* Examine the current line for an entry telling us what is allowed...       */
/*                                                                           */
/* Arguments: indx -- index into the array we are dealing with...(in)        */
/*            i    -- index of the array we came from...(in)                 */
/*            tmp  -- postion in the buffer...(in)                           */
void PermsHandler(void **array, int indx, int i, char *tmp) {
    ConfTuple *entry = &(*(ConfTuple **)array)[indx];

    if (indx >= nplines) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(1), 0, "Config: Not enough permission entries allocated");
	return;
    }

    SKIPNONSPACE(tmp);
    entry->realline = lsLineNo;
    entry->permit = (i == PERMIT_IND)?AUTH_OK:AUTH_FAIL;

    if (lsGetAuthMethods   (&tmp, &entry->authlist) < 0) badline(entry->realline, "auth methods");
    if (lsGetPermCommand   (&tmp, &entry->command)  < 0) badline(entry->realline, "command");
    if (lsGetHostAndMask   (&tmp, &entry->source)   < 0) badline(entry->realline, "source host");
    if (lsGetHostAndMask   (&tmp, &entry->dest)     < 0) badline(entry->realline, "destination host");
    if (lsGetPortOrService (&tmp, &entry->sport)    < 0) badline(entry->realline, "source port");
    if (lsGetPortOrService (&tmp, &entry->dport)    < 0) badline(entry->realline, "destination port");
    if (lsGetPermUsers     (&tmp, &entry->userlist) < 0) badline(entry->realline, "userlist");
}

void AuthsHandler(void **array, int indx, int i, char *tmp) {
    AuthTuple *entry = &(*(AuthTuple **)array)[indx];

    if (indx >= nalines) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(1), 0, "Config: Not enough authentication entries allocated");
	return;
    }

    SKIPNONSPACE(tmp);
    entry->realline = lsLineNo;

    if (lsGetHostAndMask   (&tmp, &entry->source)   < 0) badline(entry->realline, "source host");
    if (lsGetPortOrService (&tmp, &entry->sport)    < 0) badline(entry->realline, "source port");

    if (i != BAN_IND) {
	if (lsGetAuthMethods   (&tmp, &entry->authlist) < 0) badline(entry->realline, "auth methods");
	entry->banned = 0;
    } else {
	entry->banned = 1;
    }

}

void ReadConfig(void) {
    char *file;
    static int mi = 0;

    if (mi == 0) {
        mi++;
        IFTHREADED(MUTEX_SETUP(confid_mutex);)
    }
    MUTEX_LOCK(env_mutex);
    file = getenv("SOCKS5_CONFFILE");
    if (file) file = strdup(file);
    MUTEX_UNLOCK(env_mutex);

    if (!file) file = strdup(SRVCONF_FILE);
    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Config: Reading config file: %s", file);

    lsSetupIntfcs(&intfcs, &ifcnt);

    MUTEX_LOCK(confid_mutex);
    ClearEntryList(confids, NCONFIDS);
    lsReadConfig(file, confids, NCONFIDS);
    MUTEX_UNLOCK(confid_mutex);

    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Config: Config file read");
    if (file) free(file);
/*
    S5LogStart(&S5LogDefaultHandle, -1, -1,"Socks5");
*/
}

/* See what kind of auths we will have to use...If the client can't do one   */
/* of them, then we'll probably quit.  Otherwise, we'll use this list to     */
/* decide which one we do do...                                              */
int GetAuths(S5LinkInfo *pri, list **authlist) {
    int i;
    
    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Checking Authentication");
    *authlist = NULL;

    if (nalines == 0) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Auth: No auth line specified");
	return 0;
    }

    MUTEX_LOCK(confid_mutex);
    for (i = 0; i < nalines; i++) {
	if (!lsCheckHost(&authList[i].source, &pri->srcAddr, pri->srcName)) {
	    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Auth: Line %d: Source host didn't match", authList[i].realline);
	    continue;
	}

	if (pri->peerCommand != SOCKS_PING && pri->peerCommand != SOCKS_TRACER
		&& !lsCheckPort(&authList[i].sport, &pri->srcAddr, NULL, (pri->peerCommand == SOCKS_UDP)?"udp":"tcp")) {
	    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Auth: Line %d: Source port didn't match", authList[i].realline);
	    continue;
	}

	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Auth: Line %d: Matched", authList[i].realline);

	if (authList[i].banned) {
	    MUTEX_UNLOCK(confid_mutex);
	    return -1;
	} else {
	    *authlist = authList[i].authlist;
	    MUTEX_UNLOCK(confid_mutex);
	    return 0;
	}
    }

    MUTEX_UNLOCK(confid_mutex);
    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Auth: No line matched");
    return -1;
}

/* Check to see if the current set of global variables is allowed to proceed */
/* by the current state of the configuration file.  If we are accepting, we  */
/* don't need to necessariy match the destinaion host and port since they    */
/* are not specified yet...                                                  */
int Authorize(S5LinkInfo *pri, int accepting) {
    int rval, i;
    
    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Checking Authorization");

    /* Before check with ACL, following address checking is performed:       */
    /*                                                                       */
    /* if src == dst => AUTH_FAIL...                                         */ 
    /* if dst == LOOPBACK => AUTH_FAIL...                                    */ 
    /* if src is one of the next proxy => AUTH_FAIL (avoid looping)...       */ 
    if (pri->peerCommand != SOCKS_UDP && !lsAddrComp(&pri->srcAddr, &pri->dstAddr)) return AUTH_FAIL;
    if (pri->dstAddr.sin.sin_addr.s_addr == htonl(INADDR_LOOPBACK)) return AUTH_FAIL;

    for (i = 0; i < pri->nAltSckAddrs; i++) {
    	if (!lsAddrComp(&pri->srcAddr, &pri->altSckAddrs[i])) return AUTH_FAIL;
    }

    MUTEX_LOCK(confid_mutex);
    for (i = 0; i < nplines; i++) {
	if (!lsCheckByte(accessList[i].command, pri->peerCommand, "commands")) {
	    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Perm: Line %d: Command didn't match", accessList[i].realline);
	    continue;
	}

	if (!lsCheckByte(accessList[i].authlist, pri->peerAuth, "auths")) {
	    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Perm: Line %d: Authentication didn't match", accessList[i].realline);
	    continue;
	}

	if (!lsCheckHost(&accessList[i].source, &pri->srcAddr, pri->srcName)) {
	    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Perm: Line %d: Source host didn't match", accessList[i].realline);
	    continue;
	}
	
	if (pri->peerCommand != SOCKS_PING && pri->peerCommand != SOCKS_TRACER &&
		!lsCheckPort(&accessList[i].sport, &pri->srcAddr, NULL, (pri->peerCommand == SOCKS_UDP)?"udp":"tcp")) {
	    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Perm: Line %d: Source port didn't match", accessList[i].realline);
	    continue;
	}

	/* if it is a UDP request, source address matches destination        */
        /* address, and permission is OK, we are done...                     */
	if (pri->peerCommand == SOCKS_UDP && !lsAddrComp(&pri->srcAddr, &pri->dstAddr)) {
	    if (accessList[i].permit != AUTH_OK) continue;

	    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Perm: Line %d:matched", accessList[i].realline);
	    MUTEX_UNLOCK(confid_mutex);
	    return AUTH_OK;
	}

        /* If we are accepting, we don't know the port. Therefore we should  */
	/* only care about permit lines. If we know the destination address  */
  	/* we will check the address.                                        */
        if (accepting) {
	    if (accessList[i].permit != AUTH_OK) continue;
	    if (lsAddrIsNull(&pri->dstAddr) != 0) {
            	if (!lsCheckHost(&accessList[i].dest, &pri->dstAddr, pri->dstName)) {
                    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Perm: Line %d: Destination host didn't match", accessList[i].realline);
                    continue;
                }
	    }
	} else {
	    if (!lsCheckHost(&accessList[i].dest, &pri->dstAddr, pri->dstName)) {
	        S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Perm: Line %d: Destination host didn't match", accessList[i].realline);
	        continue;
	    }

	    if (!lsCheckPort(&accessList[i].dport, &pri->dstAddr, pri->dstServ, (pri->peerCommand == SOCKS_UDP)?"udp":"tcp")) {
	        S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Perm: Line %d: Destination port didn't match", accessList[i].realline);
	        continue;
	    }
	}

	if (!lsCheckUser(accessList[i].userlist, pri->srcUser)) {
	    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Perm: Line %d: Username didn't match", accessList[i].realline);
	    continue;
	}
	
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Perm: Line %d:matched", accessList[i].realline);
	rval = accessList[i].permit;
	MUTEX_UNLOCK(confid_mutex);
	return rval;
    }
    
    MUTEX_UNLOCK(confid_mutex);
    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Perm: No line matched");
    return AUTH_FAIL;
}

/* Check the destination is on the same subnet. returns cwnumber of matching */
/* interfaces...                                                             */
static int lsCheckIntfc(const S5NetAddr *dst, S5NetAddr *addr) {
    struct intaddr tmpaddr;
    struct ifreq ifr;
    int i, j, k = 0;

    memset((char *)&ifr, 0, sizeof(struct ifreq));

    for (i = 0; i < ifcnt; i++) {
	if (intfcs[i].addrcnt == 0 || intfcs[i].up != 1 || intfcs[i].type == 1) {
	    /* not an IP interface...                                    */
            if (intfcs[i].up == 1 && intfcs[i].type != 1) continue;

	    /* the interface is still down...                            */
	    strcpy(ifr.ifr_name, intfcs[i].name);
	    if (lsLookupIntfc(S5InvalidIOHandle, NET_STAT, &ifr) <= 0) continue;

	    /* the interface is up now. Find the address and mask, and   */
	    /* check if the dst is on the same subnet ...                */
	    if (lsLookupIntfc(S5InvalidIOHandle, NET_ADDR, &ifr) < 0) continue;
	    if (ifssi(ifr)->sin_family != AF_INET) continue;
	    tmpaddr.ip = ifssi(ifr)->sin_addr;

	    if (lsLookupIntfc(S5InvalidIOHandle, NET_MASK, &ifr) < 0) continue;
            tmpaddr.net = ifssi(ifr)->sin_addr;

            if (checkifc(tmpaddr, dst->sin.sin_addr)) {
	        addr->sin.sin_addr = tmpaddr.ip;
	        k++;
            }

	    continue; 
	}

	/* loopback interface ...                                        */
	if (intfcs[i].addrlist[0].ip.s_addr == htonl(INADDR_LOOPBACK)) continue;
    
	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;

	    /* on the same subnet...                                     */
            if (checkifc(intfcs[i].addrlist[j], dst->sin.sin_addr)) {
		addr->sin.sin_addr = intfcs[i].addrlist[j].ip;
		k++;
	    }
	}
    }

    return k;
}

/* GetProxy takes the destination and the type of the destination and picks  */
/* how it should and a demand for what kind of server is needed, and finds a */
/* server of that type, or returns -1 if there is no such server.            */
/*                                                                           */
/* Globals used: proxyList, the array of proxy entries....                   */
int GetProxy(const S5NetAddr *dst, const char *name, char *proto, S5NetAddr *sckAddrs, int *nsckAddrs, u_char *verp) {
    int i;
    S5NetAddr tmpaddr;

    *verp = 0;
    *nsckAddrs = 0;

    if (!lsAddrIsNull(dst)) return 0;

    /* Before check the proxy lines, check if the dst is on the same subnet  */
    /* as the server is on.                                                  */
    if (getenv("SOCKS5_NONETMASKCHECK") == NULL) {
	if (lsCheckIntfc(dst, &tmpaddr) > 0) {
            S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Proxy: dst on the same subnet");
            return 0;
        }
    }

    MUTEX_LOCK(confid_mutex);
    for (i = 0; i < nslines; i++) {
	if (!lsCheckHost(&proxyList[i].source, dst, name)) {
	    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Proxy: Line %d: Destination host didn't match", proxyList[i].realline);
	    continue;
	}

	if (!lsCheckPort(&proxyList[i].sport, dst, NULL, proto)) {
	    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Proxy: Line %d: Destination port didn't match", proxyList[i].realline);
	    continue;
	}

#define REALDEST(x) ((x)->sa.sa_family == AF_INET && (x)->sin.sin_addr.s_addr != INVALIDADDR && (x)->sin.sin_addr.s_addr != INADDR_ANY)

	if (proxyList[i].nextver && !REALDEST(&proxyList[i].nextaddr[0])) {
	    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Proxy: Line %d: Invalid server address", proxyList[i].realline);
	    continue;
	}

	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Proxy: Line %d: Matched", proxyList[i].realline);
	if (!proxyList[i].nextver) break;

	*verp      = proxyList[i].nextver;
	memcpy((char *)sckAddrs, (char *)proxyList[i].nextaddr, sizeof(proxyList[i].nextaddr));
	*nsckAddrs = proxyList[i].nnextaddr;

	MUTEX_UNLOCK(confid_mutex);
	return 0;
    }

    MUTEX_UNLOCK(confid_mutex);
    return -1;
}

/* Determine the outbound address for a given destination           */
/* If not route does match, default will by any interface           */
int GetRoute(const S5NetAddr *dst, const char *name, char *proto, S5NetAddr *addr) {
    struct ifreq ifr;
    int i;

    memset((char *)&ifr, 0, sizeof(struct ifreq));

    /* If we return before we change this, there was an error...             */
    memset((char *)addr, 0, sizeof(S5NetAddr));
    addr->sin.sin_family = AF_INET;

    /* If dst is loopback, the route will be loopback....                    */
    if (dst->sa.sa_family != AF_S5NAME && !lsAddrIsNull(dst)) {
        lsAddrCopy(addr, dst, lsAddrSize(dst));
        lsAddrSetPort(addr, 0);
        return 0;
    }

    MUTEX_LOCK(confid_mutex);

    for (i = 0; i < nrlines; i++) {
	if (!lsCheckHost(&routeList[i].source, dst, name)) {
	    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Route: Line %d: Destination host didn't match", routeList[i].realline);	    
	    continue;
	}

	if (!lsCheckPort(&routeList[i].sport, dst, NULL, proto)) {
	    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Route: Line %d: Destination port didn't match", routeList[i].realline);	    
	    continue;
	}

	if (routeList[i].nexthop.type == INVALID_TYPE) {
	    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Route: Line %d: Invalid interface type", routeList[i].realline);	    
	    continue;
	}

	if (routeList[i].nexthop.type == NAME) {
	    /* If the interface is not up when the daemon started or the interface */
	    /* has multiple addresses, get the current active address... otherwise */
	    /* we are done...                                                      */
            if (routeList[i].nexthop.ifp->type == 1 || routeList[i].nexthop.ifp->up <= 0 || routeList[i].nexthop.ifp->addrcnt != 1) {
		strcpy(ifr.ifr_name, routeList[i].nexthop.ifp->name);

	    	if (lsLookupIntfc(S5InvalidIOHandle, NET_STAT, &ifr) <= 0) {
            	    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Route: Line %d: Interface is down", routeList[i].realline);
	    	    continue;
		}

	    	if (lsLookupIntfc(S5InvalidIOHandle, NET_ADDR, &ifr) < 0 || ifssi(ifr)->sin_family != AF_INET) {
            	    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Route: Line %d: Invalid interface address", routeList[i].realline);
	    	    continue;
		}

		addr->sin.sin_addr = ifssi(ifr)->sin_addr;
		break;
	    } else if (routeList[i].nexthop.ifp->addrcnt == 0) continue;
	    else {
		addr->sin.sin_addr = routeList[i].nexthop.ifp->addrlist[0].ip;
		break;
	    }
	} else {
	    addr->sin.sin_addr = routeList[i].nexthop.ifaddr;
	    break;
	}
    }

    if (i < nrlines) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Route: Line %d: Matched", routeList[i].realline);	    
	MUTEX_UNLOCK(confid_mutex);
	return 0;
    }

    /* No match...                                                           */
    MUTEX_UNLOCK(confid_mutex);

    /* Before check the route lines, check if the server is single-homed or  */
    /* if the dst is on the same subnet as the server is on. If the server   */
    /* is single-homed, route line is useless. If the dst is on the same     */
    /* subnet, use that interface IP...                                      */
    if (getenv("SOCKS5_NONETMASKCHECK") == NULL) {
	if (lsCheckIntfc(dst, addr) == 1) {
            S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Route: dst on the same subnet");
            return 0;
	}
    }

    return -1;
}

int GetFilter(S5LinkInfo *pri, char *name) {
    int i;
    
    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Checking Filter");

    MUTEX_LOCK(confid_mutex);
    for (i = 0; i < nflines; i++) {
	if (!lsCheckByte(filterList[i].command,  pri->peerCommand, "commands")) {
	    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Filter: Line %d: Command didn't match", filterList[i].realline);
	    continue;
	}

	if (!lsCheckByte(filterList[i].authlist, pri->peerAuth, "auths")) {
	    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Filter: Line %d: Authentication didn't match", filterList[i].realline);
	    continue;
	}

	if (!lsCheckHost(&filterList[i].source,  &pri->srcAddr, pri->srcName)) {
	    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Filter: Line %d: Source host didn't match", filterList[i].realline);
	    continue;
	}
	
	if (!lsCheckPort(&filterList[i].sport,   &pri->srcAddr, NULL, (pri->peerCommand == SOCKS_UDP)?"udp":"tcp")) {
	    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Filter: Line %d: Source port didn't match", filterList[i].realline);
	    continue;
	}

	if (!lsCheckHost(&filterList[i].dest,    &pri->dstAddr, pri->dstName)) {
	    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Filter: Line %d: Destination host didn't match", filterList[i].realline);
	    continue;
	}

	if (!lsCheckPort(&filterList[i].dport, &pri->dstAddr, pri->dstServ, (pri->peerCommand == SOCKS_UDP)?"udp":"tcp")) {
	    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Filter: Line %d: Destination port didn't match", filterList[i].realline);
	    continue;
	}

	if (!lsCheckUser(filterList[i].userlist, pri->srcUser)) {
	    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Filter: Line %d: Username didn't match", filterList[i].realline);
	    continue;
	}
	
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Filter: Line %d:matched", filterList[i].realline);
	strcpy(name, filterList[i].name);
	MUTEX_UNLOCK(confid_mutex);
	return 0;
    }
    
    MUTEX_UNLOCK(confid_mutex);
    memset(name, 0, S5_NAME_SIZE);
    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Filter: No line matched");
    return -1;
}
