ossp-pkg/lmtp2nntp/lmtp2nntp_config.c
1.25
/*
** Copyright (c) 2001-2002 The OSSP Project <http://www.ossp.org/>
** Copyright (c) 2001-2002 Cable & Wireless Deutschland <http://www.cw.com/de/>
**
** This file is part of OSSP lmtp2nntp, an LMTP speaking local
** mailer which forwards mails as Usenet news articles via NNTP.
** It can be found at http://www.ossp.org/pkg/lmtp2nntp/.
**
** This program is free software; you can redistribute it and/or
** modify it under the terms of the GNU General Public License
** as published by the Free Software Foundation; either version
** 2.0 of the License, or (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
** General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this file; if not, write to the Free Software
** Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
** USA, or contact the OSSP project <ossp@ossp.org>.
**
** lmtp2nntp_config.c: config handling
*/
#include <stdlib.h>
#include <stdio.h>
#include <sys/stat.h>
#include <errno.h>
/* third party (included) */
#include "lmtp2nntp_argz.h"
/* third party (linked in) */
#include "str.h"
#include "val.h"
#include "popt.h"
#include "l2.h"
/* library version check (compile-time) */
#define STR_VERSION_HEX_REQ 0x009206
#define STR_VERSION_STR_REQ "0.9.6"
#ifdef STR_VERSION_HEX
#if STR_VERSION_HEX < STR_VERSION_HEX_REQ
#error "require a newer version of OSSP Str"
#endif
#endif
/* own headers */
#include "lmtp2nntp_global.h"
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#if defined(HAVE_DMALLOC_H) && defined(DMALLOC)
#include "dmalloc.h"
#endif
#include "lmtp2nntp_option.h"
#include "lmtp2nntp_config.h"
#include "lmtp2nntp_lmtp.h"
#include "lmtp2nntp_nntp.h"
#include "lmtp2nntp_msg.h"
#include "fixme.h"
#define _LMTP2NNTP_VERSION_C_AS_HEADER_
#include "lmtp2nntp_version.c"
#undef _LMTP2NNTP_VERSION_C_AS_HEADER_
#ifndef FALSE
#define FALSE (1 != 1)
#endif
#ifndef TRUE
#define TRUE (!FALSE)
#endif
#ifndef NUL
#define NUL '\0'
#endif
static l2_result_t
formatter_prefix(l2_context_t *_ctx, const char id, const char *param,
char *bufptr, size_t bufsize, size_t *buflen, va_list *ap)
{
lmtp2nntp_t *ctx = (lmtp2nntp_t *)_ctx->vp;
if ((ctx->msg != NULL) && (ctx->msg->cpFid != NULL)) {
sprintf(bufptr, "%s: ", ctx->msg->cpFid);
*buflen = strlen(bufptr);
}
else
*buflen = 0;
return L2_OK;
}
static l2_result_t
formatter_errno(l2_context_t *_ctx, const char id, const char *param,
char *bufptr, size_t bufsize, size_t *buflen, va_list *ap)
{
sprintf(bufptr, "(%d) %s", errno, strerror(errno));
*buflen = strlen(bufptr);
return L2_OK;
}
void config_context(lmtp2nntp_t *ctx)
{
ex_t ex;
optionval_t *ov;
//char *cp;
int 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(ERR_EXECUTION);
}
if (l2_env_formatter(ctx->l2_env, 'P', formatter_prefix, &ctx->ctx) != L2_OK) {
fprintf(stderr, "%s:Error: logging failed to register prefix formatter\n", ctx->progname);
CU(ERR_EXECUTION);
}
if (l2_env_formatter(ctx->l2_env, 'D', l2_util_fmt_dump, NULL) != L2_OK) {
fprintf(stderr, "%s:Error: logging failed to register dump formatter\n", ctx->progname);
CU(ERR_EXECUTION);
}
if (l2_env_formatter(ctx->l2_env, 'S', l2_util_fmt_string, NULL) != L2_OK) {
fprintf(stderr, "%s:Error: logging failed to register string formatter\n", ctx->progname);
CU(ERR_EXECUTION);
}
if (l2_env_formatter(ctx->l2_env, 'm', formatter_errno, NULL) != L2_OK) {
fprintf(stderr, "%s:Error: logging failed to register errno formatter\n", ctx->progname);
CU(ERR_EXECUTION);
}
if (val_get(ctx->val, "option.l2spec", &ov) != VAL_OK) {
fprintf(stderr, "%s:Error: (internal) config did not register 'l2spec' option\n", ctx->progname);
CU(ERR_EXECUTION);
}
if (ov->data.s != NULL) {
if ((rc = l2_spec(&ctx->l2, ctx->l2_env, ov->data.s)) != L2_OK) {
fprintf(stderr, "%s:Error: logging failed to create stream\n", ctx->progname);
CU(ERR_EXECUTION);
}
if (l2_channel_levels(ctx->l2, L2_LEVEL_ALL, L2_LEVEL_NONE) != L2_OK) { /* FIXME should this globalmask and flushmask be user-configurable? */
fprintf(stderr, "%s:Error: logging failed to set global logging level\n", ctx->progname);
CU(ERR_EXECUTION);
}
if (l2_channel_open(ctx->l2) != L2_OK) {
fprintf(stderr, "%s:Error: logging failed to open channel stream\n", ctx->progname);
CU(ERR_EXECUTION);
}
}
/* from this point on logging is up and running and fprintf(stderr, ...)
* should not be used in the remainder of the program flow.
*/
log1(ctx, NOTICE, "startup, version %s", lmtp2nntp_version.v_gnu);
/* --childsmax SINGLE */
try {
if ( (val_get(ctx->val, "option.childsmax", &ov) != VAL_OK)
|| (ov->ndata != 1)
|| (ov->data.s == NULL)
) throw(0,0,0);
log1(ctx, TRACE, "--childsmax = \"%s\"", ov->data.s);
if ((ctx->option_childsmax = atoi(ov->data.s)) <= 0) {
log1(ctx, ERROR, "option --childsmax, number (%d) out of range", ctx->option_childsmax);
throw(0,0,0);
}
}
catch (ex) {
rethrow;
}
/* --daemonize FLAG */
try {
if ( (val_get(ctx->val, "option.daemonize", &ov) != VAL_OK)
|| (ov->ndata != 1)
|| (ov->data.f != 1)
) throw(0,0,0);
log1(ctx, TRACE, "--daemonize = %d", ov->data.f);
ctx->option_daemon = TRUE;
}
catch (ex)
rethrow;
/* --kill FLAG */
try {
if ( (val_get(ctx->val, "option.kill", &ov) != VAL_OK)
|| (ov->ndata != 1)
|| (ov->data.f != 1)
) throw(0,0,0);
log1(ctx, TRACE, "--kill = %d", ov->data.f);
ctx->option_killflag = TRUE;
}
catch (ex)
rethrow;
/* --pidfile SINGLE */
try {
if ( (val_get(ctx->val, "option.pidfile", &ov) != VAL_OK)
|| (ov->ndata != 1)
|| (ov->data.s == NULL)
) throw(0,0,0);
log1(ctx, TRACE, "--pidfile = \"%s\"", ov->data.s);
ctx->option_pidfile = ov->data.s;
}
catch (ex)
rethrow;
/* --acl MULTI */
try {
char *cp;
int i;
if ( (val_get(ctx->val, "option.acl", &ov) != VAL_OK)
|| ((ov->ndata >= 1) && (ov->data.m == NULL))
) throw(0,0,0);
log1(ctx, DEBUG, "ov->ndata = %d", ov->ndata);
for (i = 0; i < ov->ndata; i++)
log2(ctx, TRACE, "--acl[%d] = \"%s\"", i, (ov->data.m)[i]);
if ((ctx->pacl = (struct acl *)malloc(ov->ndata * sizeof(struct acl))) == NULL) throw(0,0,0);
for (i = 0; i < ov->ndata; i++) {
cp = (ov->data.m)[i];
log2(ctx, DEBUG, "cp = (data.m)[%d] = \"%s\"", i, cp);
if (cp[0] == '!') {
ctx->pacl[i].acl = strdup(cp + 1);
ctx->pacl[i].not = TRUE;
}
else {
ctx->pacl[i].acl = strdup(cp);
ctx->pacl[i].not = TRUE;
}
log2(ctx, DEBUG, "ctx->pacl[%d].not = %s", i, ctx->pacl[i].not == TRUE ? "TRUE" : "FALSE");
log2(ctx, DEBUG, "ctx->pacl[%d].acl = %s", i, ctx->pacl[i].acl);
if ((cp = strrchr(ctx->pacl[i].acl, '/')) != NULL)
*cp++ = NUL;
else
cp = "-1";
ctx->pacl[i].prefixlen = atoi(cp);
log2(ctx, DEBUG, "ctx->pacl[%d].prefixlen = %d", i, ctx->pacl[i].prefixlen);
if ((rc = sa_addr_create(&(ctx->pacl[i].saa))) != SA_OK) {
log1(ctx, ERROR, "option --acl, create address (internal) failed with \"%s\"", sa_error(rc));
throw(0,0,0);
}
if ((rc = sa_addr_u2a(ctx->pacl[i].saa, "inet://%s:0", ctx->pacl[i].acl)) != SA_OK) {
log2(ctx, ERROR, "option --acl, parsing address (%s) failed with \"%s\"", ctx->pacl[i].acl, sa_error(rc));
throw(0,0,0);
}
}
ctx->nacl = i;
}
catch (ex)
rethrow;
/* --bind SINGLE */
try {
if ( (val_get(ctx->val, "option.bind", &ov) != VAL_OK)
|| (ov->ndata != 1)
|| (ov->data.s == NULL)
) throw(0,0,0);
log1(ctx, TRACE, "--bind = \"%s\"", ov->data.s);
/* dash means stdio */
if (strcmp(ov->data.s, "-") != 0) { //FIXME does this work with popt()?
if ((rc = sa_create(&ctx->saServerbind)) != SA_OK) {
log1(ctx, ERROR, "option --bind, creating TCP socket (internal) failed with \"%s\"", sa_error(rc));
throw(0,0,0);
}
if ((rc = sa_addr_create(&ctx->saaServerbind)) != SA_OK) {
log1(ctx, ERROR, "option --bind, create address (internal) failed with \"%s\"", sa_error(rc));
throw(0,0,0);
}
/* slash means UNIX socket */
if (ov->data.s[0] == '/') {
char *cpPath;
char *cpPerm;
int nPerm;
int n;
int i;
cpPath = strdup(optarg);
cpPerm = NULL;
nPerm = -1;
if ((cpPerm = strrchr(cpPath, ':')) != NULL) {
*cpPerm++ = '\0';
nPerm = 0;
for (i = 0; i < 4 && cpPerm[i] != '\0'; i++) {
if (!isdigit((int)cpPerm[i])) {
nPerm = -1;
break;
}
n = cpPerm[i] - '0';
if (n > 7) {
nPerm = -1;
break;
}
nPerm = ((nPerm << 3) | n);
}
if (nPerm == -1 || cpPerm[i] != '\0') {
log1(ctx, ERROR, "option --bind, invalid permissions \"%s\"", cpPerm);
throw(0,0,0);
}
}
if ((rc = sa_addr_u2a(ctx->saaServerbind, "unix:%s", cpPath)) != SA_OK) {
log2(ctx, ERROR, "option --bind, parsing alternate IO guessing UNIX socket (%s) failed with \"%s\"", cpPath, sa_error(rc));
throw(0,0,0);
}
if ((rc = sa_bind(ctx->saServerbind, ctx->saaServerbind)) != SA_OK) {
log2(ctx, ERROR, "option --bind, bind (%s) failed with \"%s\"", cpPath, sa_error(rc));
throw(0,0,0);
}
if (nPerm != -1) {
if (chmod(cpPath, nPerm) == -1) {
log3(ctx, ERROR, "option --bind, chmod (%s, 0%o) failed with \"%s\"", cpPath, nPerm, strerror(errno));
throw(0,0,0);
}
}
if (getuid() == 0 && getuid() != ctx->option_uid) {
if (chown(cpPath, ctx->option_uid, -1) == -1) {
log3(ctx, ERROR, "option --bind, chown (%s, %d) failed with \"%s\"", cpPath, ctx->option_uid, strerror(errno));
throw(0,0,0);
}
}
free(cpPath);
}
/* otherwise assume INET socket */
else {
if ((rc = sa_addr_u2a(ctx->saaServerbind, "inet://%s", ov->data.s)) != SA_OK) {
log2(ctx, ERROR, "option --bind, parsing alternate IO guessing INET socket (%s) failed with \"%s\"", ov->data.s, sa_error(rc));
throw(0,0,0);
}
if ((rc = sa_bind(ctx->saServerbind, ctx->saaServerbind)) != SA_OK) {
log2(ctx, ERROR, "option --bind, bind (%s) failed with \"%s\"", ov->data.s, sa_error(rc));
throw(0,0,0);
}
}
/* for either sockets */
if ((rc = sa_listen(ctx->saServerbind, -1)) != SA_OK) {
log2(ctx, ERROR, "option --bind, listen (%s) failed with \"%s\"", ov->data.s, sa_error(rc));
throw(0,0,0);
}
}
}
catch (ex)
rethrow;
/* --client SINGLE */
try {
if ( (val_get(ctx->val, "option.client", &ov) != VAL_OK)
|| (ov->ndata != 1)
|| (ov->data.s == NULL)
) throw(0,0,0);
log1(ctx, TRACE, "--client = \"%s\"", ov->data.s);
if ((rc = sa_addr_create(&ctx->saaClientbind)) != SA_OK) {
log1(ctx, ERROR, "option --client, create address (internal) failed with \"%s\"", sa_error(rc));
throw(0,0,0);
}
if ((rc = sa_addr_u2a(ctx->saaClientbind, "inet://%s", ov->data.s)) != SA_OK) {
log2(ctx, ERROR, "option --client, parsing alternate IO guessing INET socket (%s) failed with \"%s\"", ov->data.s, sa_error(rc));
throw(0,0,0);
}
}
catch (ex)
rethrow;
/* --destination MULTI */
try {
char *cp;
int i;
if ( (val_get(ctx->val, "option.destination", &ov) != VAL_OK)
|| ((ov->ndata >= 1) && (ov->data.m == NULL))
) throw(0,0,0);
log1(ctx, DEBUG, "ov->ndata = %d", ov->ndata);
for (i = 0; i < ov->ndata; i++)
log2(ctx, TRACE, "--destination[%d] = \"%s\"", i, (ov->data.m)[i]);
if ((ctx->pacl = (struct acl *)malloc(ov->ndata * sizeof(struct acl))) == NULL) throw(0,0,0);
for (i = 0; i < ov->ndata; i++) {
cp = (ov->data.m)[i];
log2(ctx, DEBUG, "cp = (data.m)[%d] = \"%s\"", i, cp);
if (strrchr(cp, ':') == NULL)
cp = str_concat(cp, ":nntp", NULL); //FIXME is this a config var/val?
else
cp = str_concat(cp, NULL); /* prepare for free() */
if ((rc = sa_addr_create(&ctx->ns[i].saa)) != SA_OK) {
log1(ctx, ERROR, "option --destination, create address (internal) failed with \"%s\"", sa_error(rc));
throw(0,0,0);
}
if ((rc = sa_addr_u2a(ctx->ns[i].saa, "inet://%s", cp)) != SA_OK) {
log2(ctx, ERROR, "option --destination, parsing host address (%s) failed with \"%s\"", cp /*FIXME again, option vs. config */, sa_error(rc));
throw(0,0,0);
}
if ((rc = sa_create(&ctx->ns[i].sa)) != SA_OK) {
log2(ctx, ERROR, "option --destination, creating TCP socket (%s) failed with \"%s\"", cp /*FIXME again, option vs. config */, sa_error(rc));
throw(0,0,0);
}
free(cp);
}
ctx->nns = i;
}
catch (ex)
rethrow;
/* --groupmode SINGLE */
try {
if ( (val_get(ctx->val, "option.groupmode", &ov) != VAL_OK)
|| (ov->ndata != 1)
|| (ov->data.s == NULL)
) throw(0,0,0);
log1(ctx, TRACE, "--groupmode = \"%s\"", ov->data.s);
if (strcasecmp(ov->data.s, "arg") == 0)
ctx->option_groupmode = GROUPMODE_ARG;
else if (strcasecmp(ov->data.s, "envelope") == 0)
ctx->option_groupmode = GROUPMODE_ENVELOPE;
else if (strcasecmp(ov->data.s, "header") == 0)
ctx->option_groupmode = GROUPMODE_HEADER;
else {
log1(ctx, ERROR, "option --groupmode, invalid mode (%s)", ov->data.s);
throw(0,0,0);
}
}
catch (ex)
rethrow;
/* --headervalue MULTI */
try {
char *cp;
int i;
char *cpHeader;
char *cpValue;
if ( (val_get(ctx->val, "option.headervalue", &ov) != VAL_OK)
|| ((ov->ndata >= 1) && (ov->data.m == NULL))
) throw(0,0,0);
log1(ctx, DEBUG, "ov->ndata = %d", ov->ndata);
for (i = 0; i < ov->ndata; i++)
log2(ctx, TRACE, "--headervalue[%d] = \"%s\"", i, (ov->data.m)[i]);
if ((ctx->pacl = (struct acl *)malloc(ov->ndata * sizeof(struct acl))) == NULL) throw(0,0,0);
for (i = 0; i < ov->ndata; i++) {
cp = (ov->data.m)[i];
log2(ctx, DEBUG, "cp = (data.m)[%d] = \"%s\"", i, cp);
cpHeader = strdup(cp);
if ((cp = strchr(cpHeader, ':')) == NULL) {
log1(ctx, ERROR, "option --headervalue, header (%s) terminating colon missing", (ov->data.m)[i]);
throw(0,0,0);
}
*cp = NUL;
log2(ctx, DEBUG, "header[%d] = \"%s\"", i, cpHeader);
cp++;
while (*cp == ' ') cp++; //FIXME note this in NEWS
if (*cp == NUL) {
log1(ctx, ERROR, "option --headervalue, value (%s) missing", (ov->data.m)[i]);
throw(0,0,0);
}
cpValue = strdup(cp);
log2(ctx, DEBUG, " value[%d] = \"%s\"", i, cpValue);
argz_add(&ctx->azHeaderValuePairs, &ctx->asHeaderValuePairs, cpHeader);
argz_add(&ctx->azHeaderValuePairs, &ctx->asHeaderValuePairs, cpValue);
free(cpHeader);
free(cpValue);
}
}
catch (ex)
rethrow;
CUS:
return;
}