/* $XConsortium: access.c,v 1.44 89/11/12 15:38:59 rws Exp $ */

#include <stdio.h>
#include <stdlib.h>
#include "X.h"
#include "dixstruct.h"
#include "misc.h"
#include "Xos.h"
#include "Xproto.h"
#include "site.h"

#undef NULL
#include "osdep.h"
#include "funcs.h"
#include "generic.h"

#define acmp(a1, a2, len) bcmp((char *)(a1), (char *)(a2), len)
#define acopy(a1, a2, len) bcopy((char *)(a1), (char *)(a2), len)
#define addrEqual(fam, address, length, host) \
			 ((fam) == (host)->family &&\
			  (length) == (host)->len &&\
			  !acmp (address, (host)->addr, length))

static void NewHost (short family, pointer addr, int len);
static Bool AuthorizedClient(ClientPtr client);
extern unsigned long ClientNetAddr(ClientPtr client);

typedef struct _host {
	short		family;
	short		len;
#ifdef DNETCONN
	unsigned char	addr[sizeof(struct dn_naddr)];
#else
	unsigned char	addr[4];	/* will need to be bigger eventually */
#endif
	struct _host *next;
} HOST;

static HOST *selfhosts = NULL;
static HOST *validhosts = NULL;
static int AccessEnabled = DEFAULT_ACCESS_CONTROL;
static int UsingXdmcp = FALSE;

/*
 * called when authorization is not enabled to add the
 * local host to the access list
 */

#ifdef notdef
void
EnableLocalHost ()
{
    if (!UsingXdmcp)
    {
	AddLocalHosts ();
    }
}
#endif

/*
 * called at init time when XDMCP will be used; xdmcp always
 * adds local hosts manually when needed
 */

void
AccessUsingXdmcp ()
{
    UsingXdmcp = TRUE;
    AccessEnabled = DEFAULT_ACCESS_CONTROL;
}

/* Define this host for access control.  Find all the hosts the OS knows about 
 * for this fd and add them to the selfhosts list.
 */
void
AugmentSelf (addr)
unsigned long addr;
{
    HOST 	*host;
    
    for (host = selfhosts; host; host = host->next)
	 if (addrEqual (FamilyInternet, (pointer)&addr, 4, host))
		return;
    host = (HOST *) xalloc (sizeof (HOST));
    if (host)
    {
	host->family = FamilyInternet;
	host->len = 4;
	acopy((pointer)&addr, host->addr, 4);
	host->next = selfhosts;
	selfhosts = host;
    }
}

#ifdef notdef
/* Not needed.  ResetHosts does this anyway. */
void
AddLocalHosts ()
{
    HOST    *self;

    for (self = selfhosts; self; self = self->next)
	NewHost (self->family, self->addr, self->len);
}
#endif

/* Reset access control list to initial hosts */
void
ResetHosts (display)
    char *display;
{
    HOST	*host;
    char 		hostname[120];
    char		fname[32];
    FILE		*fd;
    char		*ptr;
    unsigned long	addr;

    while ((host = validhosts) != 0)
    {
        validhosts = host->next;
        xfree (host);
    }
    while ((host = selfhosts) != 0)
    {
        selfhosts = host->next;
        xfree (host);
    }
    ptr = getenv("X11");
    if (ptr) {
	strcpy(fname, ptr);
	ptr = fname + strlen (fname) - 1;
	if (*ptr != '\\') {
		*++ptr = '\\';
		*++ptr = '\0';
	}
    } else
	ptr = fname;
    strcpy (ptr, "HOSTS.X");
    strcat (ptr, display);
    if ((fd = fopen (fname, "r")) != 0)
    {
	AccessEnabled = DEFAULT_ACCESS_CONTROL;
        while (fgets (hostname, sizeof (hostname), fd))
	{
    	if ((ptr = index (hostname, '\n')) != 0)
    	    *ptr = 0;
#ifdef DNETCONN
    	if ((ptr = index (hostname, ':')) && (*(ptr + 1) == ':'))
	{
    	    /* node name (DECnet names end in "::") */
    	    *ptr = 0;
	    dnaddrp = dnet_addr(hostname);
    	    if (!dnaddrp && (np = getnodebyname (hostname)))
	    {
		/* node was specified by name */
		saddr.sa.sa_family = np->n_addrtype;
		len = sizeof(saddr.sa);
		if (ConvertAddr (&saddr.sa, &len, &addr) == AF_DECnet)
		{
		    bzero ((char *) &dnaddr, sizeof (dnaddr));
		    dnaddr.a_len = np->n_length;
		    acopy (np->n_addr, dnaddr.a_addr, np->n_length);
		    dnaddrp = &dnaddr;
		}
    	    }
	    if (dnaddrp)
		NewHost((short)AF_DECnet, (pointer)dnaddrp,
			(int)(dnaddrp->a_len + sizeof(dnaddrp->a_len)));
    	}
	else
	{
#endif /* DNETCONN */
#ifdef TCPCONN
	    if (hostname[0] == '+')
		addr = inet_addr(hostname + 1);
	    else
		addr = inet_addr(hostname);
	    if (addr != 0) {
		if (hostname[0] == '+')
			AugmentSelf(addr);
		NewHost(FamilyInternet, (pointer)&addr, 4);
    	    }
#endif /* TCPCONN */
#ifdef DNETCONN
    	}	
#endif /* DNETCONN */
        }
        fclose (fd);
    } else {
	AccessEnabled = FALSE;
    }
}

static Bool
AuthorizedClient(client)
    ClientPtr client;
{
    unsigned long	from;
    pointer		addr;
    register HOST	*host;

    from = ClientNetAddr(client);
    addr = (pointer)&from;
    for (host = selfhosts; host; host = host->next)
    {
	if (addrEqual (FamilyInternet, addr, sizeof (addr), host))
	    return TRUE;
    }
    return FALSE;
}

/* Add a host to the access control list.  This is the external interface
 * called from the dispatcher */

int
AddHost (client, family, length, pAddr)
    ClientPtr		client;
    unsigned char       family;
    unsigned            length;        /* of bytes in pAddr */
    pointer             pAddr;
{
    register HOST	*host;

    if (!AuthorizedClient(client))
	return(BadAccess);
    for (host = validhosts; host; host = host->next)
    {
        if (addrEqual (family, pAddr, length, host))
    	    return (Success);
    }
    if (family != FamilyInternet || length != sizeof host->addr)
	return BadValue;
    host = (HOST *) xalloc (sizeof (HOST));
    if (!host)
	return(BadAlloc);
    host->family = family;
    host->len = length;
    acopy(pAddr, host->addr, length);
    host->next = validhosts;
    validhosts = host;
    return (Success);
}

/* Add a host to the access control list. This is the internal interface 
 * called when starting or resetting the server */
static void
NewHost (family, addr, len)
    short	family;
    pointer	addr;
    int		len;
{
    register HOST *host;

    for (host = validhosts; host; host = host->next)
    {
        if (addrEqual (family, addr, len, host))
	    return;
    }
    host = (HOST *) xalloc (sizeof (HOST));
    if (host)
    {
	host->family = family;
	host->len = len;
	acopy(addr, host->addr, len);
	host->next = validhosts;
	validhosts = host;
    }
}

/* Remove a host from the access control list */

int
RemoveHost (client, family, length, pAddr)
    ClientPtr		client;
    unsigned char	family;
    unsigned            length;        /* of bytes in pAddr */
    pointer             pAddr;
{
    register HOST	*host, * *prev;

    if (!AuthorizedClient(client))
	return(BadAccess);
    for (prev = &validhosts;
         (host = *prev) != 0 && (!addrEqual (family, pAddr, length, host));
         prev = &host->next)
        ;
    if (host)
    {
        *prev = host->next;
        xfree (host);
    }
    return (Success);
}

/* Get all hosts in the access control list */
int
GetHosts (data, pnHosts, pLen, pEnabled)
    pointer		*data;
    int			*pnHosts;
    int			*pLen;
    BOOL		*pEnabled;
{
    int			len;
    int 	n = 0;
    unsigned char *ptr;
    HOST	*host;
    int			nHosts = 0;
    int			*lengths = (int *) NULL;

    *pEnabled = AccessEnabled ? EnableAccess : DisableAccess;
    for (host = validhosts; host; host = host->next)
    {
	lengths = lengths ? xrealloc(lengths, (nHosts + 1) * sizeof (int)) :
		xalloc((nHosts + 1) * sizeof (int));
	if (!lengths)
	    return(BadAlloc);
	lengths[nHosts++] = host->len;
	n += (((host->len + 3) >> 2) << 2) + sizeof(xHostEntry);
    }
    if (n)
    {
        ptr = (u_char *)(*data = (pointer) xalloc (n));
	if (!ptr)
	{
	    xfree(lengths);
	    return(BadAlloc);
	}
	nHosts = 0;
        for (host = validhosts; host; host = host->next)
	{

	    len = lengths[nHosts++];
	    ((xHostEntry *)ptr)->family = host->family;
	    ((xHostEntry *)ptr)->length = len;
	    ptr += sizeof(xHostEntry);
	    acopy (host->addr, ptr, len);
	    ptr += ((len + 3) >> 2) << 2;
        }
    } else {
	*data = NULL;
    }
    *pnHosts = nHosts;
    *pLen = n;
    xfree(lengths);
    return(Success);
}

/* Check if a host is not in the access control list. 
 * Returns 1 if host is invalid, 0 if we've found it. */

int
InvalidHost (saddr, len)
    pointer saddr;
    int				len;
{
    int 			family = FamilyInternet;
    register HOST 		*host;

    if (!AccessEnabled)   /* just let them in */
        return(0);    
    for (host = validhosts; host; host = host->next)
    {
        if (addrEqual (family, saddr, len, host))
    	    return (0);
    }
    return (1);
}

int
ChangeAccessControl(client, fEnabled)
    ClientPtr client;
    int fEnabled;
{
    if (!AuthorizedClient(client))
	return BadAccess;
    AccessEnabled = fEnabled;
    return Success;
}

/* return host in network byte order */
/* This function assumes a 4-component name. */
u_long
inet_addr(name)
char *name;
{
	u_char addr[4];
	u_char *ap;

	memset(addr, 0, 4);
	for (ap = addr; ap < &addr[4]; ap++) {
		while (*name >= '0' && *name <= '9')
			*ap = (u_char)10 * *ap + *name++ - '0';
		if (*name++ != '.')
			break;
	}
	return *(u_long *)addr;
}
