/*
**  OSSP lmtp2nntp - Mail to News Gateway
**  Copyright (c) 2002-2003 Ralf S. Engelschall <rse@engelschall.com>
**  Copyright (c) 2002-2003 The OSSP Project <http://www.ossp.org/>
**  Copyright (c) 2002-2003 Cable & Wireless Germany <http://www.cw.com/de/>
**
**  This file is part of OSSP lmtp2nntp, an LMTP speaking local
**  mailer which forwards mails as Usenet news articles via NNTP.
**  It can be found at http://www.ossp.org/pkg/tool/lmtp2nntp/.
**
**  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
**  2.0 of the License, 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 file; if not, write to the Free Software
**  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
**  USA, or contact the OSSP project <ossp@ossp.org>.
**
**  lmtp2nntp_config.c: config handling
*/

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

/* third party (included) */
#include "lmtp2nntp_argz.h"

/* third party (linked in) */
#include "l2.h"
#include "pcre.h"
#include "popt.h"
#include "str.h"
#include "var.h"
#include "val.h"

/* library version check (compile-time) */
#define STR_VERSION_HEX_REQ 0x009206
#define STR_VERSION_STR_REQ "0.9.6"
#ifdef STR_VERSION_HEX
#if STR_VERSION_HEX < STR_VERSION_HEX_REQ
#error "require a newer version of OSSP Str"
#endif
#endif

/* own headers */
#include "lmtp2nntp_global.h"
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#if defined(HAVE_DMALLOC_H) && defined(WITH_DMALLOC)
#include "dmalloc.h"
#endif
#include "lmtp2nntp_option.h"
#include "lmtp2nntp_config.h"
#include "lmtp2nntp_lmtp.h"
#include "lmtp2nntp_nntp.h"
#include "lmtp2nntp_msg.h"
#include "lmtp2nntp_common.h"
#define _LMTP2NNTP_VERSION_C_AS_HEADER_
#include "lmtp2nntp_version.c"
#undef  _LMTP2NNTP_VERSION_C_AS_HEADER_

#ifndef FALSE
#define FALSE (1 != 1)
#endif
#ifndef TRUE
#define TRUE (!FALSE)
#endif

#ifndef NUL
#define NUL '\0'
#endif

static l2_result_t
formatter_prefix(l2_context_t *_ctx, const char id, const char *param,
          char *bufptr, size_t bufsize, size_t *buflen, va_list *ap)
{
    lmtp2nntp_t *ctx = (lmtp2nntp_t *)_ctx->vp;

    if ((ctx->msg != NULL) && (ctx->msg->cpFid != NULL)) {
        sprintf(bufptr, "%s: ", ctx->msg->cpFid);
        *buflen = strlen(bufptr);
    }
    else
        *buflen = 0;
    return L2_OK;
}

static var_syntax_t syntax_regex = {
    '\\',         /* escape        */
    '$',          /* varinit       */
    '{',          /* startdelim    */
    '}',          /* enddelim      */
    NUL,          /* startindex    */
    NUL,          /* endindex      */
    NUL,          /* current_index */
    "0-9"         /* namechars     */
};

lmtp2nntp_config_rc_t config_context(lmtp2nntp_t *ctx)
{
    lmtp2nntp_config_rc_t rc = CONFIG_OK;
    ex_t ex;
    optionval_t *ov;
    sa_rc_t sa_rc;

    /* create L2 environment */
    if (l2_env_create(&ctx->l2_env) != L2_OK) {
        fprintf(stderr, "%s:Error: failed to create L2 environment\n", ctx->progname);
        CU(CONFIG_ERR_LOG);
    }
    if (l2_env_levels(ctx->l2_env, L2_LEVEL_ALL, L2_LEVEL_NONE) != L2_OK) {
        fprintf(stderr, "%s:Error: logging failed to set global logging level defaults\n", ctx->progname);
        CU(CONFIG_ERR_LOG);
    }
    if (l2_env_formatter(ctx->l2_env, 'P', formatter_prefix, &ctx->ctx) != L2_OK) {
        fprintf(stderr, "%s:Error: logging failed to register prefix formatter\n", ctx->progname);
        CU(CONFIG_ERR_LOG);
    }
    if (l2_env_formatter(ctx->l2_env, 'D', l2_util_fmt_dump, NULL) != L2_OK) {
        fprintf(stderr, "%s:Error: logging failed to register dump formatter\n", ctx->progname);
        CU(CONFIG_ERR_LOG);
    }
    if (l2_env_formatter(ctx->l2_env, 'S', l2_util_fmt_string, NULL) != L2_OK) {
        fprintf(stderr, "%s:Error: logging failed to register string formatter\n", ctx->progname);
        CU(CONFIG_ERR_LOG);
    }
    if (l2_env_formatter(ctx->l2_env, 'm', l2_util_fmt_errno, NULL) != L2_OK) {
        fprintf(stderr, "%s:Error: logging failed to register errno formatter\n", ctx->progname);
        CU(CONFIG_ERR_LOG);
    }
    if (val_get(ctx->val, "option.l2spec", &ov) != VAL_OK) {
        fprintf(stderr, "%s:Error: (internal) config did not register 'l2spec' option\n", ctx->progname);
        CU(CONFIG_ERR_LOG);
    }
    if (ov->data.s != NULL) {
        l2_channel_t *ch;

        if (l2_env_handler(ctx->l2_env, &l2_handler_var) != L2_OK) {
            fprintf(stderr, "%s:Error: logging failed to register \"%s\" handler \n", ctx->progname, l2_handler_var.name);
            CU(CONFIG_ERR_LOG);
        }
        if ((l2_channel_create(&ctx->l2, ctx->l2_env, l2_handler_var.name)) != L2_OK) {
            fprintf(stderr, "%s:Error: logging failed to create \"%s\" channel\n", ctx->progname, l2_handler_var.name);
            CU(CONFIG_ERR_LOG);
        }
        if ((l2_channel_configure(ctx->l2, "", ctx->config_varctx)) != L2_OK) {
            fprintf(stderr, "%s:Error: logging failed to configure \"%s\" channel\n", ctx->progname, l2_handler_var.name);
            CU(CONFIG_ERR_LOG);
        }
        if ((l2_spec(&ch, ctx->l2_env, "%s", ov->data.s)) != L2_OK) {
            fprintf(stderr, "%s:Error: logging failed to create stream\n", ctx->progname);
            CU(CONFIG_ERR_LOG);
        }
        if (l2_channel_link(ctx->l2, L2_LINK_CHILD, ch, NULL) != L2_OK) {
            fprintf(stderr, "%s:Error: logging failed to link child channel\n", ctx->progname);
            CU(CONFIG_ERR_LOG);
        }
        if (l2_channel_open(ctx->l2) != L2_OK) {
            fprintf(stderr, "%s:Error: logging failed to open channel stream\n", ctx->progname);
            CU(CONFIG_ERR_LOG);
        }
    }
    /* from this point on logging is up and running and fprintf(stderr, ...)
     * should not be used in the remainder of the program flow.
     */
    logbook(ctx->l2, L2_LEVEL_NOTICE, "startup, version %s", lmtp2nntp_version.v_gnu);

    /* --version FLAG */
    try {
        if (   (val_get(ctx->val, "option.version", &ov) != VAL_OK)
            || (ov->ndata <  0)
            || (ov->ndata == 1 && ov->data.f < 0)
            || (ov->ndata == 1 && ov->data.f > 1)
            || (ov->ndata >  1)
              ) throw(0,0,0);
        logbook(ctx->l2, L2_LEVEL_TRACE, "--version = %d", ov->data.f);

        if (ov->data.f == 1) {
            fprintf(stdout, "%s\n", lmtp2nntp_version.v_gnu);
            CU(CONFIG_OK_DRY);
        }
    }
    catch (ex)
        rethrow;

    /* --childsmax SINGLE */
    try {
        if (   (val_get(ctx->val, "option.childsmax", &ov) != VAL_OK)
            || (ov->ndata <  0)
            || (ov->ndata == 1 && ov->data.s == NULL)
            || (ov->ndata >  1)
              ) throw(0,0,0);
        logbook(ctx->l2, L2_LEVEL_TRACE, "--childsmax = \"%s\"", ov->data.s);

        if (ov->ndata == 1)
            if ((ctx->option_childsmax = atoi(ov->data.s)) <= 0) {
                logbook(ctx->l2, L2_LEVEL_ERROR, "option --childsmax, number (%d) out of range", ctx->option_childsmax);
                throw(0,0,0);
            }
    }
    catch (ex) {
        rethrow;
    }

    /* --daemonize FLAG */
    try {
        if (   (val_get(ctx->val, "option.daemonize", &ov) != VAL_OK)
            || (ov->ndata <  0)
            || (ov->ndata == 1 && ov->data.f < 0)
            || (ov->ndata == 1 && ov->data.f > 1)
            || (ov->ndata >  1)
              ) throw(0,0,0);
        logbook(ctx->l2, L2_LEVEL_TRACE, "--daemonize = %d", ov->data.f);

        ctx->option_daemon = ov->data.f == 1 ? TRUE : FALSE;
    }
    catch (ex)
        rethrow;

    /* --kill FLAG */
    try {
        if (   (val_get(ctx->val, "option.kill", &ov) != VAL_OK)
            || (ov->ndata <  0)
            || (ov->ndata == 1 && ov->data.f < 0)
            || (ov->ndata == 1 && ov->data.f > 1)
            || (ov->ndata >  1)
              ) throw(0,0,0);
        logbook(ctx->l2, L2_LEVEL_TRACE, "--kill = %d", ov->data.f);

        ctx->option_killflag = ov->data.f == 1 ? TRUE : FALSE;
    }
    catch (ex)
        rethrow;

    /* --pidfile SINGLE */
    try {
        if (   (val_get(ctx->val, "option.pidfile", &ov) != VAL_OK)
            || (ov->ndata <  0)
            || (ov->ndata == 1 && ov->data.s == NULL)
            || (ov->ndata >  1)
              ) throw(0,0,0);
        logbook(ctx->l2, L2_LEVEL_TRACE, "--pidfile = \"%s\"", ov->data.s);

        if (ov->ndata == 1)
            ctx->option_pidfile = strdupex(ov->data.s);
    }
    catch (ex)
        rethrow;

    /* --acl MULTI */
    try {
        char *cp;
        int i;
        int somepass;

        if (   (val_get(ctx->val, "option.acl", &ov) != VAL_OK)
            || (ov->ndata <  0)
            || (ov->ndata >= 1 && ov->data.m == NULL)
              ) throw(0,0,0);
        logbook(ctx->l2, L2_LEVEL_DEBUG, "ov->ndata = %d", ov->ndata);
        for (i = 0; i < ov->ndata; i++)
            logbook(ctx->l2, L2_LEVEL_TRACE, "--acl[%d] = \"%s\"", i, (ov->data.m)[i]);

        /* check if only blocking ACLs exist */
        somepass = FALSE;
        if (ov->ndata >= 1) {
            for (i = 0; i < ov->ndata; i++) {
                cp = (ov->data.m)[i];
                if (cp[0] != '!') {
                    somepass = TRUE;
                    break;
                }
            }
        }

        /* if only blocking ACLs exist, reserve space for two additional pass-through wildcards */
        ctx->pacl = (struct acl *)mallocex((ov->ndata + (somepass ? 0 : 2 )) * sizeof(struct acl));

        if (ov->ndata >= 1) {
            for (i = 0; i < ov->ndata; i++) {
                cp = (ov->data.m)[i];
                logbook(ctx->l2, L2_LEVEL_DEBUG, "cp = (data.m)[%d] = \"%s\"", i, cp);
                if (cp[0] == '!') {
                    ctx->pacl[i].acl = strdup(cp + 1);
                    ctx->pacl[i].not = TRUE;
                }
                else {
                    ctx->pacl[i].acl = strdup(cp);
                    ctx->pacl[i].not = FALSE;
                }
                logbook(ctx->l2, L2_LEVEL_DEBUG, "ctx->pacl[%d].not = %s", i, ctx->pacl[i].not == TRUE ? "TRUE" : "FALSE");
                logbook(ctx->l2, L2_LEVEL_DEBUG, "ctx->pacl[%d].acl = %s", i, ctx->pacl[i].acl);
                if ((cp = strrchr(ctx->pacl[i].acl, '/')) != NULL)
                    *cp++ = NUL;
                else
                    cp = "-1";
                ctx->pacl[i].prefixlen = atoi(cp);
                logbook(ctx->l2, L2_LEVEL_DEBUG, "ctx->pacl[%d].prefixlen = %d", i, ctx->pacl[i].prefixlen);
                if ((sa_rc = sa_addr_create(&(ctx->pacl[i].saa))) != SA_OK) {
                    if (sa_rc == SA_ERR_SYS)
                        logbook(ctx->l2, L2_LEVEL_ERROR, "option --acl, create address (internal) failed with \"%s\" (%d) %s", sa_error(sa_rc), errno, strerror(errno));
                    else
                        logbook(ctx->l2, L2_LEVEL_ERROR, "option --acl, create address (internal) failed with \"%s\"", sa_error(sa_rc));
                    throw(0,0,0);
                    }
                if ((sa_rc = sa_addr_u2a(ctx->pacl[i].saa, "inet://%s:0", ctx->pacl[i].acl)) != SA_OK) {
                    if (sa_rc == SA_ERR_SYS)
                        logbook(ctx->l2, L2_LEVEL_ERROR, "option --acl, parsing address (%s) failed with \"%s\" (%d) %", ctx->pacl[i].acl, sa_error(sa_rc), errno, strerror(errno));
                    else
                        logbook(ctx->l2, L2_LEVEL_ERROR, "option --acl, parsing address (%s) failed with \"%s\"", ctx->pacl[i].acl, sa_error(sa_rc));
                    throw(0,0,0);
                    }
            }
            ctx->nacl = i;
        }

        /* if only blocking ACLs exist, append a wildcard pass-through for IPv4 */
        if (!somepass) {
            i = ctx->nacl;
            ctx->pacl[i].acl = "0.0.0.0";
            ctx->pacl[i].not = FALSE;
            ctx->pacl[i].prefixlen = 0;
            if ((sa_rc = sa_addr_create(&ctx->pacl[i].saa)) != SA_OK) {
                if (sa_rc == SA_ERR_SYS)
                    logbook(ctx->l2, L2_LEVEL_ERROR, "option --acl, create IPv4 pass-through address (internal) failed with \"%s\" (%d) %s", sa_error(sa_rc), errno, strerror(errno));
                else
                    logbook(ctx->l2, L2_LEVEL_ERROR, "option --acl, create IPv4 pass-through address (internal) failed with \"%s\"", sa_error(sa_rc));
                throw(0,0,0);
            }
            if ((sa_rc = sa_addr_u2a(ctx->pacl[i].saa, "inet://%s:0", ctx->pacl[i].acl)) != SA_OK) {
                if (sa_rc == SA_ERR_SYS)
                    logbook(ctx->l2, L2_LEVEL_ERROR, "option --acl, parsing IPv4 pass-through address (%s) failed with \"%s\" (%d) %s", ctx->pacl[i].acl, sa_error(sa_rc), errno, strerror(errno));
                else
                    logbook(ctx->l2, L2_LEVEL_ERROR, "option --acl, parsing IPv4 pass-through address (%s) failed with \"%s\"", ctx->pacl[i].acl, sa_error(sa_rc));
                throw(0,0,0);
            }
            i++;
            ctx->nacl = i;
        }

        /* if only blocking ACLs exist, append a wildcard pass-through for IPv6 */
        if (!somepass) {
            i = ctx->nacl;
            ctx->pacl[i].acl = "[::]";
            ctx->pacl[i].not = FALSE;
            ctx->pacl[i].prefixlen = 0;
            if ((sa_rc = sa_addr_create(&ctx->pacl[i].saa)) != SA_OK) {
                if (sa_rc == SA_ERR_SYS)
                    logbook(ctx->l2, L2_LEVEL_ERROR, "option --acl, create IPv6 pass-through address (internal) failed with \"%s\" (%d) %s", sa_error(sa_rc), errno, strerror(errno));
                else
                    logbook(ctx->l2, L2_LEVEL_ERROR, "option --acl, create IPv6 pass-through address (internal) failed with \"%s\"", sa_error(sa_rc));
                throw(0,0,0);
            }
            if ((sa_rc = sa_addr_u2a(ctx->pacl[i].saa, "inet://%s:0", ctx->pacl[i].acl)) != SA_OK) {
                if (sa_rc == SA_ERR_SYS)
                    logbook(ctx->l2, L2_LEVEL_ERROR, "option --acl, parsing IPv6 pass-through address (%s) failed with \"%s\" (%d) %s", ctx->pacl[i].acl, sa_error(sa_rc), errno, strerror(errno));
                else
                    logbook(ctx->l2, L2_LEVEL_ERROR, "option --acl, parsing IPv6 pass-through address (%s) failed with \"%s\"", ctx->pacl[i].acl, sa_error(sa_rc));
                throw(0,0,0);
            }
            i++;
            ctx->nacl = i;
        }
    }
    catch (ex)
        rethrow;

    /* --bind SINGLE */
    try {
        char *cp;

        if (   (val_get(ctx->val, "option.bind", &ov) != VAL_OK)
            || (ov->ndata <  0)
            || (ov->ndata == 1 && ov->data.s == NULL)
            || (ov->ndata >  1)
              ) throw(0,0,0);
        logbook(ctx->l2, L2_LEVEL_TRACE, "--bind = \"%s\"", ov->data.s);

        if (ov->ndata == 1) {
            /* dash means stdio */
            if (strcmp(ov->data.s, "-") != 0) {
                if ((sa_rc = sa_create(&ctx->saServerbind)) != SA_OK) {
                    if (sa_rc == SA_ERR_SYS)
                        logbook(ctx->l2, L2_LEVEL_ERROR, "option --bind, creating TCP socket (internal) failed with \"%s\" (%d) %s", sa_error(sa_rc), errno, strerror(errno));
                    else
                        logbook(ctx->l2, L2_LEVEL_ERROR, "option --bind, creating TCP socket (internal) failed with \"%s\"", sa_error(sa_rc));
                    throw(0,0,0);
                }
                if ((sa_rc = sa_addr_create(&ctx->saaServerbind)) != SA_OK) {
                    if (sa_rc == SA_ERR_SYS)
                        logbook(ctx->l2, L2_LEVEL_ERROR, "option --bind, create address (internal) failed with \"%s\", (%d) %s", sa_error(sa_rc), errno, strerror(errno));
                    else
                        logbook(ctx->l2, L2_LEVEL_ERROR, "option --bind, create address (internal) failed with \"%s\"", sa_error(sa_rc));
                    throw(0,0,0);
                }
            /* slash means UNIX socket */
                if (ov->data.s[0] == '/') {
                    char *cpPath;
                    char *cpPerm;
                    int nPerm;
                    int n;
                    int i;

                    cpPath = strdup(ov->data.s);
                    cpPerm = NULL;
                    nPerm  = -1;
                    if ((cpPerm = strrchr(cpPath, ':')) != NULL) {
                        *cpPerm++ = '\0';
                        nPerm = 0;
                        for (i = 0; i < 4 && cpPerm[i] != '\0'; i++) {
                            if (!isdigit((int)cpPerm[i])) {
                                nPerm = -1;
                                break;
                            }
                            n = cpPerm[i] - '0';
                            if (n > 7) {
                                nPerm = -1;
                                break;
                            }
                            nPerm = ((nPerm << 3) | n);
                        }
                        if (nPerm == -1 || cpPerm[i] != '\0') {
                            logbook(ctx->l2, L2_LEVEL_ERROR, "option --bind, invalid permissions \"%s\"", cpPerm);
                            throw(0,0,0);
                        }
                    }
                    if ((sa_rc = sa_addr_u2a(ctx->saaServerbind, "unix:%s", cpPath)) != SA_OK) {
                        if (sa_rc == SA_ERR_SYS)
                            logbook(ctx->l2, L2_LEVEL_ERROR, "option --bind, parsing alternate IO guessing UNIX socket (%s) failed with \"%s\" (%d) %s", cpPath, sa_error(sa_rc), errno, strerror(errno));
                        else
                            logbook(ctx->l2, L2_LEVEL_ERROR, "option --bind, parsing alternate IO guessing UNIX socket (%s) failed with \"%s\"", cpPath, sa_error(sa_rc));
                        throw(0,0,0);
                    }
                    if ((sa_rc = sa_bind(ctx->saServerbind, ctx->saaServerbind)) != SA_OK) {
                        if (sa_rc == SA_ERR_SYS)
                            logbook(ctx->l2, L2_LEVEL_ERROR, "option --bind, bind (%s) failed with \"%s\" (%d) %s", cpPath, sa_error(sa_rc), errno, strerror(errno));
                        else
                            logbook(ctx->l2, L2_LEVEL_ERROR, "option --bind, bind (%s) failed with \"%s\"", cpPath, sa_error(sa_rc));
                        throw(0,0,0);
                    }
                    if (nPerm != -1) {
                        if (chmod(cpPath, nPerm) == -1) {
                            logbook(ctx->l2, L2_LEVEL_ERROR, "option --bind, chmod (%s, 0%o) failed with \"%s\"", cpPath, nPerm, strerror(errno));
                            throw(0,0,0);
                        }
                    }
                    if (getuid() == 0 && getuid() != ctx->option_uid) {
                        if (chown(cpPath, ctx->option_uid, -1) == -1) {
                            logbook(ctx->l2, L2_LEVEL_ERROR, "option --bind, chown (%s, %d) failed with \"%s\"", cpPath, ctx->option_uid, strerror(errno));
                            throw(0,0,0);
                        }
                    }
                    free(cpPath);
                }
            /* otherwise assume INET socket */
                else {
                    cp = ov->data.s;
                    if (strrchr(cp, ':') == NULL)
                        cp = str_concat(cp, ":24", NULL); /* http://www.iana.org/assignments/port-numbers (and names) */
                    else
                        cp = str_concat(cp, NULL); /* prepare for free() */
                    logbook(ctx->l2, L2_LEVEL_DEBUG, "data.s = \"%s\", cp = \"%s\"", ov->data.s, cp);
                    if ((sa_rc = sa_addr_u2a(ctx->saaServerbind, "inet://%s", cp)) != SA_OK) {
                        if (sa_rc == SA_ERR_SYS)
                            logbook(ctx->l2, L2_LEVEL_ERROR, "option --bind, parsing alternate IO guessing INET socket (%s) failed with \"%s\" (%d) %s", ov->data.s, sa_error(sa_rc), errno, strerror(errno));
                        else
                            logbook(ctx->l2, L2_LEVEL_ERROR, "option --bind, parsing alternate IO guessing INET socket (%s) failed with \"%s\"", ov->data.s, sa_error(sa_rc));
                        throw(0,0,0);
                    }
                    if ((sa_rc = sa_bind(ctx->saServerbind, ctx->saaServerbind)) != SA_OK) {
                        if (sa_rc == SA_ERR_SYS)
                            logbook(ctx->l2, L2_LEVEL_ERROR, "option --bind, bind (%s) failed with \"%s\" (%d) %s", ov->data.s, sa_error(sa_rc), errno, strerror(errno));
                        else
                            logbook(ctx->l2, L2_LEVEL_ERROR, "option --bind, bind (%s) failed with \"%s\"", ov->data.s, sa_error(sa_rc));
                        throw(0,0,0);
                    }
                    free(cp);
                }
            /* for either sockets */
                if ((sa_rc = sa_listen(ctx->saServerbind, -1)) != SA_OK) {
                    if (sa_rc == SA_ERR_SYS)
                        logbook(ctx->l2, L2_LEVEL_ERROR, "option --bind, listen (%s) failed with \"%s\" (%d) %s", ov->data.s, sa_error(sa_rc), errno, strerror(errno));
                    else
                        logbook(ctx->l2, L2_LEVEL_ERROR, "option --bind, listen (%s) failed with \"%s\"", ov->data.s, sa_error(sa_rc));
                    throw(0,0,0);
                }
            }
        }
    }
    catch (ex)
        rethrow;

    /* --client SINGLE */
    try {
        if (   (val_get(ctx->val, "option.client", &ov) != VAL_OK)
            || (ov->ndata <  0)
            || (ov->ndata == 1 && ov->data.s == NULL)
            || (ov->ndata >  1)
              ) throw(0,0,0);
        logbook(ctx->l2, L2_LEVEL_TRACE, "--client = \"%s\"", ov->data.s);

        if (ov->ndata == 1) {
            if ((sa_rc = sa_addr_create(&ctx->saaClientbind)) != SA_OK) {
                if (sa_rc == SA_ERR_SYS)
                    logbook(ctx->l2, L2_LEVEL_ERROR, "option --client, create address (internal) failed with \"%s\" (%d) %s", sa_error(sa_rc), errno, strerror(errno));
                else
                    logbook(ctx->l2, L2_LEVEL_ERROR, "option --client, create address (internal) failed with \"%s\"", sa_error(sa_rc));
                throw(0,0,0);
            }
            if ((sa_rc = sa_addr_u2a(ctx->saaClientbind,
                                  (strchr(ov->data.s, ':') == NULL) ?  "inet://%s:0" : "inet://%s",
                                  ov->data.s)) != SA_OK) {
                if (sa_rc == SA_ERR_SYS)
                    logbook(ctx->l2, L2_LEVEL_ERROR, "option --client, parsing alternate IO guessing INET socket (%s) failed with \"%s\" (%d) %s", ov->data.s, sa_error(sa_rc), errno, strerror(errno));
                else
                    logbook(ctx->l2, L2_LEVEL_ERROR, "option --client, parsing alternate IO guessing INET socket (%s) failed with \"%s\"", ov->data.s, sa_error(sa_rc));
                throw(0,0,0);
            }
        }
    }
    catch (ex)
        rethrow;

    /* --destination MULTI */
    try {
        char *cp;
        int i;

        if (   (val_get(ctx->val, "option.destination", &ov) != VAL_OK)
            || (ov->ndata <  0)
            || (ov->ndata >= 1 && ov->data.m == NULL)
              ) throw(0,0,0);
        logbook(ctx->l2, L2_LEVEL_DEBUG, "ov->ndata = %d", ov->ndata);
        for (i = 0; i < ov->ndata; i++)
            logbook(ctx->l2, L2_LEVEL_TRACE, "--destination[%d] = \"%s\"", i, (ov->data.m)[i]);

        if (ov->ndata >= 1) {
            if ((ctx->pns = (struct ns *)malloc(ov->ndata * sizeof(struct ns))) == NULL) throw(0,0,0);
            for (i = 0; i < ov->ndata;) {
                cp = (ov->data.m)[i];
                if (strrchr(cp, ':') == NULL)
                    cp = str_concat(cp, ":nntp", NULL); /* http://www.iana.org/assignments/port-numbers (and names) */
                else
                    cp = str_concat(cp, NULL); /* prepare for free() */
                logbook(ctx->l2, L2_LEVEL_DEBUG, "(data.m)[%d] = \"%s\", cp = \"%s\"", i, (ov->data.m)[i], cp);
                if ((sa_rc = sa_addr_create(&ctx->pns[i].saa)) != SA_OK) {
                    if (sa_rc == SA_ERR_SYS)
                        logbook(ctx->l2, L2_LEVEL_ERROR, "option --destination, create address (internal) failed with \"%s\" (%d) %s", sa_error(sa_rc), errno, strerror(errno));
                    else
                        logbook(ctx->l2, L2_LEVEL_ERROR, "option --destination, create address (internal) failed with \"%s\"", sa_error(sa_rc));
                    throw(0,0,0);
                }
                if ((sa_rc = sa_addr_u2a(ctx->pns[i].saa, "inet://%s", cp)) != SA_OK) {
                    if (sa_rc == SA_ERR_SYS)
                        logbook(ctx->l2, L2_LEVEL_ERROR, "option --destination, parsing host address (%s) failed with \"%s\", (%d) %s", cp, sa_error(sa_rc), errno, strerror(errno));
                    else
                        logbook(ctx->l2, L2_LEVEL_ERROR, "option --destination, parsing host address (%s) failed with \"%s\"", cp, sa_error(sa_rc));
                    throw(0,0,0);
                }
                if ((sa_rc = sa_create(&ctx->pns[i].sa)) != SA_OK) {
                    if (sa_rc == SA_ERR_SYS)
                        logbook(ctx->l2, L2_LEVEL_ERROR, "option --destination, creating TCP socket (%s) failed with \"%s\" (%d) %s", cp, sa_error(sa_rc), errno, strerror(errno));
                    else
                        logbook(ctx->l2, L2_LEVEL_ERROR, "option --destination, creating TCP socket (%s) failed with \"%s\"", cp, sa_error(sa_rc));
                    throw(0,0,0);
                }
                ctx->pns[i].nntp = NULL;
                ctx->pns[i].rc = NNTP_OK;
                ctx->pns[i].l2 = ctx->l2;
                free(cp);
                ctx->nns = ++i;
            }
        }
    }
    catch (ex)
        rethrow;

    /* --groupmode SINGLE */
    try {
        if (   (val_get(ctx->val, "option.groupmode", &ov) != VAL_OK)
            || (ov->ndata <  0)
            || (ov->ndata == 1 && ov->data.s == NULL)
            || (ov->ndata >  1)
              ) throw(0,0,0);
        logbook(ctx->l2, L2_LEVEL_TRACE, "--groupmode = \"%s\"", ov->data.s);

        if (ov->ndata == 1) {
            if      (strcasecmp(ov->data.s, "arg") == 0)
                ctx->option_groupmode = GROUPMODE_ARG;
            else if (strcasecmp(ov->data.s, "envelope") == 0)
                ctx->option_groupmode = GROUPMODE_ENVELOPE;
            else if (strcasecmp(ov->data.s, "header") == 0)
                ctx->option_groupmode = GROUPMODE_HEADER;
            else {
                logbook(ctx->l2, L2_LEVEL_ERROR, "option --groupmode, invalid mode (%s)", ov->data.s);
                throw(0,0,0);
            }
        }
    }
    catch (ex)
        rethrow;

    /* --headerrule MULTI */
    {
    volatile headerrule_t *hrNew = NULL; /* declare and initialize variables which might have resources allocated that need to be cleaned up when an exception is caught */
        try {
            char *cp, *cpP;
            int n;
            int i;
            headerrule_t *hrI;
            headerrule_t *hrP;
            const char *cpError;
            int iError;

            if (   (val_get(ctx->val, "option.headerrule", &ov) != VAL_OK)
                || (ov->ndata <  0)
                || (ov->ndata >= 1 && ov->data.m == NULL)
                  ) throw(0,0,0);
            logbook(ctx->l2, L2_LEVEL_DEBUG, "ov->ndata = %d", ov->ndata);
            for (i = 0; i < ov->ndata; i++)
                logbook(ctx->l2, L2_LEVEL_TRACE, "--headerule[%d] = \"%s\"", i, (ov->data.m)[i]);

            if (ov->ndata >= 1) {
                for (i = 0; i < ov->ndata; i++) {
                    cp = (ov->data.m)[i];
                    logbook(ctx->l2, L2_LEVEL_DEBUG, "cp = (data.m)[%d] = \"%s\"", i, cp);

                    hrNew = (headerrule_t *)mallocex(sizeof(headerrule_t));
                    hrNew->next      = NULL;
                    hrNew->pri       = 500; /* default priority */
                    hrNew->regex     = NULL;
                    hrNew->name      = NULL;
                    hrNew->val       = NULL;
                    hrNew->pcreRegex = NULL;
                    hrNew->pcreExtra = NULL;

                    /* priority */
                    cpP = cp;
                    if ((cp = strchr(cp, ':')) == NULL) {
                        logbook(ctx->l2, L2_LEVEL_ERROR, "option --headerrule, priority (%s) terminating colon missing", (ov->data.m)[i]);
                        throw(0,0,0);
                    }
                    cp++;
                    n = cp - cpP;
                    if (n >= 2) /* mandatory colon and at least one more char */
                        hrNew->pri = atoi(cpP);
                    /* regex */
                    cpP = cp;
                    if ((cp = strchr(cp, ':')) == NULL) {
                        logbook(ctx->l2, L2_LEVEL_ERROR, "option --headerrule, regex (%s) terminating colon missing", (ov->data.m)[i]);
                        throw(0,0,0);
                    }
                    cp++;
                    n = cp - cpP;
                    if (n >= 2) /* mandatory colon and at least one more char */
                        hrNew->regex = str_dupex(cpP, n);
                    /* header */
                    cpP = cp;
                    if ((cp = strchr(cp, ':')) == NULL) {
                        logbook(ctx->l2, L2_LEVEL_ERROR, "option --headerrule, header (%s) terminating colon missing", (ov->data.m)[i]);
                        throw(0,0,0);
                    }
                    cp++;
                    n = cp - cpP;
                    if (n == 0) {
                        logbook(ctx->l2, L2_LEVEL_ERROR, "option --headerrule, header (%s) missing", (ov->data.m)[i]);
                        throw(0,0,0);
                    }
                    hrNew->name   = str_dupex(cpP, n);
                    /* value */
                    cpP = cp;
                    n = strlen(cpP);
                    if (n >= 1)
                        hrNew->val = str_dupex(cpP, n);

                    if (hrNew->regex != NULL) {
                        /* compile regular expression into finite state machine and optimize */
                        if ((hrNew->pcreRegex = pcre_compile(hrNew->regex, PCRE_CASELESS, &cpError, &iError, NULL)) == NULL) {
                            logbook(ctx->l2, L2_LEVEL_ERROR, "option --headerrule, regex (%s) failed at pos %d with %s", hrNew->regex, iError, cpError);
                            throw(0,0,0);
                        }
                        hrNew->pcreExtra = pcre_study(hrNew->pcreRegex, 0, &cpError);
                        if (cpError != NULL) {
                            logbook(ctx->l2, L2_LEVEL_ERROR, "option --headerrule, regex optimization failed with %s", cpError);
                            throw(0,0,0);
                        }
                    }

                    if (ctx->option_firstheaderrule == NULL)
                        ctx->option_firstheaderrule = (headerrule_t *)hrNew; /* first */
                    else {
                        for (hrP = NULL, hrI = ctx->option_firstheaderrule;
                             hrI != NULL && hrI->pri <= hrNew->pri;
                             hrP = hrI, hrI = hrI->next);
                        if (hrI != NULL)
                            hrNew->next = hrI; /* insert */
                        if (hrP != NULL)
                            hrP->next = (headerrule_t *)hrNew; /* append */
                        else
                            ctx->option_firstheaderrule = (headerrule_t *)hrNew; /* new first */
                    }
                    hrNew = NULL; /* release cleanup responsibility */
                }
                /* establish variable expansion context */
                {
                    var_rc_t rc2;

                    if ((rc2 = var_create(&ctx->config_varregex)) != VAR_OK) {
                        logbook(ctx->l2, L2_LEVEL_ERROR, "option --headerrule, create regex context failed with %s (%d)", var_strerror(ctx->config_varregex, rc2, &cp) == VAR_OK ? cp : "Unknown Error", rc2);
                        throw(0,0,0);
                    }
                    if ((rc2 = var_config(ctx->config_varregex, VAR_CONFIG_SYNTAX, &syntax_regex)) != VAR_OK) {
                        logbook(ctx->l2, L2_LEVEL_ERROR, "option --headerrule, config regex context failed with %s (%d)", var_strerror(ctx->config_varregex, rc2, &cp) == VAR_OK ? cp : "Unknown Error", rc2);
                        throw(0,0,0);
                    }
                }
            }
        }
        cleanup {
            if (hrNew != NULL) {
                if (hrNew->pcreExtra != NULL)
                    free(hrNew->pcreExtra);
                if (hrNew->pcreRegex != NULL)
                    free(hrNew->pcreRegex);
                if (hrNew->val != NULL)
                    freeex(hrNew->val);
                if (hrNew->name != NULL)
                    freeex(hrNew->name);
                if (hrNew->regex != NULL)
                    freeex(hrNew->regex);
                freeex((headerrule_t *)hrNew);
            }
        }
        catch (ex)
            rethrow;
    }

    /* --mailfrom SINGLE */
    try {
        char *cp;

        if (   (val_get(ctx->val, "option.mailfrom", &ov) != VAL_OK)
            || (ov->ndata <  0)
            || (ov->ndata == 1 && ov->data.s == NULL)
            || (ov->ndata >  1)
              ) throw(0,0,0);
        logbook(ctx->l2, L2_LEVEL_TRACE, "--mailfrom = \"%s\"", ov->data.s);

        if (ov->ndata == 1) {
            ctx->option_mailfrom = strdup(ov->data.s);
            /* protect ourselfs from the substitution of backreferences.
             * Missing varargs would cause segfaults.  Rewrite capturing
             * brackets to clustering syntax. Use poor man's s///g
             * simulator as current str library doesn't support global
             * substitution */
            while (str_parse(ctx->option_mailfrom, "s/(.*?)\\((?!\\?:)(.*)/$1(?:$2/", &cp) > 0) {
                free(ctx->option_mailfrom);
                ctx->option_mailfrom = cp;
            }
            if (str_parse("<>", ctx->option_mailfrom) == -1) {
                logbook(ctx->l2, L2_LEVEL_ERROR, "option --mailfrom, illegal regex (%s)", ctx->option_mailfrom);
                throw(0,0,0);
            }
        }
    }
    catch (ex)
        rethrow;

    /* --nodename SINGLE */
    try {
        if (   (val_get(ctx->val, "option.nodename", &ov) != VAL_OK)
            || (ov->ndata <  0)
            || (ov->ndata == 1 && ov->data.s == NULL)
            || (ov->ndata >  1)
              ) throw(0,0,0);
        logbook(ctx->l2, L2_LEVEL_TRACE, "--nodename = \"%s\"", ov->data.s);

        if (ov->ndata == 1) {
            ctx->option_nodename = strdupex(ov->data.s);
        }
        else {
            struct utsname name;

            if (uname(&name) == -1) {
                logbook(ctx->l2, L2_LEVEL_ERROR, "option --nodename, uname() failed %m");
                throw(0,0,0);
            }
            ctx->option_nodename = strdupex(name.nodename);
        }
    }
    catch (ex)
        rethrow;

    /* --operationmode SINGLE */
    try {
        char *cp;

        if (   (val_get(ctx->val, "option.operationmode", &ov) != VAL_OK)
            || (ov->ndata <  0)
            || (ov->ndata == 1 && ov->data.s == NULL)
            || (ov->ndata >  1)
              ) throw(0,0,0);
        logbook(ctx->l2, L2_LEVEL_TRACE, "--operationmode = \"%s\"", ov->data.s);

        if (ov->ndata == 1) {
            cp = strdup(ov->data.s);
            if      (strcasecmp(cp, "post") == 0)
                ctx->option_operationmode = OPERATIONMODE_POST;
            else if (strcasecmp(cp, "feed") == 0)
                ctx->option_operationmode = OPERATIONMODE_FEED;
            else {
                ctx->option_operationmode = OPERATIONMODE_FAKE;
                if (strlen(cp) != 9) {
                    logbook(ctx->l2, L2_LEVEL_ERROR, "option --operationmode, invalid length (%s)", cp);
                    throw(0,0,0);
                }
                if (cp[3] != '/') {
                    logbook(ctx->l2, L2_LEVEL_ERROR, "option --operationmode, missing slash (%s)", cp);
                    throw(0,0,0);
                }
                cp[3] = NUL;
                ctx->option_operationmodefakestatus = &cp[0];
                ctx->option_operationmodefakedsn    = &cp[4];
                if (   strlen(ctx->option_operationmodefakestatus) != 3
                    || !isdigit((int)ctx->option_operationmodefakestatus[0])
                    || !isdigit((int)ctx->option_operationmodefakestatus[1])
                    || !isdigit((int)ctx->option_operationmodefakestatus[2])
                      ) {
                    logbook(ctx->l2, L2_LEVEL_ERROR, "option --operationmode, invalid status code (%s)", cp);
                    throw(0,0,0);
                }
                if (   (strlen(ctx->option_operationmodefakedsn) != 5)
                    || !isdigit((int)ctx->option_operationmodefakedsn[0])
                    || (ctx->option_operationmodefakedsn[1] != '.')
                    || !isdigit((int)ctx->option_operationmodefakedsn[2])
                    || (ctx->option_operationmodefakedsn[3] != '.')
                    || !isdigit((int)ctx->option_operationmodefakedsn[4])
                    || (ctx->option_operationmodefakedsn[0] != ctx->option_operationmodefakestatus[0])
                      ) {
                    logbook(ctx->l2, L2_LEVEL_ERROR, "option --operationmode, invalid dsn code (%s)", cp);
                    throw(0,0,0);
                }
            }
        }
        else {
            logbook(ctx->l2, L2_LEVEL_ERROR, "option --operationmode, is mandatory but neither given nor preset (internal)");
            throw(0,0,0);
        }
    }
    catch (ex)
        rethrow;

    /* --restrictheader SINGLE */
    try {
        char *cp;

        if (   (val_get(ctx->val, "option.restrictheader", &ov) != VAL_OK)
            || (ov->ndata <  0)
            || (ov->ndata == 1 && ov->data.s == NULL)
            || (ov->ndata >  1)
              ) throw(0,0,0);
        logbook(ctx->l2, L2_LEVEL_TRACE, "--restrictheader = \"%s\"", ov->data.s);

        if (ov->ndata == 1) {
            ctx->option_restrictheader = strdup(ov->data.s);
            /* protect ourselfs from the substitution of backreferences.
             * Missing varargs would cause segfaults.  Rewrite capturing
             * brackets to clustering syntax. Use poor man's s///g
             * simulator as current str library doesn't support global
             * substitution */
            while (str_parse(ctx->option_restrictheader, "s/(.*?)\\((?!\\?:)(.*)/$1(?:$2/", &cp) > 0) {
                free(ctx->option_restrictheader);
                ctx->option_restrictheader = cp;
            }
            if (str_parse("<>", ctx->option_restrictheader) == -1) {
                logbook(ctx->l2, L2_LEVEL_ERROR, "option --restrictheader, illegal regex (%s)", ctx->option_restrictheader);
                throw(0,0,0);
            }
        }
    }
    catch (ex)
        rethrow;

    /* --size SINGLE */
    try {
        if (   (val_get(ctx->val, "option.size", &ov) != VAL_OK)
            || (ov->ndata <  0)
            || (ov->ndata == 1 && ov->data.s == NULL)
            || (ov->ndata >  1)
              ) throw(0,0,0);
        logbook(ctx->l2, L2_LEVEL_TRACE, "--size = \"%s\"", ov->data.s);

        if (ov->ndata == 1)
            if ((ctx->option_maxmessagesize = atoi(ov->data.s)) <= 0) {
                logbook(ctx->l2, L2_LEVEL_ERROR, "option --size, number (%d) out of range", ctx->option_maxmessagesize);
                throw(0,0,0);
            }
    }
    catch (ex)
        rethrow;

    /* --timeoutlmtp SINGLE */
    try {
        int i;

        if (   (val_get(ctx->val, "option.timeoutlmtp", &ov) != VAL_OK)
            || (ov->ndata <  0)
            || (ov->ndata == 1 && ov->data.s == NULL)
            || (ov->ndata >  1)
              ) throw(0,0,0);
        logbook(ctx->l2, L2_LEVEL_TRACE, "--timeoutlmtp= \"%s\"", ov->data.s);

        if (ov->ndata == 1) {
            if ((i = atoi(ov->data.s)) < 0) {
                logbook(ctx->l2, L2_LEVEL_ERROR, "option --timeoutlmtp, number (%d) out of range", i);
                throw(0,0,0);
            }
            ctx->option_timeout_lmtp_accept = i;
            ctx->option_timeout_lmtp_read = i;
            ctx->option_timeout_lmtp_write = i;
        }
    }
    catch (ex)
        rethrow;

    /* --timeoutlmtpaccept SINGLE */
    try {
        if (   (val_get(ctx->val, "option.timeoutlmtpaccept", &ov) != VAL_OK)
            || (ov->ndata <  0)
            || (ov->ndata == 1 && ov->data.s == NULL)
            || (ov->ndata >  1)
              ) throw(0,0,0);
        logbook(ctx->l2, L2_LEVEL_TRACE, "--timeoutlmtpaccept = \"%s\"", ov->data.s);

        if (ov->ndata == 1)
            if ((ctx->option_timeout_lmtp_accept = atoi(ov->data.s)) < 0) {
                logbook(ctx->l2, L2_LEVEL_ERROR, "option --timeoutlmtpaccept, number (%d) out of range", ctx->option_timeout_lmtp_accept);
                throw(0,0,0);
            }
    }
    catch (ex)
        rethrow;

    /* --timeoutlmtpread SINGLE */
    try {
        if (   (val_get(ctx->val, "option.timeoutlmtpread", &ov) != VAL_OK)
            || (ov->ndata <  0)
            || (ov->ndata == 1 && ov->data.s == NULL)
            || (ov->ndata >  1)
              ) throw(0,0,0);
        logbook(ctx->l2, L2_LEVEL_TRACE, "--timeoutlmtpread = \"%s\"", ov->data.s);

        if (ov->ndata == 1)
            if ((ctx->option_timeout_lmtp_read = atoi(ov->data.s)) < 0) {
                logbook(ctx->l2, L2_LEVEL_ERROR, "option --timeoutlmtpread, number (%d) out of range", ctx->option_timeout_lmtp_read);
                throw(0,0,0);
            }
    }
    catch (ex)
        rethrow;

    /* --timeoutlmtpwrite SINGLE */
    try {
        if (   (val_get(ctx->val, "option.timeoutlmtpwrite", &ov) != VAL_OK)
            || (ov->ndata <  0)
            || (ov->ndata == 1 && ov->data.s == NULL)
            || (ov->ndata >  1)
              ) throw(0,0,0);
        logbook(ctx->l2, L2_LEVEL_TRACE, "--timeoutlmtpwrite = \"%s\"", ov->data.s);

        if (ov->ndata == 1)
            if ((ctx->option_timeout_lmtp_write = atoi(ov->data.s)) < 0) {
                logbook(ctx->l2, L2_LEVEL_ERROR, "option --timeoutlmtpwrite, number (%d) out of range", ctx->option_timeout_lmtp_write);
                throw(0,0,0);
            }
    }
    catch (ex)
        rethrow;

    /* --timeoutnntp SINGLE */
    try {
        int i;

        if (   (val_get(ctx->val, "option.timeoutnntp", &ov) != VAL_OK)
            || (ov->ndata <  0)
            || (ov->ndata == 1 && ov->data.s == NULL)
            || (ov->ndata >  1)
              ) throw(0,0,0);
        logbook(ctx->l2, L2_LEVEL_TRACE, "--timeoutnntp= \"%s\"", ov->data.s);

        if (ov->ndata == 1) {
            if ((i = atoi(ov->data.s)) < 0) {
                logbook(ctx->l2, L2_LEVEL_ERROR, "option --timeoutnntp, number (%d) out of range", i);
                throw(0,0,0);
            }
            ctx->option_timeout_nntp_connect = i;
            ctx->option_timeout_nntp_read = i;
            ctx->option_timeout_nntp_write = i;
        }
    }
    catch (ex)
        rethrow;

    /* --timeoutnntpconnect SINGLE */
    try {
        if (   (val_get(ctx->val, "option.timeoutnntpconnect", &ov) != VAL_OK)
            || (ov->ndata <  0)
            || (ov->ndata == 1 && ov->data.s == NULL)
            || (ov->ndata >  1)
              ) throw(0,0,0);
        logbook(ctx->l2, L2_LEVEL_TRACE, "--timeoutnntpconnect = \"%s\"", ov->data.s);

        if (ov->ndata == 1)
            if ((ctx->option_timeout_nntp_connect = atoi(ov->data.s)) < 0) {
                logbook(ctx->l2, L2_LEVEL_ERROR, "option --timeoutnntpconnect, number (%d) out of range", ctx->option_timeout_nntp_connect);
                throw(0,0,0);
            }
    }
    catch (ex)
        rethrow;

    /* --timeoutnntpread SINGLE */
    try {
        if (   (val_get(ctx->val, "option.timeoutnntpread", &ov) != VAL_OK)
            || (ov->ndata <  0)
            || (ov->ndata == 1 && ov->data.s == NULL)
            || (ov->ndata >  1)
              ) throw(0,0,0);
        logbook(ctx->l2, L2_LEVEL_TRACE, "--timeoutnntpread = \"%s\"", ov->data.s);

        if (ov->ndata == 1)
            if ((ctx->option_timeout_nntp_read = atoi(ov->data.s)) < 0) {
                logbook(ctx->l2, L2_LEVEL_ERROR, "option --timeoutnntpread, number (%d) out of range", ctx->option_timeout_nntp_read);
                throw(0,0,0);
            }
    }
    catch (ex)
        rethrow;

    /* --timeoutnntpwrite SINGLE */
    try {
        if (   (val_get(ctx->val, "option.timeoutnntpwrite", &ov) != VAL_OK)
            || (ov->ndata <  0)
            || (ov->ndata == 1 && ov->data.s == NULL)
            || (ov->ndata >  1)
              ) throw(0,0,0);
        logbook(ctx->l2, L2_LEVEL_TRACE, "--timeoutnntpwrite = \"%s\"", ov->data.s);

        if (ov->ndata == 1)
            if ((ctx->option_timeout_nntp_write = atoi(ov->data.s)) < 0) {
                logbook(ctx->l2, L2_LEVEL_ERROR, "option --timeoutnntpwrite, number (%d) out of range", ctx->option_timeout_nntp_write);
                throw(0,0,0);
            }
    }
    catch (ex)
        rethrow;

    /* --user SINGLE */
    try {
        struct passwd *sPasswd;

        if (   (val_get(ctx->val, "option.user", &ov) != VAL_OK)
            || (ov->ndata <  0)
            || (ov->ndata == 1 && ov->data.s == NULL)
            || (ov->ndata >  1)
              ) throw(0,0,0);
        logbook(ctx->l2, L2_LEVEL_TRACE, "--user = \"%s\"", ov->data.s);

        if (ov->ndata == 1) {
            if (isdigit((int)ov->data.s[0])) {
                if ((sPasswd = getpwuid((uid_t)atoi(ov->data.s))) == NULL) {
                    logbook(ctx->l2, L2_LEVEL_ERROR, "option --user, uid (%s) not found", ov->data.s);
                    throw(0,0,0);
                }
            }
            else {
                if ((sPasswd = getpwnam(ov->data.s)) == NULL) {
                    logbook(ctx->l2, L2_LEVEL_ERROR, "option --user, name (%s) not found", ov->data.s);
                    throw(0,0,0);
                }
            }
            ctx->option_uid = sPasswd->pw_uid;
        }
    }
    catch (ex)
        rethrow;

    /* --newsgroup MULTI */
    try {
        char *cp;
        int i;

        if (   (val_get(ctx->val, "option.newsgroup", &ov) != VAL_OK)
            || (ov->ndata <  0)
            || (ov->ndata >= 1 && ov->data.m == NULL)
              ) throw(0,0,0);
        logbook(ctx->l2, L2_LEVEL_DEBUG, "ov->ndata = %d", ov->ndata);
        for (i = 0; i < ov->ndata; i++)
            logbook(ctx->l2, L2_LEVEL_TRACE, "--newsgroup[%d] = \"%s\"", i, (ov->data.m)[i]);

        if (ov->ndata >= 1) {
            for (i = 0; i < ov->ndata; i++) {
                cp = (ov->data.m)[i];
                logbook(ctx->l2, L2_LEVEL_DEBUG, "cp = (data.m)[%d] = \"%s\"", i, cp);
                argz_add(&ctx->azGroupargs, &ctx->asGroupargs, cp);
            }
        }
    }
    catch (ex)
        rethrow;

    /* --testfile MULTI */
    try {
        char *cp;
        int i;
        char *cpBuf = NULL;

        if (   (val_get(ctx->val, "option.testfile", &ov) != VAL_OK)
            || (ov->ndata <  0)
            || (ov->ndata >= 1 && ov->data.m == NULL)
              ) throw(0,0,0);
        logbook(ctx->l2, L2_LEVEL_DEBUG, "ov->ndata = %d", ov->ndata);
        for (i = 0; i < ov->ndata; i++)
            logbook(ctx->l2, L2_LEVEL_TRACE, "--testfile[%d] = \"%s\"", i, (ov->data.m)[i]);

        if (ov->ndata >= 1) {
            for (i = 0; i < ov->ndata; i++) {
                cp = (ov->data.m)[i];
                logbook(ctx->l2, L2_LEVEL_DEBUG, "cp = (data.m)[%d] = \"%s\"", i, cp);
                {
                    const char *filename = cp;
                    struct stat sb;
                    volatile int fd = -1;
                    /* ex_t ex; */

                    try {
                        if (stat(filename, &sb) == -1) throw(0, 0, "stat");
                        if ((cpBuf = (char *)malloc((size_t)sb.st_size + 1)) == NULL) throw(0, 0, "malloc");
                        if ((fd = open(filename, O_RDONLY)) == -1) throw(0, 0, "open");
                        if (read(fd, (void *)cpBuf, (size_t)sb.st_size) != (ssize_t)sb.st_size) throw(0, 0, "read");
                        cpBuf[(int)sb.st_size] = '\0';
                    }
                    cleanup {
                        if (fd != -1) close(fd);
                    }
                    catch (ex) {
                        fprintf(stderr, "ERROR: caught %s\n", ex.ex_value == NULL ? "N/A" : (char *)ex.ex_value);
                        rethrow;
                    }
                }
                {
                    volatile msg_t *msg = NULL;
                    msg_rc_t rc2;

                    try {
                        ctx->msgcount++;
                        if ((msg = msg_create(ctx->prival)) == NULL) throw(0, 0, "msg_create");
                        msg->l2 = ctx->l2;
                        msg->cpMsg = cpBuf;
                        if ((rc2 = msg_split((msg_t *)msg)) != MSG_OK) {
                            logbook(ctx->l2, L2_LEVEL_ERROR, "Error splitting message: %s", msg_error(rc2));
                            throw(0, 0, "msg_split");
                        }
                        msg_headermatrixbuildup((msg_t *)msg);
                        ctx->msg = (msg_t *)msg;
                        headerrewrite(ctx);
                        msg_headermatrixteardwn((msg_t *)msg);
                        argz_add(&((msg_t *)msg)->azNewsgroups, &((msg_t *)msg)->asNewsgroups, "invalid.test");
                        if ((rc2 = msg_join((msg_t *)msg)) != MSG_OK) {
                            logbook(ctx->l2, L2_LEVEL_ERROR, "Error joining message: %s", msg_error(rc2));
                            throw(0, 0, "msg_join");
                        }
                        printf("%s", msg->cpMsg);
                    }
                    cleanup {
                        if (msg != NULL)
                            msg_destroy((msg_t *)msg);
                        ctx->msg = NULL;
                    }
                    catch (ex) {
                        rethrow;
                    }
                }
            }
        CU(CONFIG_OK_DRY);
        }
    }
    catch (ex)
        rethrow;
    CU(CONFIG_OK);
    CUS:
    return rc;
}

