/*
 * This module provides support routines for multihoming operations,
 * tracking information on a per-hostname basis.  Each hostname has
 * associated with it an IP address (specified in 'dot' format) and optionally
 * a log file.  Functions exist to return the IP address for a given hostname
 * (http_cname_address) and to return hostname and accesslog index for a
 * given an IP address.
 *
 * When a multihomed hostname is defined it also appends a 'localaddress' 
 * rule to the ident_map database.
 *
 * When an alias is defined it is added to a list of aliases and associated
 * with the last hostname defined.  If a cname lookup fails, the alias
 * list is then searched for a match.
 *
 * No locking it done when accessing these routines, therefore all hostname
 * definitions should be done by a single thread prior to all lookup
 * requests.
 *
 * Author:	David Jones
 * Date:	12-APR-1997
 * Revised:	9-JUN-1997		Bug in http_set_multihome_accesslog().
 * Revised:	6-MAR-1998		Make http_cname_address more robust.
 *					in parsing out host name.
 * Revised:	19-FEB-2000		Add alias list support.
 */
#include <stdlib.h>
#include <stdio.h>

#include "tutil.h"
#include "access.h"
#include "multihome.h"
char *http_default_host;		/* Default host name for server */
int http_define_ident();
/*
 * Define structure used to store hostname list and global (static) variables
 * to point to access this list:
 *    multihome_listm, multihome_last:   Sequential list of all defined hostnames.
 *    multihome_nhash:			Hash table for lookup by name.
 *    multihome_ahash:			Hash table for lookup by IP address.
 */
#define NHASH_SIZE 128
#define AHASH_SIZE 64
static struct host_def {
    struct host_def *next;
    struct host_def *next_a;	/* address hash chain */
    struct host_def *next_n;	/* name hash chain */
    char *name;			/* host name to use */
    char *cmpnam;		/* upcased version for caseless compares */
    union {
        unsigned long number;
	unsigned char octet[4];
    } a;
    int accesslog;
} *multihome_list, *multihome_last;
static struct alias_def {
    struct alias_def *next;
    struct host_def *translation;
    char cmpnam[1];			/* variable-sized allocation */
} *alias_list, *alias_last;

static int multihome_count=0;		/* # of allocated host_def blocks */
static int alias_count = 0;		/* # of allocate alias_def blocks */
static struct host_def *multihome_nhash[NHASH_SIZE];
static struct host_def *multihome_ahash[AHASH_SIZE];

/***************************************************************************/
/* Initialize global structures to empty.
 */
int http_init_multihome()
{
    int i;
    http_multihomed = http_cname_count = 0;
    for (i=0; i < AHASH_SIZE; i++) multihome_ahash[i]=(struct host_def *) 0;
    for (i=0; i < NHASH_SIZE; i++) multihome_nhash[i]=(struct host_def *) 0;
    return 1;
}

/***************************************************************************/
int http_set_multihome_alias ( char *alias, int alen )
{
    struct alias_def *new;
    /*
     * allocate new block and initialize.
     */
    new = (struct alias_def *) malloc ( sizeof(struct alias_def) + alen );
    if ( !new ) return 0;
    tu_strnzcpy ( new->cmpnam, alias, alen );
    tu_strupcase ( new->cmpnam, new->cmpnam );
    new->next = (struct alias_def *) 0;
    new->translation = (multihome_count > 0) ? multihome_last :
	(struct host_def *) 0;
    /*
     * add to end of list.
     */
    if ( alias_count <= 0 ) {
	alias_list = new;		/* first in list */
    } else {
	alias_last->next = new;
    }
    alias_last = new;
    alias_count++;
    return 1;
}
/***************************************************************************/
/*  Change accesslog index for most recently defined host to specified value.
 */
int http_set_multihome_accesslog ( int ndx )
{
    if ( http_multihomed==0 ) return 0;	/* invalid call */
    multihome_last->accesslog = ndx;
    return 1;
}
/*****************************************************************************/
/* Define new multihome block.
 */
void http_define_multihome ( char *host, int hlen, char *dot_address )
{
    /*
     * Allocate block and load host name.
     */
    struct host_def *new; unsigned int a[4];
    int i, j, hash;

    new = (struct host_def *) malloc(sizeof(struct host_def));
    new->next = (struct host_def *) 0;
    new->next_a = (struct host_def *) 0;
    new->next_n = (struct host_def *) 0;
    new->name = malloc(hlen+1);
    new->cmpnam = malloc(hlen+1);
    tu_strnzcpy ( new->name, host, hlen );
    tu_strupcase ( new->cmpnam, new->name );
    /*
     * Decode 'dot' address.
     */
    sscanf ( dot_address, "%d.%d.%d.%d", &a[0],&a[1],&a[2],&a[3]);
    new->a.octet[0] = a[0]; new->a.octet[1] = a[1];
    new->a.octet[2] = a[2]; new->a.octet[3] = a[3];
    new->accesslog = -1;
    /*
     * link into hash table for address to name conversion.  Preserve order.
     */
    hash = (AHASH_SIZE-1) & (a[0] ^ a[1] ^ a[2] ^ a[3]);
    if ( multihome_ahash[hash] ) {
	/* Append new block to end of chain */
	struct host_def *blk;
	for ( blk = multihome_ahash[hash]; blk->next_a; blk = blk->next_a );
	blk->next_a = new;
    } else multihome_ahash[hash] = new;
    /*
     * link into hash table for cname translation.  Preserve order.
     */
    for ( i = hash = 0; i < hlen; i++ ) {
	/* build hash value */
	j = (unsigned) new->cmpnam[i];
	hash = (NHASH_SIZE-1) & (j*577 ^ hash*3);
    }
    if ( multihome_nhash[hash] ) {
	/* Append new block to end of chain */
	struct host_def *blk;
	for ( blk = multihome_nhash[hash]; blk->next_n; blk = blk->next_n );
	blk->next_n = new;
    } else multihome_nhash[hash] = new;
    /*
     * link into global list for http_multihome_scan function.
     */
    if ( multihome_count == 0 ) {
	multihome_list = multihome_last = new;
    } else {
	multihome_last->next = new;
	multihome_last = new;
    }
    /*
     * Link localaddress rule into global structure.  Use 'permanent' character
     * string for name.
     */
    http_define_ident ( "*", 9, new->name );
    multihome_count++;
}
/*****************************************************************************/
/* Return multihomed info.  Return status is 0 if not-multihomed or
 * index out of range, 1 on success.  Index starts at 0.
 */
int http_multihome_scan ( int ndx, 	/* position in list, 0..n-1 */
	char **name,			/* Returns hostname */
	unsigned char **address )	/* IP address */
{
    int i;
    struct host_def *ptr;
    if ( !http_multihomed || (ndx < 0) ) return 0;	/* list empty */
    for ( ptr = multihome_list; ptr; ptr = ptr->next ) {
	--ndx;
	if ( ndx < 0 ) {
	    *name = ptr->name;
	    *address = ptr->a.octet;
	    return 1;
	}
    }
    return 0;		/* index out of range */
}
/*****************************************************************************/
/* Lookup IP address in multihomed list and set local_address element in
 * access info structure to point to corresponding host name.  If no match,
 * field is set to http_default_host.
 *
 * If an accesslog rule was present in the localaddress block, return
 * the tlog index number for it (negative number).
 *
 * Return value:
 *	1		Success.
 *	0		Lookup error, acc loaded with default hostname.
 */
int http_multihomed_hostname ( unsigned int address, access_info acc,
	int *accesslog )
{
    struct host_def *blk;

    if ( multihome_count > 0 ) {
	unsigned int hash;
        hash = ((address>>24)&255) ^ ((address>>16)&255) ^
		((address>>8)&255) ^ (address&255);
	hash = hash & (AHASH_SIZE-1);
	for ( blk = multihome_ahash[hash]; blk; blk = blk->next_a ) {
	    if ( blk->a.number == address ) {
		acc->local_address = blk->name;
		*accesslog = blk->accesslog;
		return 1;
	    }
	}
    }
    acc->local_address = http_default_host;	/* not found */
    return 0;
}
/*****************************************************************************/
/* Lookup hostname and return matching address.  host string may be modified
 * (upcased and stripped of port number).  Return value is 1 for success
 * and 0 for no match.
 */
int http_cname_address ( char *host, unsigned int *address )
{
    struct host_def *blk;
    int i, length;
    unsigned int j, hash;
    char *hname, *pname;
    /*
     * prepare host string for comparisons.  parse out name portion only
     * and upcase.
     */
    if ( (multihome_count <= 0) || !*host ) return 0;	/* null name */
    hname = host;
    pname = (char *) 0;				/* port string (not used) */
    for ( length = 0; host[length]; length++ ) {
	if ( host[length] == '@' ) {
	    hname = &host[length+1];		/* reset host name */
	    pname = (char *) 0;
	} if ( host[length] == ':' ) {
	    if ( !*hname ) return 0;		/* null name */
	    host[length] = '\0';
	    pname = &host[length];
	}
    }
    tu_strupcase ( hname, hname );
    hash = 0;
    for (length = 0; hname[length]; length++) {
	/* build hash value */
	j = (unsigned) hname[length];
	hash = (NHASH_SIZE-1) & (j*577 ^ hash*3);
    }
    /*
     * Scan hash chain for first match.
     */
    length++;
    for ( blk = multihome_nhash[hash]; blk; blk = blk->next_n ) {
	if ( 0 == tu_strncmp ( hname, blk->cmpnam, length ) ) {
	    *address = blk->a.number;
	    return 1;
	}
    }
    /*
     * Getting to this point means hash chain search failed, do linear search
     * of alias list if present.
     */
    if ( alias_count > 0 ) {
	struct alias_def *alias;
	for ( alias = alias_list; alias; alias = alias->next ) {
	    if ( 0 == tu_strmatchwild ( hname, alias->cmpnam ) ) {
		blk = alias->translation;
		if ( !blk ) return 0;	/* no cname translation */
		*address = blk->a.number;
		return 1;
	    }
	}
    }
    return 0;			/* no match */
}
