
/*
 *  ALIAS.C
 *
 *  (C) Copyright 1989-1990 by Matthew Dillon,  All Rights Reserved.
 *
 *  Interpret UULIB:Aliases file.  To save space we do not load
 *  the entire file, just sections on demand.
 *
 *  #	= comment
 *  id: name [, name...]
 *
 *  name is a user name, path, or |filter, or quoted name.  Example,
 *  "|more" or "|rnews"
 */

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <time.h>
#include "config.h"

#include "log.h"

#define HASHSIZE    256
#define HASHMASK    (HASHSIZE-1)

#define HF_TERM     0x01    /*	terminator name 	*/
#define HF_ALIAS    0x02    /*	alias			*/
#define HF_LOADED   0x04    /*	def loaded		*/
#define HF_NOEXPAND 0x08    /*	do not expand alias	*/

typedef struct Hash {
    struct Hash *Next;
    short   NumAlias;	/*  # of aliases	*/
    short   Flags;
    char    *Name;	/*  aliased user name	*/
    union {
	struct Hash    **Alias;    /*  list of aliases	   */
	long	Offset;     /*	offset into file    */
    } u;
} Hash;

Prototype void LoadAliases(void);
Prototype int  UserAliasList(const char *, int (*)(const char *, long, int), long, int);

Local Hash *FindHashObject(const char *);
Local void LoadHashObject(Hash *);
Local int  HashFunc(const char *);

static Hash    *HashTab[HASHSIZE];
static char    Tmp[256];

void
LoadAliases()
{
    FILE *fi = fopen(MakeConfigPath(UULIB, "Aliases"), "r");
    short i;
    short j;
    short k;
    short line = 0;
    long newoffset = 0;
    long offset;
    Hash *h;
    char *buf = Tmp;

    if (fi == NULL) {
	strcpy(buf, MakeConfigPath(UULIB, "Aliases"));
	ulog(-1, "Can't open %s", buf);
	return;
    }
    while (fgets(buf, 256, fi)) {
	offset = newoffset;
	newoffset = ftell(fi);
	++line;
	for (i = 0; buf[i] == ' ' || buf[i] == 9; ++i);
	if (buf[i] == '#' || buf[i] == '\n')
	    continue;
	for (j = i; buf[j] && buf[j] != ':'; ++j);
	if (buf[j] == 0) {
	    strcpy(buf, MakeConfigPath(UULIB, "Aliases"));
	    ulog(-1, "No Colon %s line %d", buf, line);
	    continue;
	}
	buf[j] = 0;

	k = HashFunc(buf + i);
	h = malloc(sizeof(Hash));
	h->Next = HashTab[k];
	h->NumAlias = 0;
	h->Flags = HF_ALIAS;
	h->Name = malloc(strlen(buf+i) + 1);
	if (buf[j+1] == ':') {
	    h->Flags |= HF_NOEXPAND;
	    ++j;
	}
	h->u.Offset = offset + j + 1;
	strcpy(h->Name, buf + i);

	HashTab[k] = h;

	/*
	 *  if trailing comma, list continues onto next line
	 */

	for (;;) {
	    for (++j; buf[j]; ++j);
	    while (buf[j-1] == ' ' || buf[j-1] == 9 || buf[j-1] == '\n')
		--j;
	    if (buf[j-1] != ',')
		break;
	    if (fgets(buf, 256, fi) == NULL)
		break;
	    newoffset = ftell(fi);
	    j = 0;
	}
    }
    fclose(fi);
}

static
Hash *
FindHashObject(name)
const char *name;
{
    short k = HashFunc(name);
    Hash *h;

    for (h = HashTab[k]; h; h = h->Next) {
	if (strcmp(name, h->Name) == 0)
	    return(h);
    }
    return(NULL);
}

static
void
LoadHashObject(hash)
Hash *hash;
{
    FILE *fi = fopen(MakeConfigPath(UULIB, "Aliases"), "r");
    char *buf = Tmp;
    short i, j;
    short c;
    short numalloc = 4;
    Hash **hv = malloc(sizeof(Hash *) * 4);
    Hash *h;

    if (fi == NULL) {
	ulog(-1, "Can't open %s", MakeConfigPath(UULIB, "Aliases"));
	return;
    }

    hash->Flags |= HF_LOADED;
    fseek(fi, hash->u.Offset, 0);

    while (fgets(buf, 256, fi)) {
	i = 0;
	c = 'x';

	for (i = 0; buf[i] == ' ' || buf[i] == 9; ++i);
	if (buf[i] == '#')
	    continue;

	for (;;) {
	    while (buf[i] == ' ' || buf[i] == 9)
		++i;
	    if (buf[i] == 0 || buf[i] == '\n' || buf[i] == '#')
		break;

	    for (j = i; buf[j] != '\n' && buf[j] != ' ' && buf[j] != 9 && buf[j] != ','; ++j) {
		if (buf[j] == '\"') {
		    i = j + 1;
		    for (++j; buf[j] != '\n' && buf[j] != '\"'; ++j);
		    break;
		}
	    }
	    c = buf[j];
	    buf[j] = 0;

	    /*
	     *	skip remaining junk before comma
	     */

	    while (c && c != '\n' && c != ',')
		c = buf[++j];

	    if ((h = FindHashObject(buf + i)) == NULL) {
		short k = HashFunc(buf + i);

		h = malloc(sizeof(Hash));
		h->Next = HashTab[k];
		h->NumAlias = 0;
		h->Flags = HF_TERM;
		h->Name = malloc(strlen(buf + i) + 1);
		h->u.Alias = NULL;
		strcpy(h->Name, buf + i);

		HashTab[k] = h;
	    }

	    if (hash->NumAlias == numalloc) {
		Hash **hvo = hv;
		short add = 4;

		hv = malloc(sizeof(Hash *) * (numalloc + add));
		movmem((char *)hvo, (char *)hv, sizeof(Hash *) * numalloc);
		numalloc += add;
	    }
	    hv[hash->NumAlias++] = h;

	    if (c == '\n' || c == 0)
		i = j;
	    else
		i = j + 1;
	}
	if (c != ',')
	    break;
    }
    hash->u.Alias = hv;
}

/*
 *  UserAliasList returns whether the 'user' should be displayed in the
 *  To: field of the letter.  Normally it isn't, but if an alias is
 *  specified to NOT expand on the To: field then the alias name itself
 *  IS put on the To: field.
 *
 *  showto is passed from an upper level.  If set, the callback specifies
 *  expansion (unless overriden by the alias), else the callback specifies
 *  no expansion.
 *
 *  In the case where a high level alias is expanded but a low level alias
 *  is not, the callback function is called for the low level alias with
 *  a showto of -1, indicating that it should be placed on the To: list
 *  WITHOUT being placed on the actual send-to list (because its expansion
 *  is passed normally)
 */

int
UserAliasList(user, callback, arg, showto)
const char *user;
int (*callback)(const char *, long, int);
long arg;
int showto;
{
    short i;
    int r;
    Hash *hash = FindHashObject(user);
    static short stack;

    if (++stack == 32) {
	ulog(-1, "%s recursion near user %s", MakeConfigPath(UULIB, "Aliases"), user);
	--stack;
	return;
    }

    if (hash) {
	if ((hash->Flags & HF_TERM) == 0) {
	    if ((hash->Flags & HF_LOADED) == 0)
		LoadHashObject(hash);
	    for (i = 0; i < hash->NumAlias; ++i) {
		Hash *h = hash->u.Alias[i];
		int r;

		if (showto)
		    r = UserAliasList(h->Name, callback, arg, !(hash->Flags & HF_NOEXPAND));
		else
		    r = UserAliasList(h->Name, callback, arg, 0);
		--stack;
		if (r && showto && !(hash->Flags & HF_NOEXPAND))
		    (*callback)(h->Name, arg, -1);
	    }
	} else {
	    if (showto)
		(*callback)(user, arg, !(hash->Flags & HF_NOEXPAND));
	    else
		(*callback)(user, arg, 0);
	}
    } else {
	(*callback)(user, arg, showto);
    }
    --stack;
    if (hash && (hash->Flags & HF_NOEXPAND))
	return(1);
    return(0);
}

static int
HashFunc(str)
const char *str;
{
    unsigned long v = 0x14FBA5C3;
    while (*str) {
	v = (v << 5) ^ (*str & 0x1F) ^ (v >> 27);
	++str;
    }
    return((int)(v & HASHMASK));
}

