OSSP CVS Repository

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

ossp-pkg/lmtp2nntp/lmtp2nntp_msg.c 1.14
/*
**  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>.
**
**  msg.c: mail message manipulation library
*/

#include <stdlib.h>
#include <stdio.h>

#include "lmtp2nntp_msg.h"
#include "lmtp2nntp_argz.h"
#include "fixme.h" //FIMXE logbook only
#include "tai.h"
#include <sys/utsname.h> //FIXME createmessageid() hack


#include "str.h"

/* third party */
#include "l2.h"

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#if defined(HAVE_DMALLOC_H) && defined(DMALLOC)
#include "dmalloc.h"
#endif

msg_t *msg_create(void)
{
    msg_t *msg;

    if ((msg = (msg_t *)malloc(sizeof(msg_t))) == NULL)
        return NULL;

    msg->azEnvgroups = NULL;
    msg->asEnvgroups = 0;
    msg->cpMsg = NULL;
    msg->azHeaders = NULL;
    msg->asHeaders = 0;
    msg->hdFirst = NULL;
    msg->cpFid = NULL;
    msg->cpBody = NULL;
    msg->cpMsgid = NULL;
    msg->mail_from = NULL;
    msg->azRcpt = NULL;
    msg->asRcpt = 0;
    msg->azNewsgroups = NULL;
    msg->asNewsgroups = 0;
    msg->l2 = NULL; /* this is a copy only */

    return msg;
}

void msg_destroy(msg_t *msg)
{
    if (msg == NULL)
        return;

    if (msg->azEnvgroups != NULL)
        free(msg->azEnvgroups);
    if (msg->cpMsg != NULL)
        free(msg->cpMsg);
    if (msg->azHeaders != NULL)
        free(msg->azHeaders);
    if (msg->cpFid != NULL)
        free(msg->cpFid);
    if (msg->cpBody != NULL)
        free(msg->cpBody);
    if (msg->cpMsgid != NULL)
        free(msg->cpMsgid);
    if (msg->mail_from != NULL)
        free(msg->mail_from);
    if (msg->azRcpt != NULL)
        free(msg->azRcpt);
    if (msg->azNewsgroups != NULL)
        free(msg->azNewsgroups);
    msg->l2 = NULL; /* this is a copy only, the "parent" needs to clean this up */

    free(msg);
    return;
}

msg_rc_t msg_split(msg_t *msg)
{
    char        *cpName;
    char        *cpValue;
    char        *cpRem;     /* Remainder */
    char        *cp;
    char        *cpHeaders;

    /* INPUTS
     *
     * msg->cpMsg
     * must contain the wholly RFC0822 formatted message with native
     * (unescaped) dots at the beginning of a line, the 'From ' envelope,
     * headers, double newline, body, NUL, no trailing dot;
     *
     * OUTPUTS
     *
     * msg->cpMsg
     * free()d and set to NULL
     * 
     * msg->azHeaders, msg->asHeaders contains the headers in argz format, one
     * logical NUL-terminated line per header which might be wrapped into
     * multiple '\n'-ended physical lines. The "From " envelope, "Received:",
     * "Path:", "To:" and "Cc:" headers are removed silently. The
     * "Newsgroups:" and "Message-ID" headers are removed and their values are
     * stored in separate structures (see below).
     *
     * msg->cpBody
     * contains the unmodified body of the message, NUL-terminated, no
     * trailing dot.
     *
     * msg->cpMsgid
     * contains the message id including surrounding angle brackets.
     *
     * msg->azNewsgroups, asNewsgroups
     * is a argz-type array of strings containing the Newsgroups based on the
     * header information.
     */

    logbook(msg->l2, L2_LEVEL_DEBUG, "split message into header and body");
    if (str_parse(msg->cpMsg, "m/((?:.*?)\\n)\\n(.*)$/s", &cpHeaders, &msg->cpBody) <= 0)
        return MSG_ERR_SPLITHEADBODY;

    free(msg->cpMsg);
    msg->cpMsg = NULL;

    logbook(msg->l2, L2_LEVEL_DEBUG, "replace envelope From w/o colon by X-F: pseudotag");
    /* This eliminates the special case of having one header, which is really
     * an embedded envelope, not ending with a colon while all others do.
     * After splitting headers into name and value pairs this envelope ist
     * stripped off.
     */
    if (strncasecmp(cpHeaders, "From", 4) == 0)
        memcpy(cpHeaders, "X-F:", 4);

    logbook(msg->l2, L2_LEVEL_DEBUG, "unwrap header lines");
    /* poor man's s///g simulator as current str library doesn't support global substitution */
    while (str_parse(cpHeaders, "s/(.*?)\\n[ \\t]+(.*)/$1 $2/s", &cpRem) > 0) {
        free(cpHeaders);
        cpHeaders = cpRem;
    }

    logbook(msg->l2, L2_LEVEL_DEBUG, "split header lines into names and values");
    while (str_parse(cpHeaders, "m/^[> \\t]*([\\x21-\\x7e]+?:)[ \\t]*([^\\n]*?)[ \\t]*\\n(.*)/s", &cpName, &cpValue, &cpRem) > 0) {
        free(cpHeaders);
        cpHeaders = cpRem;
        argz_add(&msg->azHeaders, &msg->asHeaders, cpName);
        argz_add(&msg->azHeaders, &msg->asHeaders, cpValue);
        free(cpName);
        free(cpValue);
    }

    logbook(msg->l2, L2_LEVEL_DEBUG, "check for headers we care about and do whatever neccessary");
    msg->cpMsgid = NULL;
    msg->azNewsgroups = NULL;
    msg->asNewsgroups = 0;
    cp = msg->azHeaders;
    while (cp != NULL) {
        logbook(msg->l2, L2_LEVEL_DEBUG, "processing header \"%s\"", cp);
        if (strcasecmp("X-F:", cp) == 0) {
            argz_delete(&msg->azHeaders, &msg->asHeaders, cp);             /* del  name  */
            argz_delete(&msg->azHeaders, &msg->asHeaders, cp);             /* del  value */
            continue;
        }
        if (strcasecmp("Path:", cp) == 0) {
            argz_delete(&msg->azHeaders, &msg->asHeaders, cp);             /* del  name  */
            argz_delete(&msg->azHeaders, &msg->asHeaders, cp);             /* del  value */
            continue;
        }
        if (strcasecmp("Received:", cp) == 0) {
            argz_delete(&msg->azHeaders, &msg->asHeaders, cp);             /* del  name  */
            if ((msg->cpFid == NULL) &&
                (str_parse(cp, "m/\\sid\\s+<?([\\w\\d]{1,30})/i", &msg->cpFid) > 0))
                    logbook(msg->l2, L2_LEVEL_DEBUG, "found foreign-ID \"%s\" for logging", msg->cpFid);
            argz_delete(&msg->azHeaders, &msg->asHeaders, cp);             /* del  value */
            continue;
        }
        if (strcasecmp("To:", cp) == 0) {
            argz_delete(&msg->azHeaders, &msg->asHeaders, cp);             /* del  name  */
            argz_delete(&msg->azHeaders, &msg->asHeaders, cp);             /* del  value */
            continue;
        }
        if (strcasecmp("Cc:", cp) == 0) {
            argz_delete(&msg->azHeaders, &msg->asHeaders, cp);             /* del  name  */
            argz_delete(&msg->azHeaders, &msg->asHeaders, cp);             /* del  value */
            continue;
        }
        if (strcasecmp("Message-ID:", cp) == 0) {
            if (msg->cpMsgid != NULL)
                return MSG_ERR_SPLITIDMULTI;
            argz_delete(&msg->azHeaders, &msg->asHeaders, cp);             /* del  name  */
            if ((cp == NULL) || (strlen(cp) == 0))                         /* get  value */
                return MSG_ERR_SPLITIDEMPTY;
            if ((msg->cpMsgid = strdup(cp)) == NULL)
                return MSG_ERR_MEM;
            argz_delete(&msg->azHeaders, &msg->asHeaders, cp);             /* del  value */
            continue;
        }
        if (strcasecmp("Newsgroups:", cp) == 0) {
            argz_delete(&msg->azHeaders, &msg->asHeaders, cp);             /* del  name  */
            if (argz_add(&msg->azNewsgroups, &msg->asNewsgroups, cp) != 0) /* get  value */
                return MSG_ERR_MEM;
            argz_delete(&msg->azHeaders, &msg->asHeaders, cp);             /* del  value */
            continue;
        }
        if ((cp = argz_next(msg->azHeaders, msg->asHeaders, cp)) == NULL)  /* next value */
            break;
        if ((cp = argz_next(msg->azHeaders, msg->asHeaders, cp)) == NULL)  /* next name  */
            break;
    }

    logbook(msg->l2, L2_LEVEL_DEBUG, "checking Message-ID");
    if (msg->cpMsgid == NULL)
        return MSG_ERR_SPLITIDNONE;

    logbook(msg->l2, L2_LEVEL_DEBUG, "checking Newsgroups");
    if (msg->azNewsgroups != NULL) {
        argz_stringify(msg->azNewsgroups, msg->asNewsgroups, ',');
        if (argz_create_sep(msg->azNewsgroups, ',', &msg->azNewsgroups, &msg->asNewsgroups) != 0)
            return MSG_ERR_MEM;
    }

    logbook(msg->l2, L2_LEVEL_DEBUG, "adding mandatory Path: header");
    argz_add(&msg->azHeaders, &msg->asHeaders, "Path:");
    argz_add(&msg->azHeaders, &msg->asHeaders, "lmtp2nntp!not-for-mail");

    logbook(msg->l2, L2_LEVEL_DEBUG, "split complete");
    return MSG_OK;
}

msg_rc_t msg_join(msg_t *msg)
{
    char        *cp;
    char        *cpRem;
    char       **aHeaders;
    int          i;
    int          o;
    char        *cpCut;
    char        *cpWrap;
    char         c;
    char         cOld;
    int          n;
    char        *cpHeaders;
    char        *azNewheaders;
    size_t       asNewheaders;

    logbook(msg->l2, L2_LEVEL_DEBUG, "verify Newsgroups");
    if (msg->azNewsgroups == NULL)
        return MSG_ERR_JOINGROUPNONE;
    argz_stringify(msg->azNewsgroups, msg->asNewsgroups, ',');
    if (strlen(msg->azNewsgroups) == 0)
        return MSG_ERR_JOINGROUPEMPTY;
    argz_add(&msg->azHeaders, &msg->asHeaders, "Newsgroups:");
    argz_add(&msg->azHeaders, &msg->asHeaders, msg->azNewsgroups);

    logbook(msg->l2, L2_LEVEL_DEBUG, "verify Message-ID");
    if (msg->cpMsgid == NULL)
        return MSG_ERR_JOINIDNONE;
    if (strlen(msg->cpMsgid) == 0)
        return MSG_ERR_JOINIDEMPTY;
    argz_add(&msg->azHeaders, &msg->asHeaders, "Message-ID:");
    argz_add(&msg->azHeaders, &msg->asHeaders, msg->cpMsgid);

    logbook(msg->l2, L2_LEVEL_DEBUG, "merge name/value pairs into single string");
    argz_add(&msg->azHeaders, &msg->asHeaders, ""); /* append empty string */
    if ((aHeaders = (char **)malloc((argz_count(msg->azHeaders, msg->asHeaders) + 1) * sizeof(char *))) == NULL)
        return MSG_ERR_MEM;
    argz_extract(msg->azHeaders, msg->asHeaders, aHeaders);
    /* replace the trailing NUL, which is *(cp-1) of the predecessor, with a
     * space at every second string. Break action when terminating NULL string
     * is detected */
    i=0;
    while(1) {
        if ((cp = aHeaders[++i]) == NULL)
            break;
        *(cp-1) = ' ';
        if ((cp = aHeaders[++i]) == NULL)
            break;
    }
    free(aHeaders);

    logbook(msg->l2, L2_LEVEL_DEBUG, "fold headers");
    /* A logical line is split into one or more physical '\n'-terminated
     * lines. The physical line is never longer than WRAPAT characters. This
     * includes the folded data and the header name + colon + space for the
     * first line and WRAPUSING string prefix for all other lines. Leading and
     * trailing blanks of folded lines are removed while blanks inside the
     * line are preserved.  The header is never left alone in a physical line.
     * Fragments exceeding WRAPAT characters without having a blank as a
     * splitting point are forcibly cut at a non-blank character.
     */
    azNewheaders = NULL;
    asNewheaders = 0;
    cp = NULL;
    while ((cp = argz_next(msg->azHeaders, msg->asHeaders, cp)) != NULL) {
        if (strlen(cp) > WRAPAT) {
            cpRem = cp;
            cpWrap = NULL;
            for (o = 0; (cpRem[o] != ':') && (cpRem[o] != NUL); o++); /* offset name so at least one char of value remains in first line */
            o += 2; /* skip ": " */
            while ((strlen(cpRem) + (cpWrap == NULL ? 0 : strlen(WRAPUSING))) > WRAPAT) {
                for (i = WRAPAT - 1 - (cpWrap == NULL ? 0 :
                    strlen(WRAPUSING)); (i >= o) && !isspace((int)cpRem[i]); i--);
                if (i < o)
                    i = WRAPAT - 1 - (cpWrap == NULL ? 0 : strlen(WRAPUSING) - 1); /* sorry, forced cut at non-blank */
                cpCut = cpRem;
                cpRem += i;
                for (; (isspace((int)*cpRem) && (*cpRem != NUL)); cpRem++); /* skip next lines leading blanks */
                for (; (i >= o) && isspace((int)cpCut[i-1]); i--); /* chop off this lines trailing blanks */
                if (i >= o) { /* only keep line fragment if some non-blanks inside */
                    if (cpWrap == NULL) {
                        if ((cpWrap = (char *)malloc(i+strlen(WRAPUSING)+1)) == NULL)
                            return MSG_ERR_MEM;
                        *cpWrap = NUL;
                        o = 1;
                    }
                    else {
                        if ((cpWrap = (char *)realloc(cpWrap, strlen(cpWrap)+i+strlen(WRAPUSING)+1)) == NULL)
                            return MSG_ERR_MEM;
                        strcat(cpWrap, WRAPUSING);
                    }
                    strncat(cpWrap, cpCut, i);
                }
            }
            if (strlen(cpRem) > 0) {
                if ((cpWrap = (char *)realloc(cpWrap, strlen(cpWrap)+strlen(cpRem)+strlen(WRAPUSING)+1)) == NULL)
                        return MSG_ERR_MEM;
                strcat(cpWrap, WRAPUSING);
                strcat(cpWrap, cpRem);
            }
            argz_add(&azNewheaders, &asNewheaders, cpWrap);
            logbook(msg->l2, L2_LEVEL_DEBUG, "a folded header \"%{text}D\"", cpWrap, strlen(cpWrap));
            free(cpWrap);
        }
        else {
            argz_add(&azNewheaders, &asNewheaders, cp);
            logbook(msg->l2, L2_LEVEL_DEBUG, "verbatim header \"%{text}D\"", cp, strlen(cp));
        }
    }
    free(msg->azHeaders);
    msg->azHeaders = azNewheaders;
    msg->asHeaders = asNewheaders;

    logbook(msg->l2, L2_LEVEL_DEBUG, "strigify headers");
    argz_stringify(msg->azHeaders, msg->asHeaders, '\n');
    cpHeaders = msg->azHeaders;

    /********************************************************************
     * header + CRLF + body + '.' + CRLF + NUL, replacing NL with CRLF *
     ********************************************************************/

    logbook(msg->l2, L2_LEVEL_DEBUG, "assemble header and body");
    n = 0;
    /* count size of headers, reserve space for NL to CRLF conversion */
    for (i = 0; ((c = cpHeaders[i]) != NUL); i++) {
        if (c == '\n')
            n++;
        n++;
    }
    /* if headers don't end with NL, reserve space for CRLF */
    if (i >= 0 && cpHeaders[i - 1] != '\n')
        n+=2;
    /* reserve space for CRLF between headers and body */
    n+=2;
    /* count size of body, reserve space for NL-DOT escape and NL to CRLF conversion */
    cOld = '\n';
    for (i = 0; ((c = msg->cpBody[i]) != NUL); i++) {
        if (c == '\n')
            n++;
        if (c == '.' && cOld == '\n')
            n++;
        n++;
        cOld = c;
    }
    /* if body doesn't end with NL, reserve space for CRLF */
    if (i >= 0 && msg->cpBody[i - 1] != '\n')
        n+=2;
    /* reserve space for terminating '.'-CRLF-NUL at the end of the message */
    n+=4;

    if ((msg->cpMsg = (char *)malloc(n)) == NULL)
        return MSG_ERR_MEM;

    n = 0;
    /* copy headers, do NL to CRLF conversion */
    for (i = 0; ((c = cpHeaders[i]) != NUL); i++) {
        if (c == '\n')
            msg->cpMsg[n++] = '\r';
        msg->cpMsg[n++] = c;
    }
    /* if headers don't end with NL, append CRLF */
    if (i >= 0 && cpHeaders[i - 1] != '\n') {
        msg->cpMsg[n++] = '\r';
        msg->cpMsg[n++] = '\n';
    }
    /* add CRLF between headers and body */
    msg->cpMsg[n++] = '\r';
    msg->cpMsg[n++] = '\n';
    /* copy body, do NL-DOT escape and NL to CRLF conversion */
    cOld = '\n';
    for (i = 0; ((c = msg->cpBody[i]) != NUL); i++) {
        if (c == '\n')
            msg->cpMsg[n++] = '\r';
        if (c == '.' && cOld == '\n')
            msg->cpMsg[n++] = '.';
        msg->cpMsg[n++] = c;
        cOld = c;
    }
    /* if body doesn't end with NL, append CRLF */
    if (i >= 0 && msg->cpBody[i - 1] != '\n') {
        msg->cpMsg[n++] = '\r';
        msg->cpMsg[n++] = '\n';
    }
    /* add terminating '.'-CRLF-NUL at the end of the message */
    msg->cpMsg[n++] =  '.';
    msg->cpMsg[n++] = '\r';
    msg->cpMsg[n++] = '\n';
    msg->cpMsg[n]   = NUL;

    logbook(msg->l2, L2_LEVEL_DEBUG, "join complete");
    return MSG_OK;
}

char *msg_error(msg_rc_t rc)
{
    char *str;
                                              str = "MSG: no description";
    if      (rc == MSG_OK                   ) str = "MSG: no error";
    else if (rc == MSG_ERR_MEM              ) str = "MSG: memory";
    else if (rc == MSG_ERR_SPLITHEADBODY    ) str = "MSG: split into header and body failed";
    else if (rc == MSG_ERR_SPLITLEN         ) str = "MSG: header is too short";
    else if (rc == MSG_ERR_SPLITMISSINGFROM ) str = "MSG: header is missing 'From ' envelope";
    else if (rc == MSG_ERR_SPLITIDNONE      ) str = "MSG: header is missing 'Message-ID'";
    else if (rc == MSG_ERR_SPLITIDEMPTY     ) str = "MSG: header has empty 'Message-ID'";
    else if (rc == MSG_ERR_SPLITIDMULTI     ) str = "MSG: header has multiple 'Message-ID's";
    else if (rc == MSG_ERR_JOINGROUPNONE    ) str = "MSG: join with no 'Newsgroup'";
    else if (rc == MSG_ERR_JOINGROUPEMPTY   ) str = "MSG: join with empty 'Newsgroup'";
    else if (rc == MSG_ERR_JOINIDNONE       ) str = "MSG: join with no 'Message-ID'";
    else if (rc == MSG_ERR_JOINIDEMPTY      ) str = "MSG: join with empty 'Message-ID'";
    return str;
}

    //FIXME below is the header rewriting engine which must be cleaned up and integrated

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)
        throw(0,0,0);
    if (hdC->next != NULL && hdC->next->prev == hdC)
        throw(0,0,0);
    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;
}

static var_rc_t regex_lookup(
    var_t *var, 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;
    var_rc_t rc;
    char *cp;
    int i;

    logbook(ctx->l2, L2_LEVEL_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)
        logbook(ctx->l2, L2_LEVEL_DEBUG, "rgx_lookup variable \"%s\" (%d) ok: result is \"%s\" (%d)", var_ptr, var_len, *val_ptr, *val_len);
    else
        logbook(ctx->l2, L2_LEVEL_DEBUG, "rgx_lookup variable \"%s\" (%d) failed: %s (%d)", var_ptr, var_len, var_strerror(var, rc, &cp) == VAR_OK ? cp : "Unknown Error", rc);
    return rc;
}

static var_rc_t canonifydate(
    const char  *cpArg,  size_t nArg,
    const char  *cpVal,  size_t nVal,
          char **cppOut, size_t *pnOut, size_t *pnOutsize)
{
    /* RFC0822
     5.  DATE AND TIME SPECIFICATION
     5.1.  SYNTAX

     date-time   =  [ day "," ] date time          ; "dd mm yy" or "hh:mm:ss zzz"
     day         =  "Mon" / "Tue" / "Wed" / "Thu" / "Fri" / "Sat" / "Sun"
     date        =  1*2DIGIT month 2DIGIT          ; day month year e.g. 20 Jun 82
     month       =  "Jan" / "Feb"/ "Mar" / "Apr"/ "May" / "Jun"/ "Jul" / "Aug"/ "Sep" / "Oct"/ "Nov" / "Dec"
     time        =  hour zone                      ; ANSI and Military
     hour        =  2DIGIT ":" 2DIGIT [":" 2DIGIT] ; 00:00:00 - 23:59:59
     zone        =  "UT"  / "GMT"                  ; Universal Time, North American : UT
                 /  "EST" / "EDT"                  ;  Eastern:  - 5/ - 4
                 /  "CST" / "CDT"                  ;  Central:  - 6/ - 5
                 /  "MST" / "MDT"                  ;  Mountain: - 7/ - 6
                 /  "PST" / "PDT"                  ;  Pacific:  - 8/ - 7
                 /  1ALPHA                         ; Military: Z = UT; A:-1; (J not used);  M:-12; N:+1; Y:+12
                 / ( ("+" / "-") 4DIGIT )          ; Local differential  hours+min. (HHMM)
    */
    tai_rc_t rv;
    int i;
    int bOk;
    char *fmt[] = {
        "%a, %d %b %Y %H:%M:%S %z", /* RFC0822 but four digit year */
        "%a, %d %b %y %H:%M:%S %z", /* RFC0822 strict */
        "%a, %d %b %Y %H:%M %z",    /* RFC0822 but four digit year */
        "%a, %d %b %y %H:%M %z",    /* RFC0822 strict */
        "%d %b %Y %H:%M:%S %z",     /* RFC0822 but four digit year */
        "%d %b %y %H:%M:%S %z",     /* RFC0822 strict */
        "%d %b %Y %H:%M %z",        /* RFC0822 but four digit year */
        "%d %b %y %H:%M %z",        /* RFC0822 strict */
        "%a %b %d %H:%M:%S %Y",     /* strange Mon Jan 27 12:34:56 2001 */
        NULL };
    tai_t *now;
    tai_t *pastrange;
    tai_t *futurerange;
    tai_t *value;

    tai_create(&now);   //FIXME ex
    (void /*FIXME*/)tai_import(now, TAI_TYPE_UNIX);

    /* parse argument ([past][,[future]])
        () (,)     infinite range in the past, infinite range in the future => canonify only
        (7), (7,)  seven days in the past, infinite range in the future
        (,3)       infinite range in the past, three days in the future
        (7,0)      seven days in the past, no point in future allowed
        (0,3)      no point in past allowed, three days in the future => useless
        (0,0)      now
     */
    pastrange   = NULL; /* infinite */
    futurerange = NULL; /* infinite */
    if (cpArg != NULL) {
        char *cpP, *cpF;
        cpP = strdupex(cpArg);
        cpF = strchr(cpP, ',');
        if (cpF == NULL)
            cpF = "";
        else
            *cpF++ = NUL;
        if (strlen(cpP) != 0) {
            tai_create(&pastrange);   //FIXME ex
            (void /*FIXME*/)tai_import(pastrange  , TAI_TYPE_SECONDS, 24*60*60*atoi(cpP));
        }
        if (strlen(cpF) != 0) {
            tai_create(&futurerange); //FIXME ex
            (void /*FIXME*/)tai_import(futurerange, TAI_TYPE_SECONDS, 24*60*60*atoi(cpF));
        }
        free(cpP);
    }

    printf("DEBUG: cpVal=\"%41s\", cpArg=\"%s\"\n", cpVal, cpArg);
    if ((cpVal == NULL) || (strlen(cpVal) == 0)) {
        *cppOut    = (char *)mallocex(DATELENMAX);
        *pnOutsize = DATELENMAX;
        (void /*FIXME*/)tai_format(now, *cppOut, DATELENMAX, "%a, %d %b %Y %H:%M:%S %z");
        *pnOut     = strlen(*cppOut);
    }
    else {
        tai_create(&value);   //FIXME ex
        bOk = FALSE;
        for (i = 0; !bOk && (fmt[i] != NULL); i++) {
            if ((rv = tai_parse(value, cpVal, strlen(cpVal), fmt[i])) == TAI_OK) 
                bOk = TRUE;
            //FIXME printf("DEBUG: checked against \"%41s\" returned %d\n", fmt[i], rv);
        }
        if (   bOk
#if 0
            tai_op is not yet implemented
            && ((pastrange   != NULL) && (tai_op(value, TAI_OP_GT, pastrange  ) == TAI_OK))
            && ((futurerange != NULL) && (tai_op(value, TAI_OP_LT, futurerange) == TAI_OK))
#endif
            ) {
            *cppOut    = (char *)mallocex(DATELENMAX);
            *pnOutsize = DATELENMAX;
            (void /*FIXME*/)tai_format(value, *cppOut, DATELENMAX, "%a, %d %b %Y %H:%M:%S %z");
            *pnOut     = strlen(*cppOut);
        }
        else {
            *cppOut    = (char *)mallocex(0);
            *pnOutsize = 0;
            *pnOut     = 0;
        }
    }

    /* cleanup */
    if(now != NULL)
        tai_destroy(now);
    if(pastrange != NULL)
        tai_destroy(pastrange);
    if(futurerange != NULL)
        tai_destroy(futurerange);
    if(value != NULL)
        tai_destroy(value);

    return VAR_OK;
}

static var_rc_t createmessageid(
    const char  *cpArg,  size_t nArg,
    const char  *cpVal,  size_t nVal,
          char **cppOut, size_t *pnOut, size_t *pnOutsize)
{
    char *cp;
    static int mcounter = 0; //FIXME no statics in this program!

    struct utsname name; //FIXME this must be taken from global - requires context
    if (uname(&name) == -1) {
        memcpy(name.nodename, "localhost.invalid", strlen("localhost.invalid") + 1);
    }

    /* idea and first implementation as a contribution to lmtp2nntp v1.0.0
       by Christos Ricudis <ricudis@paiko.gr> */

    cp = (char *)malloc( 100); //FIXME how much exacly?
    str_format(cp, 100, "<%0.11d$%0.6d$%0.2d$%.20s$@%.40s>",
            time(NULL), getpid(), mcounter++, "noFid" /*FIXME ((msg->cpFid == NULL)?"noFid":(msg->cpFid))*/, name.nodename);
    /*if (msg->cpMsgid != NULL)
        free(msg->cpMsgid);
    msg->cpMsgid = cp; //FIXME what about aligning lib_val here?
    */
    *cppOut    = strdupex(cp);
    *pnOutsize = strlen(cp) + 1;
    *pnOut     = strlen(cp);
    return VAR_OK;
}

static var_rc_t operate_cb(
    var_t *var, void *ctx,
    const char  *op_ptr, size_t op_len,
    const char  *arg_ptr, size_t arg_len,
    const char  *val_ptr, size_t val_len,
    char **out_ptr, size_t *out_len, size_t *out_size)
{
    int i;

    if (val_ptr == NULL) {
        *out_ptr = "";
        *out_len = 0;
        *out_size = 0;
        return VAR_OK;
    }
    if (op_len == 6 && strncmp(op_ptr, "return", 6) == 0) { //FIXME needless block
        *out_ptr = malloc(arg_len);
        *out_len = arg_len;
        *out_size = arg_len;
        memcpy(*out_ptr, arg_ptr, arg_len);
        return VAR_OK;
    }
    else if (op_len == 15 && strncmp(op_ptr, "createmessageid", 15) == 0) {
        return createmessageid(arg_ptr, arg_len, val_ptr, val_len, out_ptr, out_len, out_size);
    }
    else if (op_len == 12 && strncmp(op_ptr, "canonifydate", 12) == 0) {
        return canonifydate(arg_ptr, arg_len, val_ptr, val_len, out_ptr, out_len, out_size);
    }
    else 
        return VAR_ERR_UNDEFINED_OPERATION;
}

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 */

            /*FIXME we want O(1) here */
            for (hdP = NULL, hdI = msg->hdFirst; hdI != NULL; hdP = hdI, hdI = hdI->next) { /* for each matrix header */
                if (hdI->name == NULL || strlen(hdI->name) == 0 || hdI->ndata == 0)
                    continue;
                if (strcasecmp(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) {
                logbook(msg->l2, L2_LEVEL_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;
                logbook(msg->l2, L2_LEVEL_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 {
                logbook(msg->l2, L2_LEVEL_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;
            }
        }
    }
    cleanup {
        if (hdNew != NULL)
            free((headerdata_t *)hdNew);
    }
    catch(ex) {
        rethrow;
    }
}

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 */
            logbook(msg->l2, L2_LEVEL_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) {
                logbook(msg->l2, L2_LEVEL_DEBUG, "header=%s, no data", hdI->name);
            }
            else if(hdI->ndata == 1) { /* header data is single valued */
                logbook(msg->l2, L2_LEVEL_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++) {
                    logbook(msg->l2, L2_LEVEL_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]);
                }
            }
        }
    }
    catch(ex) {
        rethrow;
    }
}

void headerrewrite(lmtp2nntp_t *ctx)
{
    headerrule_t *hrI;
    headerdata_t *hdI, *hdNew;
    regex_ctx_t *regex_ctx;
#define OVECSIZE 30
    int            ovec[OVECSIZE];
    var_rc_t rc; char *cp; //FIXME what a bad name, it's not the returncode of this function

    /* short circuit in case no headerrules were set up */
    if (ctx->option_firstheaderrule == NULL)
        return;

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

        logbook(ctx->l2, L2_LEVEL_DEBUG, "FIXME trace ---------- headerrewrite() ----------");
        for (hrD = ctx->option_firstheaderrule; hrD != NULL; hrD = hrD->next)
            logbook(ctx->l2, L2_LEVEL_DEBUG, "hrD->header=%s", hrD->header);
        for (hdD = ctx->msg->hdFirst; hdD != NULL; hdD = hdD->next) {
            if (hdD->ndata == 0)
                logbook(ctx->l2, L2_LEVEL_DEBUG, "hdD->name=%s: (NO DATA)", hdD->name);
            if (hdD->ndata == 1)
                logbook(ctx->l2, L2_LEVEL_DEBUG, "hdD->name:hdD->data.s    %s %s", hdD->name, hdD->data.s);
            if (hdD->ndata > 1)
                for (i = 0; i < hdD->ndata; i++)
                    logbook(ctx->l2, L2_LEVEL_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;
    if ((rc = var_config(ctx->config_varregex, VAR_CONFIG_CB_VALUE, regex_lookup, regex_ctx)) != VAR_OK) {
        logbook(ctx->l2, L2_LEVEL_ERROR, "configure regex callback failed with %s (%d)", var_strerror(ctx->config_varregex, rc, &cp) == VAR_OK ? cp : "Unknown Error", rc);
        throw(0,0,0);
    }
    if ((rc = var_config(ctx->config_varctx, VAR_CONFIG_CB_OPERATION, operate_cb, NULL)) != VAR_OK) { //FIXME isn't main a better place to do this?
        logbook(ctx->l2, L2_LEVEL_ERROR, "configure operate callback failed with %s (%d)", var_strerror(ctx->config_varctx, rc, &cp) == VAR_OK ? cp : "Unknown Error", rc);
        throw(0,0,0);
    }
    for (hrI = ctx->option_firstheaderrule; hrI != NULL; hrI = hrI->next) { /* for each rule */
        { //FIXME debug code block
            int i;
            headerrule_t *hrD;
            headerdata_t *hdD;

            logbook(ctx->l2, L2_LEVEL_DEBUG, "FIXME trace ---------- headerrewrite() ---------- MIDDLE");
            for (hrD = ctx->option_firstheaderrule; hrD != NULL; hrD = hrD->next)
                logbook(ctx->l2, L2_LEVEL_DEBUG, "hrD->header=%s", hrD->header);
            for (hdD = ctx->msg->hdFirst; hdD != NULL; hdD = hdD->next) {
                //logbook(ctx->l2, L2_LEVEL_DEBUG, "hdD=%.8lx, hdD->name=%.8lx, hdD->data.s=%.8lx", (long)hdD, (long)&hdD->name, (long)&hdD->data.s);
                if (hdD->ndata == 0)
                    logbook(ctx->l2, L2_LEVEL_DEBUG, "hdD->name=%s: (NO DATA)", hdD->name);
                if (hdD->ndata == 1)
                    logbook(ctx->l2, L2_LEVEL_DEBUG, "hdD->name:hdD->data.s    %s %s", hdD->name, hdD->data.s);
                if (hdD->ndata > 1)
                    for (i = 0; i < hdD->ndata; i++)
                        logbook(ctx->l2, L2_LEVEL_DEBUG, "hdD->name:hdD->data.m[%d] %s %s", i, hdD->name, hdD->data.m[i]);
            }
        }
        if (hrI->regex != NULL) {
            logbook(ctx->l2, L2_LEVEL_DEBUG, "rule has regex %s", hrI->regex);
            for (hdI = ctx->msg->hdFirst; hdI != NULL; hdI = hdI->next) { /* for each header */
                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;
                    logbook(ctx->l2, L2_LEVEL_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++)
                            logbook(ctx->l2, L2_LEVEL_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;
                        logbook(ctx->l2, L2_LEVEL_DEBUG, "expanding regex references in headername '%s'", hrI->header);
                        if ((var_rc = var_expand(ctx->config_varregex, hrI->header, strlen(hrI->header), &res_ptr, NULL, FALSE)) != VAR_OK) {
                            logbook(ctx->l2, L2_LEVEL_ERROR, "expansion of '%s' failed: %s", hrI->header, var_strerror(ctx->config_varregex, rc, &cp) == VAR_OK ? cp : "Unknown Error", rc);
                        }
                        logbook(ctx->l2, L2_LEVEL_DEBUG, "expansion result '%s'", res_ptr);
                        if (strlen(res_ptr) == 0) {
                            logbook(ctx->l2, L2_LEVEL_DEBUG, "marking deleted - emtpy headername");
                            hdNew->name = NULL; //FIXME rename ->header to ->name
                            /*FIXME clean up data.s and data.m */
                            hdNew->ndata = 0;
                        }
                        else {
                            hdNew->name = res_ptr; //FIXME rename ->header to ->name
                        }
                    }
                    if (hrI->val == NULL) {
                        logbook(ctx->l2, L2_LEVEL_DEBUG, "marking deleted - empty headervalue before expansion");
                        /*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;
                            logbook(ctx->l2, L2_LEVEL_DEBUG, "expanding regex references in header value '%s'", hrI->val);
                            if ((var_rc = var_expand(ctx->config_varregex, hrI->val, strlen(hrI->val), &res_ptr, NULL, FALSE)) != VAR_OK) {
                                logbook(ctx->l2, L2_LEVEL_ERROR, "expansion of '%s' failed: %s", hrI->val, var_strerror(ctx->config_varregex, rc, &cp) == VAR_OK ? cp : "Unknown Error", rc);
                            }
                            logbook(ctx->l2, L2_LEVEL_DEBUG, "expansion result '%s'", res_ptr);
                            cp = res_ptr;
                        }
                        /* expanding variables into header value */
                        if (hrI->val != NULL) {
                            var_rc_t var_rc;
                            char *res_ptr;
                            logbook(ctx->l2, L2_LEVEL_DEBUG, "expanding variables in header value '%s'", hrI->val);
                            if ((var_rc = var_expand(ctx->config_varctx, cp, strlen(cp), &res_ptr, NULL, FALSE)) != VAR_OK) {
                                logbook(ctx->l2, L2_LEVEL_ERROR, "expansion of '%s' failed: %s", cp, var_strerror(ctx->config_varctx, rc, &cp) == VAR_OK ? cp : "Unknown Error", rc);
                            }
                            logbook(ctx->l2, L2_LEVEL_DEBUG, "expansion result '%s'", res_ptr);
                            if (strlen(res_ptr) == 0) {
                                logbook(ctx->l2, L2_LEVEL_DEBUG, "marking deleted - empty headervalue after expansion");
                                /*FIXME clean up data.s and data.m */
                                hdNew->ndata = 0;
                            }
                            else {
                                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 {
            logbook(ctx->l2, L2_LEVEL_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) {
                logbook(ctx->l2, L2_LEVEL_DEBUG, "marking deleted");
                /*FIXME clean up data.s and data.m */
                hdNew->ndata = 0;
            }
            else {
                /*FIXME clean up data.m */
                /* expanding variables into header value */
                var_rc_t var_rc;
                char *res_ptr;
                logbook(ctx->l2, L2_LEVEL_DEBUG, "expanding variables in header value '%s'", hrI->val);
                if ((var_rc = var_expand(ctx->config_varctx, hrI->val, strlen(hrI->val), &res_ptr, NULL, FALSE)) != VAR_OK) {
                    logbook(ctx->l2, L2_LEVEL_ERROR, "expansion of '%s' failed: %s", hrI->val, var_strerror(ctx->config_varctx, rc, &cp) == VAR_OK ? cp : "Unknown Error", rc);
                }
                logbook(ctx->l2, L2_LEVEL_DEBUG, "expansion result '%s'", res_ptr);
                if (strlen(res_ptr) == 0) {
                    logbook(ctx->l2, L2_LEVEL_DEBUG, "marking deleted - empty headervalue after expansion");
                    /*FIXME clean up data.s and data.m */
                    hdNew->ndata = 0;
                }
                else {
                    hdNew->data.s = res_ptr;
                    hdNew->ndata = 1;
                }
            }
            for (hdI = ctx->msg->hdFirst; hdI != NULL; hdI = hdI->next) { /* for each header */
                if (hdI->name == NULL || strlen(hdI->name) == 0)
                    continue;
                logbook(ctx->l2, L2_LEVEL_DEBUG, "hrI->header=%s, hdI->name=%s", hrI->header, hdI->name);
                if (strcasecmp(hrI->header, hdI->name) == 0)
                    break;
            }
            if (hdI != NULL) {
                logbook(ctx->l2, L2_LEVEL_DEBUG, "replacing header %s", hrI->header);
                headerreplace(hdI, hdNew);
                if (hdNew->prev == NULL) {
                    logbook(ctx->l2, L2_LEVEL_DEBUG, "FIXME trace #1");
                    ctx->msg->hdFirst = hdNew;
                }
            }
            else {
                logbook(ctx->l2, L2_LEVEL_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;

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

CVSTrac 2.0.1