/*
**  OSSP lmtp2nntp - Mail to News Gateway
**  Copyright (c) 2002-2003 Ralf S. Engelschall <rse@engelschall.com>
**  Copyright (c) 2002-2003 The OSSP Project <http://www.ossp.org/>
**  Copyright (c) 2002-2003 Cable & Wireless Germany <http://www.cw.com/de/>
**
**  This file is part of OSSP lmtp2nntp, an LMTP speaking local
**  mailer which forwards mails as Usenet news articles via NNTP.
**  It can be found at http://www.ossp.org/pkg/tool/lmtp2nntp/.
**
**  This program is free software; you can redistribute it and/or
**  modify it under the terms of the GNU General Public  License
**  as published by the Free Software Foundation; either version
**  2.0 of the License, or (at your option) any later version.
**
**  This program is distributed in the hope that it will be useful,
**  but WITHOUT ANY WARRANTY; without even the implied warranty of
**  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
**  General Public License for more details.
**
**  You should have received a copy of the GNU General Public License
**  along with this file; if not, write to the Free Software
**  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
**  USA, or contact the OSSP project <ossp@ossp.org>.
**
**  lmtp2nntp_option.c: option parsing
*/

#include <stdlib.h>
#include <stdio.h>
#include <sys/stat.h>

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

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

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

/* own headers */
#include "lmtp2nntp_global.h"
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#if defined(HAVE_DMALLOC_H) && defined(WITH_DMALLOC)
#include "dmalloc.h"
#endif
#include "lmtp2nntp_option.h"
#include "lmtp2nntp_config.h"
#include "lmtp2nntp_lmtp.h"
#include "lmtp2nntp_nntp.h"
#include "lmtp2nntp_msg.h"
#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

#if 0
static val_rc_t dumper(void *ctx, const char *name, int type, const char *desc, void *data)
{
    optionval_t *oc;
    int i;

    if (type != VAL_TYPE_PTR)
        return VAL_OK;

    oc = *(optionval_t **)data;

    switch (oc->type) {
        case OPT_FLAG:
            printf("DEBUG: <%5s>, name=<%20s>, OPT_FLAG,   desc=<%20s>, data@%.8lx->[%d]%d\n",     (char *)ctx, name, desc, (long)oc, oc->ndata, oc->data.f); break;
        case OPT_SINGLE:
            printf("DEBUG: <%5s>, name=<%20s>, OPT_SINGLE, desc=<%20s>, data@%.8lx->[%d]\"%s\"\n", (char *)ctx, name, desc, (long)oc, oc->ndata, oc->data.s == NULL ? "NULL" : oc->data.s);
            break;
        case OPT_MULTI:
            printf("DEBUG: <%5s>, name=<%20s>, OPT_MULTI,  desc=<%20s>, data@%.8lx->[%d]%.8lx\n",  (char *)ctx, name, desc, (long)oc, oc->ndata, (long)oc->data.m);
            for (i = 0; i < oc->ndata; i++) {
                {
                    int j;
                    printf("DEBUG: ");
                    for (j=0; j<8; j++) printf("%.2x ", (unsigned char)oc->data.m[i][j]);
                    printf(" ");
                    for (j=0; j<8; j++) printf("%c", isprint(oc->data.m[i][j]) ? oc->data.m[i][j] : '.');
                    printf(" ");
                }
                printf("DEBUG: [%3d] %.8lx \"%s\"\n", i, (long)oc->data.m[i], oc->data.m[i]);
            }
            break;
        default:
            break;
    }
    return VAL_OK;
}
#endif

static lmtp2nntp_option_rc_t option_find(lmtp2nntp_option_t *o, int number, optionval_t **ocp)
{
    if (o == NULL || ocp == NULL)
        return OPTION_ERR_ARG;

    *ocp = o->first;
    while (*ocp != NULL && (*ocp)->number != number)
        *ocp = (*ocp)->next;

    if (*ocp == NULL)
        return OPTION_ERR_NUM;

    return OPTION_OK;
}


static void option_register(lmtp2nntp_option_t *o, char *longname, char shortname, optiontype_t type, char *def, char *descrip, char *argdescrip, optionloop_cb_t *cb, char *cbctx)
{
    volatile struct {
        optionval_t *oc;
    } v;
    ex_t ex;

    v.oc = NULL;
    try {
        if (o == NULL || longname == NULL)
            throw(option_register, o, OPTION_ERR_ARG);

        /* create a optionval_t structure and initialize exception-uncritical data */
        v.oc = (optionval_t *)mallocex(sizeof(optionval_t));
        v.oc->next       = NULL;
        v.oc->parent     = o;
        v.oc->longname   = NULL;
        v.oc->shortname  = shortname;
        v.oc->descrip    = NULL;
        v.oc->argdescrip = NULL;
        v.oc->type       = type;
        v.oc->cb         = cb;
        v.oc->cbctx      = cbctx;
        v.oc->val        = o->vo;
        v.oc->number     = o->pi + 1; /* 0 is a reserved val in popt, so offset 1 */
        switch (type) {
            case OPT_FLAG:   v.oc->data.f = 0;    break;
            case OPT_SINGLE: v.oc->data.s = NULL; break;
            case OPT_MULTI:  v.oc->data.m = NULL; break;
        }
        v.oc->ndata      = 0;
        /* preinitialization complete, now initialize exception-critical data*/
        v.oc->longname = strdupex(longname);
        if (descrip != NULL)
            v.oc->descrip = strdupex(descrip);
        if (argdescrip != NULL)
            v.oc->argdescrip = strdupex(argdescrip);
        if (type == OPT_SINGLE && def != NULL) {
            v.oc->data.s = strdupex(def);
            v.oc->ndata = 1;
        }

        /* feed lib_val */
        if (val_reg(v.oc->val, v.oc->longname, VAL_TYPE_PTR, v.oc->descrip, NULL) != VAL_OK)
            throw(option_register, o, OPTION_ERR_USE);
        if (val_set(v.oc->val, v.oc->longname, v.oc) != VAL_OK)
            throw(option_register, o, OPTION_ERR_USE);

        /* feed lib_popt */
        if (o->pi >= (o->pn-2)) { /* correction by two here, in mallv.oc and reallv.oc is for AUTOHELP and TABLEEND */
            if (o->pt == NULL) {
                o->pt = (struct popt_option *)mallocex(                (1 + 2) * sizeof(struct popt_option));
                o->pn = 1;
            }
            else {
                o->pt = (struct popt_option *)reallocex(o->pt, (o->pn * 2 + 2) * sizeof(struct popt_option));
                o->pn = o->pn * 2;
            }
        }
        o->pt[o->pi].longName   = v.oc->longname;
        o->pt[o->pi].shortName  = v.oc->shortname;
        o->pt[o->pi].argInfo    = v.oc->type == OPT_FLAG ? POPT_ARG_NONE : POPT_ARG_STRING;
        o->pt[o->pi].arg        = NULL;
        o->pt[o->pi].val        = v.oc->number;
        o->pt[o->pi].descrip    = v.oc->descrip;
        o->pt[o->pi].argDescrip = v.oc->argdescrip;

        /* append POPT_AUTOHELP */
        o->pt[o->pi+1].longName   = NULL;
        o->pt[o->pi+1].shortName  = '\0';
        o->pt[o->pi+1].argInfo    = POPT_ARG_INCLUDE_TABLE;
        o->pt[o->pi+1].arg        = popt_helpoptions;
        o->pt[o->pi+1].val        = 0;
        o->pt[o->pi+1].descrip    = "Help options:";
        o->pt[o->pi+1].argDescrip = NULL;

        /* append POPT_TABLEEND */
        o->pt[o->pi+2].longName   = NULL;
        o->pt[o->pi+2].shortName  = '\0';
        o->pt[o->pi+2].argInfo    = 0;
        o->pt[o->pi+2].arg        = 0;
        o->pt[o->pi+2].val        = 0;
        o->pt[o->pi+2].descrip    = NULL;
        o->pt[o->pi+2].argDescrip = NULL;
        o->pi++;

        /* link in this new optionval_t structure */
        if (o->first == NULL) {
            o->first = v.oc;
            o->last  = v.oc;
        }
        else {
            o->last->next = v.oc;
            o->last       = v.oc;
        }
    }
    catch(ex) {
        int i;
        if (v.oc != NULL) {
            if (type == OPT_SINGLE && v.oc->data.s != NULL)
                free(v.oc->data.s);
            if (type == OPT_MULTI  && v.oc->data.m != NULL) {
                for (i = 0; i < v.oc->ndata; i++)
                    if (v.oc->data.m[i] == NULL)
                        break;
                    else
                        free(v.oc->data.m[i]);
                free(v.oc->data.m);
            }
            if (v.oc->argdescrip != NULL)
                free(v.oc->argdescrip);
            if (v.oc->descrip != NULL)
                free(v.oc->descrip);
            if (v.oc->longname != NULL)
                free(v.oc->longname);
            free(v.oc);
        }
        rethrow;
    }
    return;
}

/* this public function catches all underlying exceptions and properly returns a code */
lmtp2nntp_option_rc_t option_create(lmtp2nntp_option_t **op, val_t *parent)
{
    ex_t ex;

    try {
        if (op == NULL || parent == NULL)
            return OPTION_ERR_ARG;

        (*op = (lmtp2nntp_option_t *)mallocex(sizeof(lmtp2nntp_option_t)));
        (*op)->first = NULL;
        (*op)->last = NULL;
        (*op)->vo = NULL;
        (*op)->pi = 0;
        (*op)->pn = 0;
        (*op)->pt = NULL;

        if (val_create(&((*op)->vo)) != VAL_OK)
            return OPTION_ERR_VAL;

        if (val_reg(parent, "option", VAL_TYPE_VAL, "option", (void *)&((*op)->vo)) != VAL_OK)
            return OPTION_ERR_VAL;
    }
    catch(ex) {
        if (*op != NULL) {
            if ((*op)->vo != NULL)
                val_unreg(parent, "option");
            free(*op);
        }
        if (ex.ex_class == (void *)option_create)
            return (lmtp2nntp_option_rc_t)ex.ex_value;
        return OPTION_ERR_TRY;
    }
    return OPTION_OK;
}

static lmtp2nntp_option_rc_t option_parse_internal(lmtp2nntp_option_t *o, int argc, char **argv)
{
    lmtp2nntp_option_rc_t rc = OPTION_OK;
    lmtp2nntp_option_rc_t rv;
    int i;
    char *cp;
    optionval_t *ocp;
    popt_context poptCon;

    /* internal function trusts args */

    /* init lib_popt */
    poptCon = popt_getcontext(NULL, argc, (const char **)argv, o->pt, 0);
    popt_setotheroptionhelp(poptCon, "[OPTIONS]* [newsgroup ...]");

    /* print usage if too few argv's */
    if (argc < 2) {
        popt_printusage(poptCon, stderr, 0);
        return OPTION_ERR_USE;
    }
    /* parse every option, continue when optarg missing or bad option found */
    while ((i = popt_getnextopt(poptCon)) >= 0 || i == POPT_ERROR_NOARG || i == POPT_ERROR_BADOPT) {
        if (i == POPT_ERROR_NOARG || i == POPT_ERROR_BADOPT) {
            fprintf(stderr, "ERROR: %s (%d) \"%s\"\n", popt_strerror(i), i, popt_badoption(poptCon, POPT_BADOPTION_NOALIAS));
            rc = OPTION_ERR_USE;
            continue;
        }
        if ((option_find(o, i, &ocp) == OPTION_OK) && (ocp->cb != NULL)) {
            rv = ocp->cb(ocp, cp = (ocp->type == OPT_FLAG ? NULL : (char *)popt_getoptarg(poptCon)), ocp->cbctx);
            if (cp != NULL)
                free(cp); /* TODO this should be the task of popt_freecontext but debugging showed popt does not free(3) */
            if (rc == OPTION_OK)
                rc = rv;
        }
    }

    /* create a "--newsgroup" argc/argv for every leftover argument */
    {
        ex_t ex;
        volatile struct {
            char **largv;
            } v;

        try {
            int largc;

            v.largv = (char **)mallocex((1 + 1) * sizeof(char **));
            largc = 0;
            v.largv[largc] = NULL;
            v.largv[largc++] = strdupex("leftover");
            v.largv[largc] = NULL;
            while ((cp = (char *)popt_getarg(poptCon)) != NULL) {
                v.largv = (char **)reallocex(v.largv, (1 + largc + 2) * sizeof(char **));
                v.largv[largc++] = strdupex("--newsgroup");
                v.largv[largc] = NULL;
                v.largv[largc++] = strdupex(cp);
                v.largv[largc] = NULL;
            }
            if (largc > 1) {
                rv = option_parse_internal(o, largc, v.largv);
                if (rc == OPTION_OK)
                    rc = rv;
            }
        }
        cleanup {
            if (v.largv != NULL) {
                for (i = 0; v.largv[i] != NULL; i++)
                    free(v.largv[i]);
                free(v.largv);
            }
        }
        catch(ex) {
            rc = OPTION_ERR_TRY;
        }
    }
    popt_freecontext(poptCon);
    return rc;
}

static lmtp2nntp_option_rc_t stdsyntax(optionval_t *oc, char *arg, char *cbctx)
{
    switch (oc->type) {
        case OPT_FLAG:
            if (arg != NULL || cbctx != NULL)
                return OPTION_ERR_ARG;
            if (oc->ndata >= 1)
                return OPTION_ERR_USE;
            oc->data.f = 1;
            oc->ndata = 1;
            break;
        case OPT_SINGLE:
            if (arg == NULL)
                return OPTION_ERR_ARG;
        /*  add this if repeated overwriting definitions of single values is not allowed
         *  however, this will inhibit preinitialization with a default value
         *  if (oc->ndata != 0)
         *      return OPTION_ERR_USE;
         */
            if (oc->ndata == 1 && oc->data.s != NULL) { /* free previous (default) assignment */
                free(oc->data.s);
                oc->data.s = NULL;
                oc->ndata = 0;
            }
            if (cbctx != NULL)
                if (str_parse(arg, cbctx) <= 0) {
                    fprintf(stderr, "ERROR: argument \"%s\" does NOT match syntax \"%s\"\n", arg, cbctx);
                    return OPTION_ERR_USE;
                }
            if ((oc->data.s = strdup(arg)) == NULL)
                    return OPTION_ERR_MEM;
            oc->ndata = 1;
            break;
        case OPT_MULTI:
            if (arg == NULL)
                return OPTION_ERR_ARG;
            if (oc->ndata >= 1 && oc->data.m == NULL)
                    return OPTION_ERR_USE;
            if (cbctx != NULL)
                if (str_parse(arg, cbctx) <= 0) {
                    fprintf(stderr, "ERROR: argument \"%s\" does NOT match syntax \"%s\"\n", arg, cbctx);
                    return OPTION_ERR_USE;
                }
            if (oc->data.m == NULL)                        /* existing + this new + terminating NULL */
                oc->data.m = (char **)mallocex(             (        0 + 1 + 1) * sizeof(char **));
            else
                oc->data.m = (char **)reallocex(oc->data.m, (oc->ndata + 1 + 1) * sizeof(char **));
            oc->data.m[oc->ndata] = strdupex(arg);
            oc->ndata++;
            oc->data.m[oc->ndata] = NULL;
            break;
        default:
            return OPTION_ERR_ARG;
            break;
    }
    return OPTION_OK;
}

static lmtp2nntp_option_rc_t includeit(optionval_t *oc, char *arg, char *cbctx)
{
    lmtp2nntp_option_rc_t rc = OPTION_OK;
    lmtp2nntp_option_t *o;
    volatile char *cpBuf = NULL;
    int argc = 0;
    char **argv = NULL;
    const char *filename = arg;
    struct stat sb;
    volatile int fd = -1;
    ex_t ex;
    char *cpI;  /* pointer to next character to be read */
    char *cpO;  /* pointer to next character to be written. Used for eliminating backslash+newline at a line continuation */
    char *cpL;  /* pointer to start of line */
    int pline;  /* current physical (disregarding line continuation) line number */
    int lline;  /* current logical lines first physical line number */
    int eline;  /* flag signaling empty or just whitespace-filled line */
    char c;     /* current character */
    char p;     /* previous character */
    int eof;    /* flag signaling end of file detected */

    if ((o = oc->parent) == NULL)
        return OPTION_ERR_USE;

    try {
        if (stdsyntax(oc, arg, cbctx) != OPTION_OK)
            throw(0, 0, 0);
        if (stat(filename, &sb) == -1)
            throw(includeit, oc, "stat");
        if (sb.st_size == 0)
            throw(includeit, oc, "size");
        cpBuf = (char *)mallocex((size_t)sb.st_size + 1);
        if ((fd = open(filename, O_RDONLY)) == -1)
            throw(includeit, oc, "open");
        if (read(fd, (void *)cpBuf, (size_t)sb.st_size) != (ssize_t)sb.st_size)
            throw(includeit, oc, "read");
        cpBuf[(int)sb.st_size] = '\0';

        cpI = (char *)cpBuf;
        cpO = (char *)cpBuf;
        eof = FALSE;
        pline = 1;
        p = NUL;

        cpL = cpO;
        lline = pline;
        eline = TRUE;
        while(!eof) {
            c = *cpI++;
            *cpO++ = c;
            if (c == NUL)
                eof = TRUE;
            else
                if (!isspace(c))
                    eline = FALSE;
            if (eof || (c == '\n')) {
                pline++;
                if (!eof && (p == '\\')) { /* line continuation situation */
                    cpO-=2; /* need to remove both backslash+newline */
                }
                else {
                    if (!eline) { /* process logical line unless it's empty */
                        *(cpO-1) = NUL;
                        if (lline == (pline-1))
                            ;/*printf("DEBUG: line[%3d] = ***%s***\n", lline, cpL);*/
                        else
                            ;/*printf("DEBUG: [%3d-%3d] = ***%s***\n", lline, pline-1, cpL);*/
                        {
                            char *cp = cpL;
                            char *option;
                            char *value;
                            char *cpNew;
                            argz_t Newarg;

                            Newarg.as = 0;
                            Newarg.az = NULL;
                            if ((option = str_token(&cp, " \t", "\"'", "#", STR_STRIPQUOTES|STR_BACKSLASHESC)) == NULL)
                                ;/* no option? comment only! don't care about that. */
                            else {
                                /* create fake argv[0] */
                                if (argv == NULL) {
                                    argv = (char **)mallocex((1 + 1) * sizeof(char **)); /* argv[0] + NULL */
                                    argv[argc++] = strdupex("include");
                                    argv[argc] = NULL;
                                }
                                /* handle option */
                                cpNew = (char *)mallocex(2 + strlen(option) + 1); /* dash dash option[] NUL */
                                cpNew[0]=NUL;
                                strcat(cpNew, "--");
                                strcat(cpNew, option);
                                argv = (char **)reallocex(argv, (1 + argc + 1) * sizeof(char **)); /* argv[0] + argv[1...argc] + NULL */
                                argv[argc++] = cpNew;
                                argv[argc] = NULL;

                                if ((value = str_token(&cp, " \t", "\"'", "#", STR_STRIPQUOTES|STR_BACKSLASHESC)) == NULL)
                                    ;/* no value? optional anyway */
                                else {
                                    while(isspace((int)*value)) value++;
                                    /* handle value */
                                    cpNew = strdupex(value);
                                    argv = (char **)reallocex(argv, (1 + argc + 1 + 1) * sizeof(char **)); /* argv[0] + argv[1...argc] + NULL */
                                    argv[argc++] = cpNew;
                                    argv[argc] = NULL;
                                }
                            }
                        }
                    }
                    cpL = cpO;
                    lline = pline;
                    eline = TRUE;
                }
            }
            p = c;
        }
        rc = option_parse_internal(o, argc, argv);
    }
    cleanup {
        int i;
        if (fd != -1)
            close(fd);
        if (argv != NULL) {
            for (i = 0; argv[i] != NULL; i++) {
                free(argv[i]);
            }
            free(argv);
        }
        if (cpBuf != NULL)
            free((char *)cpBuf);
    }
    catch(ex) {
        if (ex.ex_class == (void *)includeit && ex.ex_value != NULL) {
            fprintf(stderr, "ERROR: problem with \"%s\" while including \"%s\"\n", (char *)ex.ex_value, filename);
        }
        rc = OPTION_ERR_TRY;
    }
    return rc;
}

/* this public function catches all underlying exceptions and properly returns a code */
lmtp2nntp_option_rc_t option_parse(lmtp2nntp_option_t *o, int argc, char **argv)
{
    ex_t ex;

    if (o == NULL || argc < 0 || argv == NULL)
        return OPTION_ERR_ARG;

    try {
        option_register(o, "childsmax",          'C', OPT_SINGLE,  "10",        "Childs the daemon spawns at max.",   "childsmax",                  &stdsyntax, "m/\\d+/" ); /*"m/[0-9]+/"*/
        option_register(o, "daemonize",          'D', OPT_FLAG,    NULL,        "Daemonize and detach from terminal", NULL,                         &stdsyntax, NULL );
        option_register(o, "kill",               'K', OPT_FLAG,    NULL,        "Kill a previously run daemon",       NULL,                         &stdsyntax, NULL );
        option_register(o, "pidfile",            'P', OPT_SINGLE,  NULL,        "Pidfile holding the process ID",     "filename",                   &stdsyntax, "m/.*/" );
        option_register(o, "acl",                'a', OPT_MULTI,   NULL,        "LMTP daemon access control list",    "addr[/mask]",                &stdsyntax, "m/.*/" );
        option_register(o, "bind",               'b', OPT_SINGLE,  NULL,        "LMTP daemon bind",                   "addr[:port]|-|path[:perms]", &stdsyntax, "m/.*/" );
        option_register(o, "client",             'c', OPT_SINGLE,  NULL,        "NNTP client bind",                   "addr[:port]",                &stdsyntax, "m/.*/" );
        option_register(o, "destination",        'd', OPT_MULTI,   NULL,        "NNTP client destination",            "addr[:port]",                &stdsyntax, "m/.*/" );
        option_register(o, "groupmode",          'g', OPT_SINGLE,  "arg",       "Groupmode configures news group(s)", "arg|envelope|header",        &stdsyntax, "m/.*/" ); /*"m/(arg|envelope|header)/"*/
        option_register(o, "headerrule",         'h', OPT_MULTI,   NULL,        "Header rewriting rule",              "[pri]:[regex]:header:[val]", &stdsyntax, "m/^[0-9]*:.*:.+:.*$/" );
        option_register(o, "include",            'i', OPT_MULTI,   NULL,        "Include a configuration file",       "configfile",                 &includeit, "m/.*/" );
        option_register(o, "l2spec",             'l', OPT_SINGLE,  NULL,        "L2 channel tree specification",      "l2spec",                     &stdsyntax, "m/.*/" );
        option_register(o, "mailfrom",           'm', OPT_SINGLE,  NULL,        "Mail from envelope restriction",     "regex",                      &stdsyntax, "m/.*/" );
        option_register(o, "nodename",           'n', OPT_SINGLE,  NULL,        "System nodename",                    "name",                       &stdsyntax, "m/.*/" );
        option_register(o, "operationmode",      'o', OPT_SINGLE,  "553/5.7.1", "Set fake status or operationmode",   "abc/a.d.e|post|feed",        &stdsyntax, "m/.*/" ); /*"m/([0-9]{3}\\/[0-9]\\.[0-9]\\.[0-9]|post|feed)/" ); 553 = Requested action not taken: mailbox name not allowed, 5.7.1 =  Delivery not authorized, message refused */
        option_register(o, "restrictheader",     'r', OPT_SINGLE,  NULL,        "Restrict messages by header",        "regex",                      &stdsyntax, "m/.*/" );
        option_register(o, "size",               's', OPT_SINGLE,  "8388608",   "Size limitation on message",         "bytes",                      &stdsyntax, "m/.*/" ); /*"m/[0-9]+/"*/
        option_register(o, "testfile",           't', OPT_MULTI,   NULL,        "Testfile for headerrule",            "filename",                   &stdsyntax, "m/.*/" );
        option_register(o, "timeoutlmtp",        NUL, OPT_SINGLE,  NULL,        "LMTP server default timeout",        "sec",                        &stdsyntax, "m/.*/" );
        option_register(o, "timeoutlmtpaccept",  NUL, OPT_SINGLE,  "0",         "LMTP server accept timeout",         "sec",                        &stdsyntax, "m/.*/" );
        option_register(o, "timeoutlmtpread",    NUL, OPT_SINGLE,  "10",        "LMTP server read timeout",           "sec",                        &stdsyntax, "m/.*/" );
        option_register(o, "timeoutlmtpwrite",   NUL, OPT_SINGLE,  "10",        "LMTP server write timeout",          "sec",                        &stdsyntax, "m/.*/" );
        option_register(o, "timeoutnntp",        NUL, OPT_SINGLE,  NULL,        "NNTP client default timeout",        "sec",                        &stdsyntax, "m/.*/" );
        option_register(o, "timeoutnntpconnect", NUL, OPT_SINGLE,  "360",       "NNTP client connect timeout",        "sec",                        &stdsyntax, "m/.*/" );
        option_register(o, "timeoutnntpread",    NUL, OPT_SINGLE,  "60",        "NNTP client read timeout",           "sec",                        &stdsyntax, "m/.*/" );
        option_register(o, "timeoutnntpwrite",   NUL, OPT_SINGLE,  "60",        "NNTP client write timeout",          "sec",                        &stdsyntax, "m/.*/" );
        option_register(o, "user",               'u', OPT_SINGLE,  NULL,        "User identity",                      "uid|name",                   &stdsyntax, "m/.*/" );
        option_register(o, "version",            'v', OPT_FLAG,    NULL,        "Version information",                NULL,                         &stdsyntax, NULL    );
        option_register(o, "newsgroup",          NUL, OPT_MULTI,   NULL,        "Newsgroup name or match",            "newsgroup|wildmat",          &stdsyntax, "m/.*/" );
    }
    catch(ex) {
        if (ex.ex_class == (void *)option_create)
            return (lmtp2nntp_option_rc_t)ex.ex_value;
        return OPTION_ERR_TRY;
    }
    return option_parse_internal(o, argc, argv);
}

/* this public function catches all underlying exceptions and properly returns a code */
lmtp2nntp_option_rc_t option_destroy(lmtp2nntp_option_t *o)
{
    optionval_t *oc;
    optionval_t *ocn;
    int i;

    if (o == NULL)
        return OPTION_ERR_ARG;

    oc = o->first;
    while (oc != NULL) {
        if (oc->type == OPT_SINGLE && oc->data.s != NULL) {
            free(oc->data.s);
        }
        if (oc->type == OPT_MULTI  && oc->data.m != NULL) {
            for (i = 0; i < oc->ndata; i++)
                if (oc->data.m[i] == NULL)
                    break;
                else
                    free(oc->data.m[i]);
            free(oc->data.m);
        }
        if (oc->argdescrip != NULL)
            free(oc->argdescrip);
        if (oc->descrip != NULL)
            free(oc->descrip);
        if (oc->longname != NULL)
            free(oc->longname);
        ocn = oc->next;
        free(oc);
        oc = ocn;
    }
    if (o->vo != NULL)
        val_destroy(o->vo);
    if (o->pt != NULL)
        free(o->pt);
    free(o);

    return OPTION_OK;
}
