
/*
 * aliasbr.c -- new aliasing mechanism
 *
 * $Id$
 */

#include <h/mh.h>
#include <h/aliasbr.h>
#include <grp.h>
#include <pwd.h>

static int akvis;
static char *akerrst;

struct aka *akahead = NULL;
struct aka *akatail = NULL;

struct home *homehead = NULL;
struct home *hometail = NULL;

/*
 * prototypes
 */
int alias (char *); 
int akvisible (void);
void init_pw (void);
char *akresult (struct aka *);
char *akvalue (char *);
char *akerror (int);

static  char *akval (struct aka *, char *);
static int aleq (char *, char *);
static char *scanp (char *);
static char *getp (char *);
static char *seekp (char *, char *, char **);
static int addfile (struct aka *, char *);
static int addgroup (struct aka *, char *);
static int addmember (struct aka *, char *);
static int addall (struct aka *);
static char *getalias (char *);
static void add_aka (struct aka *, char *);
static struct aka *akalloc (char *);
static struct home *hmalloc (struct passwd *);
#ifndef MMDFMTS
struct home *seek_home (char *);
#endif


char *
akvalue (char *s)
{
    register char *v;

    if (akahead == NULL)
	alias (AliasFile);

    akvis = -1;
    v = akval (akahead, s);
    if (akvis == -1)
	akvis = 0;
    return v;
}


int
akvisible (void)
{
    return akvis;
}


char *
akresult (struct aka *ak)
{
    register char *cp = NULL, *dp, *pp;
    register struct adr *ad;

    for (ad = ak->ak_addr; ad; ad = ad->ad_next) {
	pp = ad->ad_local ? akval (ak->ak_next, ad->ad_text)
	    : getcpy (ad->ad_text);

	if (cp) {
	    dp = cp;
	    cp = concat (cp, ",", pp, NULL);
	    free (dp);
	    free (pp);
	}
	else
	    cp = pp;
    }

    if (akvis == -1)
	akvis = ak->ak_visible;
    return cp;
}


static	char *
akval (struct aka *ak, char *s)
{
    if (!s)
	return s;			/* XXX */

    for (; ak; ak = ak->ak_next)
	if (aleq (s, ak->ak_name))
	    return akresult (ak);

    return getcpy (s);
}


static int
aleq (char *string, char *aliasent)
{
    register char c;

    while ((c = *string++))
	if (*aliasent == '*')
	    return 1;
	else
	    if ((c | 040) != (*aliasent | 040))
		return 0;
	    else
		aliasent++;

    return (*aliasent == 0 || *aliasent == '*');
}


int
alias (char *file)
{
    int i;
    register char *bp, *cp, *pp;
    char lc, *ap;
    register struct aka *ak = NULL;
    register FILE *fp;

    if (*file != '/'
	    && (strncmp (file, "./", 2) && strncmp (file, "../", 3)))
	file = etcpath (file);
    if ((fp = fopen (file, "r")) == NULL) {
	akerrst = file;
	return AK_NOFILE;
    }

    while (vfgets (fp, &ap) == OK) {
	bp = ap;
	switch (*(pp = scanp (bp))) {
	    case '<': 		/* recurse a level */
		if (!*(cp = getp (pp + 1))) {
		    akerrst = "'<' without alias-file";
		    fclose (fp);
		    return AK_ERROR;
		}
		if ((i = alias (cp)) != AK_OK) {
		    fclose (fp);
		    return i;
		}

	    case ':': 		/* comment */
	    case ';': 
	    case '#':
	    case 0: 
		continue;
	}

	akerrst = bp;
	if (!*(cp = seekp (pp, &lc, &ap))) {
	    fclose (fp);
	    return AK_ERROR;
	}
	if (!(ak = akalloc (cp))) {
	    fclose (fp);
	    return AK_LIMIT;
	}
	switch (lc) {
	    case ':': 
		ak->ak_visible = 0;
		break;

	    case ';': 
		ak->ak_visible = 1;
		break;

	    default: 
		fclose (fp);
		return AK_ERROR;
	}

	switch (*(pp = scanp (ap))) {
	    case 0: 		/* EOL */
		fclose (fp);
		return AK_ERROR;

	    case '<': 		/* read values from file */
		if (!*(cp = getp (pp + 1))) {
		    fclose (fp);
		    return AK_ERROR;
		}
		if (!addfile (ak, cp)) {
		    fclose (fp);
		    return AK_NOFILE;
		}
		break;

	    case '=': 		/* UNIX group */
		if (!*(cp = getp (pp + 1))) {
		    fclose (fp);
		    return AK_ERROR;
		}
		if (!addgroup (ak, cp)) {
		    fclose (fp);
		    return AK_NOGROUP;
		}
		break;

	    case '+': 		/* UNIX group members */
		if (!*(cp = getp (pp + 1))) {
		    fclose (fp);
		    return AK_ERROR;
		}
		if (!addmember (ak, cp)) {
		    fclose (fp);
		    return AK_NOGROUP;
		}
		break;

	    case '*': 		/* Everyone */
		addall (ak);
		break;

	    default: 		/* list */
		while ((cp = getalias (pp)))
		    add_aka (ak, cp);
		break;
	}
    }

    fclose (fp);
    return AK_OK;
}


char *
akerror (int i)
{
    static char buffer[BUFSIZ];

    switch (i) {
	case AK_NOFILE: 
	    sprintf (buffer, "unable to read '%s'", akerrst);
	    break;

	case AK_ERROR: 
	    sprintf (buffer, "error in line '%s'", akerrst);
	    break;

	case AK_LIMIT: 
	    sprintf (buffer, "out of memory while on '%s'", akerrst);
	    break;

	case AK_NOGROUP: 
	    sprintf (buffer, "no such group as '%s'", akerrst);
	    break;

	default: 
	    sprintf (buffer, "unknown error (%d)", i);
	    break;
    }

    return buffer;
}


static char *
scanp (char *p)
{
    while (isspace (*p))
	p++;
    return p;
}


static char *
getp (char *p)
{
    register char  *cp = scanp (p);

    p = cp;
    while (!isspace (*cp) && *cp)
	cp++;
    *cp = 0;

    return p;
}


static char *
seekp (char *p, char *c, char **a)
{
    register char *cp;

    p = cp = scanp (p);
    while (!isspace (*cp) && *cp && *cp != ':' && *cp != ';')
	cp++;
    *c = *cp;
    *cp++ = 0;
    *a = cp;

    return p;
}


static int
addfile (struct aka *ak, char *file)
{
    register char *cp;
    char buffer[BUFSIZ];
    register FILE *fp;

    if (!(fp = fopen (etcpath (file), "r"))) {
	akerrst = file;
	return 0;
    }

    while (fgets (buffer, sizeof buffer, fp))
	while ((cp = getalias (buffer)))
	    add_aka (ak, cp);

    fclose (fp);
    return 1;
}


static int
addgroup (struct aka *ak, char *grp)
{
    register char *gp;
    register struct group *gr = getgrnam (grp);
    register struct home *hm = NULL;

    if (!gr)
	gr = getgrgid (atoi (grp));
    if (!gr) {
	akerrst = grp;
	return 0;
    }

#ifndef DBMPWD
    if (homehead == NULL)
	init_pw ();
#endif /* DBMPWD */

    while ((gp = *gr->gr_mem++))
#ifdef DBMPWD
    {
	struct passwd *pw;
#endif /* DBMPWD */
	for (hm = homehead; hm; hm = hm->h_next)
	    if (!strcmp (hm->h_name, gp)) {
		add_aka (ak, hm->h_name);
		break;
	    }
#ifdef DBMPWD
        if ((pw = getpwnam(gp)))
	{
		hmalloc(pw);
		add_aka (ak, gp);
	}
    }
#endif /* DBMPWD */

    return 1;
}


static int
addmember (struct aka *ak, char *grp)
{
    gid_t gid;
    register struct group *gr = getgrnam (grp);
    register struct home *hm = NULL;

    if (gr)
	gid = gr->gr_gid;
    else {
	gid = atoi (grp);
	gr = getgrgid (gid);
    }
    if (!gr) {
	akerrst = grp;
	return 0;
    }

#ifndef DBMPWD
    if (homehead == NULL)
#endif /* DBMPWD */
	init_pw ();

    for (hm = homehead; hm; hm = hm->h_next)
	if (hm->h_gid == gid)
	    add_aka (ak, hm->h_name);

    return 1;
}


static int
addall (struct aka *ak)
{
    int noshell = NoShell == NULL || *NoShell == 0;
    register struct home *hm;

#ifndef DBMPWD
    if (homehead == NULL)
#endif /* DBMPWD */
	init_pw ();
    if (Everyone < 0)
	Everyone = EVERYONE;

    for (hm = homehead; hm; hm = hm->h_next)
	if (hm->h_uid > Everyone
		&& (noshell || strcmp (hm->h_shell, NoShell)))
	    add_aka (ak, hm->h_name);

    return homehead != NULL;
}


static char *
getalias (char *addrs)
{
    register char *pp, *qp;
    static char *cp = NULL;

    if (cp == NULL)
	cp = addrs;
    else
	if (*cp == 0)
	    return (cp = NULL);

    for (pp = cp; isspace (*pp); pp++)
	continue;
    if (*pp == 0)
	return (cp = NULL);
    for (qp = pp; *qp != 0 && *qp != ','; qp++)
	continue;
    if (*qp == ',')
	*qp++ = 0;
    for (cp = qp, qp--; qp > pp; qp--)
	if (*qp != 0)
	    if (isspace (*qp))
		*qp = 0;
	    else
		break;

    return pp;
}


static void
add_aka (struct aka *ak, char *pp)
{
    register struct adr *ad, *ld;

    for (ad = ak->ak_addr, ld = NULL; ad; ld = ad, ad = ad->ad_next)
	if (!strcmp (pp, ad->ad_text))
	    return;

    ad = (struct adr *) malloc (sizeof(*ad));
    if (ad == NULL)
	return;
    ad->ad_text = getcpy (pp);
    ad->ad_local = strchr(pp, '@') == NULL && strchr(pp, '!') == NULL;
    ad->ad_next = NULL;
    if (ak->ak_addr)
	ld->ad_next = ad;
    else
	ak->ak_addr = ad;
}


void
init_pw (void)
{
    register struct passwd  *pw;
#ifdef DBMPWD
    static int init;
  
    if (!init)
    {
          /* if the list has yet to be initialized */
	    /* zap the list, and rebuild from scratch */
	    homehead=NULL;
	    hometail=NULL;
	    init++;
#endif /* DBMPWD */

    setpwent ();

    while ((pw = getpwent ()))
	if (!hmalloc (pw))
	    break;

    endpwent ();
#ifdef DBMPWD
    }
#endif /* DBMPWD */
}


static struct aka *
akalloc (char *id)
{
    register struct aka *p;

    if (!(p = (struct aka *) malloc (sizeof(*p))))
	return NULL;

    p->ak_name = getcpy (id);
    p->ak_visible = 0;
    p->ak_addr = NULL;
    p->ak_next = NULL;
    if (akatail != NULL)
	akatail->ak_next = p;
    if (akahead == NULL)
	akahead = p;
    akatail = p;

    return p;
}


static struct home *
hmalloc (struct passwd *pw)
{
    register struct home *p;

    if (!(p = (struct home *) malloc (sizeof(*p))))
	return NULL;

    p->h_name = getcpy (pw->pw_name);
    p->h_uid = pw->pw_uid;
    p->h_gid = pw->pw_gid;
    p->h_home = getcpy (pw->pw_dir);
    p->h_shell = getcpy (pw->pw_shell);
#ifdef	BSD42
    p->h_ngrps = 0;
#endif	/* BSD42 */
    p->h_next = NULL;
    if (hometail != NULL)
	hometail->h_next = p;
    if (homehead == NULL)
	homehead = p;
    hometail = p;

    return p;
}


#ifndef	MMDFMTS
struct home *
seek_home (char *name)
{
    register struct home *hp;
#ifdef DBMPWD
    struct passwd *pw;
    char lname[32];
    char *c,*c1;
#else  /* DBMPWD */

    if (homehead == NULL)
	init_pw ();
#endif /* DBMPWD */

    for (hp = homehead; hp; hp = hp->h_next)
	if (uleq (name, hp->h_name))
	    return hp;

#ifdef DBMPWD       /* The only place where there might be problems */
                /* This assumes that ALL usernames are kept in lowercase */
    for (c = name,c1 = lname; *c; c++, c1++)
        if (isalpha(*c) && isupper(*c))
	    *c1 = tolower(*c);
	else
	    *c1 = *c;
    *c1 = NULL;
    if ((pw = getpwnam(lname)))
	return(hmalloc(pw));
#endif /* DBMPWD */
	
    return NULL;
}
#endif	/* MMDFMTS */

