/* ** 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_config.c: config handling */ #include #include #include #include #include #include /* third party (included) */ #include "lmtp2nntp_argz.h" /* third party (linked in) */ #include "l2.h" #include "pcre.h" #include "popt.h" #include "str.h" #include "var.h" #include "val.h" /* library version check (compile-time) */ #define STR_VERSION_HEX_REQ 0x009206 #define STR_VERSION_STR_REQ "0.9.6" #ifdef STR_VERSION_HEX #if STR_VERSION_HEX < STR_VERSION_HEX_REQ #error "require a newer version of OSSP Str" #endif #endif /* own headers */ #include "lmtp2nntp_global.h" #ifdef HAVE_CONFIG_H #include "config.h" #endif #if defined(HAVE_DMALLOC_H) && defined(WITH_DMALLOC) #include "dmalloc.h" #endif #include "lmtp2nntp_option.h" #include "lmtp2nntp_config.h" #include "lmtp2nntp_lmtp.h" #include "lmtp2nntp_nntp.h" #include "lmtp2nntp_msg.h" #include "lmtp2nntp_common.h" #define _LMTP2NNTP_VERSION_C_AS_HEADER_ #include "lmtp2nntp_version.c" #undef _LMTP2NNTP_VERSION_C_AS_HEADER_ #ifndef FALSE #define FALSE (1 != 1) #endif #ifndef TRUE #define TRUE (!FALSE) #endif #ifndef NUL #define NUL '\0' #endif static l2_result_t formatter_prefix(l2_context_t *_ctx, const char id, const char *param, char *bufptr, size_t bufsize, size_t *buflen, va_list *ap) { lmtp2nntp_t *ctx = (lmtp2nntp_t *)_ctx->vp; if ((ctx->msg != NULL) && (ctx->msg->cpFid != NULL)) { sprintf(bufptr, "%s: ", ctx->msg->cpFid); *buflen = strlen(bufptr); } else *buflen = 0; return L2_OK; } static var_syntax_t syntax_regex = { '\\', /* escape */ '$', /* varinit */ '{', /* startdelim */ '}', /* enddelim */ NUL, /* startindex */ NUL, /* endindex */ NUL, /* current_index */ "0-9" /* namechars */ }; lmtp2nntp_config_rc_t config_context(lmtp2nntp_t *ctx) { lmtp2nntp_config_rc_t rc = CONFIG_OK; ex_t ex; optionval_t *ov; sa_rc_t sa_rc; /* create L2 environment */ if (l2_env_create(&ctx->l2_env) != L2_OK) { fprintf(stderr, "%s:Error: failed to create L2 environment\n", ctx->progname); CU(CONFIG_ERR_LOG); } if (l2_env_levels(ctx->l2_env, L2_LEVEL_ALL, L2_LEVEL_NONE) != L2_OK) { fprintf(stderr, "%s:Error: logging failed to set global logging level defaults\n", ctx->progname); CU(CONFIG_ERR_LOG); } if (l2_env_formatter(ctx->l2_env, 'P', formatter_prefix, &ctx->ctx) != L2_OK) { fprintf(stderr, "%s:Error: logging failed to register prefix formatter\n", ctx->progname); CU(CONFIG_ERR_LOG); } if (l2_env_formatter(ctx->l2_env, 'D', l2_util_fmt_dump, NULL) != L2_OK) { fprintf(stderr, "%s:Error: logging failed to register dump formatter\n", ctx->progname); CU(CONFIG_ERR_LOG); } if (l2_env_formatter(ctx->l2_env, 'S', l2_util_fmt_string, NULL) != L2_OK) { fprintf(stderr, "%s:Error: logging failed to register string formatter\n", ctx->progname); CU(CONFIG_ERR_LOG); } if (l2_env_formatter(ctx->l2_env, 'm', l2_util_fmt_errno, NULL) != L2_OK) { fprintf(stderr, "%s:Error: logging failed to register errno formatter\n", ctx->progname); CU(CONFIG_ERR_LOG); } if (val_get(ctx->val, "option.l2spec", &ov) != VAL_OK) { fprintf(stderr, "%s:Error: (internal) config did not register 'l2spec' option\n", ctx->progname); CU(CONFIG_ERR_LOG); } if (ov->data.s != NULL) { l2_channel_t *ch; if (l2_env_handler(ctx->l2_env, &l2_handler_var) != L2_OK) { fprintf(stderr, "%s:Error: logging failed to register \"%s\" handler \n", ctx->progname, l2_handler_var.name); CU(CONFIG_ERR_LOG); } if ((l2_channel_create(&ctx->l2, ctx->l2_env, l2_handler_var.name)) != L2_OK) { fprintf(stderr, "%s:Error: logging failed to create \"%s\" channel\n", ctx->progname, l2_handler_var.name); CU(CONFIG_ERR_LOG); } if ((l2_channel_configure(ctx->l2, "", ctx->config_varctx)) != L2_OK) { fprintf(stderr, "%s:Error: logging failed to configure \"%s\" channel\n", ctx->progname, l2_handler_var.name); CU(CONFIG_ERR_LOG); } if ((l2_spec(&ch, ctx->l2_env, "%s", ov->data.s)) != L2_OK) { fprintf(stderr, "%s:Error: logging failed to create stream\n", ctx->progname); CU(CONFIG_ERR_LOG); } if (l2_channel_link(ctx->l2, L2_LINK_CHILD, ch, NULL) != L2_OK) { fprintf(stderr, "%s:Error: logging failed to link child channel\n", ctx->progname); CU(CONFIG_ERR_LOG); } if (l2_channel_open(ctx->l2) != L2_OK) { fprintf(stderr, "%s:Error: logging failed to open channel stream\n", ctx->progname); CU(CONFIG_ERR_LOG); } } /* from this point on logging is up and running and fprintf(stderr, ...) * should not be used in the remainder of the program flow. */ logbook(ctx->l2, L2_LEVEL_NOTICE, "startup, version %s", lmtp2nntp_version.v_gnu); /* --version FLAG */ try { if ( (val_get(ctx->val, "option.version", &ov) != VAL_OK) || (ov->ndata < 0) || (ov->ndata == 1 && ov->data.f < 0) || (ov->ndata == 1 && ov->data.f > 1) || (ov->ndata > 1) ) throw(0,0,0); logbook(ctx->l2, L2_LEVEL_TRACE, "--version = %d", ov->data.f); if (ov->data.f == 1) { fprintf(stdout, "%s\n", lmtp2nntp_version.v_gnu); CU(CONFIG_OK_DRY); } } catch (ex) rethrow; /* --childsmax SINGLE */ try { if ( (val_get(ctx->val, "option.childsmax", &ov) != VAL_OK) || (ov->ndata < 0) || (ov->ndata == 1 && ov->data.s == NULL) || (ov->ndata > 1) ) throw(0,0,0); logbook(ctx->l2, L2_LEVEL_TRACE, "--childsmax = \"%s\"", ov->data.s); if (ov->ndata == 1) if ((ctx->option_childsmax = atoi(ov->data.s)) <= 0) { logbook(ctx->l2, L2_LEVEL_ERROR, "option --childsmax, number (%d) out of range", ctx->option_childsmax); throw(0,0,0); } } catch (ex) { rethrow; } /* --daemonize FLAG */ try { if ( (val_get(ctx->val, "option.daemonize", &ov) != VAL_OK) || (ov->ndata < 0) || (ov->ndata == 1 && ov->data.f < 0) || (ov->ndata == 1 && ov->data.f > 1) || (ov->ndata > 1) ) throw(0,0,0); logbook(ctx->l2, L2_LEVEL_TRACE, "--daemonize = %d", ov->data.f); ctx->option_daemon = ov->data.f == 1 ? TRUE : FALSE; } catch (ex) rethrow; /* --kill FLAG */ try { if ( (val_get(ctx->val, "option.kill", &ov) != VAL_OK) || (ov->ndata < 0) || (ov->ndata == 1 && ov->data.f < 0) || (ov->ndata == 1 && ov->data.f > 1) || (ov->ndata > 1) ) throw(0,0,0); logbook(ctx->l2, L2_LEVEL_TRACE, "--kill = %d", ov->data.f); ctx->option_killflag = ov->data.f == 1 ? TRUE : FALSE; } catch (ex) rethrow; /* --pidfile SINGLE */ try { if ( (val_get(ctx->val, "option.pidfile", &ov) != VAL_OK) || (ov->ndata < 0) || (ov->ndata == 1 && ov->data.s == NULL) || (ov->ndata > 1) ) throw(0,0,0); logbook(ctx->l2, L2_LEVEL_TRACE, "--pidfile = \"%s\"", ov->data.s); if (ov->ndata == 1) ctx->option_pidfile = strdupex(ov->data.s); } catch (ex) rethrow; /* --acl MULTI */ try { char *cp; int i; int somepass; if ( (val_get(ctx->val, "option.acl", &ov) != VAL_OK) || (ov->ndata < 0) || (ov->ndata >= 1 && ov->data.m == NULL) ) throw(0,0,0); logbook(ctx->l2, L2_LEVEL_DEBUG, "ov->ndata = %d", ov->ndata); for (i = 0; i < ov->ndata; i++) logbook(ctx->l2, L2_LEVEL_TRACE, "--acl[%d] = \"%s\"", i, (ov->data.m)[i]); /* check if only blocking ACLs exist */ somepass = FALSE; if (ov->ndata >= 1) { for (i = 0; i < ov->ndata; i++) { cp = (ov->data.m)[i]; if (cp[0] != '!') { somepass = TRUE; break; } } } /* if only blocking ACLs exist, reserve space for two additional pass-through wildcards */ ctx->pacl = (struct acl *)mallocex((ov->ndata + (somepass ? 0 : 2 )) * sizeof(struct acl)); if (ov->ndata >= 1) { for (i = 0; i < ov->ndata; i++) { cp = (ov->data.m)[i]; logbook(ctx->l2, L2_LEVEL_DEBUG, "cp = (data.m)[%d] = \"%s\"", i, cp); if (cp[0] == '!') { ctx->pacl[i].acl = strdup(cp + 1); ctx->pacl[i].not = TRUE; } else { ctx->pacl[i].acl = strdup(cp); ctx->pacl[i].not = FALSE; } logbook(ctx->l2, L2_LEVEL_DEBUG, "ctx->pacl[%d].not = %s", i, ctx->pacl[i].not == TRUE ? "TRUE" : "FALSE"); logbook(ctx->l2, L2_LEVEL_DEBUG, "ctx->pacl[%d].acl = %s", i, ctx->pacl[i].acl); if ((cp = strrchr(ctx->pacl[i].acl, '/')) != NULL) *cp++ = NUL; else cp = "-1"; ctx->pacl[i].prefixlen = atoi(cp); logbook(ctx->l2, L2_LEVEL_DEBUG, "ctx->pacl[%d].prefixlen = %d", i, ctx->pacl[i].prefixlen); if ((sa_rc = sa_addr_create(&(ctx->pacl[i].saa))) != SA_OK) { if (sa_rc == SA_ERR_SYS) logbook(ctx->l2, L2_LEVEL_ERROR, "option --acl, create address (internal) failed with \"%s\" (%d) %s", sa_error(sa_rc), errno, strerror(errno)); else logbook(ctx->l2, L2_LEVEL_ERROR, "option --acl, create address (internal) failed with \"%s\"", sa_error(sa_rc)); throw(0,0,0); } if ((sa_rc = sa_addr_u2a(ctx->pacl[i].saa, "inet://%s:0", ctx->pacl[i].acl)) != SA_OK) { if (sa_rc == SA_ERR_SYS) logbook(ctx->l2, L2_LEVEL_ERROR, "option --acl, parsing address (%s) failed with \"%s\" (%d) %", ctx->pacl[i].acl, sa_error(sa_rc), errno, strerror(errno)); else logbook(ctx->l2, L2_LEVEL_ERROR, "option --acl, parsing address (%s) failed with \"%s\"", ctx->pacl[i].acl, sa_error(sa_rc)); throw(0,0,0); } } ctx->nacl = i; } /* if only blocking ACLs exist, append a wildcard pass-through for IPv4 */ if (!somepass) { i = ctx->nacl; ctx->pacl[i].acl = "0.0.0.0"; ctx->pacl[i].not = FALSE; ctx->pacl[i].prefixlen = 0; if ((sa_rc = sa_addr_create(&ctx->pacl[i].saa)) != SA_OK) { if (sa_rc == SA_ERR_SYS) logbook(ctx->l2, L2_LEVEL_ERROR, "option --acl, create IPv4 pass-through address (internal) failed with \"%s\" (%d) %s", sa_error(sa_rc), errno, strerror(errno)); else logbook(ctx->l2, L2_LEVEL_ERROR, "option --acl, create IPv4 pass-through address (internal) failed with \"%s\"", sa_error(sa_rc)); throw(0,0,0); } if ((sa_rc = sa_addr_u2a(ctx->pacl[i].saa, "inet://%s:0", ctx->pacl[i].acl)) != SA_OK) { if (sa_rc == SA_ERR_SYS) logbook(ctx->l2, L2_LEVEL_ERROR, "option --acl, parsing IPv4 pass-through address (%s) failed with \"%s\" (%d) %s", ctx->pacl[i].acl, sa_error(sa_rc), errno, strerror(errno)); else logbook(ctx->l2, L2_LEVEL_ERROR, "option --acl, parsing IPv4 pass-through address (%s) failed with \"%s\"", ctx->pacl[i].acl, sa_error(sa_rc)); throw(0,0,0); } i++; ctx->nacl = i; } /* if only blocking ACLs exist, append a wildcard pass-through for IPv6 */ if (!somepass) { i = ctx->nacl; ctx->pacl[i].acl = "[::]"; ctx->pacl[i].not = FALSE; ctx->pacl[i].prefixlen = 0; if ((sa_rc = sa_addr_create(&ctx->pacl[i].saa)) != SA_OK) { if (sa_rc == SA_ERR_SYS) logbook(ctx->l2, L2_LEVEL_ERROR, "option --acl, create IPv6 pass-through address (internal) failed with \"%s\" (%d) %s", sa_error(sa_rc), errno, strerror(errno)); else logbook(ctx->l2, L2_LEVEL_ERROR, "option --acl, create IPv6 pass-through address (internal) failed with \"%s\"", sa_error(sa_rc)); throw(0,0,0); } if ((sa_rc = sa_addr_u2a(ctx->pacl[i].saa, "inet://%s:0", ctx->pacl[i].acl)) != SA_OK) { if (sa_rc == SA_ERR_SYS) logbook(ctx->l2, L2_LEVEL_ERROR, "option --acl, parsing IPv6 pass-through address (%s) failed with \"%s\" (%d) %s", ctx->pacl[i].acl, sa_error(sa_rc), errno, strerror(errno)); else logbook(ctx->l2, L2_LEVEL_ERROR, "option --acl, parsing IPv6 pass-through address (%s) failed with \"%s\"", ctx->pacl[i].acl, sa_error(sa_rc)); throw(0,0,0); } i++; ctx->nacl = i; } } catch (ex) rethrow; /* --bind SINGLE */ try { char *cp; if ( (val_get(ctx->val, "option.bind", &ov) != VAL_OK) || (ov->ndata < 0) || (ov->ndata == 1 && ov->data.s == NULL) || (ov->ndata > 1) ) throw(0,0,0); logbook(ctx->l2, L2_LEVEL_TRACE, "--bind = \"%s\"", ov->data.s); if (ov->ndata == 1) { /* dash means stdio */ if (strcmp(ov->data.s, "-") != 0) { if ((sa_rc = sa_create(&ctx->saServerbind)) != SA_OK) { if (sa_rc == SA_ERR_SYS) logbook(ctx->l2, L2_LEVEL_ERROR, "option --bind, creating TCP socket (internal) failed with \"%s\" (%d) %s", sa_error(sa_rc), errno, strerror(errno)); else logbook(ctx->l2, L2_LEVEL_ERROR, "option --bind, creating TCP socket (internal) failed with \"%s\"", sa_error(sa_rc)); throw(0,0,0); } if ((sa_rc = sa_addr_create(&ctx->saaServerbind)) != SA_OK) { if (sa_rc == SA_ERR_SYS) logbook(ctx->l2, L2_LEVEL_ERROR, "option --bind, create address (internal) failed with \"%s\", (%d) %s", sa_error(sa_rc), errno, strerror(errno)); else logbook(ctx->l2, L2_LEVEL_ERROR, "option --bind, create address (internal) failed with \"%s\"", sa_error(sa_rc)); throw(0,0,0); } /* slash means UNIX socket */ if (ov->data.s[0] == '/') { char *cpPath; char *cpPerm; int nPerm; int n; int i; cpPath = strdup(ov->data.s); cpPerm = NULL; nPerm = -1; if ((cpPerm = strrchr(cpPath, ':')) != NULL) { *cpPerm++ = '\0'; nPerm = 0; for (i = 0; i < 4 && cpPerm[i] != '\0'; i++) { if (!isdigit((int)cpPerm[i])) { nPerm = -1; break; } n = cpPerm[i] - '0'; if (n > 7) { nPerm = -1; break; } nPerm = ((nPerm << 3) | n); } if (nPerm == -1 || cpPerm[i] != '\0') { logbook(ctx->l2, L2_LEVEL_ERROR, "option --bind, invalid permissions \"%s\"", cpPerm); throw(0,0,0); } } if ((sa_rc = sa_addr_u2a(ctx->saaServerbind, "unix:%s", cpPath)) != SA_OK) { if (sa_rc == SA_ERR_SYS) logbook(ctx->l2, L2_LEVEL_ERROR, "option --bind, parsing alternate IO guessing UNIX socket (%s) failed with \"%s\" (%d) %s", cpPath, sa_error(sa_rc), errno, strerror(errno)); else logbook(ctx->l2, L2_LEVEL_ERROR, "option --bind, parsing alternate IO guessing UNIX socket (%s) failed with \"%s\"", cpPath, sa_error(sa_rc)); throw(0,0,0); } if ((sa_rc = sa_bind(ctx->saServerbind, ctx->saaServerbind)) != SA_OK) { if (sa_rc == SA_ERR_SYS) logbook(ctx->l2, L2_LEVEL_ERROR, "option --bind, bind (%s) failed with \"%s\" (%d) %s", cpPath, sa_error(sa_rc), errno, strerror(errno)); else logbook(ctx->l2, L2_LEVEL_ERROR, "option --bind, bind (%s) failed with \"%s\"", cpPath, sa_error(sa_rc)); throw(0,0,0); } if (nPerm != -1) { if (chmod(cpPath, nPerm) == -1) { logbook(ctx->l2, L2_LEVEL_ERROR, "option --bind, chmod (%s, 0%o) failed with \"%s\"", cpPath, nPerm, strerror(errno)); throw(0,0,0); } } if (getuid() == 0 && getuid() != ctx->option_uid) { if (chown(cpPath, ctx->option_uid, -1) == -1) { logbook(ctx->l2, L2_LEVEL_ERROR, "option --bind, chown (%s, %d) failed with \"%s\"", cpPath, ctx->option_uid, strerror(errno)); throw(0,0,0); } } free(cpPath); } /* otherwise assume INET socket */ else { cp = ov->data.s; if (strrchr(cp, ':') == NULL) cp = str_concat(cp, ":24", NULL); /* http://www.iana.org/assignments/port-numbers (and names) */ else cp = str_concat(cp, NULL); /* prepare for free() */ logbook(ctx->l2, L2_LEVEL_DEBUG, "data.s = \"%s\", cp = \"%s\"", ov->data.s, cp); if ((sa_rc = sa_addr_u2a(ctx->saaServerbind, "inet://%s", cp)) != SA_OK) { if (sa_rc == SA_ERR_SYS) logbook(ctx->l2, L2_LEVEL_ERROR, "option --bind, parsing alternate IO guessing INET socket (%s) failed with \"%s\" (%d) %s", ov->data.s, sa_error(sa_rc), errno, strerror(errno)); else logbook(ctx->l2, L2_LEVEL_ERROR, "option --bind, parsing alternate IO guessing INET socket (%s) failed with \"%s\"", ov->data.s, sa_error(sa_rc)); throw(0,0,0); } if ((sa_rc = sa_bind(ctx->saServerbind, ctx->saaServerbind)) != SA_OK) { if (sa_rc == SA_ERR_SYS) logbook(ctx->l2, L2_LEVEL_ERROR, "option --bind, bind (%s) failed with \"%s\" (%d) %s", ov->data.s, sa_error(sa_rc), errno, strerror(errno)); else logbook(ctx->l2, L2_LEVEL_ERROR, "option --bind, bind (%s) failed with \"%s\"", ov->data.s, sa_error(sa_rc)); throw(0,0,0); } free(cp); } /* for either sockets */ if ((sa_rc = sa_listen(ctx->saServerbind, -1)) != SA_OK) { if (sa_rc == SA_ERR_SYS) logbook(ctx->l2, L2_LEVEL_ERROR, "option --bind, listen (%s) failed with \"%s\" (%d) %s", ov->data.s, sa_error(sa_rc), errno, strerror(errno)); else logbook(ctx->l2, L2_LEVEL_ERROR, "option --bind, listen (%s) failed with \"%s\"", ov->data.s, sa_error(sa_rc)); throw(0,0,0); } } } } catch (ex) rethrow; /* --client SINGLE */ try { if ( (val_get(ctx->val, "option.client", &ov) != VAL_OK) || (ov->ndata < 0) || (ov->ndata == 1 && ov->data.s == NULL) || (ov->ndata > 1) ) throw(0,0,0); logbook(ctx->l2, L2_LEVEL_TRACE, "--client = \"%s\"", ov->data.s); if (ov->ndata == 1) { if ((sa_rc = sa_addr_create(&ctx->saaClientbind)) != SA_OK) { if (sa_rc == SA_ERR_SYS) logbook(ctx->l2, L2_LEVEL_ERROR, "option --client, create address (internal) failed with \"%s\" (%d) %s", sa_error(sa_rc), errno, strerror(errno)); else logbook(ctx->l2, L2_LEVEL_ERROR, "option --client, create address (internal) failed with \"%s\"", sa_error(sa_rc)); throw(0,0,0); } if ((sa_rc = sa_addr_u2a(ctx->saaClientbind, (strchr(ov->data.s, ':') == NULL) ? "inet://%s:0" : "inet://%s", ov->data.s)) != SA_OK) { if (sa_rc == SA_ERR_SYS) logbook(ctx->l2, L2_LEVEL_ERROR, "option --client, parsing alternate IO guessing INET socket (%s) failed with \"%s\" (%d) %s", ov->data.s, sa_error(sa_rc), errno, strerror(errno)); else logbook(ctx->l2, L2_LEVEL_ERROR, "option --client, parsing alternate IO guessing INET socket (%s) failed with \"%s\"", ov->data.s, sa_error(sa_rc)); throw(0,0,0); } } } catch (ex) rethrow; /* --destination MULTI */ try { char *cp; int i; if ( (val_get(ctx->val, "option.destination", &ov) != VAL_OK) || (ov->ndata < 0) || (ov->ndata >= 1 && ov->data.m == NULL) ) throw(0,0,0); logbook(ctx->l2, L2_LEVEL_DEBUG, "ov->ndata = %d", ov->ndata); for (i = 0; i < ov->ndata; i++) logbook(ctx->l2, L2_LEVEL_TRACE, "--destination[%d] = \"%s\"", i, (ov->data.m)[i]); if (ov->ndata >= 1) { if ((ctx->pns = (struct ns *)malloc(ov->ndata * sizeof(struct ns))) == NULL) throw(0,0,0); for (i = 0; i < ov->ndata;) { cp = (ov->data.m)[i]; if (strrchr(cp, ':') == NULL) cp = str_concat(cp, ":nntp", NULL); /* http://www.iana.org/assignments/port-numbers (and names) */ else cp = str_concat(cp, NULL); /* prepare for free() */ logbook(ctx->l2, L2_LEVEL_DEBUG, "(data.m)[%d] = \"%s\", cp = \"%s\"", i, (ov->data.m)[i], cp); if ((sa_rc = sa_addr_create(&ctx->pns[i].saa)) != SA_OK) { if (sa_rc == SA_ERR_SYS) logbook(ctx->l2, L2_LEVEL_ERROR, "option --destination, create address (internal) failed with \"%s\" (%d) %s", sa_error(sa_rc), errno, strerror(errno)); else logbook(ctx->l2, L2_LEVEL_ERROR, "option --destination, create address (internal) failed with \"%s\"", sa_error(sa_rc)); throw(0,0,0); } if ((sa_rc = sa_addr_u2a(ctx->pns[i].saa, "inet://%s", cp)) != SA_OK) { if (sa_rc == SA_ERR_SYS) logbook(ctx->l2, L2_LEVEL_ERROR, "option --destination, parsing host address (%s) failed with \"%s\", (%d) %s", cp, sa_error(sa_rc), errno, strerror(errno)); else logbook(ctx->l2, L2_LEVEL_ERROR, "option --destination, parsing host address (%s) failed with \"%s\"", cp, sa_error(sa_rc)); throw(0,0,0); } if ((sa_rc = sa_create(&ctx->pns[i].sa)) != SA_OK) { if (sa_rc == SA_ERR_SYS) logbook(ctx->l2, L2_LEVEL_ERROR, "option --destination, creating TCP socket (%s) failed with \"%s\" (%d) %s", cp, sa_error(sa_rc), errno, strerror(errno)); else logbook(ctx->l2, L2_LEVEL_ERROR, "option --destination, creating TCP socket (%s) failed with \"%s\"", cp, sa_error(sa_rc)); throw(0,0,0); } ctx->pns[i].nntp = NULL; ctx->pns[i].rc = NNTP_OK; ctx->pns[i].l2 = ctx->l2; free(cp); ctx->nns = ++i; } } } catch (ex) rethrow; /* --groupmode SINGLE */ try { if ( (val_get(ctx->val, "option.groupmode", &ov) != VAL_OK) || (ov->ndata < 0) || (ov->ndata == 1 && ov->data.s == NULL) || (ov->ndata > 1) ) throw(0,0,0); logbook(ctx->l2, L2_LEVEL_TRACE, "--groupmode = \"%s\"", ov->data.s); if (ov->ndata == 1) { if (strcasecmp(ov->data.s, "arg") == 0) ctx->option_groupmode = GROUPMODE_ARG; else if (strcasecmp(ov->data.s, "envelope") == 0) ctx->option_groupmode = GROUPMODE_ENVELOPE; else if (strcasecmp(ov->data.s, "header") == 0) ctx->option_groupmode = GROUPMODE_HEADER; else { logbook(ctx->l2, L2_LEVEL_ERROR, "option --groupmode, invalid mode (%s)", ov->data.s); throw(0,0,0); } } } catch (ex) rethrow; /* --headerrule MULTI */ { volatile headerrule_t *hrNew = NULL; /* declare and initialize variables which might have resources allocated that need to be cleaned up when an exception is caught */ try { char *cp, *cpP; int n; int i; headerrule_t *hrI; headerrule_t *hrP; const char *cpError; int iError; if ( (val_get(ctx->val, "option.headerrule", &ov) != VAL_OK) || (ov->ndata < 0) || (ov->ndata >= 1 && ov->data.m == NULL) ) throw(0,0,0); logbook(ctx->l2, L2_LEVEL_DEBUG, "ov->ndata = %d", ov->ndata); for (i = 0; i < ov->ndata; i++) logbook(ctx->l2, L2_LEVEL_TRACE, "--headerule[%d] = \"%s\"", i, (ov->data.m)[i]); if (ov->ndata >= 1) { for (i = 0; i < ov->ndata; i++) { cp = (ov->data.m)[i]; logbook(ctx->l2, L2_LEVEL_DEBUG, "cp = (data.m)[%d] = \"%s\"", i, cp); hrNew = (headerrule_t *)mallocex(sizeof(headerrule_t)); hrNew->next = NULL; hrNew->pri = 500; /* default priority */ hrNew->regex = NULL; hrNew->name = NULL; hrNew->val = NULL; hrNew->pcreRegex = NULL; hrNew->pcreExtra = NULL; /* priority */ cpP = cp; if ((cp = strchr(cp, ':')) == NULL) { logbook(ctx->l2, L2_LEVEL_ERROR, "option --headerrule, priority (%s) terminating colon missing", (ov->data.m)[i]); throw(0,0,0); } cp++; n = cp - cpP; if (n >= 2) /* mandatory colon and at least one more char */ hrNew->pri = atoi(cpP); /* regex */ cpP = cp; if ((cp = strchr(cp, ':')) == NULL) { logbook(ctx->l2, L2_LEVEL_ERROR, "option --headerrule, regex (%s) terminating colon missing", (ov->data.m)[i]); throw(0,0,0); } cp++; n = cp - cpP; if (n >= 2) /* mandatory colon and at least one more char */ hrNew->regex = str_dupex(cpP, n); /* header */ cpP = cp; if ((cp = strchr(cp, ':')) == NULL) { logbook(ctx->l2, L2_LEVEL_ERROR, "option --headerrule, header (%s) terminating colon missing", (ov->data.m)[i]); throw(0,0,0); } cp++; n = cp - cpP; if (n == 0) { logbook(ctx->l2, L2_LEVEL_ERROR, "option --headerrule, header (%s) missing", (ov->data.m)[i]); throw(0,0,0); } hrNew->name = str_dupex(cpP, n); /* value */ cpP = cp; n = strlen(cpP); if (n >= 1) hrNew->val = str_dupex(cpP, n); if (hrNew->regex != NULL) { /* compile regular expression into finite state machine and optimize */ if ((hrNew->pcreRegex = pcre_compile(hrNew->regex, PCRE_CASELESS, &cpError, &iError, NULL)) == NULL) { logbook(ctx->l2, L2_LEVEL_ERROR, "option --headerrule, regex (%s) failed at pos %d with %s", hrNew->regex, iError, cpError); throw(0,0,0); } hrNew->pcreExtra = pcre_study(hrNew->pcreRegex, 0, &cpError); if (cpError != NULL) { logbook(ctx->l2, L2_LEVEL_ERROR, "option --headerrule, regex optimization failed with %s", cpError); throw(0,0,0); } } if (ctx->option_firstheaderrule == NULL) ctx->option_firstheaderrule = (headerrule_t *)hrNew; /* first */ else { for (hrP = NULL, hrI = ctx->option_firstheaderrule; hrI != NULL && hrI->pri <= hrNew->pri; hrP = hrI, hrI = hrI->next); if (hrI != NULL) hrNew->next = hrI; /* insert */ if (hrP != NULL) hrP->next = (headerrule_t *)hrNew; /* append */ else ctx->option_firstheaderrule = (headerrule_t *)hrNew; /* new first */ } hrNew = NULL; /* release cleanup responsibility */ } /* establish variable expansion context */ { var_rc_t rc2; if ((rc2 = var_create(&ctx->config_varregex)) != VAR_OK) { logbook(ctx->l2, L2_LEVEL_ERROR, "option --headerrule, create regex context failed with %s (%d)", var_strerror(ctx->config_varregex, rc2, &cp) == VAR_OK ? cp : "Unknown Error", rc2); throw(0,0,0); } if ((rc2 = var_config(ctx->config_varregex, VAR_CONFIG_SYNTAX, &syntax_regex)) != VAR_OK) { logbook(ctx->l2, L2_LEVEL_ERROR, "option --headerrule, config regex context failed with %s (%d)", var_strerror(ctx->config_varregex, rc2, &cp) == VAR_OK ? cp : "Unknown Error", rc2); throw(0,0,0); } } } } cleanup { if (hrNew != NULL) { if (hrNew->pcreExtra != NULL) free(hrNew->pcreExtra); if (hrNew->pcreRegex != NULL) free(hrNew->pcreRegex); if (hrNew->val != NULL) freeex(hrNew->val); if (hrNew->name != NULL) freeex(hrNew->name); if (hrNew->regex != NULL) freeex(hrNew->regex); freeex((headerrule_t *)hrNew); } } catch (ex) rethrow; } /* --mailfrom SINGLE */ try { char *cp; if ( (val_get(ctx->val, "option.mailfrom", &ov) != VAL_OK) || (ov->ndata < 0) || (ov->ndata == 1 && ov->data.s == NULL) || (ov->ndata > 1) ) throw(0,0,0); logbook(ctx->l2, L2_LEVEL_TRACE, "--mailfrom = \"%s\"", ov->data.s); if (ov->ndata == 1) { ctx->option_mailfrom = strdup(ov->data.s); /* protect ourselfs from the substitution of backreferences. * Missing varargs would cause segfaults. Rewrite capturing * brackets to clustering syntax. Use poor man's s///g * simulator as current str library doesn't support global * substitution */ while (str_parse(ctx->option_mailfrom, "s/(.*?)\\((?!\\?:)(.*)/$1(?:$2/", &cp) > 0) { free(ctx->option_mailfrom); ctx->option_mailfrom = cp; } if (str_parse("<>", ctx->option_mailfrom) == -1) { logbook(ctx->l2, L2_LEVEL_ERROR, "option --mailfrom, illegal regex (%s)", ctx->option_mailfrom); throw(0,0,0); } } } catch (ex) rethrow; /* --nodename SINGLE */ try { if ( (val_get(ctx->val, "option.nodename", &ov) != VAL_OK) || (ov->ndata < 0) || (ov->ndata == 1 && ov->data.s == NULL) || (ov->ndata > 1) ) throw(0,0,0); logbook(ctx->l2, L2_LEVEL_TRACE, "--nodename = \"%s\"", ov->data.s); if (ov->ndata == 1) { ctx->option_nodename = strdupex(ov->data.s); } else { struct utsname name; if (uname(&name) == -1) { logbook(ctx->l2, L2_LEVEL_ERROR, "option --nodename, uname() failed %m"); throw(0,0,0); } ctx->option_nodename = strdupex(name.nodename); } } catch (ex) rethrow; /* --operationmode SINGLE */ try { char *cp; if ( (val_get(ctx->val, "option.operationmode", &ov) != VAL_OK) || (ov->ndata < 0) || (ov->ndata == 1 && ov->data.s == NULL) || (ov->ndata > 1) ) throw(0,0,0); logbook(ctx->l2, L2_LEVEL_TRACE, "--operationmode = \"%s\"", ov->data.s); if (ov->ndata == 1) { cp = strdup(ov->data.s); if (strcasecmp(cp, "post") == 0) ctx->option_operationmode = OPERATIONMODE_POST; else if (strcasecmp(cp, "feed") == 0) ctx->option_operationmode = OPERATIONMODE_FEED; else { ctx->option_operationmode = OPERATIONMODE_FAKE; if (strlen(cp) != 9) { logbook(ctx->l2, L2_LEVEL_ERROR, "option --operationmode, invalid length (%s)", cp); throw(0,0,0); } if (cp[3] != '/') { logbook(ctx->l2, L2_LEVEL_ERROR, "option --operationmode, missing slash (%s)", cp); throw(0,0,0); } cp[3] = NUL; ctx->option_operationmodefakestatus = &cp[0]; ctx->option_operationmodefakedsn = &cp[4]; if ( strlen(ctx->option_operationmodefakestatus) != 3 || !isdigit((int)ctx->option_operationmodefakestatus[0]) || !isdigit((int)ctx->option_operationmodefakestatus[1]) || !isdigit((int)ctx->option_operationmodefakestatus[2]) ) { logbook(ctx->l2, L2_LEVEL_ERROR, "option --operationmode, invalid status code (%s)", cp); throw(0,0,0); } if ( (strlen(ctx->option_operationmodefakedsn) != 5) || !isdigit((int)ctx->option_operationmodefakedsn[0]) || (ctx->option_operationmodefakedsn[1] != '.') || !isdigit((int)ctx->option_operationmodefakedsn[2]) || (ctx->option_operationmodefakedsn[3] != '.') || !isdigit((int)ctx->option_operationmodefakedsn[4]) || (ctx->option_operationmodefakedsn[0] != ctx->option_operationmodefakestatus[0]) ) { logbook(ctx->l2, L2_LEVEL_ERROR, "option --operationmode, invalid dsn code (%s)", cp); throw(0,0,0); } } } else { logbook(ctx->l2, L2_LEVEL_ERROR, "option --operationmode, is mandatory but neither given nor preset (internal)"); throw(0,0,0); } } catch (ex) rethrow; /* --restrictheader SINGLE */ try { char *cp; if ( (val_get(ctx->val, "option.restrictheader", &ov) != VAL_OK) || (ov->ndata < 0) || (ov->ndata == 1 && ov->data.s == NULL) || (ov->ndata > 1) ) throw(0,0,0); logbook(ctx->l2, L2_LEVEL_TRACE, "--restrictheader = \"%s\"", ov->data.s); if (ov->ndata == 1) { ctx->option_restrictheader = strdup(ov->data.s); /* protect ourselfs from the substitution of backreferences. * Missing varargs would cause segfaults. Rewrite capturing * brackets to clustering syntax. Use poor man's s///g * simulator as current str library doesn't support global * substitution */ while (str_parse(ctx->option_restrictheader, "s/(.*?)\\((?!\\?:)(.*)/$1(?:$2/", &cp) > 0) { free(ctx->option_restrictheader); ctx->option_restrictheader = cp; } if (str_parse("<>", ctx->option_restrictheader) == -1) { logbook(ctx->l2, L2_LEVEL_ERROR, "option --restrictheader, illegal regex (%s)", ctx->option_restrictheader); throw(0,0,0); } } } catch (ex) rethrow; /* --size SINGLE */ try { if ( (val_get(ctx->val, "option.size", &ov) != VAL_OK) || (ov->ndata < 0) || (ov->ndata == 1 && ov->data.s == NULL) || (ov->ndata > 1) ) throw(0,0,0); logbook(ctx->l2, L2_LEVEL_TRACE, "--size = \"%s\"", ov->data.s); if (ov->ndata == 1) if ((ctx->option_maxmessagesize = atoi(ov->data.s)) <= 0) { logbook(ctx->l2, L2_LEVEL_ERROR, "option --size, number (%d) out of range", ctx->option_maxmessagesize); throw(0,0,0); } } catch (ex) rethrow; /* --timeoutlmtp SINGLE */ try { int i; if ( (val_get(ctx->val, "option.timeoutlmtp", &ov) != VAL_OK) || (ov->ndata < 0) || (ov->ndata == 1 && ov->data.s == NULL) || (ov->ndata > 1) ) throw(0,0,0); logbook(ctx->l2, L2_LEVEL_TRACE, "--timeoutlmtp= \"%s\"", ov->data.s); if (ov->ndata == 1) { if ((i = atoi(ov->data.s)) < 0) { logbook(ctx->l2, L2_LEVEL_ERROR, "option --timeoutlmtp, number (%d) out of range", i); throw(0,0,0); } ctx->option_timeout_lmtp_accept = i; ctx->option_timeout_lmtp_read = i; ctx->option_timeout_lmtp_write = i; } } catch (ex) rethrow; /* --timeoutlmtpaccept SINGLE */ try { if ( (val_get(ctx->val, "option.timeoutlmtpaccept", &ov) != VAL_OK) || (ov->ndata < 0) || (ov->ndata == 1 && ov->data.s == NULL) || (ov->ndata > 1) ) throw(0,0,0); logbook(ctx->l2, L2_LEVEL_TRACE, "--timeoutlmtpaccept = \"%s\"", ov->data.s); if (ov->ndata == 1) if ((ctx->option_timeout_lmtp_accept = atoi(ov->data.s)) < 0) { logbook(ctx->l2, L2_LEVEL_ERROR, "option --timeoutlmtpaccept, number (%d) out of range", ctx->option_timeout_lmtp_accept); throw(0,0,0); } } catch (ex) rethrow; /* --timeoutlmtpread SINGLE */ try { if ( (val_get(ctx->val, "option.timeoutlmtpread", &ov) != VAL_OK) || (ov->ndata < 0) || (ov->ndata == 1 && ov->data.s == NULL) || (ov->ndata > 1) ) throw(0,0,0); logbook(ctx->l2, L2_LEVEL_TRACE, "--timeoutlmtpread = \"%s\"", ov->data.s); if (ov->ndata == 1) if ((ctx->option_timeout_lmtp_read = atoi(ov->data.s)) < 0) { logbook(ctx->l2, L2_LEVEL_ERROR, "option --timeoutlmtpread, number (%d) out of range", ctx->option_timeout_lmtp_read); throw(0,0,0); } } catch (ex) rethrow; /* --timeoutlmtpwrite SINGLE */ try { if ( (val_get(ctx->val, "option.timeoutlmtpwrite", &ov) != VAL_OK) || (ov->ndata < 0) || (ov->ndata == 1 && ov->data.s == NULL) || (ov->ndata > 1) ) throw(0,0,0); logbook(ctx->l2, L2_LEVEL_TRACE, "--timeoutlmtpwrite = \"%s\"", ov->data.s); if (ov->ndata == 1) if ((ctx->option_timeout_lmtp_write = atoi(ov->data.s)) < 0) { logbook(ctx->l2, L2_LEVEL_ERROR, "option --timeoutlmtpwrite, number (%d) out of range", ctx->option_timeout_lmtp_write); throw(0,0,0); } } catch (ex) rethrow; /* --timeoutnntp SINGLE */ try { int i; if ( (val_get(ctx->val, "option.timeoutnntp", &ov) != VAL_OK) || (ov->ndata < 0) || (ov->ndata == 1 && ov->data.s == NULL) || (ov->ndata > 1) ) throw(0,0,0); logbook(ctx->l2, L2_LEVEL_TRACE, "--timeoutnntp= \"%s\"", ov->data.s); if (ov->ndata == 1) { if ((i = atoi(ov->data.s)) < 0) { logbook(ctx->l2, L2_LEVEL_ERROR, "option --timeoutnntp, number (%d) out of range", i); throw(0,0,0); } ctx->option_timeout_nntp_connect = i; ctx->option_timeout_nntp_read = i; ctx->option_timeout_nntp_write = i; } } catch (ex) rethrow; /* --timeoutnntpconnect SINGLE */ try { if ( (val_get(ctx->val, "option.timeoutnntpconnect", &ov) != VAL_OK) || (ov->ndata < 0) || (ov->ndata == 1 && ov->data.s == NULL) || (ov->ndata > 1) ) throw(0,0,0); logbook(ctx->l2, L2_LEVEL_TRACE, "--timeoutnntpconnect = \"%s\"", ov->data.s); if (ov->ndata == 1) if ((ctx->option_timeout_nntp_connect = atoi(ov->data.s)) < 0) { logbook(ctx->l2, L2_LEVEL_ERROR, "option --timeoutnntpconnect, number (%d) out of range", ctx->option_timeout_nntp_connect); throw(0,0,0); } } catch (ex) rethrow; /* --timeoutnntpread SINGLE */ try { if ( (val_get(ctx->val, "option.timeoutnntpread", &ov) != VAL_OK) || (ov->ndata < 0) || (ov->ndata == 1 && ov->data.s == NULL) || (ov->ndata > 1) ) throw(0,0,0); logbook(ctx->l2, L2_LEVEL_TRACE, "--timeoutnntpread = \"%s\"", ov->data.s); if (ov->ndata == 1) if ((ctx->option_timeout_nntp_read = atoi(ov->data.s)) < 0) { logbook(ctx->l2, L2_LEVEL_ERROR, "option --timeoutnntpread, number (%d) out of range", ctx->option_timeout_nntp_read); throw(0,0,0); } } catch (ex) rethrow; /* --timeoutnntpwrite SINGLE */ try { if ( (val_get(ctx->val, "option.timeoutnntpwrite", &ov) != VAL_OK) || (ov->ndata < 0) || (ov->ndata == 1 && ov->data.s == NULL) || (ov->ndata > 1) ) throw(0,0,0); logbook(ctx->l2, L2_LEVEL_TRACE, "--timeoutnntpwrite = \"%s\"", ov->data.s); if (ov->ndata == 1) if ((ctx->option_timeout_nntp_write = atoi(ov->data.s)) < 0) { logbook(ctx->l2, L2_LEVEL_ERROR, "option --timeoutnntpwrite, number (%d) out of range", ctx->option_timeout_nntp_write); throw(0,0,0); } } catch (ex) rethrow; /* --user SINGLE */ try { struct passwd *sPasswd; if ( (val_get(ctx->val, "option.user", &ov) != VAL_OK) || (ov->ndata < 0) || (ov->ndata == 1 && ov->data.s == NULL) || (ov->ndata > 1) ) throw(0,0,0); logbook(ctx->l2, L2_LEVEL_TRACE, "--user = \"%s\"", ov->data.s); if (ov->ndata == 1) { if (isdigit((int)ov->data.s[0])) { if ((sPasswd = getpwuid((uid_t)atoi(ov->data.s))) == NULL) { logbook(ctx->l2, L2_LEVEL_ERROR, "option --user, uid (%s) not found", ov->data.s); throw(0,0,0); } } else { if ((sPasswd = getpwnam(ov->data.s)) == NULL) { logbook(ctx->l2, L2_LEVEL_ERROR, "option --user, name (%s) not found", ov->data.s); throw(0,0,0); } } ctx->option_uid = sPasswd->pw_uid; } } catch (ex) rethrow; /* --newsgroup MULTI */ try { char *cp; int i; if ( (val_get(ctx->val, "option.newsgroup", &ov) != VAL_OK) || (ov->ndata < 0) || (ov->ndata >= 1 && ov->data.m == NULL) ) throw(0,0,0); logbook(ctx->l2, L2_LEVEL_DEBUG, "ov->ndata = %d", ov->ndata); for (i = 0; i < ov->ndata; i++) logbook(ctx->l2, L2_LEVEL_TRACE, "--newsgroup[%d] = \"%s\"", i, (ov->data.m)[i]); if (ov->ndata >= 1) { for (i = 0; i < ov->ndata; i++) { cp = (ov->data.m)[i]; logbook(ctx->l2, L2_LEVEL_DEBUG, "cp = (data.m)[%d] = \"%s\"", i, cp); argz_add(&ctx->azGroupargs, &ctx->asGroupargs, cp); } } } catch (ex) rethrow; /* --testfile MULTI */ try { char *cp; int i; char *cpBuf = NULL; if ( (val_get(ctx->val, "option.testfile", &ov) != VAL_OK) || (ov->ndata < 0) || (ov->ndata >= 1 && ov->data.m == NULL) ) throw(0,0,0); logbook(ctx->l2, L2_LEVEL_DEBUG, "ov->ndata = %d", ov->ndata); for (i = 0; i < ov->ndata; i++) logbook(ctx->l2, L2_LEVEL_TRACE, "--testfile[%d] = \"%s\"", i, (ov->data.m)[i]); if (ov->ndata >= 1) { for (i = 0; i < ov->ndata; i++) { cp = (ov->data.m)[i]; logbook(ctx->l2, L2_LEVEL_DEBUG, "cp = (data.m)[%d] = \"%s\"", i, cp); { const char *filename = cp; struct stat sb; volatile int fd = -1; /* ex_t ex; */ try { if (stat(filename, &sb) == -1) throw(0, 0, "stat"); if ((cpBuf = (char *)malloc((size_t)sb.st_size + 1)) == NULL) throw(0, 0, "malloc"); if ((fd = open(filename, O_RDONLY)) == -1) throw(0, 0, "open"); if (read(fd, (void *)cpBuf, (size_t)sb.st_size) != (ssize_t)sb.st_size) throw(0, 0, "read"); cpBuf[(int)sb.st_size] = '\0'; } cleanup { if (fd != -1) close(fd); } catch (ex) { fprintf(stderr, "ERROR: caught %s\n", ex.ex_value == NULL ? "N/A" : (char *)ex.ex_value); rethrow; } } { volatile msg_t *msg = NULL; msg_rc_t rc2; try { ctx->msgcount++; if ((msg = msg_create(ctx->prival)) == NULL) throw(0, 0, "msg_create"); msg->l2 = ctx->l2; msg->cpMsg = cpBuf; if ((rc2 = msg_split((msg_t *)msg)) != MSG_OK) { logbook(ctx->l2, L2_LEVEL_ERROR, "Error splitting message: %s", msg_error(rc2)); throw(0, 0, "msg_split"); } msg_headermatrixbuildup((msg_t *)msg); ctx->msg = (msg_t *)msg; headerrewrite(ctx); msg_headermatrixteardwn((msg_t *)msg); argz_add(&((msg_t *)msg)->azNewsgroups, &((msg_t *)msg)->asNewsgroups, "invalid.test"); if ((rc2 = msg_join((msg_t *)msg)) != MSG_OK) { logbook(ctx->l2, L2_LEVEL_ERROR, "Error joining message: %s", msg_error(rc2)); throw(0, 0, "msg_join"); } printf("%s", msg->cpMsg); } cleanup { if (msg != NULL) msg_destroy((msg_t *)msg); ctx->msg = NULL; } catch (ex) { rethrow; } } } CU(CONFIG_OK_DRY); } } catch (ex) rethrow; CU(CONFIG_OK); CUS: return rc; }