/*
#ident	"@(#)smail/src/directors:RELEASE-3_2_0_120:user.c,v 1.37 2004/08/17 19:49:10 woods Exp"
 */

/*
 *    Copyright (C) 1987, 1988 Ronald S. Karr and Landon Curt Noll
 *    Copyright (C) 1992  Ronald S. Karr
 * 
 * See the file COPYING, distributed with smail, for restriction
 * and warranty information.
 */

/*
 * user.c:
 *	direct mail to a transport which will deliver to local user
 *	mailboxes.  Match only local addresses which are login names
 *	on the local host.
 *
 * Specifications for the user directing driver:
 *
 *	private attribute data:
 *	    transport (name):  the name of the transport to use in delivering
 *		mail to local users.
 */

#include "defs.h"

#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <errno.h>
#include <pwd.h>

#ifdef STDC_HEADERS
# include <stdlib.h>
# include <stddef.h>
#else
# ifdef HAVE_STDLIB_H
#  include <stdlib.h>
# endif
#endif

#ifdef HAVE_STRING_H
# if !defined(STDC_HEADERS) && defined(HAVE_MEMORY_H)
#  include <memory.h>
# endif
# include <string.h>
#endif
#ifdef HAVE_STRINGS_H
# include <strings.h>
#endif

#ifdef __STDC__
# include <stdarg.h>
#else
# include <varargs.h>
#endif

#include <pcre.h>

#include "../smail.h"
#include "../alloc.h"
#include "../list.h"
#include "../smailstring.h"
#include "../dys.h"
#include "../main.h"
#include "../parse.h"
#include "../addr.h"
#include "../log.h"
#include "../lookup.h"
#include "../match.h"
#include "../exitcodes.h"
#include "../direct.h"
#include "../transport.h"
#include "../smailconf.h"
#include "user.h"
#include "../debug.h"
#include "../error.h"
#include "../extern.h"
#include "../smailport.h"


static struct attr_table user_attributes[] = {
    { "transport", t_string, 0, NULL, NULL, OFFSET(user_private, transport) },
    { "prefix", t_string, 0, NULL, NULL, OFFSET(user_private, prefix) },
    { "suffix", t_string, 0, NULL, NULL, OFFSET(user_private, suffix) },
/**/{ "ignore-case", t_boolean, FA_FMT_DEPRECATED, NULL, NULL, USER_IGNORE_CASE },
#ifdef HAVE_FGETPWENT
    { "pwfile", t_string, 0, NULL, NULL, OFFSET(user_private, pwfile) },
#endif
};
static struct attr_table *end_user_attributes =
	ENDTABLE(user_attributes);

static struct user_private user_template = {
    "local",			/* transport */
    NULL,			/* prefix */
    NULL,			/* suffix */
#ifdef HAVE_FGETPWENT
    NULL,			/* pwfile */
#endif
};

#ifdef HAVE_FGETPWENT
void direct_userinfo_pwfile __P((struct addr *, char *));
#endif


/*
 * dtd_user - direct to local user mailboxes
 */
/*ARGSUSED*/
struct addr *
dtd_user(dp, in, out, new, defer, fail)
    struct director *dp;		/* director entry */
    struct addr *in;			/* input local-form addrs */
    struct addr **out;			/* output resolved addrs */
    struct addr **new GCC_UNUSED_HACK;	/* output new addrs to resolve */
    struct addr **defer;		/* addrs to defer to a later time */
    struct addr **fail GCC_UNUSED_HACK;	/* unresolvable addrs */
{
    register struct addr *cur;		/* temp for processing input */
    struct addr *new2 = NULL;		/* addr created if prefix or suffix was stripped */
    struct addr *pass = NULL;		/* addrs to pass to next director */
    struct addr *next;			/* next value for cur */
    struct user_private *priv;

    if (!(priv = (struct user_private *) dp->private)) {
	priv = &user_template;
    }

    DEBUG1(DBG_DRIVER_HI, "dtd_user called for director %v\n", dp->name);

    for (cur = in; cur; cur = next) {
	next = cur->succ;

	new2 = NULL;			/* XXX just to be safe */
	if (priv->prefix) {
	    char *anchored_re;
	    char *start = NULL;
	    char *beg = NULL;
	    char *end = NULL;
	    char *last = NULL;

	    /*
	     * if we are testing against a prefix, strip the prefix,
	     * remembering it in rem_prefix
	     */
	    anchored_re = xprintf("^%s", expand_string(priv->prefix, (struct addr *) NULL, (char *) NULL, (char *) NULL));
	    if (match_re(cur->remainder, anchored_re, (ignore_user_case ? TRUE : FALSE), &start, &beg, &end, &last) != MATCH_MATCHED) {
		cur->succ = pass;
		pass = cur;
		xfree(anchored_re);
		continue;		/* did not start with prefix */
	    }
	    xfree(anchored_re);
	    new2 = alloc_addr();
	    new2->remainder = COPY_STRING(last);
	    new2->rem_prefix = rcopy(beg, end);
	    DEBUG2(DBG_DRIVER_HI, "dtd_user(): stripped address: %v, prefix: %v.\n", new2->remainder, new2->rem_prefix);
	}
	if (priv->suffix) {
	    char *newrem;
	    char *anchored_re;
	    char *start = NULL;
	    char *beg = NULL;
	    char *end = NULL;
	    char *last = NULL;

	    /*
	     * if we are testing against a suffix, strip the suffix,
	     * remembering it in rem_suffix
	     */
	    if (!new2) {
		newrem = cur->remainder;
	    } else {
		newrem = new2->remainder; /* use new value without prefix */
	    }
	    anchored_re = xprintf("%s$", expand_string(priv->suffix, (struct addr *) NULL, (char *) NULL, (char *) NULL));
	    if (match_re(newrem, anchored_re, (ignore_user_case ? TRUE : FALSE), &start, &beg, &end, &last) != MATCH_MATCHED) {
		cur->succ = pass;
		pass = cur;
		xfree(anchored_re);
		if (new2) {		/* don't leak space allocated if prefix matched */
		    xfree(new2->remainder);
		    xfree((char *) new2);
		    new2 = NULL;
		}
		continue;		/* did not end with suffix */
	    }
	    xfree(anchored_re);
	    if (!new2) {
		new2 = alloc_addr();
	    } else {
		xfree(new2->remainder);	/* don't leak copy just made above! */
	    }
	    new2->remainder = rcopy(newrem, start);
	    new2->rem_prefix = rcopy(beg, end);
	    DEBUG2(DBG_DRIVER_HI, "dtd_user(): stripped address: %v, suffix: %v.\n", new2->remainder, new2->rem_suffix);
	}
	/*
	 * If we stripped a prefix and/or suffix string then lookup the
	 * resulting name (now in the remainder field) in the passwd file.  If
	 * a matching username is found, then substitute the new fully resolved
	 * addr structure in place of the original, leaving the original now as
	 * its parent.
	 */
	if (new2) {
#ifdef HAVE_FGETPWENT
	    if (priv->pwfile) {
		direct_userinfo_pwfile(new2, priv->pwfile);
	    } else
#endif
	    {
		director_user_info(new2);
	    }

	    if ((new2->flags & ADDR_NOTUSER) || (new2->error)) {
		/*
		 * the stripped name apparently did not match a username
		 */
	        cur->error = new2->error;	/* keep any error info */

		/* clean up temporary new addr */
		xfree(new2->remainder);
		if (new2->rem_prefix) {
		    xfree(new2->rem_prefix);
		}
		if (new2->rem_suffix) {
		    xfree(new2->rem_suffix);
		}
		xfree((char *) new2);
		new2 = NULL;

		/*
		 * NOTE: we can't easily factor out the error handling of these
		 * two blocks because we can't corrupt cur->flags with
		 * ADDR_NOTUSER.  If the prefix and/or suffix didn't match then
		 * some other director must still be given a chance, including
		 * another which calls director_user_info() and which might try
		 * to match a username with some different prefix and/or
		 * suffix.
		 */
		if (cur->error) {
		    cur->succ = *defer;
		    *defer = cur;
		} else {
		    cur->succ = pass;
 		    pass = cur;
		}
		continue;			/* no such user with pre/suf */
	    }
	    if (!(cur->flags & ADDR_VRFY_ONLY)) {
		DEBUG2(DBG_DRIVER_MID, "dtd_user: re-writing addr %v to %v.\n", cur->in_addr, new2->remainder);
		new2->in_addr = COPY_STRING(new2->remainder);
		new2->director = dp;
		new2->parent = cur;
		cur = new2;
		new2 = NULL;
	    }
	} else {
#ifdef HAVE_FGETPWENT
	    if (priv->pwfile) {
		direct_userinfo_pwfile(cur, priv->pwfile);
	    } else
#endif
	    {
		director_user_info(cur);
	    }

	    /* don't match it if it is not a local user */
	    if ((cur->flags & ADDR_NOTUSER) || (cur->error)) {
		if (cur->error) {
		    cur->succ = *defer;
		    *defer = cur;
		} else {
		    cur->succ = pass;
		    pass = cur;
		}
		continue;			/* no such user */
	    }
	}

	DEBUG2(DBG_DRIVER_LO, "director %v matched user %v\n",
	       dp->name, cur->remainder);

	/* attach this director */
	cur->director = dp;

	/* attach the specified transport */
	cur->transport = find_transport(priv->transport);
	if (cur->transport == NULL) {
	    /*
	     * ERR_122 - user transport not specified
	     *
	     * DESCRIPTION
	     *      No transport attribute was specified for a user
	     *      director.  The attribute is required.
	     *
	     * ACTIONS
	     *      Defer the message with a configuration error.
	     *
	     * RESOLUTION
	     *      The director file should be edited to specify the
	     *      correct transport for local delivery.
	     */
	    cur->error = note_error(ERR_CONFERR|ERR_122,
				    xprintf("director %v: no transport defined!",
					    dp->name));
	    cur->succ = *defer;
	    *defer = cur;
	    continue;
	}

	/* assign the final delivery address from the remainder */
	cur->next_addr = COPY_STRING(cur->remainder);

	/* hook this address into the resolved address list */
	cur->succ = *out;
	*out = cur;
    }

    return pass;	/* return remaining addrs for the next director */
}

/*
 * dtb_user - read the configuration file attributes
 */
char *
dtb_user(dp, attrs)
    struct director *dp;		/* director entry being defined */
    struct attribute *attrs;		/* list of per-driver attributes */
{
    char *error;
    struct user_private *priv;		/* new user_private structure */
    char *fn = xprintf("user director: %v", dp->name);

    /* copy the template private data */
    priv = (struct user_private *)xmalloc(sizeof(*priv));
    (void) memcpy((char *)priv, (char *)&user_template, sizeof(*priv));

    dp->private = (char *)priv;
    /* fill in the attributes of the private data */
    error = fill_attributes((char *)priv,
			    attrs,
			    &dp->flags,
			    user_attributes,
			    end_user_attributes,
			    fn);
    if (error) {
	return error;
    }
    if (priv->transport == NULL) {
	return xprintf("%v: transport attribute required for director %v", fn, dp->name);
    }
    if (find_transport(priv->transport) == NULL) {
	return xprintf("%v: director %v: unknown transport: %v", fn, dp->name, priv->transport);
    }
    xfree(fn);

    return NULL;
}


/*
 * dtp_user - dump user config
 */
void
dtp_user(f, dp)
    FILE * f;
    struct director *dp;
{
    (void) dump_standard_config(f,
				(dp->private) ? dp->private : (char *)&user_template,
				dp->name,
				dp->flags,
				user_attributes,
				end_user_attributes);
}


#ifdef HAVE_FGETPWENT
/*
 * direct_userinfo_pwfile - like director_user_info using a custom pw file
 *
 * XXX some *BSDs may have a setpwfile() to do the equivalent with getpwent(),
 * though according to the NetBSD manual page this interface is deprecated
 * _and_ no longer available.
 */
void
direct_userinfo_pwfile(addr, pwfile)
    struct addr * addr;
    char * pwfile;
{
    FILE * pwfd;		/* password file descriptior */
    struct passwd *pw;		/* passwd structure */

    if ((pwfd = fopen(pwfile, "r")) == NULL) {
	/*
	 * ERR_185 - user transport not specified
	 *
	 * DESCRIPTION
	 *      A specified passwd file cannot be opened
	 *
	 * ACTIONS
	 *      Defer the message with a configuration error.
	 *
	 * RESOLUTION
	 *      The director file should be edited to specify the
	 *      correct passwd file.
	 */
	addr->error = note_error(ERR_CONFERR|ERR_185,
				 xprintf("director user: specified passwd file missing"));
	return;
    } 
    while ((pw = (struct passwd *) fgetpwent(pwfd))) {
	if ((ignore_user_case ?
		strcmpic(pw->pw_name, addr->remainder) :
		strcmp(pw->pw_name, addr->remainder)) == 0) {
	    /* matching username entry found */
	    addr->flags |= ADDR_ISUSER;
	    addr->uid = pw->pw_uid;
	    addr->gid = pw->pw_gid;
	    addr->home = COPY_STRING(pw->pw_dir);
	    if (ignore_user_case) {
		strcpy(addr->remainder, pw->pw_name); /* preserve case */
	    }
	    fclose(pwfd);
	    return;
	}
    }
    addr->flags |= ADDR_NOTUSER;
    fclose(pwfd);
    return;
}
#endif

/* 
 * Local Variables:
 * c-file-style: "smail"
 * End:
 */
