/* This file is part of Mailfromd.
   Copyright (C) 2008-2022 Sergey Poznyakoff

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 3, or (at your option)
   any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>. */

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif

#include <stdlib.h>
#include <syslog.h>
#include <mailutils/mailutils.h>
#include <mailutils/daemon.h>
#include <mailutils/syslog.h>
#include <mailutils/cli.h>
#include <mailutils/alloc.h>
#include <mailutils/cctype.h>
#include <sysexits.h>
#include <pthread.h>
#include <gacopyz.h>
#include "libmf.h"

#include "sm/error.h"
#include "sm/smreplycodes.h"
#include "sm/pmilter.h"
#include "sm/pmfdef.h"
#include "sm/pmfapi.h"

#ifndef PM_MACROS_MAX	    /* MeTA 1.0.0.0 has this constant */
# ifdef PM_MAX_MACROS       /* This was used in older versions */
#  define PM_MACROS_MAX PM_MAX_MACROS
# else
#  define PM_MACROS_MAX 16  /* Provide a reasonable default anyway */
# endif
#endif

const char *program_version = "pmult (" PACKAGE_STRING ")";

int log_to_stderr = -1;
const char *log_stream;
char *portspec; /* Communication socket */
unsigned pmilter_debug_level;
char *pidfile;          /* pidfile name */
int no_sig_handler;     /* Disable signal handler */
int want_auth_macros;   /* Pass auth_ macros to MAIL handler */

static pthread_mutex_t pmult_debug_mutex = PTHREAD_MUTEX_INITIALIZER;
size_t pmult_debug;

#define PMU_DEBUG(cat, lev, s)				\
  do							\
    {							\
      pthread_mutex_lock (&pmult_debug_mutex);		\
      mu_debug (cat, lev, s);				\
      pthread_mutex_unlock (&pmult_debug_mutex);	\
    }							\
  while (0)

unsigned int max_threads_soft;
unsigned int max_threads_hard;
unsigned int max_pmilter_fd;

struct pmult_client
{
  int type; /* Type, unused so far */
  char *name;
  struct timeval timeout[GACOPYZ_TO_COUNT];
  char *url;
  int logmask;
};

/* List of configured clients: */
mu_list_t /* of struct pmult_client */ client_list;

enum pmult_msg_state
  {
    pmult_msg_state_initial,
    pmult_msg_state_headers,
    pmult_msg_state_cr1,
    pmult_msg_state_crlf1,
    pmult_msg_state_cr2,
    pmult_msg_state_body,
    pmult_msg_state_dot,
    pmult_msg_state_dot_cr,
    pmult_msg_state_eom
  };

struct pmult_priv_data
{
  mu_list_t /* of gacopyz_srv_t */ srvlist;
  pthread_mutex_t mutex;
  char *seid;
  char *seid_c;
  char *taid;
  unsigned nrcpt;
  unsigned nbadrcpts;
  enum pmult_msg_state state;
  mu_stream_t hdrstream;
  mu_header_t hdr;
  unsigned char *hdrbuf;
  mu_opool_t body_chunks;
  mu_iterator_t body_itr;
  size_t argc;
  char **argv;
};

#define PRIV_SEID(p) ((p)->seid ? (p)->seid : "")
#define PRIV_SEID_C(p) ((p)->seid_c ? (p)->seid_c : pmult_seid_c (p))
#define PRIV_LOCK(p) pthread_mutex_lock (&(p)->mutex)
#define PRIV_UNLOCK(p) pthread_mutex_unlock (&(p)->mutex)

static char *
pmult_seid_c (struct pmult_priv_data *p)
{
  if (p->seid)
    {
      p->seid_c = malloc (strlen (p->seid) + 2);
      if (p->seid_c)
	{
	  strcpy (p->seid_c, p->seid);
	  strcat (p->seid_c, ":");
	  return p->seid_c;
	}
    }
  return "";
}

static int
pmult_append_body_chunk (struct pmult_priv_data *p, char *buf, size_t size)
{
  int rc;
  if (!p->body_chunks)
    {
      rc = mu_opool_create (&p->body_chunks, MU_OPOOL_DEFAULT);
      if (rc)
	{
	  mu_error (_("cannot create opool: %s"), mu_strerror (rc));
	  return rc;
	}
      mu_opool_set_bucket_size (p->body_chunks, PMILTER_CHUNK_SIZE);
    }
  rc = mu_opool_append (p->body_chunks, buf, size);
  if (rc)
    mu_error (_("cannot append body chunk: %s"), mu_strerror (rc));
  return rc;
}


struct mu_kwd macro_trans[] = {
  { "hostname",         PMM_SRVHOSTNAME,  },      /* hostname of server */
  { "client_resolve",   PMM_CLIENT_RESOLVE },	/* result of client lookups */
  
  { "tls_version",      PMM_TLS_VERSION },
  { "tls_cipher_suite", PMM_TLS_CIPHER_SUITE  },
  { "tls_cipher_bits",  PMM_TLS_CIPHER_BITS },
  { "tls_cert_subject", PMM_TLS_CERT_SUBJECT },
  { "tls_cert_issuer",  PMM_TLS_CERT_ISSUER },
  { "tls_alg_bits",     PMM_TLS_ALG_BITS },
  { "tls_vrfy",         PMM_TLS_VRFY },
  { "tls_cn_subject",   PMM_TLS_CN_SUBJECT },
  { "tls_cn_issuer",    PMM_TLS_CN_ISSUER },

  { "cn_issuer",        PMM_TLS_CN_ISSUER },
  { "cn_subject",       PMM_TLS_CN_SUBJECT },
  
  { "auth_type",        PMM_AUTH_TYPE },
  { "auth_authen",      PMM_AUTH_AUTHEN },
  { "auth_author",      PMM_AUTH_AUTHOR },

  { "taid",             PMM_MAIL_TAID },

  { "msgid",            PMM_DOT_MSGID },
  { "c",                PMM_DOT_HOPS },

  { NULL }
};

struct mu_kwd meta_stage_trans[] = {
  { "connect",          PM_SMST_CONNECT },
  { "helo",             PM_SMST_EHLO },
  { "envfrom",          PM_SMST_MAIL },
  { "mail",             PM_SMST_MAIL },
  { "envrcpt",          PM_SMST_RCPT },
  { "rcpt",             PM_SMST_RCPT },
  { "data",             PM_SMST_DATA },
  { "header",           PM_SMST_DATA },
  { "body",             PM_SMST_DATA },
  { "eom",              PM_SMST_DOT },
  { "dot",              PM_SMST_DOT },
  { NULL }
};

static uint32_t macrotab[PM_SMST_MAX][PM_MACROS_MAX+1] = {
  { PMM_SEID, PMM_END },
  { PMM_END },
  { PMM_END },
  { PMM_END },
  { PMM_END },
  { PMM_END }
};  

static char *macronames[PM_SMST_MAX][PM_MACROS_MAX+1] = {
  { "seid" },
};

static int
macro_defined(uint32_t *tab, uint count, uint32_t macro)
{
  uint i;
  for (i = 0; i < PM_MACROS_MAX && tab[i] != PMM_END; i++)
    if (tab[i] == macro)
      return 1;
  return 0;
}

static char *builtin_macros[] = {
  "multiplexer", "pmult",
  "mult_version", PACKAGE_VERSION,
  NULL
};

static char **
translate_macros0 (pmse_ctx_P pmse_ctx, struct pmult_priv_data *p,
		   uint where, size_t reserve_count)
{
  char **kv;
  size_t count, i;

  for (count = 0; count < PM_MACROS_MAX && macrotab[where][count] != PMM_END;
       count++)
    ;
  
  count += reserve_count;
  count *= 2;
  count += MU_ARRAY_SIZE (builtin_macros) - 1;
  count++;
  if (count > p->argc)
    {
      p->argc = count;
      p->argv = mu_realloc (p->argv, count * sizeof (p->argv[0]));
    }
  kv = p->argv;
  
  for (i = 0, count = 2*reserve_count; macrotab[where][i] != PMM_END; i++)
    {
      char *val;
      sm_ret_T ret = sm_pmfi_getmac (pmse_ctx, macrotab[where][i], &val);
      if (ret != SM_SUCCESS)
	mu_error (_("cannot get the value of {%s}: %s"),
		  macronames[where][i], smerr2txt (ret));
      else if (val)
	{
	  kv[count++] = macronames[where][i];
	  kv[count++] = val;
	}
      }
  for (i = 0; builtin_macros[i]; i++)
    kv[count++] = builtin_macros[i];
  kv[count] = NULL;
  return kv;
}

static char **
translate_macros (pmse_ctx_P pmse_ctx, uint where, size_t reserve_count)
{
  return translate_macros0 (pmse_ctx, sm_pmfi_get_ctx_se (pmse_ctx),
			    where, reserve_count);
}


static pthread_mutex_t pmult_mutex = PTHREAD_MUTEX_INITIALIZER;
#define protect() pthread_mutex_lock (&pmult_mutex)
#define unprotect() pthread_mutex_unlock (&pmult_mutex)


  

static int
_cb_client_type (void *data, mu_config_value_t *arg)
{
  if (mu_cfg_assert_value_type (arg, MU_CFG_STRING))
    return 1;
  if (strcmp (arg->v.string, "milter") == 0)
    /* dobrze */;
  else if (strcmp (arg->v.string, "pmilter") == 0)
    mu_error (_("client type %s is not supported yet"),
			 arg->v.string);
  else
    mu_error (_("unknown client type %s"),
			 arg->v.string);
  return 0;
}

static int
_cb_write_timeout (void *data, mu_config_value_t *arg)
{
  struct pmult_client *clt = data;
  return config_cb_timeout (&clt->timeout[GACOPYZ_TO_WRITE], arg);
}

static int
_cb_read_timeout (void *data, mu_config_value_t *arg)
{
  struct pmult_client *clt = data;
  return config_cb_timeout (&clt->timeout[GACOPYZ_TO_READ], arg);
}

static int
_cb_eom_timeout (void *data, mu_config_value_t *arg)
{
  struct pmult_client *clt = data;
  return config_cb_timeout (&clt->timeout[GACOPYZ_TO_EOM], arg);
}

static int
_cb_connect_timeout (void *data, mu_config_value_t *arg)
{
  struct pmult_client *clt = data;
  return config_cb_timeout (&clt->timeout[GACOPYZ_TO_CONNECT], arg);
}

#define PMULT_LF_REVERT 0x1
#define PMULT_LF_UPTO   0x2
#define PMULT_LF_FROM   0x4

#define PMULT_LOG_LEVEL(flags, lev)			\
  ((flags & PMULT_LF_UPTO) ? SMI_LOG_UPTO (lev) :	\
   (flags & PMULT_LF_FROM) ? SMI_LOG_FROM (lev) :	\
   SMI_LOG_MASK (lev))

static int
_cb_log_level (void *data, mu_config_value_t *arg)
{
  struct pmult_client *clt = data;
  int i;
  struct mu_wordsplit ws;
  
  if (mu_cfg_assert_value_type (arg, MU_CFG_STRING))
    return 1;
  ws.ws_delim = ",";
  if (mu_wordsplit (arg->v.string, &ws,
		    MU_WRDSF_NOVAR | MU_WRDSF_NOCMD | MU_WRDSF_DELIM))
    {
      mu_error ("cannot split line: %s", mu_wordsplit_strerror (&ws));
      return 1;
    }
  
  for (i = 0; i < ws.ws_wordc; i++)
    {
      char *p = ws.ws_wordv[i];
      int lev;
      int flags = 0;
      
      if (*p == '!')
	{
	  p++;
	  flags |= PMULT_LF_REVERT;
	}
      
      if (*p == '<')
	{
	  p++;
	  flags |= PMULT_LF_UPTO;
	}
      else if (*p == '>')
	{
	  p++;
	  flags |= PMULT_LF_FROM;
	}
		  
      lev = gacopyz_string_to_log_level (p);
      if (lev == -1)
	{
	  mu_error (_("invalid log level: %s"), p);
	  return 1;
	}
      
      if (flags & PMULT_LF_REVERT)
	clt->logmask &= ~PMULT_LOG_LEVEL (flags, lev);
      else
	clt->logmask |= PMULT_LOG_LEVEL (flags, lev);
    }
  mu_wordsplit_free (&ws);
  return 0;
}

static int
cb_debug(void *data, mu_config_value_t *arg)
{
	if (mu_cfg_assert_value_type(arg, MU_CFG_STRING))
		return 1;
	mu_debug_parse_spec(arg->v.string);
	return 0;
}

static int
cb_ignore(void *data, mu_config_value_t *arg)
{
  /* nothing */
  return 0;
}

struct mu_cfg_param client_cfg_param[] = {
  { "type", mu_cfg_callback, NULL, 0, _cb_client_type,
    N_("Set remote milter type. Only `milter' is understood so far."),
    /* TRANSLATORS: 'milter' and 'pmilter' are keywords, do not translate
       them. */
    N_("arg: milter [version: number]|pmilter") },
  { "url", mu_c_string, NULL, mu_offsetof(struct pmult_client, url), NULL,
    N_("Set remote client URL.") },
  { "write-timeout", mu_cfg_callback, NULL, 0, _cb_write_timeout,
    N_("Set write timeout."),
    N_("arg: interval") },
  { "read-timeout", mu_cfg_callback, NULL, 0, _cb_read_timeout,
    N_("Set read timeout."),
    N_("arg: interval") },
  { "eom-timeout", mu_cfg_callback, NULL, 0, _cb_eom_timeout,
    N_("Set timeout for EOM."),
    N_("arg: interval") },
  { "connect-timeout", mu_cfg_callback, NULL, 0, _cb_connect_timeout,
    N_("Set connect timeout."),
    N_("arg: interval") },
  { "log-level", mu_cfg_callback, NULL, 0, _cb_log_level,
    N_("Set log verbosity level. Arg is a list of items separated by commas "
       "or whitespace.  Each item is a log level optionally prefixed with `!' "
       "to indicate `any level except this', or '<', meaning `all levels up "
       "to and including this'.  Log levels in order of increasing priority "
       "are: proto, debug, info, warn, err, fatal."),
    N_("arg: list") },
  
  { NULL }
};  

static char *
convert_url (char const *arg)
{
  char *ptr;
  char *proto = NULL;
  char *port = NULL;
  char *path = NULL;
  size_t len;
  
  if (gacopyz_parse_connection (arg, &proto, &port, &path))
    return NULL;

  if (!proto)
    {
      if (port)
	return NULL;
      ptr = mu_strdup (path);
    }
  else
    {
      len = strlen (proto) + 1
	    + (port ? strlen (port) + 1 : 0) + strlen (path) + 1;
      ptr = mu_alloc (len);
      strcpy (ptr, proto);
      strcat (ptr, ":");
      if (port)
	{
	  strcat (ptr, port);
	  strcat (ptr, "@");
	}
      strcat (ptr, path);
    }
  free (path);
  free (port);
  free (proto);
  return ptr;
}

static int
_cb_portspec (void *data, mu_config_value_t *arg)
{
  char **pptr = data, *ptr;

  if (mu_cfg_assert_value_type (arg, MU_CFG_STRING))
    return 1;
  ptr = convert_url (arg->v.string);
  if (!ptr)
    return 1;
  *pptr = ptr;
  return 0;
}


struct macro_slot
{
  uint stage;
  uint num;
};

int
define_single_macro (const char *string, void *data)
{
  struct macro_slot *slot = data;
  int macro;

  if (slot->num == PM_MACROS_MAX)
    {
      mu_error (_("macro table %d is full, symbol {%s} ignored"),
			   slot->stage, string);
      return 0;
    }
  
  if (mu_kwd_xlat_name (macro_trans, string, &macro))
    {
      mu_error (_("unknown MeTA1 macro %s"),
			   string);
      return 1;
    }

  switch (macro)
    {
    case PMM_MAIL_TAID:
      if (slot->stage < PM_SMST_MAIL)
	{
	  mu_error (_("macro \"%s\" cannot be requested at this stage"),
		    "taid");
	  return 1;
	}
      break;

    case PMM_DOT_MSGID:
      if (slot->stage != PM_SMST_DOT)
	{
	  mu_error (_("macro \"%s\" cannot be requested at this stage"),
		    "msgid");
	  return 1;
	}
    }
      
  if (macro_defined (macrotab[slot->stage], slot->num, macro))
    {
      mu_diag_output (MU_LOG_WARNING, _("macro %s already defined"), string);
      return 1;
    }
  macrotab[slot->stage][slot->num] = macro;
  if (mu_kwd_xlat_tok (macro_trans, macro,
		       (const char**) &macronames[slot->stage][slot->num]))
    {
      mu_error (_("INTERNAL ERROR at %s:%d"),
			   __FILE__, __LINE__);
      exit (EX_SOFTWARE);
    }
  slot->num++;
  return 0;
}

static int
define_macros (const char *stage_name, mu_config_value_t *arg)
{
  int rc;
  uint i;
  struct macro_slot slot;

  if (mu_kwd_xlat_name (meta_stage_trans, stage_name, (int*) &slot.stage))
    {
      mu_error (_("unknown SMTP stage"));
      return 1;
    }
  
  for (i = 0; i < PM_MACROS_MAX && macrotab[slot.stage][i] != PMM_END; i++)
    ;
  slot.num = i;
  rc = mu_cfg_string_value_cb (arg, define_single_macro, &slot);
  macrotab[slot.stage][slot.num] = PMM_END;
  macronames[slot.stage][slot.num] = NULL;
  return rc;
}
  
struct define_closure
{
  int retcode;
  mu_config_value_t *arg;
};
  

  
static int
define_macros_iter (void *item, void *data)
{
  mu_config_value_t *arg = item;
  struct define_closure *clos = data;

  if (mu_cfg_assert_value_type (arg, MU_CFG_STRING))
    clos->retcode = 1;
  else
    clos->retcode |= define_macros (arg->v.string, clos->arg);
  return 0;
}

static int
_cb_define (void *data, mu_config_value_t *arg)
{
  int rc;
  struct define_closure dc;
  
  if (mu_cfg_assert_value_type (arg, MU_CFG_ARRAY))
    return 1;
  if (arg->v.arg.c < 2)
    {
      mu_error (_("not enough arguments to define-macros"));
      return 1;
    }
  else if (arg->v.arg.c > 2)
    {
      mu_error (_("too many arguments to define-macros"));
      return 1;
    }
    
  switch (arg->v.arg.v[0].type)
    {
    case MU_CFG_STRING:
      rc = define_macros (arg->v.arg.v[0].v.string, &arg->v.arg.v[1]);
      break;

    case MU_CFG_LIST:
      dc.retcode = 0;
      dc.arg = &arg->v.arg.v[1];
      mu_list_foreach (arg->v.arg.v[0].v.list, define_macros_iter, &dc);
      rc = dc.retcode;
      break;
    }
  return 0;
}

static void
request_auth_macros ()
{
  uint32_t authmac[] = { PMM_AUTH_TYPE, PMM_AUTH_AUTHEN, PMM_AUTH_AUTHOR,
			 PMM_END };
  uint i, j;

  for (i = 0; i < PM_MACROS_MAX && macrotab[PM_SMST_MAIL][i] != PMM_END; i++)
    ;
  for (j = 0; authmac[j] != PMM_END; j++)
    {
      if (i == PM_MACROS_MAX)
	{
	  mu_error (_("too many macros defined for stage \"mail\", while "
		      "processing \"auth-macros\" statement"));
	  exit (EX_CONFIG);
	}
      
      if (macro_defined (macrotab[PM_SMST_MAIL], i, authmac[j]))
	continue;

      macrotab[PM_SMST_MAIL][i] = authmac[j];
      if (mu_kwd_xlat_tok (macro_trans, authmac[j],
			   (const char**) &macronames[PM_SMST_MAIL][i]))
	{
	  mu_error (_("INTERNAL ERROR at %s:%d"), __FILE__, __LINE__);
	  exit (EX_SOFTWARE);
	}
      i++;
    }
  macrotab[PM_SMST_MAIL][i] = PMM_END;
  macronames[PM_SMST_MAIL][i] = NULL;
}

struct mu_cfg_param pmult_cfg_param[] = {
  { "listen", mu_cfg_callback, &portspec, 0, _cb_portspec,
    N_("Listen for milter requests on the given URL."),
    N_("url: string") },
  { "max-threads-soft", mu_c_uint, &max_threads_soft, 0, NULL,
    N_("Maximum number of threads (soft limit).") },
  { "max-threads-hard", mu_c_uint, &max_threads_hard, 0, NULL,
    N_("Maximum number of threads (hard limit).") },
  { "max-pmilter-fd", mu_c_uint, &max_pmilter_fd, 0, NULL,
    N_("Maximum number of file descriptors pmilter is allowed to open") },
  { "client", mu_cfg_section },
  { "pmilter-debug", mu_c_uint, &pmilter_debug_level, 0, NULL,
    N_("Set pmilter debug verbosity level.") },
  { "pidfile", mu_c_string, &pidfile, 0, NULL,
    N_("Write PID to this file.") },
  { "auth-macros", mu_c_bool, &want_auth_macros, 0, NULL,
    N_("Pass auth macros to MAIL handler.") },
  { "define-macros", mu_cfg_callback, NULL, 0, _cb_define,
    N_("Define macros for the given SMTP stage."),
    N_("stage: string> <macros: strings") },
  { "logger", mu_c_string, &log_stream, 0, NULL,
    N_("Set logger stream.") },


  { "debug", mu_cfg_callback, NULL, 0, cb_debug,
    N_("Set Mailfromd debug verbosity level.  Argument is a comma-"
       "separated list of debug specifications, each of which has the "
       "following form:\n"
       "  <module: string>[=<level: number>]\n"
       "where <module> is the name of a Mailfromd module, and <level> "
       "is the desired verbosity level for that module."),
    N_("spec: list") },
  
  { "database-type", mu_cfg_callback, NULL, 0, cb_ignore,
    N_("Ignored for compatibility with mailfromd"),
    N_("type: string") },
  { "database-mode", mu_cfg_callback, NULL, 0, cb_ignore,
    N_("Ignored for compatibility with mailfromd"),
    N_("mode: octal") },
  { "state-directory", mu_cfg_callback, NULL, 0, cb_ignore,
    N_("Ignored for compatibility with mailfromd"),
    N_("dir: string") },
  { "lock-retry-count", mu_cfg_callback, NULL, 0, cb_ignore,
    N_("Ignored for compatibility with mailfromd"),
    N_("arg: number")},
  { "lock-retry-timeout", mu_cfg_callback, NULL, 0, cb_ignore,
    N_("Ignored for compatibility with mailfromd"),
    N_("time: interval") },
  
  { NULL }
};

static void
pmult_client_free (struct pmult_client *clt)
{
  free (clt->name);
  free (clt);
}

static int
client_block_begin (const char *name, void **section_data)
{
  extern struct timeval default_gacopyz_timeout[GACOPYZ_TO_COUNT];
  struct pmult_client *clt = mu_calloc (1, sizeof *clt);
  clt->name = name ? mu_strdup (name) : "";
  memcpy (clt->timeout, default_gacopyz_timeout, sizeof clt->timeout);
  clt->logmask = SMI_LOG_FROM(SMI_LOG_INFO);
  *section_data = clt;
  return 0;
}

static int
client_block_end (struct pmult_client *clt)
{
  gacopyz_srv_t gsrv;
  int rc = gacopyz_srv_create (&gsrv, clt->name, clt->url, clt->logmask);
  if (rc)
    {
      pmult_client_free (clt);
      return 1; /* FIXME: error message */
    }
  
  gacopyz_srv_destroy (&gsrv);
  if (!client_list)
    MU_ASSERT (mu_list_create (&client_list));
  mu_list_append (client_list, clt);
  return 0;
}

static int
client_section_parser (enum mu_cfg_section_stage stage,
		       const mu_cfg_node_t *node,
		       const char *section_label, void **section_data,
		       void *call_data,
		       mu_cfg_tree_t *tree)
{
  const char *s;
  switch (stage)
    {
    case mu_cfg_section_start:
      if (!node->label)
	s = NULL;
      else
	{
	  if (mu_cfg_assert_value_type (node->label, MU_CFG_STRING))
	    return 1;
	  s = node->label->v.string;
	}
      return client_block_begin (s, section_data);

    case mu_cfg_section_end:
      return client_block_end ((struct pmult_client *)*section_data);
    }
  return 0;
}
  
static void
pmult_cfg_init ()
{
  struct mu_cfg_section *section;

  if (mu_create_canned_section ("client", &section))
    exit (EX_SOFTWARE);
  section->parser = client_section_parser;
  section->label = N_("ident");
  mu_cfg_section_add_params (section, client_cfg_param);
}


/* Command line parsing */

static char prog_doc[] = N_("pmilter multiplexer");

static void
opt_url (struct mu_parseopt *po, struct mu_option *opt, char const *arg)
{
  portspec = convert_url (arg);
  if (!portspec)
    exit (po->po_exit_error);
}

static void
opt_debug (struct mu_parseopt *po, struct mu_option *opt, char const *arg)
{
  mu_debug_parse_spec (arg);
}

static struct mu_option pmult_options[] = {
  { "url", 0, N_("URL"), MU_OPTION_DEFAULT,
    N_("listen on the given URL"),
    mu_c_string, NULL, opt_url },
  { "syslog", 0, NULL, MU_OPTION_DEFAULT,
    N_("log to syslog (default)"),
    mu_c_int, &log_to_stderr, NULL, "0" },
  { "stderr", 's', NULL, MU_OPTION_DEFAULT,
    N_("log to stderr"),
    mu_c_int, &log_to_stderr, NULL, "1" },
  { "log-tag", 0, N_("STRING"), MU_OPTION_DEFAULT,
    N_("set the identifier used in syslog messages to STRING"),
    mu_c_string, &mu_log_tag },
  { "debug", 'x', N_("LEVEL"), MU_OPTION_DEFAULT,
    N_("set debug verbosity level"),
    mu_c_string, NULL, opt_debug },
  //FIXME:
  { "no-signal-handler", 0, NULL, MU_OPTION_DEFAULT,
    N_("disable signal handling in the main thread (use for debugging)."),
    mu_c_int, &no_sig_handler, NULL, "1" },
  MU_OPTION_END
}, *options[] = { pmult_options, NULL };

static char *capa[] = {
  "debug",
  "logging",
  NULL
};

#define SM_ONERROR_ACTION(expr,onerr) do                        \
 {                                                              \
   sm_ret_T ret = expr;                                         \
   if (sm_is_err (ret))                                         \
     {                                                          \
       char const *cp = smerr2txt (ret);			\
       if (cp)                                                  \
         mu_error ("%s:%d: " #expr " failed: %s",               \
		   __FILE__, __LINE__, cp);                     \
       else                                                     \
         mu_error ("%s:%d: " #expr " failed: %#x",              \
		   __FILE__, __LINE__, ret);                    \
       onerr;                                                   \
     }                                                          \
  }                                                             \
while (0) 

#define SM_VERBOSE(expr) SM_ONERROR_ACTION(expr,)
#define SM_CHECK(expr) SM_ONERROR_ACTION(expr,return ret)
#define SM_ASSERT(expr) SM_ONERROR_ACTION(expr, exit (EX_CONFIG))


#define MSG_TEMPFAIL "451 4.3.2 Please try again later\r\n"
#define MSG_REJECT "550 5.7.1 Command rejected\r\n"
#define MSG_SHUTDOWN "421 4.7.0 pmult closing connection\r\n"

const char *
hdrcommname (int rc)
{
  switch (rc)
    {
    case SMFIR_CHGHEADER:
      return "chgheader";
      
    case SMFIR_ADDHEADER:
      return "addheader";
      
    case SMFIR_INSHEADER:
      return "insheader";
    }
  return "unknown";
}

static int
get_mod_index (struct pmult_priv_data *p, const char *header_name,
	       size_t pos, size_t *pidx)
{
  size_t i, count;
  const char *name;
	  
  if (mu_header_get_field_count (p->hdr, &count))
    return 1;
  for (i = 1; i <= count; i++)
    {
      if (mu_header_sget_field_name (p->hdr, i, &name))
	continue;
      if (strcmp (name, header_name) == 0 && --pos == 0)
	{
	  *pidx = i - 1;
	  return 0;
	}
    }
  return 1;
}

static int
cb_reply (gacopyz_srv_t gsrv, int cmd, int rcmd, void *data)
{
  char *buf;
  size_t size;
  char *cp;
  char *header;
  uint type;
  uint32_t idx = 0;
  pmse_ctx_P pmse_ctx = data;
  struct pmult_priv_data *p = sm_pmfi_get_ctx_se (pmse_ctx);
  
  switch (rcmd)
    {
    case SMFIR_CHGHEADER:
    case SMFIR_ADDHEADER:
    case SMFIR_INSHEADER:
      gacopyz_srv_reply (gsrv, &buf, &size);
      if (rcmd != SMFIR_ADDHEADER)
	{
	  idx = ntohl (*(uint32_t*)buf);
	  buf += sizeof (uint32_t);
	}
      cp = memchr (buf, 0, size);
      if (!cp)
	{
	  mu_diag_output (MU_DIAG_NOTICE, "Malformed CHGHEADER command");
	  break;
	}
      cp++;
      PMU_DEBUG (pmult_debug, MU_DEBUG_TRACE2, 		
		 ("%s=%u '%s','%s'",
		  hdrcommname (rcmd), idx, buf, cp));

      header = malloc (size + 5);
      if (!header)
	{
	  mu_error ("%s", mu_strerror (ENOMEM));
	  return SMTP_R_ACCEPT;
	}
      strcpy (header, buf);
      strcat (header, ": ");
      strcat (header, cp);
      strcat (header, "\r\n");
      
      switch (rcmd)
	{
	case SMFIR_CHGHEADER:
	  {
	    /* Emulate milter semantics */
	    size_t index;
	    
	    type = (*cp == 0) ? SM_HDRMOD_T_REMOVE : SM_HDRMOD_T_REPLACE;
	    if (get_mod_index (p, buf, idx, &index))
	      {
		PMU_DEBUG (pmult_debug, MU_DEBUG_TRACE1,
			  ("no such header: %s", buf));
		return 0;
	      }
	    idx = index;
	  }
	  break;
	  
	case SMFIR_ADDHEADER:
 	  type = SM_HDRMOD_T_APPEND;
	  break;
	  
	case SMFIR_INSHEADER:
	  type = SM_HDRMOD_T_INSERT;
	}
      
      SM_VERBOSE (sm_pmfi_hdr_mod (pmse_ctx, type, idx,
				   (unsigned char*) header));
      free (header);
      break;
      
    case SMFIR_REPLBODY:
      gacopyz_srv_reply (gsrv, &buf, &size);
      PMU_DEBUG (pmult_debug, MU_DEBUG_TRACE2, 		
		("replbody, len=%lu",
		 (unsigned long) size));
      PMU_DEBUG (pmult_debug, MU_DEBUG_TRACE7, ("bodyp=%.*s", (int) size, buf));
      pmult_append_body_chunk (p, buf, size);
      break;
    }
  return 0;
}

#define CRLF "\r\n"

static char *
trailcrlf (char **pbuf)
{
  char *buf = *pbuf;
  char *retp = NULL;
  int len;
  
  if (!buf)
    *pbuf = CRLF;
  else if ((len = strlen (buf)) < 2 || memcmp (buf + len - 2, CRLF, 2))
    {
      retp = malloc (len + 3);
      if (!retp)
	{
	  mu_error ("%s", mu_strerror (ENOMEM));
	  *pbuf = MSG_TEMPFAIL;
	}
      strcat (strcpy (retp, buf), CRLF);
      *pbuf = retp;
    }
  return retp;
}

static int
pmult_std_reply (struct pmult_priv_data *p, pmse_ctx_P pmse_ctx,
		 gacopyz_srv_t gsrv, int rc,
		 const char *ident, const char *arg)
{
  char *buf, *tmp;
  size_t size;
  int status;
    
  if (!arg)
    arg = "";
  
  switch (rc)
    {
    case SMFIR_CONTINUE:
      break;
      
    case SMFIR_REPLYCODE:
      gacopyz_srv_reply (gsrv, &buf, &size);
      PMU_DEBUG (pmult_debug, MU_DEBUG_TRACE2, 		
		("%s=%s, reply=%s",
		 ident, arg, buf));
      tmp = trailcrlf (&buf);
      SM_VERBOSE (sm_pmfi_setreply (pmse_ctx, tmp));
      status = (buf[0] == '4') ? SMTP_R_TEMP : SMTP_R_PERM;
      free (tmp);
      return status;

    case SMFIR_ACCEPT:
      PMU_DEBUG (pmult_debug, MU_DEBUG_TRACE2,
		("%s=%s, accept", ident, arg));
      return SMTP_R_ACCEPT;
      
    case SMFIR_REJECT:
      PMU_DEBUG (pmult_debug, MU_DEBUG_TRACE2,
		("%s=%s, reply=%s", ident, arg, MSG_REJECT));
      SM_VERBOSE (sm_pmfi_setreply (pmse_ctx, MSG_REJECT));
      return SMTP_R_PERM;

    case SMFIR_DISCARD:
      PMU_DEBUG (pmult_debug, MU_DEBUG_TRACE2,
		("%s=%s, discard", ident, arg));
      return SMTP_R_DISCARD;

    case SMFIR_TEMPFAIL:
      PMU_DEBUG (pmult_debug, MU_DEBUG_TRACE2,
		("%s=%s, reply=%s", ident, arg, MSG_TEMPFAIL));
      SM_VERBOSE (sm_pmfi_setreply (pmse_ctx, MSG_TEMPFAIL));
      return SMTP_R_TEMP;

    case SMFIR_SHUTDOWN:
      PMU_DEBUG (pmult_debug, MU_DEBUG_TRACE2,
		("%s=%s, reply=%s", ident, arg, MSG_SHUTDOWN));
      SM_VERBOSE (sm_pmfi_setreply (pmse_ctx, MSG_SHUTDOWN));
      return SMTP_R_SSD;

    case SMFIR_ADDRCPT:
      gacopyz_srv_reply (gsrv, &buf, &size);
      PMU_DEBUG (pmult_debug, MU_DEBUG_TRACE2, 		
		("%s=%s, addrcpt=%s", ident, arg, buf));
      SM_VERBOSE (sm_pmfi_rcpt_add (pmse_ctx, buf, NULL));
      break;

    case SMFIR_DELRCPT:
      gacopyz_srv_reply (gsrv, &buf, &size);
      PMU_DEBUG (pmult_debug, MU_DEBUG_TRACE2, 		
		("%s=%s, delrcpt=%s", ident, arg, buf));
      /* FIXME: Index is always 0. Should it be 1? */
      SM_VERBOSE (sm_pmfi_rcpt_del (pmse_ctx, buf, 0));
      break;

    case SMFIR_CHGHEADER:
    case SMFIR_ADDHEADER:
    case SMFIR_INSHEADER:
    case SMFIR_REPLBODY:
      break;
      
    default:
      if (mu_isprint (rc))
	mu_diag_output (MU_DIAG_WARNING, _("unsupported reply code: %c (%d)"),
			rc, rc);
      else
	mu_diag_output (MU_DIAG_WARNING, _("unsupported reply code: (%d)"),
			rc);
    }
  return SMTP_R_CONT;
}

typedef sfsistat_T (*pmult_runfun_t) (pmse_ctx_P, gacopyz_srv_t, void *);

static sfsistat_T
pmult_runlist0 (struct pmult_priv_data *p, pmult_runfun_t runfun,
		pmse_ctx_P pmse_ctx, void *data, char **macros)
{
  sfsistat_T rc;
  mu_iterator_t itr = NULL;

  mu_list_get_iterator (p->srvlist, &itr);

  for (mu_iterator_first (itr); !mu_iterator_is_done (itr);
       mu_iterator_next (itr))
    {
      int i;
      
      gacopyz_srv_t gsrv;

      mu_iterator_current (itr, (void**)&gsrv);

      if (macros)
	for (i = 0; macros[i]; i += 2)
	  gacopyz_srv_define_macro (gsrv, macros[i], macros[i+1]);
      
      rc = runfun (pmse_ctx, gsrv, data);
      if (rc != SMTP_R_CONT && rc != SMTP_R_OK)
	break;
    }

  mu_iterator_destroy (&itr);
  return rc;
}

static sfsistat_T
pmult_runlist (struct pmult_priv_data *p, pmult_runfun_t runfun,
	       pmse_ctx_P pmse_ctx, void *data, char **macros)
{
  sfsistat_T rc;
  PRIV_LOCK (p);
  rc = pmult_runlist0 (p, runfun, pmse_ctx, data, macros);
  PRIV_UNLOCK (p);
  return rc;
}

void
pmult_clear (struct pmult_priv_data *p)
{
  PRIV_LOCK (p);
  mu_stream_destroy (&p->hdrstream);
  mu_header_destroy (&p->hdr);
  mu_opool_destroy (&p->body_chunks);
  mu_iterator_destroy (&p->body_itr);
  if (p->hdrbuf)
    {
      free (p->hdrbuf);
      p->hdrbuf = NULL;
    }
  p->state = pmult_msg_state_initial;
  p->nrcpt = 0;
  p->nbadrcpts = 0;
  PRIV_UNLOCK (p);
}

void
pmult_free (struct pmult_priv_data *p)
{
  if (!p)
    return;
  
  pmult_clear (p);
  mu_list_destroy (&p->srvlist);

  free (p->taid);
  free (p->seid);
  free (p->seid_c);
  free (p->argv);
  
  pthread_mutex_destroy (&p->mutex);
  free (p);
}

void
pmult_shutdown (pmse_ctx_P pmse_ctx, struct pmult_priv_data *p)
{
  mu_iterator_t itr;

  if (!p)
    return;
  
  PRIV_LOCK (p);
  mu_list_get_iterator (p->srvlist, &itr);

  for (mu_iterator_first (itr); !mu_iterator_is_done (itr);
       mu_iterator_next (itr))
    {
      gacopyz_srv_t gsrv;

      mu_iterator_current (itr, (void**)&gsrv);
      gacopyz_srv_quit (gsrv);
      gacopyz_srv_close (gsrv);
      gacopyz_srv_destroy (&gsrv);
    }
  mu_iterator_destroy (&itr);
  sm_pmfi_set_ctx_se (pmse_ctx, NULL);
  PRIV_UNLOCK (p);
  pmult_free (p);
}


/* Translation table gacopyz_state -> PM state */
static int gacopyz_to_smst[] = {
  /* gacopyz_stage_conn -> */ PM_SMST_CONNECT,
  /* gacopyz_stage_helo -> */ PM_SMST_EHLO,
  /* gacopyz_stage_mail -> */ PM_SMST_MAIL,
  /* gacopyz_stage_rcpt -> */ PM_SMST_RCPT,
  /* gacopyz_stage_data -> */ PM_SMST_DATA,
  /* gacopyz_stage_eom  -> */ PM_SMST_DOT,
  /* gacopyz_stage_eoh  -> */ PM_SMST_DATA
};


/* Tables of macros defined internally by pmult */
/* NOTE: Update them wherever you add or remove macros in the handlers
   below.
   FIXME: Find a way to automate this. */
static const char *conn_imt[] = { "r", "i", "client_addr", "client_name",
				  "client_port", NULL };
static const char *helo_imt[] = { "s", NULL };
static const char *mail_imt[] = { "f", "ntries", "nrcpts", "nbadrcpts", NULL };
static const char *rcpt_imt[] = { "nrcpts", "nbadrcpts", "rcpt_host",
                                  "rcpt_addr", NULL };
#define data_imt NULL
#define eom_imt NULL
#define eoh_imt NULL

static const char **internal_macro_tab[gacopyz_stage_max] = {
  conn_imt,
  helo_imt,
  mail_imt,
  rcpt_imt,
  data_imt,
  eom_imt, 
  eoh_imt, 
};

static int
internal_macro_p (enum gacopyz_stage gstage, const char *macro)
{
  int i;

  for (i = 0; i <= gstage; i++)
    if (internal_macro_tab[i])
      {
	const char **p;
	for (p = internal_macro_tab[i]; *p; p++)
	  if (strcmp (macro, *p) == 0)
	    return 1;
      }
  return 0;
}

/* For each Sendmail macro name from SYMV, find the corresponding
   MeTA1 macro and register it for use at a given GSTAGE.

   Produce verbose warnings if there are no more free slots in the
   macro table. */
void
collect_stage_symbols (enum gacopyz_stage gstage, const char **symv)
{
  int i;
  int smst = gacopyz_to_smst[gstage];
  
  for (i = 0; i < PM_MACROS_MAX && macrotab[smst][i] != PMM_END; i++)
    ;
  for (; *symv; ++symv)
    {
      int macro;

      if (internal_macro_p (gstage, *symv))
	continue;
      
      if (i == PM_MACROS_MAX)
	{
	  PMU_DEBUG (pmult_debug, MU_DEBUG_TRACE1,
		    (_("macro table %d is full, symbol {%s} ignored"),
		     smst, *symv));
	  continue;
	}
      
      if (mu_kwd_xlat_name (macro_trans, *symv, &macro))
	{
	  PMU_DEBUG (pmult_debug, MU_DEBUG_TRACE1,
		    (_("Sendmail macro {%s} does not translate to a "
		       "MeTA1 one"),
		     *symv));
	  continue;
	}

      if (macro_defined (macrotab[smst], i, macro))
	continue;
      
      if (mu_kwd_xlat_tok (macro_trans, macro,
			   (const char**) &macronames[smst][i]))
	{
	  mu_error (_("INTERNAL ERROR at %s:%d"), __FILE__, __LINE__);
	  exit (EX_SOFTWARE);
	}
      macrotab[smst][i++] = macro;      
    }
}

/* Run initial negotiation with each registered Gacopyz server.  If the
   negotiation returns requested macro names, register them for further
   use. */
   
void
collect_symlists ()
{
  mu_iterator_t itr = NULL;
  
  protect ();
  mu_list_get_iterator (client_list, &itr);
  unprotect ();
  
  for (mu_iterator_first (itr); !mu_iterator_is_done (itr);
       mu_iterator_next (itr))
    {
      struct pmult_client *clt;
      gacopyz_srv_t gsrv;
      int i;
      
      mu_iterator_current (itr, (void**)&clt);
      gacopyz_srv_create (&gsrv, clt->name, clt->url, clt->logmask);
      gacopyz_srv_set_all_timeouts (gsrv, clt->timeout);
      if (gacopyz_srv_open (gsrv) != MI_SUCCESS)
	{
	  if (clt->name[0])
	    mu_error (_("failed to connect to %s (milter %s)"),
		      clt->url, clt->name);
	  else
	    mu_error (_("failed to connect to %s"), clt->url);	    
	  gacopyz_srv_destroy (&gsrv);
	  continue;
	}
      gacopyz_srv_negotiate (gsrv);

      for (i = 0; i < gacopyz_stage_max; i++)
	{
	  const char **symv = gacopyz_srv_get_required_macros (gsrv, i);
	  
	  if (symv)
	    collect_stage_symbols (i, symv);
	}
      
      gacopyz_srv_quit (gsrv);
      gacopyz_srv_close (gsrv);
      gacopyz_srv_destroy (&gsrv);
    }
  mu_iterator_destroy (&itr);
}

static sm_ret_T
pmult_negotiate (pmss_ctx_P pmss_ctx,
		 uint32_t srv_cap, uint32_t srv_fct, uint32_t srv_feat,
		 uint32_t srv_misc, uint32_t *pm_cap, uint32_t *pm_fct,
		 uint32_t *pm_feat, uint32_t *pm_misc)
{
#if HAVE_SM_PMFI_SETMACS
  uint i;

  collect_symlists ();
  for (i = 0; i < PM_SMST_MAX; i++)
    SM_CHECK (sm_pmfi_setmacs (pmss_ctx, i, macrotab[i]));
#else
  SM_CHECK (sm_pmfi_setmaclist (pmss_ctx, PM_SMST_CONNECT, PMM_SEID, PMM_END));
  if (want_auth_macros)
    SM_CHECK (sm_pmfi_setmaclist (pmss_ctx, PM_SMST_MAIL, PMM_MAIL_TAID,
				  PMM_AUTH_TYPE, PMM_AUTH_AUTHEN, PMM_AUTH_AUTHOR,
				  PMM_END));
  else
    SM_CHECK (sm_pmfi_setmaclist (pmss_ctx, PM_SMST_MAIL, PMM_MAIL_TAID,
				  PMM_END));
#endif
  return SM_SUCCESS;
}

static sfsistat_T
pmult_connect (pmse_ctx_P pmse_ctx, const char *hostname,
	       sm_sockaddr_T *hostaddr)
{
  mu_iterator_t itr;
  struct pmult_priv_data *p;
  int status = SMTP_R_CONT;
  int rc;
  char *tmp = NULL;
  char *client_addr = NULL, *client_port = NULL;
  char **kv;
  char buf[INT_BUFSIZE_BOUND (uintmax_t)];
  
  if (mu_debug_level_p (pmult_debug, MU_DEBUG_TRACE1))
    {
      char *p = mu_sockaddr_to_astr (&hostaddr->sa, sizeof *hostaddr);
      pthread_mutex_lock (&pmult_debug_mutex);
      mu_debug_log_begin ("Connect from: %s, address %s\n", hostname, p);
      pthread_mutex_unlock (&pmult_debug_mutex);
      free (p);
    }
    
  p = calloc (1, sizeof *p);
  if (!p)
    {
      mu_error ("%s: accept", mu_strerror (ENOMEM));
      return SMTP_R_ACCEPT;
    }
  pthread_mutex_init (&p->mutex, NULL);

  kv = translate_macros0 (pmse_ctx, p, PM_SMST_CONNECT, 0);
  sm_pmfi_getmac (pmse_ctx, PMM_SEID, &tmp);
  p->seid = strdup (tmp);

  rc = mu_list_create (&p->srvlist);
  if (rc)
    {
      mu_error ("%smu_list_create: %s", PRIV_SEID_C (p), mu_strerror (rc));
      free (p);
      return SMTP_R_ACCEPT;
    }

  protect ();
  mu_list_get_iterator (client_list, &itr);

  client_addr = strdup (inet_ntoa (hostaddr->sin.sin_addr));
  snprintf (buf, sizeof buf, "%hu", ntohs (hostaddr->sin.sin_port));
  client_port = strdup (buf); 
  unprotect ();
  
  PRIV_LOCK (p);
  for (mu_iterator_first (itr); !mu_iterator_is_done (itr);
       mu_iterator_next (itr))
    {
      struct pmult_client *clt;
      gacopyz_srv_t gsrv;
      int i;
      
      mu_iterator_current (itr, (void**)&clt);
      gacopyz_srv_create (&gsrv, clt->name, clt->url, clt->logmask);
      gacopyz_srv_set_callback (gsrv, cb_reply);
      gacopyz_srv_set_callback_data (gsrv, pmse_ctx);
      gacopyz_srv_set_all_timeouts (gsrv, clt->timeout);
      if (gacopyz_srv_open (gsrv) != MI_SUCCESS)
	{
	  mu_error (_("%sfailed to connect to %s (milter %s)"),
		    PRIV_SEID_C (p), clt->url, clt->name);
	  gacopyz_srv_destroy (&gsrv);
	  continue;
	}
      gacopyz_srv_negotiate (gsrv);
      gacopyz_srv_define_macro (gsrv, "r", "SMTP");
      gacopyz_srv_define_macro (gsrv, "i", PRIV_SEID (p));
      
      if (client_addr)
	gacopyz_srv_define_macro (gsrv, "client_addr", client_addr);
      gacopyz_srv_define_macro (gsrv, "client_name", hostname);
      if (client_port)
	gacopyz_srv_define_macro (gsrv, "client_port", client_port);
      for (i = 0; kv[i]; i += 2)
	gacopyz_srv_define_macro (gsrv, kv[i], kv[i+1]);
      /* FIXME: client_ptr,  */
      
      rc = gacopyz_srv_connect (gsrv, hostname, &hostaddr->sa);
      status = pmult_std_reply (p, pmse_ctx, gsrv, rc, "connect", hostname);
      if (status != SMTP_R_CONT)
	{
	  gacopyz_srv_quit (gsrv);
	  gacopyz_srv_close (gsrv);
	  gacopyz_srv_destroy (&gsrv);
	  break;
	}
      mu_list_append (p->srvlist, gsrv);
    }

  free (client_addr);
  free (client_port);
  
  protect ();
  mu_iterator_destroy (&itr);
  unprotect ();

  PRIV_UNLOCK (p);
  sm_pmfi_set_ctx_se (pmse_ctx, p);
  return status;
}

static sm_ret_T
pmult_close (pmse_ctx_P pmse_ctx)
{
  struct pmult_priv_data *p = sm_pmfi_get_ctx_se (pmse_ctx);
  if (p)
    {
      PMU_DEBUG (pmult_debug, MU_DEBUG_TRACE1,
		("%sClosing connection", PRIV_SEID_C (p)));
      pmult_shutdown (pmse_ctx, p);
    }
  return SM_SUCCESS;
}

static sfsistat_T
rf_helo (pmse_ctx_P pmse_ctx, gacopyz_srv_t gsrv, void *data)
{
  struct pmult_priv_data *p = sm_pmfi_get_ctx_se (pmse_ctx);
  const char *helohost = data;
  int rc = gacopyz_srv_helo (gsrv, helohost);
  return pmult_std_reply (p, pmse_ctx, gsrv, rc, "helo", helohost);
}

static sfsistat_T
pmult_helo (pmse_ctx_P pmse_ctx, const char *helohost, bool ehlo)
{
  struct pmult_priv_data *p = sm_pmfi_get_ctx_se (pmse_ctx);
  char **kv = translate_macros (pmse_ctx, PM_SMST_EHLO, 1);

  PMU_DEBUG (pmult_debug, MU_DEBUG_TRACE1,
	     ("%sHELO %s", PRIV_SEID_C (p), helohost));
  kv[0] = "s";
  kv[1] = (char*) helohost;
  kv[2] = NULL;
  return pmult_runlist (p, rf_helo, pmse_ctx, (void*) helohost, kv);
}


struct env_arg
{
  char *ident;
  int (*srvfun) (gacopyz_srv_t,  char **);
  char **argv;
};

/* Common for envmail and envrcpt */
static sfsistat_T
rf_envfun (pmse_ctx_P pmse_ctx, gacopyz_srv_t gsrv, void *data)
{
  struct pmult_priv_data *p = sm_pmfi_get_ctx_se (pmse_ctx);
  struct env_arg *x = data;
  int rc = x->srvfun (gsrv, x->argv);
  return pmult_std_reply (p, pmse_ctx, gsrv, rc, x->ident, x->argv[0]);
}


static sfsistat_T
pmult_mail (pmse_ctx_P pmse_ctx, const char *mail, char **argv)
{
  int rc, i, n;
  mu_address_t addr;
  struct pmult_priv_data *p = sm_pmfi_get_ctx_se (pmse_ctx);
  char **kv = translate_macros (pmse_ctx, PM_SMST_MAIL, 4);
  struct env_arg x;
  sfsistat_T status;
  
  if (mu_debug_level_p (pmult_debug, MU_DEBUG_TRACE1))
    {
      int i;
      pthread_mutex_lock (&pmult_debug_mutex);
      mu_debug_log_begin ("%sMAIL FROM: %s", PRIV_SEID_C (p), mail);
      if (argv)
	{
	  for (i = 0; argv[i]; i++)
	    mu_debug_log_cont (" %s", argv[i]);
	}
      mu_debug_log_nl ();
      pthread_mutex_unlock (&pmult_debug_mutex);
    }

  /* Fill in the macro array */
  kv[0] = "f";
  rc = mu_address_create (&addr, mail);
  if (rc)
    kv[1] = strdup (mail);
  else
    {
      mu_address_aget_email (addr, 1, &kv[1]);
      mu_address_destroy (&addr);
    }
  kv[2] = "ntries";
  kv[3] = "1";
  kv[4] = "nrcpts";
  kv[5] = "0";
  kv[6] = "nbadrcpts";
  kv[7] = "0";

  /* Count arguments and allocate env array */
  if (argv)
    {
      for (n = 0; argv[n]; n++)
	;
    }
  else
    n = 0;
  
  x.argv = calloc (n + 2, sizeof (x.argv[0]));
  if (!x.argv)
    {
      mu_error ("%spmult_mail: %s", PRIV_SEID_C (p), mu_strerror (ENOMEM));
      free (kv[1]);
      return SMTP_R_CONT;
    }
  x.argv[0] = (char *) mail;
  for (i = 0; i < n; i++)
    x.argv[i+1] = argv[i];
  x.argv[i+1] = NULL;

  x.ident = "envfrom";
  x.srvfun = gacopyz_srv_envfrom;
  status = pmult_runlist (p, rf_envfun, pmse_ctx, &x, kv);
  free (kv[1]);
  free (x.argv);
  return status;
}

int
parse_email_addr (const char *arg, char **psender, char **addr, char **host)
{
  size_t len;
  const char *p = arg, *q;
  if (*p == '<')
    {
      len = strlen (p);
      if (p[len-1] != '>')
	return 1;
      p++;
      *psender = malloc (len - 1);
      if (*psender)
	{
	  memcpy (*psender, p, len - 2);
	  (*psender)[len - 2] = 0;
	}
    }
  else
    *psender = strdup (arg);
  if (!*psender)
    return 1;
  p = *psender;
  q = strchr (p, '@');
  if (q)
    len = q - p;
  else
    len = strlen (p);
  *addr = malloc (len + 1);
  if (!*addr)
    {
      free (*psender);
      return 1;
    }
  memcpy (*addr, p, len);
  (*addr)[len] = 0;
  if (q)
    q++;
  else
    q = "localhost";
  *host = strdup (q);
  if (!*host)
    {
      free (*psender);
      free (*addr);
      return 1;
    }
  return 0;
}

static sfsistat_T
pmult_rcpt (pmse_ctx_P pmse_ctx, const char *rcpt, char **argv)
{
  struct pmult_priv_data *p = sm_pmfi_get_ctx_se (pmse_ctx);
  char *sender, *addr, *host;
  char **kv = translate_macros (pmse_ctx, PM_SMST_RCPT, 4);
  int i, n;
  struct env_arg x;
  sfsistat_T status;
  char buf1[INT_BUFSIZE_BOUND (uintmax_t)];
  char buf2[INT_BUFSIZE_BOUND (uintmax_t)];
  
  if (mu_debug_level_p (pmult_debug, MU_DEBUG_TRACE1))
    {
      int i;
      pthread_mutex_lock (&pmult_debug_mutex);
      mu_debug_log_begin ("%sRCPT TO: %s", PRIV_SEID_C (p), rcpt);
      if (argv)
	{
	  for (i = 0; argv[i]; i++)
	    mu_debug_log_cont (" %s", argv[i]);
	}
      mu_debug_log_nl ();
      pthread_mutex_unlock (&pmult_debug_mutex);
    }

  p->nrcpt++;
  if (parse_email_addr (rcpt, &sender, &addr, &host))
    {
      PMU_DEBUG (pmult_debug, MU_DEBUG_ERROR,
		("%sbad recipient address %s", PRIV_SEID_C (p), rcpt));
      p->nbadrcpts++;
      return SMTP_R_CONT; /* eh? */
    }

  /* Populate macros array */
  kv[0] = "nrcpts";
  snprintf (buf1, sizeof buf1, "%lu", p->nrcpt);
  kv[1] = buf1;
  kv[2] = "nbadrcpts";
  snprintf (buf2, sizeof buf2, "%lu", p->nbadrcpts);
  kv[3] = buf2;
  kv[4] = "rcpt_host";
  kv[5] = host;
  kv[6] = "rcpt_addr";
  kv[7] = addr;
  kv[8] = NULL;

  /* Count arguments and allocate env array */
  
  if (argv)
    {
      for (n = 0; argv[n]; n++)
	;
    }
  else
    n = 0;
  
  x.argv = calloc (n + 2, sizeof (x.argv[0]));
  if (!x.argv)
    {
      mu_error ("%spmult_rcpt: %s", PRIV_SEID_C (p), mu_strerror (ENOMEM));
      return SMTP_R_CONT;
    }
  
  x.argv[0] = (char *) rcpt;
  for (i = 0; i < n; i++)
    x.argv[i+1] = argv[i];
  x.argv[i+1] = NULL;

  x.ident = "envrcpt";
  x.srvfun = gacopyz_srv_envrcpt;
  status = pmult_runlist (p, rf_envfun, pmse_ctx, &x, kv);

  free (x.argv);
  free (sender);
  free (addr);
  free (host);
  
  return status;
}


static sfsistat_T
rf_data (pmse_ctx_P pmse_ctx, gacopyz_srv_t gsrv, void *data)
{
  struct pmult_priv_data *p = sm_pmfi_get_ctx_se (pmse_ctx);
  int rc = gacopyz_srv_data (gsrv);
  return pmult_std_reply (p, pmse_ctx, gsrv, rc, "data", "");
}

static sfsistat_T
pmult_data (pmse_ctx_P pmse_ctx)
{
  struct pmult_priv_data *p = sm_pmfi_get_ctx_se (pmse_ctx);
  char **kv = translate_macros (pmse_ctx, PM_SMST_DATA, 4);
  PMU_DEBUG (pmult_debug, MU_DEBUG_TRACE1, ("%sDATA", PRIV_SEID_C (p)));
  return pmult_runlist (p, rf_data, pmse_ctx, NULL, kv);
}


static sfsistat_T
pmult_unknown (pmse_ctx_P pmse_ctx, const char *cmd)
{
  struct pmult_priv_data *p = sm_pmfi_get_ctx_se (pmse_ctx);
  PMU_DEBUG (pmult_debug, MU_DEBUG_TRACE1,
	    ("%sUnknown command %s", PRIV_SEID_C (p), cmd));
  return SMTP_R_CONT;
}


static int
str_in_array (const char *str, void *data)
{
  char **ar = data;
  for (; *ar; ar++)
    if (strcmp (*ar, str) == 0)
      return 1;
  return 0;
}

/* The macros in this array are not cleared by Milter abort requests */
static char *immutable_macros[] = {
  "r",
  "s",
  "client_addr",
  "client_name",
  "client_port",
  "client_ptr",
  NULL
};

static sfsistat_T
rf_abort (pmse_ctx_P pmse_ctx, gacopyz_srv_t gsrv, void *data)
{
  gacopyz_srv_clear_macros_pred (gsrv, str_in_array, immutable_macros);
  gacopyz_srv_abort (gsrv);
  return SMTP_R_CONT;
}

static sm_ret_T
pmult_abort (pmse_ctx_P pmse_ctx)
{
  struct pmult_priv_data *p = sm_pmfi_get_ctx_se (pmse_ctx);
  if (p)
    {
      PMU_DEBUG (pmult_debug, MU_DEBUG_TRACE1, ("%sABORT", PRIV_SEID_C (p)));
      pmult_runlist (p, rf_abort, pmse_ctx, NULL, NULL);
      pmult_clear (p);
    }
  return SM_SUCCESS;
}


struct body_chunk
{
  const char *ident;
  int (*srvfun) (gacopyz_srv_t, unsigned char *, size_t);
  size_t size;
  unsigned char *buf;
};

static sfsistat_T
rf_body (pmse_ctx_P pmse_ctx, gacopyz_srv_t gsrv, void *data)
{
  struct pmult_priv_data *p = sm_pmfi_get_ctx_se (pmse_ctx);
  struct body_chunk *bc = data;
  int rc = bc->srvfun (gsrv, bc->buf, bc->size);
  sfsistat_T status = pmult_std_reply (p, pmse_ctx, gsrv, rc, bc->ident, NULL);
  /* Meta1 won't call EOM if _msg handler returns SMT_R_CONT. Why?
     I don't know neither care. */
  return status == SMTP_R_CONT ? SMTP_R_OK : status;
}

static int
flush_buf (struct pmult_priv_data *p, unsigned char *buf, size_t start,
	   size_t end)
{
  if (end > start)
    {
      int rc = mu_stream_write (p->hdrstream, (char*) (buf + start),
				end - start, NULL);
      if (rc)
	{
	  mu_error ("%smu_stream_sequential_write: %s",
		    PRIV_SEID_C (p), mu_strerror (rc));
	  return 1; 
	}
    }
  return 0;
}

static int
collect_headers (struct pmult_priv_data *p, unsigned char *buf, size_t len,
		 size_t *poff)
{
  size_t start = 0, off = 0;

  if (p->state == pmult_msg_state_initial)
    {
      mu_memory_stream_create (&p->hdrstream, MU_STREAM_RDWR);
      p->state = pmult_msg_state_headers;
    }

  while (off < len
	 && p->state != pmult_msg_state_body
	 && p->state != pmult_msg_state_eom) 
    {
      switch (p->state)
	{
	case pmult_msg_state_headers:
	  for (; off < len; off++)
	    if (buf[off] == '\r')
	      {
		if (flush_buf (p, buf, start, off))
		  return 1;
		off++;
		start = off;
		p->state = pmult_msg_state_cr1;
		break;
	      }
	  break;

	case pmult_msg_state_cr1:
	  switch (buf[off++])
	    {
	    case '\n':
	      p->state = pmult_msg_state_crlf1;
	      break;

	    default:
	      p->state = pmult_msg_state_headers;
	      break;
	    }
	  break;

	case pmult_msg_state_crlf1:
	  switch (buf[off++]) 
	    {
	    case '.':
	      p->state = pmult_msg_state_dot;
	      break;

	    case '\r':
	      if (flush_buf (p, buf, start, off))
		return 1;
	      start = off + 1;
	      p->state = pmult_msg_state_cr2;
	      break;

	    default:
	      p->state = pmult_msg_state_headers;
	    }
	  break;

	case pmult_msg_state_cr2:
	  if (buf[off++] == '\n')
	    p->state = pmult_msg_state_body;
	  else
	    p->state = pmult_msg_state_crlf1;
	  break;

	case pmult_msg_state_dot:
	  if (buf[off] == '\r')
	    {
	      if (flush_buf (p, buf, start, off))
		return 1;
	      off++;
	      start = off;
	      p->state = pmult_msg_state_dot_cr;
	    }
	  else
	    p->state = pmult_msg_state_headers;
	  break;

	case pmult_msg_state_dot_cr:
	  if (buf[off] == '\n')
	    {
	      p->state = pmult_msg_state_eom;
	      off++;
	    }
	  else
	    p->state = pmult_msg_state_headers;
	  break;
	  
	default:
	  abort ();
	}
    }

  if (flush_buf (p, buf, start, off))
    return 1; 

  *poff = off;
  return 0;
}

struct header
{
  const char *name;
  const char *value;
};

static sfsistat_T
rf_header (pmse_ctx_P pmse_ctx, gacopyz_srv_t gsrv, void *data)
{
  struct pmult_priv_data *p = sm_pmfi_get_ctx_se (pmse_ctx);
  struct header *hp = data;
  int rc = gacopyz_srv_header (gsrv, (char*)hp->name, (char*)hp->value);
  pmult_std_reply (p, pmse_ctx, gsrv, rc, "header", NULL);
  /* FIXME: do I need to analyze its return? */
  return SMTP_R_OK;
}

static sfsistat_T
rf_eoh (pmse_ctx_P pmse_ctx, gacopyz_srv_t gsrv, void *data)
{
  struct pmult_priv_data *p = sm_pmfi_get_ctx_se (pmse_ctx);
  int rc = gacopyz_srv_eoh (gsrv);
  pmult_std_reply (p, pmse_ctx, gsrv, rc, "eoh", NULL);
  /* FIXME: do I need to analyze its return? */
  return SMTP_R_OK;
}

#define KV_ARRAY(p) (((p)->argv && (p)->argv[0]) ? (p)->argv : NULL)
 
static int
process_headers (pmse_ctx_P pmse_ctx, struct pmult_priv_data *p)
{
  mu_off_t size;
  size_t count, i;
  int rc;
  mu_transport_t tbuf[2];
  
  /* FIXME-MU: This could be largely simplified if mu_header_t had
     a _set_stream method. */
  rc = mu_stream_size (p->hdrstream, &size);
  if (rc)
    {
      mu_error (_("%scannot get the size of the header stream: %s"),
		PRIV_SEID_C (p), mu_strerror (rc));
      return 1;
    }
  mu_stream_ioctl (p->hdrstream, MU_IOCTL_TRANSPORT, MU_IOCTL_OP_GET, tbuf);
  rc = mu_header_create (&p->hdr, (char*) tbuf[0], size);
  if (rc)
    {
      mu_error (_("%scannot create header: %s"),
		PRIV_SEID_C (p), mu_strerror (rc));
      return 1;
    }

  /* FIXME: mu_header_get_iterator would be in place here. */
  mu_header_get_field_count (p->hdr, &count);
  for (i = 1; i <= count; i++)
    {
      struct header h;
      if (mu_header_sget_field_name (p->hdr, i, &h.name)
	  || mu_header_sget_field_value (p->hdr, i, &h.value))
	continue;
      pmult_runlist0 (p, rf_header, pmse_ctx, &h, KV_ARRAY (p));
    }
  pmult_runlist0 (p, rf_eoh, pmse_ctx, NULL, KV_ARRAY (p));

  return 0;
}

#define EOM_MARK "\r\n.\r\n"
#define EOM_MARK_LEN (sizeof (EOM_MARK) - 1)

static sfsistat_T
pmult_msg_handler (pmse_ctx_P pmse_ctx, unsigned char *buf, size_t len)
{
  struct pmult_priv_data *p = sm_pmfi_get_ctx_se (pmse_ctx);
  struct body_chunk bc;

  if (p)
    {
      if (mu_debug_level_p (pmult_debug, MU_DEBUG_TRACE7))
	PMU_DEBUG (pmult_debug, MU_DEBUG_TRACE1,
		  ("%sBODY %lu %.*s",
		   PRIV_SEID_C (p), (unsigned long) len, (int) len, buf));
      else
	PMU_DEBUG (pmult_debug, MU_DEBUG_TRACE1,
		  ("%sBODY %lu",
		   PRIV_SEID_C (p), (unsigned long) len));
    }
  else
    return SMTP_R_OK;

  if (p->state != pmult_msg_state_body)
    {
      size_t off;
      if (collect_headers (p, buf, len, &off))
	return SMTP_R_ACCEPT; /* Better safe than sorry. */
      if (p->state == pmult_msg_state_eom)
	process_headers (pmse_ctx, p);
      else 
	{
	  if (p->state != pmult_msg_state_body)
	    return SMTP_R_OK; /* See comment to rf_body */
	  if (process_headers (pmse_ctx, p))
	    return SMTP_R_ACCEPT;
	}
      len -= off;
      buf += off;
    }

  /* FIXME: Can the marker be split between two successive calls? */
  if (len >= EOM_MARK_LEN
      && memcmp (buf + len - EOM_MARK_LEN, EOM_MARK, EOM_MARK_LEN) == 0)
    len -= 3;
  
  bc.ident = "body";
  bc.srvfun = gacopyz_srv_body;
  bc.size = len;
  bc.buf = buf;
  return pmult_runlist0 (p, rf_body, pmse_ctx, &bc, NULL);
}

static sfsistat_T
pmult_msg (pmse_ctx_P pmse_ctx, unsigned char *buf, size_t len)
{
  sfsistat_T rc;
  struct pmult_priv_data *p = sm_pmfi_get_ctx_se (pmse_ctx);
  PRIV_LOCK (p);
  rc = pmult_msg_handler (pmse_ctx, buf, len);
  PRIV_UNLOCK (p);
  return rc;
}

static sfsistat_T
pmult_eom (pmse_ctx_P pmse_ctx)
{
  struct pmult_priv_data *p = sm_pmfi_get_ctx_se (pmse_ctx);
  char **kv = translate_macros (pmse_ctx, PM_SMST_DOT, 0); 
  struct body_chunk bc;
  sfsistat_T rc;
  
  PMU_DEBUG (pmult_debug, MU_DEBUG_TRACE1, ("%sEOM", PRIV_SEID_C (p)));
  bc.ident = "eom";
  bc.srvfun = gacopyz_srv_eom;
  bc.size = 0;
  bc.buf = (unsigned char*) "";
  rc = pmult_runlist (p, rf_body, pmse_ctx, &bc, kv);
  if (p->body_chunks)
    {
      p->state = pmult_msg_state_initial;
      p->hdrbuf = malloc (PMILTER_CHUNK_SIZE);
      if (!p->hdrbuf)
	return SMTP_R_ACCEPT; /* FIXME: or better rc? */
      return SMTP_R_RPLCMSG;
    }
  pmult_clear (p);
  return rc;
}

sfsistat_T
pmult_msg_rplc (pmse_ctx_P pmse_ctx,
		const unsigned char **pmsgchunk, size_t *pmsglen)
{
  struct pmult_priv_data *p = sm_pmfi_get_ctx_se (pmse_ctx);
  size_t rest;
  int rc;
  
  switch (p->state)
    {
    case pmult_msg_state_initial:
      mu_stream_seek (p->hdrstream, 0, SEEK_SET, NULL);
      p->state = pmult_msg_state_headers;
      /* fall through */
    case pmult_msg_state_headers:
      rc = mu_stream_read (p->hdrstream, (char*) p->hdrbuf,
			   PMILTER_CHUNK_SIZE, &rest);
      if (rc)
	{
	  mu_error (_("reading from header stream failed: %s"),
		    mu_strerror (rc));
	  return SMTP_R_TEMP;
	}
      if (rest)
	{
	  *pmsgchunk = p->hdrbuf;
	  *pmsglen = rest;
	  return SMTP_R_CONT;
	}
      free (p->hdrbuf);
      p->hdrbuf = NULL;
      rc = mu_opool_get_iterator (p->body_chunks, &p->body_itr);
      if (rc)
	{
	  mu_error (_("%s failed: %s"),
		    "mu_opool_get_iterator", mu_strerror (rc));
	  return SMTP_R_TEMP;
	}
      /* The \r\n delimiter was included in hdrstream. */
      p->state = pmult_msg_state_body;
      /* fall through */
    case pmult_msg_state_body:
      if (mu_iterator_is_done (p->body_itr))
	{
	  mu_iterator_destroy (&p->body_itr);
	  return SMTP_R_OK;
	}
      mu_iterator_current_kv (p->body_itr,
			      (const void**)pmsglen, (void **)pmsgchunk);
      mu_iterator_next (p->body_itr);
      return SMTP_R_CONT;
    default:
      abort ();
    }
}

sfsistat_T
pmult_msg_rplc_stat (pmse_ctx_P pmse_ctx, sm_ret_T status)
{
  struct pmult_priv_data *p = sm_pmfi_get_ctx_se (pmse_ctx);
  pmult_clear (p);
  return SMTP_R_OK;
}



static sm_ret_T
pmult_signal (pmg_ctx_P pmg_ctx, int sig)
{
  mu_diag_output (MU_DIAG_INFO, _("got signal %d"), sig);
  return SM_SUCCESS;
}


static pmilter_T pmilter = {
  "pmult",
  LPMILTER_VERSION,
  SM_SCAP_PM_ALL,
  0,
  0,
  0,
  pmult_negotiate,
  pmult_connect,
  pmult_helo,
  pmult_mail,
  pmult_rcpt,
  pmult_data,
  pmult_msg,
  pmult_eom,
  pmult_abort,
  pmult_close,
  pmult_unknown,
  pmult_signal,
  NULL, /* pmfi_starttls */
  NULL, /* pmfi_auth */
  pmult_msg_rplc,
  pmult_msg_rplc_stat
};

void *
main_thread (void *p)
{
  pmg_ctx_P pmg_ctx = p;
  mu_diag_output (MU_DIAG_INFO, _("%s starting"), program_version); 
  SM_ASSERT (sm_pmfi_start (pmg_ctx, &pmilter));
  mu_diag_output (MU_DIAG_INFO, _("%s terminated"), program_version); 
  exit (EX_OK);
}

int
wait_for_signal ()
{
  sigset_t set;
  int rc;
  
  sigemptyset (&set);
  sigaddset (&set, SIGHUP);
  sigaddset (&set, SIGTERM);
  sigaddset (&set, SIGSEGV);
  sigaddset (&set, SIGABRT);
  /* sigaddset (&set, SIGINT); */

  rc = pthread_sigmask (SIG_BLOCK, &set, NULL);
  if (rc)
    {
      mu_error (_("failed to set up signals: %s"),
		mu_strerror (errno));
      return EX_SOFTWARE;
    }

  sigwait (&set, &rc);
  return EX_OK;
}

static void
log_setup (const char *stream)
{
  if (logger_select (stream))
    {
      mu_error (_("unsupported logger stream: %s"), stream);
      exit (EX_USAGE);
    }
  logger_open ();
}

void
alloc_die_func ()
{
  mu_error ("not enough memory");
  abort ();
}

struct mu_cli_setup cli = {
  .optv = options,
  .cfg = pmult_cfg_param,
  .prog_doc = prog_doc,
};

int    
main (int argc, char **argv)
{
  int rc;
  size_t count;
  pmg_ctx_P pmg_ctx;
  uint32_t major, minor, patchlevel;
  pthread_t tid;
  
  mf_init_nls ();
  mu_alloc_die_hook = alloc_die_func;
  mu_set_program_name (argv[0]);
  /* Set up debugging */
  pmult_debug = mu_debug_register_category ("pmult");
  /* Set default logging */
  log_stream = stderr_closed_p() ? "syslog" : "stderr";
  log_setup (log_stream);

  pmult_cfg_init ();
  mf_getopt (&cli, &argc, &argv, capa, MF_GETOPT_DEFAULT);

  if (want_auth_macros)
    request_auth_macros ();

  if (log_to_stderr >= 0)
    log_stream = log_to_stderr ? "stderr" : "syslog";
  log_setup (log_stream);
  
  if (!portspec)
    {
      mu_error (_("URL to listen on was not specified"));
      exit (EX_CONFIG);
    }

  if (!client_list || mu_list_count (client_list, &count) || count == 0)
    {
      mu_error (_("no clients configured"));
      exit (EX_CONFIG);
    }

  SM_ASSERT (sm_pmfi_init (&pmg_ctx));
  SM_ASSERT (sm_pmfi_version (pmg_ctx, &major, &minor, &patchlevel));
  if (major != LPMILTER_VERSION_MAJOR)
    {
      mu_error (_("version mismatch: compile_time=%d, run_time=%d"),
		LPMILTER_VERSION_MAJOR, major);
      exit (EX_CONFIG);
    }

  sm_pmfi_setdbg (pmg_ctx, pmilter_debug_level);
  SM_ASSERT (sm_pmfi_setconn (pmg_ctx, portspec));
  SM_ASSERT (sm_pmfi_set_ctx_g (pmg_ctx, NULL));

  if (max_threads_soft)
    {
#if defined(HAVE_STRUCT_PMILTER_S_PMFI_THRMAX_S)
      pmilter.pmfi_thrmax_s = max_threads_soft;
#else
      mu_diag_output (MU_DIAG_WARNING, "Cannot set soft thread limit");
#endif
    }
  
  if (max_threads_hard)
    {
#if defined(HAVE_STRUCT_PMILTER_S_PMFI_THRMAX_H) 
      pmilter.pmfi_thrmax_h = max_threads_hard;
#else
      mu_diag_output (MU_DIAG_WARNING, "Cannot set hard thread limit");
#endif
    }

  if (max_pmilter_fd)
    {
#if defined(HAVE_STRUCT_PMILTER_S_PMFI_FDMAX)
      pmilter.pmfi_fdmax = max_pmilter_fd;
#else
      mu_diag_output (MU_DIAG_WARNING, "Cannot set fd limit");
#endif
    }
  
  if (pidfile)
    {
      rc = mu_daemon_create_pidfile (pidfile);
      if (rc)
	mu_error (_("cannot create PID file `%s': %s"),
		  pidfile, mu_strerror (rc));
    }

  if (no_sig_handler)
    main_thread (pmg_ctx);
  else
    {
      rc = pthread_create (&tid, NULL, main_thread, pmg_ctx);
      if (rc)
	{
	  mu_error (_("cannot create main thread: %s"),
		    mu_strerror (errno));
	  exit (EX_SOFTWARE);
	}
  
      return wait_for_signal ();
    }
}

/*
 Local Variables:
 c-file-style: "gnu"
 End:
*/
/* EOF */
