/* ** OSSP lmtp2nntp - Mail to News Gateway ** Copyright (c) 2002-2003 Ralf S. Engelschall ** Copyright (c) 2002-2003 The OSSP Project ** Copyright (c) 2002-2003 Cable & Wireless Germany ** ** 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 . ** ** lmtp2nntp_option.c: option parsing */ #include #include #include /* 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; }