/*************************************************
*     Exim - an Internet mail transport agent    *
*************************************************/

/* Copyright (c) University of Cambridge 1995 - 1999 */
/* See the file NOTICE for conditions of use and distribution. */


#include "../exim.h"
#include "smartuser.h"


/* Options specific to the smartuser director. */

optionlist smartuser_director_options[] = {
  { "new_address", opt_stringptr,
      (void *)(offsetof(smartuser_director_options_block, new_address)) },
  { "panic_expansion_fail", opt_bool,
      (void *)(offsetof(smartuser_director_options_block, panic_expansion_fail)) },
  { "rewrite",              opt_bool,
      (void *)(offsetof(smartuser_director_options_block, rewrite)) }
};

/* Size of the options list. An extern variable has to be used so that its
address can appear in the tables drtables.c. */

int smartuser_director_options_count =
  sizeof(smartuser_director_options)/sizeof(optionlist);

/* Default private options block for the smartuser director. */

smartuser_director_options_block smartuser_director_option_defaults = {
  NULL,         /* new_address */
  TRUE,         /* panic_expansion_fail */
  TRUE          /* rewrite */
};



/*************************************************
*          Initialization entry point            *
*************************************************/

/* Called for each instance, after its options have been read, to
enable consistency checks to be done, or anything else that needs
to be set up.

Argument: points to the director instance block
Returns:  nothing
*/

void
smartuser_director_init(director_instance *dblock)
{
smartuser_director_options_block *ob =
  (smartuser_director_options_block *)(dblock->options_block);

/* If no transport is specified, a new user *must* be given, except
when verifying only. */

if (dblock->transport == NULL &&
    dblock->expand_transport == NULL &&
    !dblock->verify_only)
  {
  if (ob->new_address == NULL)
    log_write(0, LOG_PANIC_DIE|LOG_CONFIG2, "%s director:\n  "
      "neither transport nor new_address specified", dblock->name);
  }

/* A new address must be fully qualified, but can check here only if
it isn't going to expand. */

if (ob->new_address != NULL && strchr(ob->new_address, '$') == NULL &&
  strchr(ob->new_address, '@') == NULL)
    log_write(0, LOG_PANIC_DIE|LOG_CONFIG2, "%s director:\n  "
      "new address has no domain", dblock->name);
}



/*************************************************
*              Main entry point                  *
*************************************************/

/* See local README for interface description. */

int
smartuser_director_entry(
  director_instance *dblock,      /* data for this instantiation */
  address_item *addr,             /* address we are working on */
  address_item **addr_local,      /* add it to this if it's local */
  address_item **addr_remote,     /* add it to this if it's remote */
  address_item **addr_new,        /* put new addresses on here */
  address_item **addr_succeed,    /* put finished with addresses here */
  BOOL verify)                    /* TRUE when verifying */
{
smartuser_director_options_block *ob =
  (smartuser_director_options_block *)(dblock->options_block);
char *errmess = NULL;
char *errors_to;
char *remove_headers;
header_line *extra_headers;
transport_instance *transport;
int start, end, domain, rc;

/* Perform file existence and sender verification checks. */

rc = direct_check_fsc(dblock, addr);
if (rc != OK) return rc;

DEBUG(2) debug_printf("%s director called for %s\n", dblock->name, addr->orig);

/* This director always matches the local address. */

/* Set up the errors address, if any. */

rc = direct_get_errors_address(addr, dblock, "director", verify, &errors_to);
if (rc != OK) return rc;

/* Set up the additional and removeable headers for the children of this
address. */

rc = direct_get_munge_headers(addr, dblock, "director", &extra_headers,
  &remove_headers);
if (rc != OK) return rc;

/* Get the transport for this director, if any. It might be expanded, or it
might be unset. */

if (!route_get_transport(dblock->transport, dblock->expand_transport,
  addr, &transport, dblock->name)) return ERROR;

/* If a new address was specified, expand it and check that its syntax is
valid. Then create it as a child of the current address. */

if (ob->new_address != NULL)
  {
  address_item *new_addr;
  char *new_address;
  char *raw_new_address = expand_string(ob->new_address);

  /* Numeric variables from expiry stuff are now finished with. */

  expand_nmax = -1;

  /* Expansion failure is either a panic or a director fail, according
  to an option. A forced fail, or search defer in the expansion is always
  soft. */

  if (raw_new_address == NULL)
    {
    if (search_find_defer)
      {
      addr->message = string_sprintf("expansion of %s in %s director "
        "failed: %s", ob->new_address, dblock->name, expand_string_message);
      return DEFER;
      }
    else if (ob->panic_expansion_fail && !expand_string_forcedfail)
      {
      log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Expansion of %s in %s director "
        "failed: %s", ob->new_address, dblock->name, expand_string_message);
      }
    else
      {
      DEBUG(4) debug_printf("expansion of %s in %s director failed: %s\n",
        ob->new_address, dblock->name, expand_string_message);
      DEBUG(2) debug_printf("%s director failed for %s\n", dblock->name,
        addr->local_part);
      return FAIL;
      }
    }

  /* Malformed new address or domainless address causes freezing */

  new_address = parse_extract_address(raw_new_address, &errmess, &start,
    &end, &domain, FALSE);

  if (new_address == NULL || domain == 0)
    {
    addr->basic_errno = ERRNO_BADADDRESS2;
    if (errmess == NULL) errmess = "missing domain";
    addr->message =
      string_sprintf("<%s> - bad address generated by %s director: %s\n",
      raw_new_address, dblock->name, errmess);
    addr->errors_address = errors_address;
    addr->special_action = SPECIAL_FREEZE;
    return ERROR;
    }

  /* Rewrite new address if configured */

  if (ob->rewrite) new_address = rewrite_address(new_address, TRUE, FALSE);
  domain = strchr(new_address, '@') - new_address + 1;

  /* Create child. If there is a transport, make a copy of the old
  address, changing the domain and local parts except when verifying.
  Otherwise make a brand new address. */

  if (transport != NULL)
    {
    new_addr = store_get(sizeof(address_item));
    *new_addr = *addr;
    if (!verify)
      {
      new_addr->local_part = string_copyn(new_address, domain-1);
      new_addr->domain = new_address + domain;
      }
    }
  else
    {
    new_addr = deliver_make_addr(new_address, FALSE);
    new_addr->ignore_error |= addr->ignore_error;
    }

  /* Put in new values, and connect to parent. */

  new_addr->errors_address = errors_to;
  new_addr->extra_headers = extra_headers;
  new_addr->remove_headers = remove_headers;
  new_addr->start_director = dblock->new;
  new_addr->parent = addr;
  addr->child_count++;

  /* Put the parent onto the succeed chain so that any
  retry item attached to it gets processed. */

  addr->next = *addr_succeed;
  *addr_succeed = addr;

  DEBUG(2) debug_printf("  generated new address: %s%s%s%s\n",
    new_addr->orig,
    (new_addr->errors_address != NULL)? " (errors to " : "",
    (new_addr->errors_address != NULL)? new_addr->errors_address : "",
    (new_addr->errors_address != NULL)? ")" : "");

  /* Operative address is now the child */

  addr = new_addr;
  }

/* No new address; put the errors and header stuff into the current address */

else
  {
  addr->errors_address = errors_to;
  addr->extra_headers = extra_headers;
  addr->remove_headers = remove_headers;
  }

/* Set the director which handled this (possibly new) address */

addr->director = dblock;

/* If a transport is configured, queue the (possibly new) address for that
transport. Any configured uid/gid will be filled in too. */

if (transport != NULL)
  {
  addr->transport = transport;
  return route_queue(addr, addr_local, addr_remote, (router_instance *)dblock,
    "director")? OK : ERROR;
  }

/* Otherwise the new_address field must be set (this is checked in the init
function), and so addr will now be pointing to it. Add it to the new
address chain for further processing. */

addr->next = *addr_new;
*addr_new = addr;
return OK_CONTINUE;   /* Indicate that verify should continue */
}

/* End of directors/smartuser.c */
