/* 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: confutil.c,v 1.74.4.11 1999/03/05 21:52:38 steve Exp $
 */

#include "socks5p.h"
#include "threads.h"
#include "buffer.h"
#include "addr.h"
#include "confutil.h"
#include "log.h"

#ifdef HAVE_IFADDRS_H
#include <ifaddrs.h>
#define ifassi(x) (((ssi *)(x))->sin_addr.s_addr)
#endif

#ifdef OSIOCGIFCONF
#define RSIOCGIFCONF OSIOCGIFCONF
#else
#define RSIOCGIFCONF SIOCGIFCONF
#endif

#ifdef SIOCGIFADDR
#define RSIOCGIFADDR SIOCGIFADDR
#else
#define RSIOCGIFADDR OSIOCGIFADDR
#endif

#ifdef SIOCGIFNETMASK
#define RSIOCGIFNETMASK SIOCGIFNETMASK
#else
#define RSIOCGIFNETMASK OSIOCGIFNETMASK
#endif

IFTHREADED(extern MUTEX_T gh_mutex;)
IFTHREADED(extern MUTEX_T gs_mutex;)
int lsLineNo = 0;



/* A function that checks if s1 is the first string in s1, and is followed   */
/* by whitespace.  Used for configuration file entries.                      */
static int CheckString(char *s1, char *s2) {
    if (s2 == NULL) return 1;
    if (strncmp(s1, s2, strlen(s2))) return 0;
    if (!isspace((unsigned char)s1[strlen(s2)]))    return 0;
    return 1;
}

/* A function that checks if c's string or abbreviation member is the first  */
/* string in s1, and is followed by whitespace...                            */
/*                                                                           */
/* Arguments: s1 -- the string which we are examining                        */
/* Arguments: c  -- a structure containing the string and abbreviation we    */
/*                  are checking for.                                        */
static int CheckStringOrAbbrev(char *s1, struct confid *c) {
    char *tmp;

    for (tmp = s1; *tmp && !isspace((unsigned char)*tmp); tmp++) if (isupper((unsigned char)*tmp)) *tmp = tolower(*tmp);
    if (CheckString(s1, c->string)) return 1;
    return CheckString(s1, c->abbrev);
}

/* This a malloc/realloc'ish function which takes orig, mallocs it if its    */
/* Null, or reallocs it if it is non-null...pretty simple...oh, if the size  */
/* is 0, and the pointer was allocated, it frees the memory, otherwise rets. */
static void *remalloc(void *orig, size_t size) {
    if (size == 0) {
	if (orig) free(orig);
	return NULL;
    }
    
    if (orig == NULL) return (void *)malloc(size);
    else              return (void *)realloc(orig, size);
}

int lsLinkedListInsertUnaligned(list **l, size_t s) {
    list *templ = remalloc(NULL, s+sizeof(list));

    if (templ == NULL) return -1;

    templ->dataptr     = (void *)(templ+1);
    templ->ptrmalloced = 0;
    templ->next        = *l;
    *l = templ;

    return 0;
}

int lsLinkedListInsertAligned(list **l, size_t s) {
    list *templ = remalloc(NULL, sizeof(list));
    void *vptr  = remalloc(NULL, s);

    if (templ == NULL || vptr == NULL) return -1;

    templ->dataptr     = vptr;
    templ->ptrmalloced = 1;
    templ->next        = *l;
    *l = templ;

    return 0;
}

/* Delete a linked list...nothing to it really, except not freeing some ptrs */
/*                                                                           */
/* Arguments: l -- a ptr to the list we need to free...will be set to null.  */
void lsDeleteLinkedList(list **l) {
    list *tmp, *cur;
    
    for (tmp = *l, *l = NULL; tmp;) {
	cur = tmp; tmp = tmp->next;
	if (cur->ptrmalloced) free(cur->dataptr);
	free(cur);
    }
}

/* Look at the buffer that ptr points to, and read an address...             */
/*                                                                           */
/* Arguments: ptr  -- a ptr to the buffer we are working with...(in/out)     */
/*             na  -- a ptr to the address we are looking up...(out)         */
int lsGetHostAddress(char **ptr, S5NetAddr *na) {
    char *tmp, tc;
    int rval;

    SKIPSPACE(*ptr);
    tmp = *ptr;

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

    rval = lsName2Addr(*ptr, na);

    *(*ptr = tmp) = tc;
    return rval;
}

/* Look at the buffer that ptr points to, and read a port... check ,'s       */
/*                                                                           */
/* 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 lsGetPort(char **ptr, u_short *val) {
    char *tmp, tc;
    int retval = 0;

    SKIPSPACE(*ptr);
    tmp = *ptr;

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

    if (tmp != *ptr) retval = lsName2Port(*ptr, NULL /* XXX OK? */, val);
    else             *val = INVALIDPORT;
    *(*ptr = tmp) = tc;

    return retval;
}

/* Look at the buffer that ptr points to, and read a port... no commas       */
/* Stuff the answer into an S5NetAddr...                                     */
/*                                                                           */
/* Arguments: ptr -- a ptr to the buffer we are working with...(in/out)      */
/*             na -- a ptr to the address we are looking up...(out)          */
int lsGetHostPort(char **ptr, S5NetAddr *na) {
    u_short port = INVALIDPORT;
    int rval;

    rval = lsGetPort(ptr, &port);
    lsAddrSetPort(na, port);

    return rval;
}

/* Look at the buffer that ptr points to, and read an address and a port...  */
/* The address and port are separated by ':' ...                             */
/*                                                                           */
/* Arguments: ptr -- a ptr to the buffer we are working with...(in/out)      */
/*             na -- a ptr to the address we are looking up...(out)          */
int lsGetHostAddressAndPort(char **ptr, S5NetAddr *na) {
    int rval;
    char *tmp, tc;

    SKIPSPACE(*ptr);
    tmp = *ptr;

    SKIPNSPNCOLNCOM(tmp);

    if (tmp == *ptr) return 0;

    tc = *tmp; *tmp = '\0';

    rval = lsName2Addr(*ptr, na);
    *(*ptr = tmp) = tc;

    if (rval < 0) return rval;

    if (tc == ':') {
	(*ptr)++;
	return lsGetHostPort(ptr, na);
    }

    return 0;
}

/* Given ip (address in net order) return the standard subnet mask for it.   */ 
static void StandardSubnetMaskForClass(struct in_addr ip, struct in_addr *mask) {
    if      ((ip.s_addr & htonl(0x80000000)) == 0) mask->s_addr = htonl(0xffff0000); /* class A  */
    else if ((ip.s_addr & htonl(0x40000000)) == 0) mask->s_addr = htonl(0xffffff00); /* class B  */
    else if ((ip.s_addr & htonl(0x20000000)) == 0) mask->s_addr = htonl(0xfffffff0); /* class C  */
    else mask->s_addr = htonl(0xffffffff);                                           /* class D  */
}

/* Given ip (address in net order) return the standard net mask for it.      */ 
static void StandardNetMaskForClass(struct in_addr ip, struct in_addr *mask) {
    if      ((ip.s_addr & htonl(0x80000000)) == 0) mask->s_addr = htonl(0xff000000); /* class A  */
    else if ((ip.s_addr & htonl(0x40000000)) == 0) mask->s_addr = htonl(0xffff0000); /* class B  */
    else if ((ip.s_addr & htonl(0x20000000)) == 0) mask->s_addr = htonl(0xffffff00); /* class C  */
    else mask->s_addr = htonl(0xffffffff);                                           /* class D  */
}

/* Look at the buffer that ptr points to, and read either a net address of a */
/* host and a netmask (n.n.n.n/m.m.m.m) or a abbreviated portion of that     */
/* (n.n.n. n.n. or n.) or the name of a valid machine (idaho.syl.dl.nec.com) */
/* or a portion of that name which must match (.syl.dl.nec.com)...           */
/*                                                                           */
/* Arguments: ptr  -- a ptr to the buffer we are working with...(in/out)     */
/*            host -- a ptr to the address/mask we are looking up...(out)    */
int lsGetHostAndMask(char **ptr, struct host *h) {
    int i, nd = 0, rval = 0;
    struct hostent *hp;
    char *tmp, *st, c;

    if (!h) return -1;
    memset((char *)h, 0, sizeof(struct host));

    SKIPSPACE(*ptr);
    tmp = *ptr;

    if (*tmp == '\n') {
	h->type        = IN_ADDR;
	h->mask.s_addr = 0xffffffff;
	h->ip.s_addr   = INVALIDADDR;
	
	return -1;
    }

    for (st = tmp; !isspace((unsigned char)*tmp) && *tmp != '\0' && *tmp != '/'; tmp++) if (*tmp == '.') nd++;
    c  = *tmp; *tmp = '\0';

    if (*st == '-') {
	/* - */

	/* This is obviously the easiest case...its a -, so any body matches */
	h->type        = IN_ADDR;
	h->mask.s_addr = htonl(0x00000000);
	h->ip.s_addr   = htonl(0x00000000);
    } else if (c == '/') {
	/* n.n.n.n/[m.m.m.m|h|s|n|a] */
	char *end, c2;

	for (end = tmp+1; !isspace((unsigned char)*end) && *end != '\0'; end++);
	c2 = *end; *end = '\0';
	
	/* There was a /, so there has to have been an IP address before it. */
	/* Read the address, then read the mask... Pretty straight forward.  */
	if ((h->ip.s_addr = inet_addr(st)) == INVALIDADDR) {
	    S5LogUpdate(S5LogDefaultHandle, S5_LOG_WARNING, 0, "Conf: Invalid address in address/mask host pair");
	    rval = -1;
	} 
	
	switch (*(tmp + 1)) {
	    case 'h': h->mask.s_addr = htonl(0xffffffff); break;
	    case 's': StandardSubnetMaskForClass(h->ip, &h->mask); break;
	    case 'n': StandardNetMaskForClass   (h->ip, &h->mask); break;
	    case 'a': h->mask.s_addr = htonl(0x00000000); break;
	    default:  h->mask.s_addr = inet_addr(tmp+1); break;
	}

	h->type       = IN_ADDR;
	h->ip.s_addr &= h->mask.s_addr;

	if (h->mask.s_addr == htonl(0xffffffff) && getenv("SOCKS5_REVERSEMAP") != NULL) {
	    /* Map the IP, since the name we will be comparing it to         */
	    /* will already have been resolved and mapped (hopefully).       */
	    MUTEX_LOCK(gh_mutex);
	    if ((hp = gethostbyaddr((char *)&h->ip, sizeof(struct in_addr), AF_INET)) != NULL) {
		/* Store all the aliases and the ip addresses                */
		h->resolve = 1;
	        strncpy(h->name, hp->h_name, MIN(strlen(hp->h_name), S5_HOSTNAME_SIZE-1));
	        h->name[MIN(strlen(hp->h_name), S5_HOSTNAME_SIZE-1)] = '\0';

		for (i = 0; hp->h_aliases[i] && i < S5_HOSTALIASES_NUM; i++) {
	            strncpy(h->aliases[i], hp->h_aliases[i], MIN(strlen(hp->h_aliases[i]), S5_HOSTNAME_SIZE-1));
	            h->aliases[i][MIN(strlen(hp->h_aliases[i]), S5_HOSTNAME_SIZE-1)] = '\0';
		}
		h->aliascnt = i;

		for (i = 0; hp->h_addr_list[i] && i < S5_HOSTIP_NUM; i++) {
	            memcpy((char *)&h->back[i], hp->h_addr_list[i], sizeof(struct in_addr));
		}
		h->ipcnt = i;
	    }
	    MUTEX_UNLOCK(gh_mutex);
	}

	*tmp = c;
	tmp  = end;
	c    = c2;

    } else if (*(tmp-1) == '.') {
	/* n.n.n. or n.n. or n. */

	/* Put a 0 on the end just for inet_addr's sake, even though it      */
	/* seems to work without it...its not documented so it probably      */
	/* won't work on some OS...restore it at the end...                  */
	char c2 = tmp[1];
	tmp[0] = '0';
	tmp[1] = '\0';

	if ((h->ip.s_addr = inet_addr(st)) == INVALIDADDR) {
	    S5LogUpdate(S5LogDefaultHandle, S5_LOG_WARNING, 0, "Conf: Invalid address in truncated address");
	    rval = -1;
	}
	
	h->type        = IN_ADDR;
	h->mask.s_addr = htonl(((u_int)0xffffffff) << (8*(4-nd)));
	h->ip.s_addr  &= h->mask.s_addr;

	tmp[1] = c2;
	tmp[0] = '\0';

    } else if ((h->ip.s_addr = inet_addr(st)) != INVALIDADDR) {
	/* n.n.n.n or n.n.n or n.n or n */

	/* This one's really simple, inet_addr worked, and the mask          */
	/* should be exact, so just do it...                                 */
	h->type        = IN_ADDR;
	h->mask.s_addr = htonl(0xffffffff);
	h->ip.s_addr  &= h->mask.s_addr;

	/* Map the IP, since the name we will be comparing it to             */
	/* will already have been resolved and mapped (hopefully).           */
	if (getenv("SOCKS5_REVERSEMAP") != NULL) {
	    MUTEX_LOCK(gh_mutex);
	    if ((hp = gethostbyaddr((char *)&h->ip, sizeof(struct in_addr), AF_INET)) != NULL) {
		/* Store all the aliases and the ip addresses                */
		h->resolve = 1;
		strncpy(h->name, hp->h_name, MIN(strlen(hp->h_name), S5_HOSTNAME_SIZE-1));
		h->name[MIN(strlen(hp->h_name), S5_HOSTNAME_SIZE-1)] = '\0';

		for (i = 0; hp->h_aliases[i] && i < S5_HOSTALIASES_NUM; i++) {
		    strncpy(h->aliases[i], hp->h_aliases[i], MIN(strlen(hp->h_aliases[i]), S5_HOSTNAME_SIZE-1));
		    h->aliases[i][MIN(strlen(hp->h_aliases[i]), S5_HOSTNAME_SIZE-1)] = '\0';
		}
		h->aliascnt = i;

		for (i = 0; hp->h_addr_list[i] && i < S5_HOSTIP_NUM; i++) {
		    memcpy((char *)&h->back[i], hp->h_addr_list[i], sizeof(struct in_addr));
		}
		h->ipcnt = i;
	    }
	    MUTEX_UNLOCK(gh_mutex);
	}
    } else {
	/* name */
	h->type    = NAME;
	h->resolve = (*st == '.')?0:1;

	/* Make this string lower case so we don't have to call (non-POSIX)  */
	/* strncasecmp, but instead strncmp.                                 */
	for (tmp = st; *tmp != '\0'; tmp++) if (isupper((unsigned char)*tmp)) *tmp = tolower(*tmp);
	
	strncpy(h->name, st, MIN(strlen(st), S5_HOSTNAME_SIZE-1));
	h->name[MIN(strlen(st), S5_HOSTNAME_SIZE-1)] = '\0';

	if (h->resolve) {
	    /* Reverse map the name, since the name we will be comparing it to   */
	    /* will already have been reverse and mapped (hopefully).            */
	    MUTEX_LOCK(gh_mutex);
	    if ((hp = REAL(gethostbyname)(st)) != NULL) {
		/* Store all the aliases and the ip addresses                */
	        strncpy(h->name, hp->h_name, MIN(strlen(hp->h_name), S5_HOSTNAME_SIZE-1));
	        h->name[MIN(strlen(hp->h_name), S5_HOSTNAME_SIZE-1)] = '\0';

		for (i = 0; hp->h_aliases[i] && i < S5_HOSTALIASES_NUM; i++) {
	            strncpy(h->aliases[i], hp->h_aliases[i], MIN(strlen(hp->h_aliases[i]), S5_HOSTNAME_SIZE-1));
	            h->aliases[i][MIN(strlen(hp->h_aliases[i]), S5_HOSTNAME_SIZE-1)] = '\0';
		}
		h->aliascnt = i;

		for (i = 0; hp->h_addr_list[i] && i < S5_HOSTIP_NUM; i++) {
	            memcpy((char *)&h->back[i], hp->h_addr_list[i], sizeof(struct in_addr));
		}
		h->ipcnt = i;
	    }
	    MUTEX_UNLOCK(gh_mutex);
	}

	h->length = strlen(h->name);
    } 

    *tmp = c;
    SKIPNONSPACE(*ptr);
    return rval;
}

/* Look at the buffer that ptr points to, and read either a port number or   */
/* the name of a service...the name may be used in service name proxying.    */
/*                                                                           */
/* Arguments: ptr -- a ptr to the buffer we are working with...(in/out)      */
/*            p -- a ptr to the port/service we are looking up...(out)       */
int lsGetPortOrService(char **ptr, struct port *p) {
    char *tmp, c, *end, ec;
    int retval = 0;
    u_short tmpport;

    SKIPSPACE(*ptr);
    tmp = *ptr;
    
    p->lport = htons(INVALIDPORT);
    p->hport = htons(INVALIDPORT);

    if (*tmp == '\n') {
	p->lport = htons(0);
	return 0;
    }

    if ((c = *tmp) == '-') {
	/* anything goes */
	p->lport = htons(0);
    } else if (*tmp != '[' && *tmp != '(') {
	retval = lsGetPort(&tmp, &p->lport);
	p->hport = p->lport;
    } else {
	for (end = tmp; !isspace((unsigned char)*end) && *end != ',' && *end != '\0'; end++);
	ec = *end; *end = '\0';
	tmp++;
	
	retval = lsName2Port(tmp, NULL /* XXX OK? */, &p->lport);
	*(tmp = end) = ec;

	if (retval) goto done;
	if (c == '(') {
	    tmpport = ntohs(p->lport) + 1;
	    p->lport = htons(tmpport);
	}

	if (*tmp != ',') {
	    p->lport = INVALIDPORT;
	    S5LogUpdate(S5LogDefaultHandle, S5_LOG_WARNING, 0, "Conf: Expected a ',' between ports in a range");
	    retval = -1;
	    goto done;
	} 

	for (end = tmp; !isspace((unsigned char)*end) && *end != ')' && *end != ']' && *end != '\0'; end++);
	ec = *end; *end = '\0';
	tmp++;

	retval = lsName2Port(tmp, NULL /* XXX OK? */, &p->hport);
	*end = ec;

	if (retval) goto done;
	if (ec == ')') {
	    tmpport = ntohs(p->hport) - 1;
	    p->hport = htons(tmpport);
	}
    }

  done:
    SKIPNONSPACE(*ptr);
    return retval;
}

/* Look at the buffer that ptr points to, and read the string representing   */
/* valid authentication methods.                                             */
/*                                                                           */
/* Arguments: ptr -- a ptr to the buffer we are working with...(in/out)      */
/*            val -- a ptr to the char which will hold valid auths...(out)   */
int lsGetAuthMethods(char **ptr, list **val) {
    char *tmp, c, tmpn[S5_NAME_SIZE];
    int len, rval = 0;

    SKIPSPACE(*ptr);

    for (*val = NULL; **ptr != '\0'; (*ptr)++) {
	for (tmp = *ptr; *tmp != '\0' && *tmp != ',' && !isspace((unsigned char)*tmp); tmp++);
	c = *tmp; *tmp = '\0';

	if (tmp == *ptr) {
	    *tmp = c;
	    break;
	}

	if (!strcmp(*ptr, "-")) {
	    if (*val) lsDeleteLinkedList(val);
	    *tmp = c;
	    break;
	}

	if (lsLinkedListInsertUnaligned(val, 0) < 0) {
	    *tmp = c;
	    break;
	}

	len = MIN(strlen(*ptr), S5_NAME_SIZE-1);
	strncpy(tmpn, *ptr, len);
	tmpn[len] = '\0';
	*(*ptr = tmp) = c;

	/* Somehow, they know the auth number, so let them use it...      */
	if (isdigit((unsigned char)*tmpn)) {
	    if (((*val)->dataint = atoi(tmpn)) >= 0xff) {
	    	(*val)->dataint = 0xff;
		rval = -1;
	    	break;
	    } else if (isspace((unsigned char)c) || c == '\0') break;
	    continue;
	}

	/* See if the long (word) or short (letter) names match...           */
	if (!strcmp(tmpn, "null")           || !strcmp(tmpn, "n")) {
	    (*val)->dataint = AUTH_NONE;           /* null                   */
	    if (isspace((unsigned char)c) || c == '\0') break;
	    continue;
	} else if (!strcmp(tmpn, "krb5gss") || !strcmp(tmpn, "k")) {
	    (*val)->dataint = AUTH_GSSAPI;         /* kerberos               */
	    if (isspace((unsigned char)c) || c == '\0') break;
	    continue;
	} else if (!strcmp(tmpn, "upwd")    || !strcmp(tmpn, "u")) {
	    (*val)->dataint = AUTH_PASSWD;         /* passwd                 */
	    if (isspace((unsigned char)c) || c == '\0') break;
	    continue;
	}

	/* Nothing matched, so mark this as a bad entry and return -1...     */
	(*val)->dataint = 0xff;
	lsDeleteLinkedList(&(*val)->next);
	rval = -1;
	break;
    }
    
    SKIPNONSPACE(*ptr);
    return rval;
}

/* Look at the buffer that ptr points to, and read the string representing   */
/* valid protocols...                                                        */
/*                                                                           */
/* Arguments: ptr -- a ptr to the buffer we are working with...(in/out)      */
/*            val -- a ptr to the char which will hold valid protos...(out)  */
int lsGetPermCommand(char **ptr, list **val) {
    char tmpn[S5_NAME_SIZE], *tmp, c;
    int len, rval = 0;

    SKIPSPACE(*ptr);

    for (*val = NULL; **ptr != '\0'; (*ptr)++) {
	for (tmp = *ptr; *tmp != '\0' && *tmp != ',' && !isspace((unsigned char)*tmp); tmp++);
	c = *tmp; *tmp = '\0';

	if (tmp == *ptr) {
	    *tmp = c;
	    break;
	}

	if (!strcmp(*ptr, "-")) {
	    if (*val) lsDeleteLinkedList(val);
	    *tmp = c;
	    break;
	}

	if (lsLinkedListInsertUnaligned(val, 0) < 0) {
	    *tmp = c;
	    break;
	}

	len = MIN(strlen(*ptr), S5_NAME_SIZE-1);
	strncpy(tmpn, *ptr, len);
	tmpn[len] = '\0';
	*(*ptr = tmp) = c;

	/* Somehow, they know the command number, so let them use it...      */
	if (isdigit((unsigned char)*tmpn)) {
	    if (((*val)->dataint = atoi(tmpn)) >= 0xff) {
	    	(*val)->dataint = 0xff;
		rval = -1;
	    	break;
	    } else if (isspace((unsigned char)c) || c == '\0') break;
	    continue;
	}


	/* See if the long (word) or short (letter) names match...           */
	if (!strcmp(tmpn, "bind")              || !strcmp(tmpn, "b")) {
	    (*val)->dataint = SOCKS_BIND;
	    if (isspace((unsigned char)c) || c == '\0') break;
	    continue;
	} else if (!strcmp(tmpn, "connect")    || !strcmp(tmpn, "c")) {
	    (*val)->dataint = SOCKS_CONNECT;
	    if (isspace((unsigned char)c) || c == '\0') break;
	    continue;
	} else if (!strcmp(tmpn, "udp")        || !strcmp(tmpn, "u")) {
	    (*val)->dataint = SOCKS_UDP;
	    if (isspace((unsigned char)c) || c == '\0') break;
	    continue;
	} else if (!strcmp(tmpn, "ping")       || !strcmp(tmpn, "p")) {
	    (*val)->dataint = SOCKS_PING;
	    if (isspace((unsigned char)c) || c == '\0') break;
	    continue;
	} else if (!strcmp(tmpn, "traceroute") || !strcmp(tmpn, "t")) {
	    (*val)->dataint = SOCKS_TRACER;
	    if (isspace((unsigned char)c) || c == '\0') break;
	    continue;
	}

	/* Nothing matched, so mark this as a bad entry and return -1...     */
	(*val)->dataint = 0xff;
	lsDeleteLinkedList(&(*val)->next);
	rval = -1;
	break;
    }
    
    SKIPNONSPACE(*ptr);
    return rval;
}

/* Look at the buffer that ptr points to, and read the string representing   */
/* a list of users.  The list is comma separated with no whitespce between   */
/* usernames.   "-" is a special string meaning any user is ok...            */
/*                                                                           */
/* Arguments: ptr  -- a ptr to the buffer we are working with...(in/out)     */
/*            l    -- a ptr to the list of users which are ok...(out)        */
int lsGetPermUsers(char **ptr, list **l) {
    char *tmp, c;

    SKIPSPACE(*ptr);

    for (*l = NULL; **ptr != '\0'; (*ptr)++) {
	for (tmp = *ptr; *tmp != '\0' && *tmp != ',' && !isspace((unsigned char)*tmp); tmp++);
	c = *tmp; *tmp = '\0';

	if (tmp == *ptr) {
	    *tmp = c;
	    break;
	}

	if (!strcmp(*ptr, "-")) {
	    if (*l) lsDeleteLinkedList(l);
	    *tmp = c;
	    break;
	}

	if (lsLinkedListInsertUnaligned(l, strlen(*ptr)+1) < 0) {
	    *tmp = c;
	    break;
	}

	strcpy((*l)->dataptr, *ptr);
	*(*ptr = tmp) = c;
	if (isspace((unsigned char)c) || c == '\0') break;
    }

    SKIPNONSPACE(*ptr);
    return 0;
}

/* Check to see if username appears in userlist or if userlist is NULL.      */
/* If username is NULL, and the list is not, it obviously does not.          */
/* Returns 1 on success 0 on failure.                                        */
int lsCheckUser(list *userlist, const char *username) {
    list *tmp;

    if (!userlist) {
	/* Authentication is ok for anybody.                                 */
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(5), 0, "Check: Checking username, %s is in -", username?username:"(null)");
	return 1; 
    }
    
    if (!username) {
	/* Authentication is ok for nobody..                                 */
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(5), 0, "Check: Checking username, username is (null)");
	return 0;
    }

    for (tmp = userlist; tmp != NULL; tmp = tmp->next) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(5), 0, "Check: Checking if %s is %s", username, tmp->dataptr);
	if (!strcmp(tmp->dataptr, username)) return 1;
    }
      
    return 0;
}

/* Check to see if s or name match the host pattern specified in h.  If so   */
/* return 1, if not return 0.                                                */
int lsCheckHost(struct host *h, const S5NetAddr *s, const char *name) {
    char tmp[S5_HOSTNAME_SIZE], nbuf[MAXHOSTNAMELEN];
    int offset, i;
    struct in_addr addr;

    /* Make sure the input address is valid                                  */
    if (!s && !name) return 0;
    if (s && s->sa.sa_family == AF_INET && s->sin.sin_addr.s_addr == INVALIDADDR) return 0;

    /* The address was stored, not the name => compare addresses.            */
    if (h->type == IN_ADDR) {
 	if (h->ip.s_addr == INVALIDADDR) return 0;
	/* It is all....                                                     */
	if (h->ip.s_addr == htonl(0x00000000) && h->mask.s_addr == htonl(0x00000000)) return 1;

	/* s is S5NAME but IP is not mapped...                               */
	if ((!s || s->sa.sa_family == AF_S5NAME) && !h->resolve) return 0;

	/* only name is available...                                         */
	if (!s) {
	    if (!strcmp(h->name, name)) return 1;

	    for (i = 0; i < h->aliascnt; i++) {
	    	if (!strcmp(h->aliases[i], name)) return 1;
	    }

	    return 0;
	}

	if (s->sa.sa_family == AF_S5NAME) {
	    if (!strcmp(h->name, s->sn.sn_name)) return 1;

	    for (i = 0; i < h->aliascnt; i++) {
	    	if (!strcmp(h->aliases[i], s->sn.sn_name)) return 1;
	    }

	    return 0;
	}

	/* s is AF_INET...                                                   */
	if (s->sin.sin_addr.s_addr != INADDR_ANY) addr = s->sin.sin_addr;
	else addr.s_addr = (name)?inet_addr((char *)name):INVALIDADDR;
	
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(5), 0, "Check: Checking host address (%08x == %08x)?", addr.s_addr & h->mask.s_addr, h->ip.s_addr);
	if ((addr.s_addr & h->mask.s_addr) == h->ip.s_addr) return 1;

	for (i = 0; i < h->ipcnt; i++) {
	    if (h->back[i].s_addr == addr.s_addr) return 1;
	}

	/* last possibility, the name maches...                               */
	if (name && h->resolve) {
	    if (!strcmp(h->name, name)) return 1;

	    for (i = 0; i < h->aliascnt; i++) {
	    	if (!strcmp(h->aliases[i], name)) return 1;
	    }
	}

	return 0;
    }

    /* The name was stored, not the address => compare names.                */
    /* If we stored a backup address, check and see if it matches...         */
    if (h->resolve) {
    	if (s && s->sa.sa_family == AF_INET && s->sin.sin_addr.s_addr != INADDR_ANY) addr = s->sin.sin_addr;
	else addr.s_addr = (name)?inet_addr((char *)name):INVALIDADDR;

	for (i = 0; i < h->ipcnt; i++) {
	    if (h->back[i].s_addr == addr.s_addr) return 1;
	}
    }

    if (s && s->sa.sa_family == AF_S5NAME) strcpy(tmp, s->sn.sn_name);
    else if (name) strcpy(tmp, name);
    else if (s && s->sa.sa_family == AF_INET) { 
    	struct hostent *hp = NULL;

        MUTEX_LOCK(gh_mutex);
        if (!(hp = gethostbyaddr((char *)&s->sin.sin_addr.s_addr, sizeof(struct in_addr), AF_INET))) {
            MUTEX_UNLOCK(gh_mutex);
            return 0;
        }

    	strcpy(tmp, hp->h_name);
        MUTEX_UNLOCK(gh_mutex);
    } else return 0;

    for (i = 0; tmp[i] != '\0'; i++) nbuf[i] = (isupper((unsigned char)tmp[i]))?tolower(tmp[i]):tmp[i];
    nbuf[i] = '\0';
    
    if (h->resolve) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(5), 0, "Check: Checking host name  (%s is %s)?",nbuf, h->name);

	if (!strcmp(h->name, nbuf)) return 1;

	for (i = 0; i < h->aliascnt; i++) {
	    if (!strcmp(h->aliases[i], nbuf)) return 1;
	}

	return 0;
    } else {
        S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(5), 0, "Check: Checking host domain (%s is in %s)?", nbuf, h->name);

    	/* How much longer is name than the stored one?  If its too short, well  */
    	/* its not a match...If it has to be exact and its too long, ditto.  If  */
    	/* h->name isn't the last part of name, ditto...otherwise, a match.   */
    	if ((offset = i - h->length) < 0) return 0;
    	if (strncmp(nbuf+offset, h->name, h->length)) return 0;
    	return 1;
    }
}

/* Check to see if s or name match the port specified by p.                  */
int lsCheckPort(struct port *p, const S5NetAddr *s, const char *name, char *proto) {
    u_short port = s?s->sin.sin_port:0;
    struct servent *sp;

    MUTEX_LOCK(gs_mutex);
    if (port == 0 && name && (sp = getservbyname((char *)name, proto))) port = sp->s_port;
    MUTEX_UNLOCK(gs_mutex);
	
    /* compare the port number to the range of numbers we stored....     */
    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(5), 0, "Check: Checking port range   (%d <= %d <= %d)?", ntohs(p->lport), ntohs(port), ntohs(p->hport));
    return ((ntohs(port) < ntohs(p->lport)) || (ntohs(port) > ntohs(p->hport)))?0:1;
}

/* Check to see if byte appears in bytes, a list of bytes.                   */
/*                                                                           */
/* Arguments: bytes - the list of bytes we're checking                       */
/*            byte  - the byte we're looking for in bytes                    */
/*            name  - a name for the logs describing what this check is      */
int lsCheckByte(list *bytes, u_char byte, const char *name) {
    list *tl;
    
    if (!bytes) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(5), 0, "Check: Checking %s: Anything is ok ", name);
	return 1;
    }

    for (tl = bytes; tl; tl = tl->next) {
	if (tl->dataint == (int)byte && tl->dataint != 0xff) {
	    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(5), 0, "Check: Checking %s: %d matched ", name, (int)byte);
	    return 1;
	} else {
	    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(5), 0, "Check: Checking %s: %d didn't match %d", name, tl->dataint, (int)byte);
	}
    }

    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(5), 0, "Check: Checking %s: No match for %d", name, (int)byte);
    return 0;
}

/* A function for use in LoopThroughFile which increments the number of      */
/* entries of this line type that need to be allocated...                    */
static void ClassifyLine(confid *confids, int nids, int indx, char *tmp) {
    if (indx >= 0 && indx < nids) {
	if (confids[indx].number) (*confids[indx].number)++;
	return;
    }

    SKIPSPACE(tmp);
    if (*tmp == '\n' || *tmp =='\0') return;
    
    S5LogUpdate(S5LogDefaultHandle, S5_LOG_ERROR, 0, "Bad line in configuration file: %d", lsLineNo);
}

/* A function for use in LoopThroughFile which actually reads the line into  */
/* the appropriate structure, by calling the right line handler...           */
static void ProcessLine(confid *confids, int nids, int indx, char *tmp) {
    char *end, c;

    for (end = tmp; *end != '\n' && *end != '\0'; end++);
    c = *end; *end = '\0';
    
    if (indx < nids)
	confids[indx].handler(confids[indx].array, confids[indx].cnum?(*confids[indx].cnum)++:0, indx, tmp);
    *end = c;
}

/* LoopTroughFile() reads through (line by line) the file (read into buf)    */
/* and performs func on each line of the file...telling func what kind of    */
/* line it seems to be...                                                    */
static void LoopThroughFile(char *buf, int fsize, confid *confids, int nids, void (*func)(confid *, int, int, char *)) {
    char *tmp, *nl;
    int i;
    
    for (lsLineNo = 1, tmp = buf; tmp && tmp < buf+fsize; tmp = nl+1, lsLineNo++) {
	if ((nl = strchr(tmp, '\n')) != NULL) *nl = '\0';
		
        SKIPSPACE(tmp);
	if (*tmp != '#' && *tmp != '\0') {
	    /* Do this even if "i" is too big (so we only warn once)         */
	    for (i = 0; i < nids; i++) if (CheckStringOrAbbrev(tmp, &confids[i])) break;
	    func(confids, nids, i, tmp); 
	}

	if (!nl) break; else *nl = '\n';
    }
}

/* A function that reads teh configuration file SRVCONF_FILE int buf and     */
/* sets *size to be the size of the new buffer...if something goes wrong, it */
/* returns NULL indicating there is no valid configuration (for now)...      */
static char *ReadConfigFile(const char *filename, int *sizep) {
    int size = 1024 * 1024;
    char *buf = NULL;
    S5IOHandle fd;
    struct stat sb;

    *sizep = 0;

    while ((fd = open(filename, O_RDONLY)) < 0 && errno == EINTR);

    if (fd < 0) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_ERROR, 0, "Config: Error opening config file (%s): %m", filename);
	return NULL;
    }

    if (fstat(fd, &sb) < 0) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_ERROR, 0, "Config: Error stating open config file (%s): %m", filename);
	goto end;
    }

    size = sb.st_size;
    
    if ((buf = (char *)malloc((size+1)*sizeof(char))) == NULL) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_ERROR, 0, "Config: Error allocating space for config file (%s): %m", filename);
	goto end;
    }
    
    if ((size = READFILE(fd, buf, size)) < 0) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_ERROR, 0, "Config: Error reading open config file (%s): %m", filename);
	free(buf);
	buf = NULL;
    } else {
	buf[size] = '\0';
	*sizep = size;
    }

  end:
    REAL(close)(fd);
    return buf;
}

/* ReadConfig() reads the configuration file specified by SRVCONF_FILE.  It  */
/* first figures out how much memory it will need, allocates the memory,     */
/* reads in the array, counts the number of entries of each type, and        */
/* allocates (or reallocates) the appropriate arrays those entries are kept  */
/* in.  After all that, it actually reads in the entries themselves (from    */
/* memory) and rstarts the logging in case its variables may have changed.   */
/*                                                                           */
/* Globals Affected: -- too many to list... big ones are...                  */
/*                   -- confids, where the entries are stored.               */
/*                   -- environ, which may have new entries in it...         */
void lsReadConfig(const char *filename, confid *confids, int nids) {
    int i, fsize;
    char *buf;

    if ((buf = ReadConfigFile(filename, &fsize)) == NULL) return; /* read in */
    LoopThroughFile(buf, fsize, confids, nids, ClassifyLine);     /* count   */

    for (i = 0; i < nids; i++) if (confids[i].size > 0) {
	*confids[i].array = (void *)remalloc(*confids[i].array, *confids[i].number * confids[i].size);
	memset(*confids[i].array, 0, *confids[i].number * confids[i].size);
    }

    LoopThroughFile(buf, fsize, confids, nids, ProcessLine);      /* process */
    free(buf);
}

/* check out interface's condition                                           */
int lsLookupIntfc(S5IOHandle sd, int query, struct ifreq *ifr) {
    int rval = -1;
    S5IOHandle osd = (sd != S5InvalidIOHandle)?sd:socket(AF_INET, SOCK_DGRAM, 0);
    if (osd == S5InvalidIOHandle) return rval;

    memset((char *)&ifr->ifr_ifru, 0, sizeof(ifr->ifr_ifru));

    switch (query) {
    case NET_STAT:
        if (ioctl(osd, SIOCGIFFLAGS, (char *)ifr) == 0) {
    	    if (!(ifr->ifr_flags & (IFF_UP | IFF_RUNNING))) {
	        rval = 0;
	    } else rval = 1;
	}
	break;
    case NET_TYPE:
        if (ioctl(osd, SIOCGIFFLAGS, (char *)ifr) == 0) {
            if ((ifr->ifr_flags & IFF_POINTOPOINT)) rval = 1;
            else rval = 0;
        }
        break;
    case NET_ADDR:
#ifdef linux
        ((struct sockaddr *)&ifr->ifr_addr)->sa_family = AF_INET;
#endif
    	if (ioctl(osd, RSIOCGIFADDR, (char *)ifr) == 0) rval = 0;
	break;
    case NET_MASK:
    	if (ioctl(osd, RSIOCGIFNETMASK, (char *)ifr) == 0) rval = 0;
	break;
    default:
	break;
    }

    if (sd == S5InvalidIOHandle && osd != S5InvalidIOHandle) CLOSESOCKET(osd);
    return rval;
}

/* Get a list of valid interfaces...                                         */
void lsSetupIntfcs(struct intfc **intfc, int *cnt) { 
#ifdef HAVE_GETIFADDRS
    struct ifaddrs *ifp, *ibuf = NULL;
#else
    struct ifreq ibuf[1024];
    struct ifconf ifc;
    S5IOHandle s;
#endif
    struct intfc *pintfc;
    struct intaddr *pintaddr;
    struct in_addr tmpaddr;
    char tmpname[16];
    int i, j, k, m, n;

    if (*intfc) {
	if (intfc[0]->addrlist) free(intfc[0]->addrlist);
        free(*intfc);
        *intfc = NULL;
    }
    *cnt = 0;

#ifdef HAVE_GETIFADDRS
    if (getifaddrs(&ibuf) < 0 || !ibuf) {
        S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(0), 0, "Interface Query: Error looking up interface names");
        return;
    }
    n = 0;
    strcpy(tmpname, ibuf->ifa_name);
#else
    if ((s = socket(AF_INET, SOCK_DGRAM, 0)) == S5InvalidIOHandle) {
        S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(0), 0, "Interface Query: socket: %m");
        return;
    }

    ifc.ifc_len = sizeof(ibuf);
    ifc.ifc_buf = (caddr_t)ibuf;

    memset((char *)ibuf, 0, sizeof(ibuf));

    /* Get the list of all the interfaces...                                */
    if (ioctl(s, RSIOCGIFCONF, (char *)&ifc) < 0 || ifc.ifc_len < sizeof(struct ifreq)) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(0), 0, "Interface Query: Error looking up interface names");
	CLOSESOCKET(s);
	return;
    }

    n = ifc.ifc_len/sizeof(struct ifreq);
    if (n > 0) strcpy(tmpname, ibuf[0].ifr_name);
#endif

    /* Walk through the list and count the interfaces that are configured   */
    /* with IP...                                                           */
    i = 0; j = 0; k = 0;
    tmpaddr.s_addr = 0L;

#ifdef HAVE_GETIFADDRS
    for (ifp = ibuf; ifp; ifp = ifp->ifa_next) {
        if (strcmp(tmpname, ifp->ifa_name)) {
            if (n) j++;
            n = 0;
            strcpy(tmpname, ifp->ifa_name);
        }

        i = (ifp->ifa_flags & (IFF_UP | IFF_RUNNING))?1:0;
        if (i && ifp->ifa_addr->sa_family != AF_INET) continue;
        if (!i && !(ifp->ifa_flags & IFF_POINTOPOINT)) continue;
        n = 1;
        if (!i || ifassi(ifp->ifa_addr) == 0L) continue;
        if (k > 0 && ifassi(ifp->ifa_addr) == tmpaddr.s_addr) continue;

        tmpaddr.s_addr = ifassi(ifp->ifa_addr);
        k++;
    }
    if (n) j++;
#else
    for (; i < n; i++) {
	if (strcmp(tmpname, ibuf[i].ifr_name)) {
	    j++;
	    strcpy(tmpname, ibuf[i].ifr_name);
	}

	if (lsLookupIntfc(s, NET_ADDR, &ibuf[i]) < 0) continue;
	if (ifssi(ibuf[i])->sin_family != AF_INET) continue;
	if (k > 0 && tmpaddr.s_addr == ifssi(ibuf[i])->sin_addr.s_addr) continue;

	tmpaddr.s_addr = ifssi(ibuf[i])->sin_addr.s_addr;
	k++;
    }
    j++;
#endif

    if ((pintfc = (struct intfc *)calloc(j, sizeof(struct intfc))) == NULL) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(0), 0, "Interface Query: no space");
	return;
    }

    if ((pintaddr = (struct intaddr *)calloc(k, sizeof(struct intaddr))) == NULL) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(0), 0, "Interface Query: no space");
	free(pintfc);
	return;
    }

    /* Store IP-configured interfaces information...                        */
    i = 0; j = 0; k = 0;
#ifdef HAVE_GETIFADDRS
    strcpy(pintfc[j].name, ibuf->ifa_name);
    pintfc[j].up = (ibuf->ifa_flags & (IFF_UP | IFF_RUNNING))?1:0;
    pintfc[j].type = (ibuf->ifa_flags & IFF_POINTOPOINT)?1:0;
    pintfc[j].addrlist = &pintaddr[k];
    pintfc[j].addrcnt = 0;
    n = 0;
    m = 0;

    for (ifp = ibuf; ifp; ifp = ifp->ifa_next) {
        if (strcmp(pintfc[j].name, ifp->ifa_name)) {
            pintfc[j].addrcnt = m;
            S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Interface Query: if%d is %s(%d) with %d IPs", j, pintfc[j].name, pintfc[j].up, m);
            if (n) j++;
            strcpy(pintfc[j].name, ifp->ifa_name);
            pintfc[j].up = (ifp->ifa_flags & (IFF_UP | IFF_RUNNING))?1:0;
            pintfc[j].type = (ifp->ifa_flags & IFF_POINTOPOINT)?1:0;
            pintfc[j].addrlist = &pintaddr[k];
            pintfc[j].addrcnt = 0;
            n = 0;
            m = 0;
        }

        if (pintfc[j].up && ifp->ifa_addr->sa_family != AF_INET) continue;
        if (!pintfc[j].up && !pintfc[j].type) continue;
        n = 1;
        if (!pintfc[j].up || ifassi(ifp->ifa_addr) == 0L) continue;
        if (k > 0 && ifassi(ifp->ifa_addr) == pintaddr[k-1].ip.s_addr) continue;

        pintaddr[k].ip.s_addr = ifassi(ifp->ifa_addr);
        pintaddr[k].net.s_addr = ifassi(ifp->ifa_netmask);

        m++; k++;

        S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Interface Query: if%d addr/mask is %08x:%08x", j, pintaddr[k-1].ip.s_addr, pintaddr[k-1].net.s_addr);
    }

    pintfc[j].addrcnt = m;
    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Interface Query: if%d is %s(%d) with %d IPs", j, pintfc[j].name, pintfc[j].up, m);

    if (n) j++;
    *cnt = j;
    *intfc = pintfc;

    free(ibuf);
#else
    strcpy(pintfc[j].name, ibuf[i].ifr_name);
    pintfc[j].up = lsLookupIntfc(s, NET_STAT, &ibuf[i]);
    pintfc[j].type = lsLookupIntfc(s, NET_TYPE, &ibuf[i]);
    pintfc[j].addrlist = &pintaddr[k];
    pintfc[j].addrcnt = 0;
    m = 0;

    for (; i < n; i++) {
	if (strcmp(pintfc[j].name, ibuf[i].ifr_name)) {
	    pintfc[j].addrcnt = m;
    	    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Interface Query: if%d is %s(%d) with %d IPs", j, pintfc[j].name, pintfc[j].up, m);
	    j++;
	    strcpy(pintfc[j].name, ibuf[i].ifr_name);
	    pintfc[j].up = lsLookupIntfc(s, NET_STAT, &ibuf[i]);
            pintfc[j].type = lsLookupIntfc(s, NET_TYPE, &ibuf[i]);
	    pintfc[j].addrlist = &pintaddr[k];
	    pintfc[j].addrcnt = 0;
	    m = 0;
	}

	if (lsLookupIntfc(s, NET_ADDR, &ibuf[i]) < 0) continue;
	if (ifssi(ibuf[i])->sin_family != AF_INET) continue;
	if (k > 0 && pintaddr[k-1].ip.s_addr == ifssi(ibuf[i])->sin_addr.s_addr) continue;

	pintaddr[k].ip.s_addr = ifssi(ibuf[i])->sin_addr.s_addr;
	if (lsLookupIntfc(s, NET_MASK, &ibuf[i]) >= 0) {
	    pintaddr[k].net.s_addr = ifssi(ibuf[i])->sin_addr.s_addr;
	} else pintaddr[k].net.s_addr = 0xffffffff;

	m++; k++;

    	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Interface Query: if%d addr/mask is %08x:%08x", j, pintaddr[k-1].ip.s_addr, pintaddr[k-1].net.s_addr);
    }

    pintfc[j].addrcnt = m;
    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Interface Query: if%d is %s(%d) with %d IPs", j, pintfc[j].name, pintfc[j].up, m);

    *cnt = ++j;
    *intfc = pintfc;

    CLOSESOCKET(s);
#endif
}
