OSSP CVS Repository

ossp - ossp-pkg/lmtp2nntp/lmtp2nntp_config.c 1.58
Not logged in
[Honeypot]  [Browse]  [Directory]  [Home]  [Login
[Reports]  [Search]  [Ticket]  [Timeline
  [Raw

ossp-pkg/lmtp2nntp/lmtp2nntp_config.c 1.58

/*
**  Copyright (c) 2001-2002 The OSSP Project <http://www.ossp.org/>
**  Copyright (c) 2001-2002 Cable & Wireless Deutschland <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/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 <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(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 "fixme.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 l2_result_t 
formatter_errno(l2_context_t *_ctx, const char id, const char *param,
          char *bufptr, size_t bufsize, size_t *buflen, va_list *ap)
{
    sprintf(bufptr, "(%d) %s", errno, strerror(errno));
    *buflen = strlen(bufptr);
    return L2_OK;
}

static void headersimulation(lmtp2nntp_t *); //FIXME
static void msg_headermatrixbuildup(msg_t *msg);
static void msg_headermatrixteardwn(msg_t *msg);
static void headerrewrite(lmtp2nntp_t *ctx);

static void extest(void)
{
    ex_t ex;

    try {
        fprintf(stderr, "try\n");
        throw(0,0,0);
    }
    cleanup {
        fprintf(stderr, "cleanup\n");
    }
    catch (ex) {
        fprintf(stderr, "catch\n");
        rethrow;
    }
    exit(0);

}
void config_context(lmtp2nntp_t *ctx)
{
    ex_t ex;
    optionval_t *ov;
    //char *cp;
    int rc;

    //FIXME ex is broken!?  extest();

    /* 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(ERR_EXECUTION);
    }
    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(ERR_EXECUTION);
    }
    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(ERR_EXECUTION);
    }
    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(ERR_EXECUTION);
    }
    if (l2_env_formatter(ctx->l2_env, 'm', formatter_errno, NULL) != L2_OK) {
        fprintf(stderr, "%s:Error: logging failed to register errno formatter\n", ctx->progname);
        CU(ERR_EXECUTION);
    }
    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(ERR_EXECUTION);
    }
    if (ov->data.s != NULL) {
        //FIXME this is cut off on command line!? fprintf(stderr, "DEBUG: ov->data.s = \"%s\"\n", ov->data.s);
        if ((rc = l2_spec(&ctx->l2, ctx->l2_env, ov->data.s)) != L2_OK) {
            fprintf(stderr, "%s:Error: logging failed to create stream\n", ctx->progname);
            CU(ERR_EXECUTION);
        }
        if (l2_channel_levels(ctx->l2, L2_LEVEL_ALL, L2_LEVEL_NONE) != L2_OK) { /* FIXME should this globalmask and flushmask be user-configurable? */
            fprintf(stderr, "%s:Error: logging failed to set global logging level\n", ctx->progname);
            CU(ERR_EXECUTION);
        }
        if (l2_channel_open(ctx->l2) != L2_OK) {
            fprintf(stderr, "%s:Error: logging failed to open channel stream\n", ctx->progname);
            CU(ERR_EXECUTION);
        }
    }
    /* from this point on logging is up and running and fprintf(stderr, ...)
     * should not be used in the remainder of the program flow.
     */
    log1(ctx, 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);
        log1(ctx, TRACE, "--version = %d", ov->data.f);

        if (ov->data.f == 1) {
            fprintf(stdout, "%s\n", lmtp2nntp_version.v_gnu);
            exit(0); //FIXME
        }
    }
    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);
        log1(ctx, TRACE, "--childsmax = \"%s\"", ov->data.s);

        if (ov->ndata == 1)
            if ((ctx->option_childsmax = atoi(ov->data.s)) <= 0) {
                log1(ctx, 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);
        log1(ctx, 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);
        log1(ctx, 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);
        log1(ctx, TRACE, "--pidfile = \"%s\"", ov->data.s);

        if (ov->ndata == 1)
            ctx->option_pidfile = 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);
        log1(ctx, DEBUG, "ov->ndata = %d", ov->ndata);
        for (i = 0; i < ov->ndata; i++)
            log2(ctx, 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 */
        if ((ctx->pacl = (struct acl *)malloc((ov->ndata + somepass ? 0 : 2 ) * sizeof(struct acl))) == NULL) throw(0,0,0);

        if (ov->ndata >= 1) {
            for (i = 0; i < ov->ndata; i++) {
                cp = (ov->data.m)[i];
                log2(ctx, 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;
                }
                log2(ctx, DEBUG, "ctx->pacl[%d].not = %s", i, ctx->pacl[i].not == TRUE ? "TRUE" : "FALSE");
                log2(ctx, 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);
                log2(ctx, DEBUG, "ctx->pacl[%d].prefixlen = %d", i, ctx->pacl[i].prefixlen);
                if ((rc = sa_addr_create(&(ctx->pacl[i].saa))) != SA_OK) {
                    log1(ctx, ERROR, "option --acl, create address (internal) failed with \"%s\"", sa_error(rc));
                    throw(0,0,0);
                    }
                if ((rc = sa_addr_u2a(ctx->pacl[i].saa, "inet://%s:0", ctx->pacl[i].acl)) != SA_OK) {
                    log2(ctx, ERROR, "option --acl, parsing address (%s) failed with \"%s\"", ctx->pacl[i].acl, sa_error(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 ((rc = sa_addr_create(&ctx->pacl[i].saa)) != SA_OK) {
                log1(ctx, ERROR, "option --acl, create IPv4 pass-through address (internal) failed with \"%s\"", sa_error(rc));
                throw(0,0,0);
            }
            if ((rc = sa_addr_u2a(ctx->pacl[i].saa, "inet://%s:0", ctx->pacl[i].acl)) != SA_OK) {
                log2(ctx, ERROR, "option --acl, parsing IPv4 pass-through address (%s) failed with \"%s\"", ctx->pacl[i].acl, sa_error(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 ((rc = sa_addr_create(&ctx->pacl[i].saa)) != SA_OK) {
                log1(ctx, ERROR, "option --acl, create IPv6 pass-through address (internal) failed with \"%s\"", sa_error(rc));
                throw(0,0,0);
            }
            if ((rc = sa_addr_u2a(ctx->pacl[i].saa, "inet://%s:0", ctx->pacl[i].acl)) != SA_OK) {
                log2(ctx, ERROR, "option --acl, parsing IPv6 pass-through address (%s) failed with \"%s\"", ctx->pacl[i].acl, sa_error(rc));
                throw(0,0,0);
            }
            i++;
            ctx->nacl = i;
        }
    }
    catch (ex)
        rethrow;

    /* --bind SINGLE */
    try {
        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);
        log1(ctx, TRACE, "--bind = \"%s\"", ov->data.s);

        if (ov->ndata == 1) {
            /* dash means stdio */
            if (strcmp(ov->data.s, "-") != 0) {
                if ((rc = sa_create(&ctx->saServerbind)) != SA_OK) {
                    log1(ctx, ERROR, "option --bind, creating TCP socket (internal) failed with \"%s\"", sa_error(rc));
                    throw(0,0,0);
                }
                if ((rc = sa_addr_create(&ctx->saaServerbind)) != SA_OK) {
                    log1(ctx, ERROR, "option --bind, create address (internal) failed with \"%s\"", sa_error(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') {
                            log1(ctx, ERROR, "option --bind, invalid permissions \"%s\"", cpPerm);
                            throw(0,0,0);
                        }
                    }
                    if ((rc = sa_addr_u2a(ctx->saaServerbind, "unix:%s", cpPath)) != SA_OK) {
                        log2(ctx, ERROR, "option --bind, parsing alternate IO guessing UNIX socket (%s) failed with \"%s\"", cpPath, sa_error(rc));
                        throw(0,0,0);
                    }
                    if ((rc = sa_bind(ctx->saServerbind, ctx->saaServerbind)) != SA_OK) {
                        log2(ctx, ERROR, "option --bind, bind (%s) failed with \"%s\"", cpPath, sa_error(rc));
                        throw(0,0,0);
                    }
                    if (nPerm != -1) {
                        if (chmod(cpPath, nPerm) == -1) {
                            log3(ctx, 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) {
                            log3(ctx, 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 {
                    if ((rc = sa_addr_u2a(ctx->saaServerbind, "inet://%s", ov->data.s)) != SA_OK) {
                        log2(ctx, ERROR, "option --bind, parsing alternate IO guessing INET socket (%s) failed with \"%s\"", ov->data.s, sa_error(rc));
                        throw(0,0,0);
                    }
                    if ((rc = sa_bind(ctx->saServerbind, ctx->saaServerbind)) != SA_OK) {
                        log2(ctx, ERROR, "option --bind, bind (%s) failed with \"%s\"", ov->data.s, sa_error(rc));
                        throw(0,0,0);
                    }
                }
            /* for either sockets */
                if ((rc = sa_listen(ctx->saServerbind, -1)) != SA_OK) {
                        log2(ctx, ERROR, "option --bind, listen (%s) failed with \"%s\"", ov->data.s, sa_error(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);
        log1(ctx, TRACE, "--client = \"%s\"", ov->data.s);

        if (ov->ndata == 1) {
            if ((rc = sa_addr_create(&ctx->saaClientbind)) != SA_OK) {
                log1(ctx, ERROR, "option --client, create address (internal) failed with \"%s\"", sa_error(rc));
                throw(0,0,0);
            }
            if ((rc = sa_addr_u2a(ctx->saaClientbind, 
                                  (strchr(ov->data.s, ':') == NULL) ?  "inet://%s:0" : "inet://%s", 
                                  ov->data.s)) != SA_OK) {
                log2(ctx, ERROR, "option --client, parsing alternate IO guessing INET socket (%s) failed with \"%s\"", ov->data.s, sa_error(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);
        log1(ctx, DEBUG, "ov->ndata = %d", ov->ndata);
        for (i = 0; i < ov->ndata; i++)
            log2(ctx, 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];
                log2(ctx, DEBUG, "cp = (data.m)[%d] = \"%s\"", i, cp);
                if (strrchr(cp, ':') == NULL)
                    cp = str_concat(cp, ":nntp", NULL); //FIXME is this a config var/val?
                else
                    cp = str_concat(cp, NULL); /* prepare for free() */
                if ((rc = sa_addr_create(&ctx->pns[i].saa)) != SA_OK) {
                    log1(ctx, ERROR, "option --destination, create address (internal) failed with \"%s\"", sa_error(rc));
                    throw(0,0,0);
                }
                if ((rc = sa_addr_u2a(ctx->pns[i].saa, "inet://%s", cp)) != SA_OK) {
                    log2(ctx, ERROR, "option --destination, parsing host address (%s) failed with \"%s\"", cp /*FIXME again, option vs. config */, sa_error(rc));
                    throw(0,0,0);
                }
                if ((rc = sa_create(&ctx->pns[i].sa)) != SA_OK) {
                    log2(ctx, ERROR, "option --destination, creating TCP socket (%s) failed with \"%s\"", cp /*FIXME again, option vs. config */, sa_error(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);
        log1(ctx, 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 {
                log1(ctx, 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);
            log1(ctx, DEBUG, "ov->ndata = %d", ov->ndata);
            for (i = 0; i < ov->ndata; i++)
                log2(ctx, 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];
                    log2(ctx, 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->header    = NULL;
                    hrNew->val       = NULL;
                    hrNew->pcreRegex = NULL;
                    hrNew->pcreExtra = NULL;
                    
                    //FIXME cp = hrNew->carve = strdupex(cp);
                    /* priority */
                    cpP = cp;
                    if ((cp = strchr(cp, ':')) == NULL) {
                        log1(ctx, 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) {
                        log1(ctx, 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) {
                        log1(ctx, ERROR, "option --headerrule, header (%s) terminating colon missing", (ov->data.m)[i]);
                        throw(0,0,0);
                    }
                    cp++;
                    n = cp - cpP;
                    if (n == 0) {
                        log1(ctx, ERROR, "option --headerrule, header (%s) missing", (ov->data.m)[i]);
                        throw(0,0,0);
                    }
                    hrNew->header = 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) {
                            log3(ctx, 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) {
                            log1(ctx, 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 */
                }
            }
        }
        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->header != NULL)
                    freeex(hrNew->header);
                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);
        log1(ctx, 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) {
                log1(ctx, 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);
        log1(ctx, TRACE, "--nodename = \"%s\"", ov->data.s);

        if (ov->ndata == 1) {
            if (strlen(ov->data.s) > sizeof(ctx->uname.nodename)-1) {
                log1(ctx, ERROR, "option --nodename, name (%s) too long", ov->data.s);
                throw(0,0,0);
            }
            strcpy(ctx->uname.nodename, ov->data.s);
        }
    }
    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);
        log1(ctx, 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) {
                    log1(ctx, ERROR, "option --operationmode, invalid length (%s)", cp);
                    throw(0,0,0);
                }
                if (cp[3] != '/') {
                    log1(ctx, 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])
                      ) {
                    log1(ctx, 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])
                      ) {
                    log1(ctx, ERROR, "option --operationmode, invalid dsn code (%s)", cp);
                    throw(0,0,0);
                }
            }
        }
        else {
            log0(ctx, 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);
        log1(ctx, 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) {
                log1(ctx, 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);
        log1(ctx, TRACE, "--size = \"%s\"", ov->data.s);

        if (ov->ndata == 1)
            if ((ctx->option_maxmessagesize = atoi(ov->data.s)) <= 0) {
                log1(ctx, 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);
        log1(ctx, TRACE, "--timeoutlmtp= \"%s\"", ov->data.s);
 
        if (ov->ndata == 1) {
            if ((i = atoi(ov->data.s)) < 0) {
                log1(ctx, 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);
        log1(ctx, TRACE, "--timeoutlmtpaccept = \"%s\"", ov->data.s);

        if (ov->ndata == 1)
            if ((ctx->option_timeout_lmtp_accept = atoi(ov->data.s)) < 0) {
                log1(ctx, 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);
        log1(ctx, TRACE, "--timeoutlmtpread = \"%s\"", ov->data.s);

        if (ov->ndata == 1)
            if ((ctx->option_timeout_lmtp_read = atoi(ov->data.s)) < 0) {
                log1(ctx, 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);
        log1(ctx, TRACE, "--timeoutlmtpwrite = \"%s\"", ov->data.s);

        if (ov->ndata == 1)
            if ((ctx->option_timeout_lmtp_write = atoi(ov->data.s)) < 0) {
                log1(ctx, 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);
        log1(ctx, TRACE, "--timeoutnntp= \"%s\"", ov->data.s);

        if (ov->ndata == 1) {
            if ((i = atoi(ov->data.s)) < 0) {
                log1(ctx, 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);
        log1(ctx, TRACE, "--timeoutnntpconnect = \"%s\"", ov->data.s);

        if (ov->ndata == 1)
            if ((ctx->option_timeout_nntp_connect = atoi(ov->data.s)) < 0) {
                log1(ctx, 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);
        log1(ctx, TRACE, "--timeoutnntpread = \"%s\"", ov->data.s);

        if (ov->ndata == 1)
            if ((ctx->option_timeout_nntp_read = atoi(ov->data.s)) < 0) {
                log1(ctx, 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);
        log1(ctx, TRACE, "--timeoutnntpwrite = \"%s\"", ov->data.s);

        if (ov->ndata == 1)
            if ((ctx->option_timeout_nntp_write = atoi(ov->data.s)) < 0) {
                log1(ctx, 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);
        log1(ctx, 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) {
                    log1(ctx, ERROR, "option --user, uid (%s) not found", ov->data.s);
                    throw(0,0,0);
                }
            }
            else {
                if ((sPasswd = getpwnam(ov->data.s)) == NULL) {
                    log1(ctx, 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);
        log1(ctx, DEBUG, "ov->ndata = %d", ov->ndata);
        for (i = 0; i < ov->ndata; i++)
            log2(ctx, 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];
                log2(ctx, DEBUG, "cp = (data.m)[%d] = \"%s\"", i, cp);
                argz_add(&ctx->azGroupargs, &ctx->asGroupargs, cp);
            }
        }
    }
    catch (ex)
        rethrow;

    //headersimulation(ctx); //FIXME
    /* --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);
        log1(ctx, DEBUG, "ov->ndata = %d", ov->ndata);
        for (i = 0; i < ov->ndata; i++)
            log2(ctx, 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];
                log2(ctx, 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;
                    }
                }
                //printf("DEBUG: *** 1 *** file as it was just read in ***\n%s***\n", cpBuf);
                {
                    volatile msg_t *msg = NULL;
                    msg_rc_t rc;

                    try {
                        if ((msg = msg_create()) == NULL) throw(0, 0, "msg_create");
                        msg->l2 = ctx->l2;
                        msg->cpMsg = cpBuf;
                        if ((rc = msg_split((msg_t *)msg)) != MSG_OK) {
                            log1(ctx, ERROR, "Error splitting message: %s", msg_error(rc));
                            throw(0, 0, "msg_split");
                        }
                        {//FIXME
                            char *cp;
                            cp = NULL;
                            log0(msg, DEBUG, "FIXME trace #20");
                            while ((cp = argz_next(msg->azHeaders, msg->asHeaders, cp)) != NULL) {
                                log1(msg, DEBUG, "header=\"%s\"", cp);
                            }
                            log0(msg, DEBUG, "FIXME trace #21");
                        }
                        msg_headermatrixbuildup((msg_t *)msg);
                        ctx->msg = (msg_t *)msg;
                        headerrewrite(ctx);
                        msg_headermatrixteardwn((msg_t *)msg);
                        {//FIXME
                            char *cp;
                            cp = NULL;
                            log0(msg, DEBUG, "FIXME trace #30");
                            while ((cp = argz_next(msg->azHeaders, msg->asHeaders, cp)) != NULL) {
                                log1(msg, DEBUG, "header=\"%s\"", cp);
                            }
                            log0(msg, DEBUG, "FIXME trace #31");
                        }
                        argz_add(&((msg_t *)msg)->azNewsgroups, &((msg_t *)msg)->asNewsgroups, "invalid.tst"); //FIXME
                        if ((rc = msg_join((msg_t *)msg)) != MSG_OK) {
                            log1(ctx, ERROR, "Error joining message: %s", msg_error(rc));
                            throw(0, 0, "msg_split");
                        }
                        printf("%s", msg->cpMsg); //FIXME
                    }
                    cleanup {
                        if (msg != NULL)
                            msg_destroy((msg_t *)msg);
                        ctx->msg = NULL;
                    }
                    catch (ex) {
                        rethrow;
                    }
                }
            }
        exit(0); //FIXME
        }
    }
    catch (ex)
        rethrow;


#if 0
//void testpcre(void)
{
    char       *szRegex;
    pcre       *pcreRegex;
    pcre_extra *pcreExtra;
    const char *szError;
    int         nErrorOffset;
    int         iCheck;
    int         opt;
    char       *buf;
    int         buf_size;
#define OVECSIZE 30
    int         ovec[OVECSIZE];
    int         i;
    const char **cpList;

    //     0         1         2
    //     012345678901234567890
    buf = "In-Reply-To: MailTo: Tom & Jerry";
    buf_size = strlen(buf);
    szRegex = "(To|Cc|Bcc):.*(Tom|Snoopy).+&(.*)";
    fprintf(stderr, "DEBUG:          0         1         2         3         4\n");
    fprintf(stderr, "DEBUG:          01234567890123456789012345678901234567890\n");
    fprintf(stderr, "DEBUG: buf    =\"%s\"\n", buf);
    fprintf(stderr, "DEBUG: szRegex=\"%s\"\n", szRegex);
    /*
    buf = "In-Reply-To: MailTo: Tom & Jerry";
    szRegex = "(To|Cc|Bcc):.*(Tom|Snoopy).+&(.*)";
    iCheck=4 [0]="To: MailTo: Tom & Jerry" [1]="To" [2]="Tom" [3]=" Jerry"
    */

    /* compile regular expression into finite state machine */
    opt = 0;
    // opt |= PCRE_CASELESS;
    // opt |= PCRE_ANCHORED;
    if ((pcreRegex = pcre_compile(szRegex, opt, &szError, &nErrorOffset, NULL)) == NULL) {
        fprintf(stderr, "DEBUG: \"%s\" '%c'\n", szError, szRegex[nErrorOffset]);
        throw(0,0,0);
    }
    /* study finite state machine for more performance */
    pcreExtra = pcre_study(pcreRegex, 0, &szError);
    if (szError != NULL) {
        free(pcreRegex);
        pcreRegex = NULL;
        fprintf(stderr, "DEBUG: \"%s\"\n", szError);
        throw(0,0,0);
    }

    /* apply filter */
    iCheck = pcre_exec(pcreRegex, pcreExtra, buf, buf_size, 0, 0, ovec, OVECSIZE);
    fprintf(stderr, "DEBUG: iCheck=%d\n", iCheck);
    for (i = 0; i < iCheck; i++)
        fprintf(stderr, "DEBUG: ovec[%d]=%3d, ovec[%d]=%3d\n", 2*i, ovec[2*i], 2*i+1, ovec[2*i+1]);
        

    pcre_get_substring_list(buf, ovec, iCheck, &cpList);
    if (cpList != NULL)
        for (i = 0; i < iCheck; i++)
            fprintf(stderr, "DEBUG: list[%d]=\"%s\"\n", i, cpList[i] == NULL ? "(NULL)" : cpList[i]);

    /* destroy channel configuration */
    if (cpList != NULL)
        pcre_free_substring_list(cpList);
    if (pcreRegex != NULL)
        free(pcreRegex);
    if (pcreExtra != NULL)
        free(pcreExtra);

}
#endif

CUS:
    return;
}



static void headerdestroy(headerdata_t *hdC)
{
    int i;

    if (hdC->ndata > 1) {
        for (i = 0; i < hdC->ndata; i++) {
            if (hdC->data.m[i] == NULL)
                break;
            free(hdC->data.m[i]);
        }
        free (hdC->data.m);
    } else
    if (hdC->ndata == 1)
        if (hdC->data.s != NULL)
            free(hdC->data.s);
    if (hdC->name != NULL)
        free(hdC->name);
    if (hdC->prev != NULL && hdC->prev->next == hdC)
        return; //FIXME still linked, cannot be destroyed
    if (hdC->next != NULL && hdC->next->prev == hdC)
        return; //FIXME still linked, cannot be destroyed
    free(hdC);
}

static void headerdelete(headerdata_t *hdC)
{
    if (hdC->prev != NULL)
        hdC->prev->next = hdC->next;
    hdC->next = NULL;
    if (hdC->next != NULL)
        hdC->next->prev = hdC->prev;
    hdC->prev = NULL;
    headerdestroy(hdC);
}

static void headerreplace(headerdata_t *hdC, headerdata_t *hdNew)
{
    hdNew->prev = hdC->prev;
    hdC->prev = NULL;
    hdNew->next = hdC->next;
    hdC->next = NULL;
    if (hdNew->prev != NULL)
        hdNew->prev->next = hdNew;
    if (hdNew->next != NULL)
        hdNew->next->prev = hdNew;
    headerdestroy(hdC);
}

static headerdata_t *headercreate(void)
{
    ex_t ex;
    volatile headerdata_t *hdNew = NULL;
    try {
        hdNew = mallocex(sizeof(headerdata_t));
        hdNew->prev  = NULL;
        hdNew->next  = NULL;
        hdNew->name  = NULL;
        hdNew->ndata = 0;
    }
    catch (ex) {
        if (hdNew != NULL)
            free((headerdata_t *)hdNew);
        rethrow;
    }
    return (headerdata_t *)hdNew;
}
#if 0
#endif

static var_config_t ctx_lookup_cfg = {
    '$',          /* varinit       */ 
    '{',          /* startdelim    */ 
    '}',          /* enddelim      */ 
    '[',          /* startindex    */ 
    ']',          /* endindex      */ 
    '#',          /* current_index */ 
    '\\',         /* escape        */ 
    "a-zA-Z0-9_.-" /* namechars     */ 
};

static var_rc_t ctx_lookup(
    void *_ctx, 
    const char *var_ptr, size_t var_len, int var_idx,
    const char **val_ptr, size_t *val_len, size_t *val_size)
{
    lmtp2nntp_t *ctx = (lmtp2nntp_t *)_ctx;
    /*FIXME
    const char *name;
    size_t len;
    */
    var_rc_t rc;
    char *cp;
    optionval_t *ov;

    log2(ctx, DEBUG, "ctx_lookup variable \"%s\" (%d)", var_ptr, var_len);
    rc = VAR_ERR_UNDEFINED_VARIABLE;
    if (strncasecmp(var_ptr, "option.", strlen("option.")) == 0) {
        cp = str_dupex(var_ptr, var_len);
        if (val_get(ctx->val, cp, &ov) == VAL_OK) {
            //log3(ctx, DEBUG, "FIXME var_idx=%d, ov->type=%d, ov->ndata=%d", var_idx, ov->type, ov->ndata);
            if ((var_idx == 0) && (ov->ndata == 1) && (ov->type == OPT_SINGLE)) { /* request first/only single value */
                *val_ptr = ov->data.s;
                *val_len = strlen(ov->data.s);
                *val_size = 0;
                rc = VAR_OK;
            }
            else if ((var_idx == 0) && (ov->ndata == 1) && (ov->type == OPT_FLAG)) { /* request first/only single value */
                *val_ptr = ov->data.f == TRUE ? "yes" : "no";
                *val_len = strlen(ov->data.s);
                *val_size = 0;
                rc = VAR_OK;
            }
            else if ((var_idx < ov->ndata) && (ov->type == OPT_MULTI)) { /* request second+ from multi value */
                *val_ptr = ov->data.m[var_idx];
                *val_len = strlen(ov->data.m[var_idx]);
                *val_size = 0;
                rc = VAR_OK;
            }
        }
        free(cp);
    }
    else if (strncasecmp(var_ptr, "xyz", var_len) == 0) {
        *val_ptr = "Hello, World!";
        *val_len = strlen("Hello, World!");
        *val_size = 0;
        rc = VAR_OK;
    }
    if (rc == VAR_OK)
        log4(ctx, DEBUG, "ctx_lookup variable \"%s\" (%d) ok: result is \"%s\" (%d)", var_ptr, var_len, *val_ptr, *val_len);
    else
        log3(ctx, DEBUG, "ctx_lookup variable \"%s\" (%d) failed: %s", var_ptr, var_len, var_strerror(rc));
    return rc;
}

struct regex_ctx_st; //FIXME go into a header!
typedef struct regex_ctx_st regex_ctx_t;
struct regex_ctx_st {
    int            nMatch;
    const char   **acpMatch;
    l2_env_t      *l2_env;
    l2_channel_t  *l2;
};

static var_config_t regex_lookup_cfg = {
    '$',          /* varinit       */ 
    '{',          /* startdelim    */ 
    '}',          /* enddelim      */ 
    '[',          /* startindex    */ //FIXME
    ']',          /* endindex      */ //FIXME
    '#',          /* current_index */ //FIXME
    '\\',         /* escape        */ 
    "0-9"         /* namechars     */ //FIXME bug in lib_val
};

static var_rc_t regex_lookup(
    void *_ctx, 
    const char *var_ptr, size_t var_len, int var_idx,
    const char **val_ptr, size_t *val_len, size_t *val_size)
{
    regex_ctx_t *ctx = (regex_ctx_t *)_ctx;
    /*FIXME
    const char *name;
    size_t len;
    char *cp;
    */
    var_rc_t rc;
    int i;

    log2(ctx, DEBUG, "rgx_lookup variable \"%s\" (%d)", var_ptr, var_len);
    rc = VAR_ERR_UNDEFINED_VARIABLE;
    i = atoi(var_ptr); /* works with both '}' and '\0' termination */
    if (i < ctx->nMatch) {
        *val_ptr = ctx->acpMatch[i];
        *val_len = strlen(ctx->acpMatch[i]);
        *val_size = 0;
        rc = VAR_OK;
    }
    if (rc == VAR_OK)
        log4(ctx, DEBUG, "rgx_lookup variable \"%s\" (%d) ok: result is \"%s\" (%d)", var_ptr, var_len, *val_ptr, *val_len);
    else
        log3(ctx, DEBUG, "rgx_lookup variable \"%s\" (%d) failed: %s", var_ptr, var_len, var_strerror(rc));
    return rc;
}

static void msg_headermatrixbuildup(msg_t *msg)
{
    ex_t ex;
    volatile headerdata_t *hdNew = NULL;
    try {
        headerdata_t *hdI, *hdP;
        char *cp;

        cp = NULL;
        while ((cp = argz_next(msg->azHeaders, msg->asHeaders, cp)) != NULL) { /* for each message header */
            //log2(msg, DEBUG, "FIXME trace loop cp=%.8lx, cp=\"%s\"", cp, cp);

            /*FIXME we want O(1) here */
            for (hdP = NULL, hdI = msg->hdFirst; hdI != NULL; hdP = hdI, hdI = hdI->next) { /* for each matrix header */
                //log2(msg, DEBUG, "FIXME trace loop hdI=%.8lx, hI->name=\"%s\"", hdI, hdI->name);
                if (hdI->name == NULL || strlen(hdI->name) == 0 || hdI->ndata == 0)
                    continue;
                if (strcmp(cp, hdI->name) == 0)
                    break;
            }

            if (hdI == NULL) {
                hdNew = headercreate(); /* not found, create new */
                hdNew->name = strdupex(cp);
                hdI = (headerdata_t *)hdNew; hdNew = NULL; /* ex cleanup */
                if (hdP == NULL)
                    msg->hdFirst = hdI; /* no previous? this is the first */
                else {
                    hdP->next = hdI;
                    hdI->prev = hdP;
                }
            }
            cp = argz_next(msg->azHeaders, msg->asHeaders, cp);
            if (hdI->ndata == 0) {
                log1(msg, DEBUG, "header=%s, currently empty", hdI->name);
                hdI->data.s = strdupex(cp);
                hdI->ndata = 1;
            }
            else if(hdI->ndata == 1) {
                char *cpOld;
                cpOld = hdI->data.s;
                log1(msg, DEBUG, "header=%s, currently single valued", hdI->name);
                hdI->data.m = (char **)mallocex(3 * sizeof(char *));
                hdI->data.m[0] = strdupex(cpOld); //FIXME
                hdI->data.m[1] = strdupex(cp);
                hdI->data.m[2] = NULL;
                hdI->ndata = 2;
            }
            else {
                log2(msg, DEBUG, "header=%s, currently multi valued %d", hdI->name, hdI->ndata);
                hdI->data.m = (char **)reallocex(hdI->data.m, (hdI->ndata + 2) * sizeof(char *));
                hdI->data.m[hdI->ndata++] = strdupex(cp);
                hdI->data.m[hdI->ndata] = NULL;
            }
        }
#if 0
        { //FIXME debug code block
            int i;
            headerdata_t *hdD;

            log0(msg, DEBUG, "FIXME trace ---------- msg_headermatrixbuildup() ----------");
            for (hdD = msg->hdFirst; hdD != NULL; hdD = hdD->next) {
                if (hdD->ndata == 0)
                    log1(msg, DEBUG, "hdD->name=%s: (NO DATA)", hdD->name);
                if (hdD->ndata == 1)
                    log2(msg, DEBUG, "hdD->name:hdD->data.s    %s %s", hdD->name, hdD->data.s);
                if (hdD->ndata > 1)
                    for (i = 0; i < hdD->ndata; i++)
                        log3(msg, DEBUG, "hdD->name:hdD->data.m[%d] %s %s", i, hdD->name, hdD->data.m[i]);
            }
        }
#endif
    }
    cleanup {
        if (hdNew != NULL)
            free((headerdata_t *)hdNew);
    }
    catch(ex) {
        rethrow;
    }
}

static void msg_headermatrixteardwn(msg_t *msg)
{
    ex_t ex;
    try {
        headerdata_t *hdI;

        if (msg->azHeaders != NULL)
            free(msg->azHeaders);
        msg->azHeaders = NULL;
        msg->asHeaders = 0;

        for (hdI = msg->hdFirst; hdI != NULL; hdI = hdI->next) { /* for each matrix header */
            log2(msg, DEBUG, "FIXME trace loop hdI=%.8lx, hI->name=\"%s\"", hdI, hdI->name);
            if (hdI->name == NULL || strlen(hdI->name) == 0 || hdI->ndata == 0)
                continue;
            if (hdI->ndata == 0) {
                log1(msg, DEBUG, "header=%s, no data", hdI->name);
            }
            else if(hdI->ndata == 1) { /* header data is single valued */
                log2(msg, DEBUG, "header=%s, data=%s", hdI->name, hdI->data.s);
                argz_add(&msg->azHeaders, &msg->asHeaders, hdI->name);
                argz_add(&msg->azHeaders, &msg->asHeaders, hdI->data.s);
            }
            else { /* header data is multi valued */
                int i;
                for (i = 0; i < hdI->ndata; i++) {
                    log3(msg, DEBUG, "header=%s[%d], data=%s", hdI->name, i, hdI->data.m[i]);
                    argz_add(&msg->azHeaders, &msg->asHeaders, hdI->name);
                    argz_add(&msg->azHeaders, &msg->asHeaders, hdI->data.m[i]);
                }
            }
        }
#if 0
        { //FIXME debug code block
            int i;
            headerdata_t *hdD;

            log0(msg, DEBUG, "FIXME trace ---------- msg_headermatrixteardwn() ----------");
            for (hdD = msg->hdFirst; hdD != NULL; hdD = hdD->next) {
                if (hdD->ndata == 0)
                    log1(msg, DEBUG, "hdD->name=%s: (NO DATA)", hdD->name);
                if (hdD->ndata == 1)
                    log2(msg, DEBUG, "hdD->name:hdD->data.s    %s %s", hdD->name, hdD->data.s);
                if (hdD->ndata > 1)
                    for (i = 0; i < hdD->ndata; i++)
                        log3(msg, DEBUG, "hdD->name:hdD->data.m[%d] %s %s", i, hdD->name, hdD->data.m[i]);
            }
        }
#endif
    }
    catch(ex) {
        rethrow;
    }
}


static void headerrewrite(lmtp2nntp_t *ctx)
{
    headerrule_t *hrI;
    headerdata_t *hdI, *hdNew;
    /*
    char *cp;
    int i;
    char *cpPri;
    char *cpRegex;
    char *cpHeader;
    char *cpVal;
    headerrule_t *hrP;
    hrNew->pri    = atoi(cpPri);
    hrNew->regex  = cpRegex;
    hrNew->header = cpHeader;
    hrNew->val    = cpVal;
    char       *szRegex;
    pcre       *pcreRegex;
    pcre_extra *pcreExtra;
    const char *szError;
    int         nErrorOffset;
    int         opt;
    char       *buf;
    int         buf_size;
    int         ovec[OVECSIZE];
    const char **cpList;
    */
    regex_ctx_t *regex_ctx;
#define OVECSIZE 30
    int            ovec[OVECSIZE];

    { //FIXME debug code block
        int i;
        headerrule_t *hrD;
        headerdata_t *hdD;

        log0(ctx, DEBUG, "FIXME trace ---------- headerrewrite() ----------");
        for (hrD = ctx->option_firstheaderrule; hrD != NULL; hrD = hrD->next)
            log1(ctx, DEBUG, "hrD->header=%s", hrD->header);
        for (hdD = ctx->msg->hdFirst; hdD != NULL; hdD = hdD->next) {
            if (hdD->ndata == 0)
                log1(ctx, DEBUG, "hdD->name=%s: (NO DATA)", hdD->name);
            if (hdD->ndata == 1)
                log2(ctx, DEBUG, "hdD->name:hdD->data.s    %s %s", hdD->name, hdD->data.s);
            if (hdD->ndata > 1)
                for (i = 0; i < hdD->ndata; i++)
                    log3(ctx, DEBUG, "hdD->name:hdD->data.m[%d] %s %s", i, hdD->name, hdD->data.m[i]);
        }
    }
    
    regex_ctx = (regex_ctx_t *)mallocex(sizeof(regex_ctx_t));
    regex_ctx->nMatch = 0;
    regex_ctx->acpMatch = NULL;
    regex_ctx->l2_env = ctx->l2_env;
    regex_ctx->l2 = ctx->l2;
    for (hrI = ctx->option_firstheaderrule; hrI != NULL; hrI = hrI->next) { /* for each rule */
        //log1(ctx, DEBUG, "FIXME trace loop hrI=%.8lx", hrI);
        { //FIXME debug code block
            int i;
            headerrule_t *hrD;
            headerdata_t *hdD;

            log0(ctx, DEBUG, "FIXME trace ---------- headerrewrite() ---------- MIDDLE");
            for (hrD = ctx->option_firstheaderrule; hrD != NULL; hrD = hrD->next)
                log1(ctx, DEBUG, "hrD->header=%s", hrD->header);
            for (hdD = ctx->msg->hdFirst; hdD != NULL; hdD = hdD->next) {
                //log3(ctx, DEBUG, "hdD=%.8lx, hdD->name=%.8lx, hdD->data.s=%.8lx", (long)hdD, (long)&hdD->name, (long)&hdD->data.s);
                if (hdD->ndata == 0)
                    log1(ctx, DEBUG, "hdD->name=%s: (NO DATA)", hdD->name);
                if (hdD->ndata == 1)
                    log2(ctx, DEBUG, "hdD->name:hdD->data.s    %s %s", hdD->name, hdD->data.s);
                if (hdD->ndata > 1)
                    for (i = 0; i < hdD->ndata; i++)
                        log3(ctx, DEBUG, "hdD->name:hdD->data.m[%d] %s %s", i, hdD->name, hdD->data.m[i]);
            }
        }
        if (hrI->regex != NULL) {
            log1(ctx, DEBUG, "rule has regex %s", hrI->regex);
            for (hdI = ctx->msg->hdFirst; hdI != NULL; hdI = hdI->next) { /* for each header */
                //log2(ctx, DEBUG, "FIXME trace loop hdI=%.8lx, hI->name=\"%s\"", hdI, hdI->name);
                if (hdI->name == NULL || strlen(hdI->name) == 0 || hdI->ndata == 0)
                    continue;
                regex_ctx->nMatch = pcre_exec(hrI->pcreRegex, hrI->pcreExtra, hdI->name, strlen(hdI->name), 0, 0, ovec, OVECSIZE);
                if (regex_ctx->nMatch >= 1) {
                    int i;
                    char *cp;
                    log1(ctx, DEBUG, "regex matches, %d references", regex_ctx->nMatch);
                    pcre_get_substring_list(hdI->name, ovec, regex_ctx->nMatch, &regex_ctx->acpMatch);
                    if (regex_ctx->acpMatch != NULL)
                        for (i = 0; i < regex_ctx->nMatch; i++)
                            log2(ctx, DEBUG, "regex reference[%d]=\'%s\'", i, regex_ctx->acpMatch[i] == NULL ? "(UNDEFINED)" : regex_ctx->acpMatch[i]);
                    hdNew = headercreate();
                    /* expanding regex references into header name */
                    {
                        var_rc_t var_rc;
                        char *res_ptr;
                        log1(ctx, DEBUG, "expanding regex references in headername '%s'", hrI->header);
                        if ((var_rc = var_expand(hrI->header, strlen(hrI->header), &res_ptr, NULL,
                                                 regex_lookup, regex_ctx, &regex_lookup_cfg, FALSE)) != VAR_OK) {
                            log2(ctx, ERROR, "expansion of '%s' failed: %s", hrI->header, var_strerror(var_rc));
                        }
                        log1(ctx, DEBUG, "expansion result '%s'", res_ptr);
                        hdNew->name = res_ptr; //FIXME rename ->header to ->name
                    }
                    if (hrI->val == NULL) {
                        log0(ctx, DEBUG, "marking deleted");
                        /*FIXME clean up data.s and data.m */
                        hdNew->ndata = 0;
                    }
                    else {
                        /* expanding regex references into header value */
                        {
                            var_rc_t var_rc;
                            char *res_ptr;
                            log1(ctx, DEBUG, "expanding regex references in header value '%s'", hrI->val);
                            if ((var_rc = var_expand(hrI->val, strlen(hrI->val), &res_ptr, NULL,
                                                     regex_lookup, regex_ctx, &regex_lookup_cfg, FALSE)) != VAR_OK) {
                                log2(ctx, ERROR, "expansion of '%s' failed: %s", hrI->val, var_strerror(var_rc));
                            }
                            log1(ctx, DEBUG, "expansion result '%s'", res_ptr);
                            cp = res_ptr;
                        }
                        /* expanding header and other variables into header value */
                        if (hrI->val != NULL) {
                            var_rc_t var_rc;
                            char *res_ptr;
                            log1(ctx, DEBUG, "expanding variables in header value '%s'", hrI->val);
                            if ((var_rc = var_expand(cp, strlen(cp), &res_ptr, NULL,
                                                     ctx_lookup, ctx, &ctx_lookup_cfg, FALSE)) != VAR_OK) {
                                log2(ctx, ERROR, "expansion of '%s' failed: %s", cp, var_strerror(var_rc));
                            }
                            log1(ctx, DEBUG, "expansion result '%s'", res_ptr);
                            hdNew->data.s = res_ptr;
                            hdNew->ndata = 1;
                        }
                    }
                    /*FIXME clean up data.m */
                    headerreplace(hdI, hdNew);
                    if (hdNew->prev == NULL)
                        ctx->msg->hdFirst = hdNew;
                    hdI = hdNew;
                }
            }
        }
        else {
            log1(ctx, DEBUG, "rule has no regex but static header %s", hrI->header);
            hdNew = headercreate();
            hdNew->name = strdupex(hrI->header); //FIXME rename ->header to ->name
            if (hrI->val == NULL) {
                log0(ctx, DEBUG, "marking deleted");
                /*FIXME clean up data.s and data.m */
                hdNew->ndata = 0;
            }
            else {
                hdNew->data.s = strdupex(hrI->val);
                hdNew->ndata = 1;
                /*FIXME clean up data.m */
            }
            for (hdI = ctx->msg->hdFirst; hdI != NULL; hdI = hdI->next) { /* for each header */
                if (hdI->name == NULL || strlen(hdI->name) == 0)
                    continue; //FIXME header w/o name cannot happen normally
                log2(ctx, DEBUG, "hrI->header=%s, hdI->name=%s", hrI->header, hdI->name);
                if (strcmp(hrI->header, hdI->name) == 0)
                    break;
            }
            if (hdI != NULL) {
                log1(ctx, DEBUG, "replacing header %s", hrI->header);
                log3(ctx, DEBUG, "ctx->msg->hdFirst=%.8lx, hdI      =%.8lx, hdNew      =%.8lx", ctx->msg->hdFirst, hdI, hdNew);
                log2(ctx, DEBUG, "                            hdI->prev=%.8lx, hdNew->prev=%.8lx", hdI->prev, hdNew->prev);
                log2(ctx, DEBUG, "                            hdI->next=%.8lx, hdNew->next=%.8lx", hdI->next, hdNew->next);
                headerreplace(hdI, hdNew);
                log3(ctx, DEBUG, "ctx->msg->hdFirst=%.8lx, hdI      =%.8lx, hdNew      =%.8lx", ctx->msg->hdFirst, hdI, hdNew);
                log2(ctx, DEBUG, "                            hdI->prev=%.8lx, hdNew->prev=%.8lx", hdI->prev, hdNew->prev);
                log2(ctx, DEBUG, "                            hdI->next=%.8lx, hdNew->next=%.8lx", hdI->next, hdNew->next);
                if (hdNew->prev == NULL) {
                    log0(ctx, DEBUG, "FIXME trace #1");
                    ctx->msg->hdFirst = hdNew;
                }
            }
            else {
                log1(ctx, DEBUG, "appending header %s", hrI->header);
                for (hdI = ctx->msg->hdFirst; hdI->next != NULL; hdI = hdI->next);
                hdI->next = hdNew;
                hdNew->prev = hdI;
            }
        }
    }

    { //FIXME debug code block
        int i;
        headerrule_t *hrD;
        headerdata_t *hdD;

        log0(ctx, DEBUG, "FIXME trace ---------- headerrewrite() ---------- FINISH");
        for (hrD = ctx->option_firstheaderrule; hrD != NULL; hrD = hrD->next)
            log1(ctx, DEBUG, "hrD->header=%s", hrD->header);
        for (hdD = ctx->msg->hdFirst; hdD != NULL; hdD = hdD->next) {
            if (hdD->ndata == 0)
                log1(ctx, DEBUG, "hdD->name=%s: (NO DATA)", hdD->name);
            if (hdD->ndata == 1)
                log2(ctx, DEBUG, "hdD->name:hdD->data.s    %s %s", hdD->name, hdD->data.s);
            if (hdD->ndata > 1)
                for (i = 0; i < hdD->ndata; i++)
                    log3(ctx, DEBUG, "hdD->name:hdD->data.m[%d] %s %s", i, hdD->name, hdD->data.m[i]);
        }
    }
}

static void headersimulation(lmtp2nntp_t *ctx)
{
    headerdata_t *hdI, *hdNew;
    char *replyto[4] = { "foo@example.com", "bar@example.com", "quux@example.com", NULL };

    log0(ctx, DEBUG, "FIXME simulation - GO");

    ctx->msg = msg_create();

    hdNew = headercreate();
    hdNew->name = "To";
    hdNew->data.s = "foo@invalid.com";
    hdNew->ndata = 1;
    ctx->msg->hdFirst = hdNew;
    hdNew->prev = NULL;
    hdI = hdNew;

    hdNew = headercreate();
    hdNew->name = "Reply-To";
    hdNew->data.m = replyto;
    hdNew->ndata = 3;
    hdI->next = hdNew;
    hdNew->prev = hdI;
    hdI = hdNew;

    hdNew = headercreate();
    hdNew->name = "Subject";
    hdNew->data.s = "a tiny little test";
    hdNew->ndata = 1;
    hdI->next = hdNew;
    hdNew->prev = hdI;
    hdI = hdNew;

    hdNew = headercreate();
    hdNew->name = "Date";
    hdNew->data.s = "Mon, 18 Feb 2002 09:43:52 +0100";
    hdNew->ndata = 1;
    hdI->next = hdNew;
    hdNew->prev = hdI;
    hdI = hdNew;

    hdNew = headercreate();
    hdNew->name = "Message-ID";
    hdNew->data.s = "<200201172300.AAA29400@procter012.pg-cw.de>";
    hdNew->ndata = 1;
    hdI->next = hdNew;
    hdNew->prev = hdI;
    hdI = hdNew;

    hdI->next = NULL;
    headerrewrite(ctx);
}

CVSTrac 2.0.1