ossp-pkg/lmtp2nntp/lmtp2nntp_lmtp.c
/*
** OSSP lmtp2nntp - Mail to News Gateway
** Copyright (c) 2001-2003 Ralf S. Engelschall <rse@engelschall.com>
** Copyright (c) 2001-2003 The OSSP Project <http://www.ossp.org/>
** Copyright (c) 2001-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_lmtp.c: Local Mail Transfer Protocol (LMTP) server library
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <ctype.h>
#include <errno.h>
#include "lmtp2nntp_lmtp.h"
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#if defined(HAVE_DMALLOC_H) && defined(WITH_DMALLOC)
#include "dmalloc.h"
#endif
#ifndef NUL
#define NUL '\0'
#endif
/* maximum LMTP protocol line length */
#define LMTP_LINE_MAXLEN 1024
/* maximum number of verbs/callbacks to be registered */
#define LMTP_MAXVERBS 32
typedef struct {
int rl_cnt;
char *rl_bufptr;
char rl_buf[LMTP_LINE_MAXLEN];
} lmtp_readline_t;
typedef struct {
char *verb;
lmtp_cb_t cb;
void *ctx;
} lmtp_dispatch_t;
struct lmtp_st {
lmtp_io_t io; /* select, read, write functions */
lmtp_readline_t rl; /* a function to read in a single line */
lmtp_dispatch_t **dispatch; /* LMTP commands to be dispatched */
};
ssize_t lmtp_fd_read(void *_ctx, void *buf, size_t buflen)
{
lmtp_fd_t *ctx = (lmtp_fd_t *)_ctx;
return read(ctx->fd, buf, buflen);
}
ssize_t lmtp_fd_write(void *_ctx, const void *buf, size_t buflen)
{
lmtp_fd_t *ctx = (lmtp_fd_t *)_ctx;
return write(ctx->fd, buf, buflen);
}
static int verbindex(lmtp_t *lmtp, char *verb)
{
/* returns the index of the verb or -1 if verb not registered */
int i;
for (i = 0; (i < LMTP_MAXVERBS) && (lmtp->dispatch[i] != NULL); i++)
if (strcasecmp(lmtp->dispatch[i]->verb, verb) == 0)
return i;
return -1;
}
static lmtp_rc_t lmtp_cb_default(lmtp_t *lmtp, lmtp_io_t *io, lmtp_req_t *req, void *ctx)
{
lmtp_res_t res;
lmtp_rc_t rc = LMTP_OK;
res.statuscode = "500";
res.dsncode = "5.5.1";
res.statusmsg = "Command unrecognized.";
lmtp_response(lmtp, &res);
return rc;
}
lmtp_t *lmtp_create(lmtp_io_t *io)
{
/* create a lmtp structure allocating memory for it and initializing it.
* A lmtp_cb_default() callback is registered for the default "" verb.
* The _rfd_ and _wfd_ args are passed to the read(), write()
* functions and must have meaning for them. If _io_ is NULL,
* the system io functions are used. You can provide an _io_ structure
* and specify alternate functions. Ommiting one or more functions inside
* the _io_ structure by NULLing it causes use of the system default
* function.
*/
lmtp_t *lmtp;
if ((lmtp = (lmtp_t *)malloc(sizeof(lmtp_t))) == NULL)
return NULL;
if (io == NULL)
return NULL;
lmtp->io.ctx = io->ctx;
lmtp->io.select = io->select;
lmtp->io.read = io->read;
lmtp->io.write = io->write;
lmtp->rl.rl_cnt = 0;
lmtp->rl.rl_bufptr = NULL;
lmtp->rl.rl_buf[0] = NUL;
if ((lmtp->dispatch = (lmtp_dispatch_t **)malloc(sizeof(void *)*LMTP_MAXVERBS)) == NULL)
return NULL;
lmtp->dispatch[0] = NULL;
lmtp_register(lmtp, "", lmtp_cb_default, NULL, NULL, NULL);
return lmtp;
}
void lmtp_destroy(lmtp_t *lmtp)
{
int i;
if (lmtp == NULL)
return;
for (i = 0; (i < LMTP_MAXVERBS) && (lmtp->dispatch[i] != NULL); i++) {
free(lmtp->dispatch[i]->verb); /* lmtp_register() */
free(lmtp->dispatch[i]); /* lmtp_register() */
}
free(lmtp->dispatch); /* lmtp_create() */
free(lmtp); /* lmtp_create() */
return;
}
lmtp_rc_t lmtp_readline(lmtp_t *lmtp, char *buf, size_t buflen)
{
/* read a line (characters until NL) from input stream */
size_t n;
char c;
lmtp_readline_t *rl = &lmtp->rl;
if (lmtp == NULL)
return LMTP_ERR_ARG;
for (n = 0; n < buflen-1;) {
/* fetch one character (but read more) */
if (rl->rl_cnt <= 0) {
do {
rl->rl_cnt = lmtp->io.read(lmtp->io.ctx, rl->rl_buf, LMTP_LINE_MAXLEN);
} while (rl->rl_cnt == -1 && errno == EINTR);
if (rl->rl_cnt == -1)
return LMTP_ERR_SYSTEM;
if (rl->rl_cnt == 0)
return LMTP_EOF;
rl->rl_bufptr = rl->rl_buf;
}
/* act on fetched character */
rl->rl_cnt--;
c = *rl->rl_bufptr++;
if (c == '\r')
continue; /* skip copying CR */
if (c == '\n')
break; /* end of line */
buf[n++] = c; /* output char into given buffer */
}
buf[n] = NUL; /* string termination */
if (n == (buflen-1))
return LMTP_ERR_OVERFLOW;
return LMTP_OK;
}
lmtp_rc_t lmtp_readmsg(lmtp_t *lmtp, char **cppBuf, size_t maxlen)
{
/* read lines until end of message, unescape dots.
* on success, returns a buffer which has to be free(3)d by the caller
*
* NOTE: the underlying lmtp_readline() already reduces any
* CR/LF combination to a string terminating zero. Callers of this
* function must assume multiline messages have lines terminated
* with NL only.
*
* RFC0821 "Simple Mail Transfer Protocol" [excerpt]
* 4.5.2. TRANSPARENCY
* When a line of mail text is received by the receiver-SMTP it checks
* the line. If the line is composed of a single period it is the end of
* mail. If the first character is a period and there are other
* characters on the line, the first character is deleted.
*/
lmtp_rc_t rc = LMTP_OK;
char *cpBuf; /* buffer as a whole */
char *cpBufrealloc;/* buffer before realloc */
char *cpPtr; /* write cursor */
char *cpLine; /* start of the current line (see offsetline) */
size_t nBuf; /* size of buffer, doubled through realloc until maximum reached */
size_t offset; /* required when cpBuf changed through realloc */
size_t offsetline; /* memorizing start of line when reallocing in the middle of a line */
for (nBuf = 4096; nBuf > maxlen; nBuf = nBuf >> 1);
if ((cpBuf = (char *)malloc(nBuf)) == NULL)
return LMTP_ERR_MEM;
*cppBuf = cpBuf; /* tell caller about the buffer */
cpPtr = cpBuf; /* initialize write cursor */
cpLine = cpBuf; /* initialize start of line */
while (1) {
rc = lmtp_readline(lmtp, cpPtr, nBuf-(cpPtr-cpBuf));
if (rc == LMTP_ERR_OVERFLOW) {
if (nBuf == maxlen)
return LMTP_ERR_OVERFLOW;
offset = nBuf-1; /* write cursor offset is end of buffer */
offsetline = cpLine - cpBuf; /* remember start of line offset */
nBuf *= 2; /* increase buffer */
if (nBuf > maxlen)
nBuf = maxlen; /* but don't exceed maximum */
if ((cpBufrealloc = (char *)realloc(cpBuf, nBuf)) == NULL) {
free(cpBuf);
return LMTP_ERR_MEM;
}
cpBuf = cpBufrealloc;
*cppBuf = cpBuf; /* tell caller about the new buffer */
cpPtr = cpBuf + offset; /* recover write cursor */
cpLine = cpBuf + offsetline; /* recover start of line */
}
else if (rc == LMTP_OK) {
if (strcmp(cpLine, ".") == 0) { /* dot alone is end of message */
*cpLine = NUL; /* hide dot from caller */
break;
}
if (*cpLine == '.') /* escaped dot */
memmove(cpLine, cpLine+1, strlen(cpLine+1)+1);
cpPtr += strlen(cpPtr); /* write cursor to the end */
*cpPtr++ = '\n'; /* artifical NL */
*cpPtr = NUL; /* artifical end of string */
cpLine = cpPtr; /* start of line */
}
else break; /* rc == LMTP_ERR* */
}
return rc;
}
lmtp_rc_t lmtp_request(lmtp_t *lmtp, lmtp_req_t *req)
{
/* reads a line and attaches the buffer to req->msg;
* pulls the verb out and attaches the verb to req->verb;
*
* LMTP_OK req->msg set, req->verb set means normal operation
* LMTP_OK req->msg set, req->verb "" means no verb seen
* LMTP_EOF req->msg set, req->verb NULL means eof
* LMTP_ERR_OVERFLOW req->msg set, req->verb NULL means buf overflow
* LMTP_ERR_SYSTEM req->msg set, req->verb NULL means system error
*
* RFC0821 "Simple Mail Transfer Protocol" [excerpts]
* 4.1.1. COMMAND SEMANTICS
* The command codes themselves are alphabetic characters terminated by
* <SP> if parameters follow and <CRLF> otherwise.
* 4.1.2. COMMAND SYNTAX
* <SP> ::= the space character (ASCII code 32)
*/
lmtp_rc_t rc;
char *verb;
int verblen;
int i;
req->verb = NULL;
if ((req->msg = (char *)malloc(LMTP_LINE_MAXLEN)) == (char *)NULL)
return LMTP_ERR_MEM;
if ((rc = lmtp_readline(lmtp, req->msg, LMTP_LINE_MAXLEN)) != LMTP_OK)
return rc;
for (i = 0; (i < LMTP_MAXVERBS) && (lmtp->dispatch[i] != NULL); i++) {
if ((verb = lmtp->dispatch[i]->verb) != NULL) { /* skip NULL verb */
if ((verblen = strlen(verb)) == 0)
continue; /* skip "" verb */
if ( (strlen(req->msg) >= verblen)
&& (strncasecmp(req->msg, verb, verblen) == 0)
&& ( (req->msg[verblen] == NUL)
|| (req->msg[verblen] == ' ') ) ) {
req->verb = verb;
return LMTP_OK;
}
}
}
req->verb = "";
return LMTP_OK;
}
lmtp_rc_t lmtp_response(lmtp_t *lmtp, lmtp_res_t *res)
{
/* write the status message. For multiline status messages it is
* neccessary to repeat the status and dsn codes for every line with a
* dash after the status for every line but the last one
*/
lmtp_rc_t rc = LMTP_OK;
int rv;
int dash;
int len;
char *cpS;
char *cpE;
char formatbuf[LMTP_LINE_MAXLEN];
if ( strlen(res->statuscode) != 3
|| !isdigit((int)res->statuscode[0])
|| !isdigit((int)res->statuscode[1])
|| !isdigit((int)res->statuscode[2]))
return LMTP_ERR_ARG;
if (res->dsncode != NULL) {
if ( (strlen(res->dsncode) != 5)
|| !isdigit((int)res->dsncode[0])
|| (res->dsncode[1] != '.')
|| !isdigit((int)res->dsncode[2])
|| (res->dsncode[3] != '.')
|| !isdigit((int)res->dsncode[4])
|| (res->dsncode[0] != res->statuscode[0]))
return LMTP_ERR_ARG;
}
cpS = res->statusmsg;
for (dash = 1; dash == 1; ) {
if ((cpE = strchr(cpS, '\n')) == NULL) {
cpE = cpS+strlen(cpS);
dash = 0;
}
if (res->dsncode != NULL)
len = sprintf(formatbuf, "%3.3s%c%5.5s ", res->statuscode, dash ? '-' : ' ', res->dsncode);
else
len = sprintf(formatbuf, "%3.3s%c", res->statuscode, dash ? '-' : ' ');
if ((len + cpE - cpS + 2) > sizeof(formatbuf)) { /* status + line + '\r\n' does not fit into formatbuf */
dash = 1;
if ((cpE = cpS + sizeof(formatbuf) - 2 - len) <= cpS) /* no space for line at all */
return LMTP_ERR_ARG;
}
strncpy(formatbuf+len, cpS, cpE-cpS);
len += (cpE-cpS);
formatbuf[len++] = '\r';
formatbuf[len++] = '\n';
do {
rv = lmtp->io.write(lmtp->io.ctx, formatbuf, len);
} while (rv == -1 && errno == EINTR);
if (rv == -1)
return LMTP_ERR_SYSTEM;
cpS = cpE;
if (*cpS == '\n')
cpS++;
}
return rc;
}
char *lmtp_error(lmtp_rc_t rc)
{
/* get an error message matching the given lmtp_rc_t code usually
* returned by a previously called function
*/
char *str;
str = "LMTP: errorcode has no description";
if (rc == LMTP_OK ) str = "LMTP: no error";
else if (rc == LMTP_EOF ) str = "LMTP: eof";
else if (rc == LMTP_ERR_SYSTEM ) str = "LMTP: see errno";
else if (rc == LMTP_ERR_MEM ) str = "LMTP: dynamic memory allocation failed";
else if (rc == LMTP_ERR_OVERFLOW) str = "LMTP: static allocated memory exhausted";
else if (rc == LMTP_ERR_ARG ) str = "LMTP: invalid arg was passed to function";
else if (rc == LMTP_ERR_UNKNOWN ) str = "LMTP: guru meditation";
return str;
}
lmtp_rc_t lmtp_register(lmtp_t *lmtp, char *verb, lmtp_cb_t cb, void *ctx, lmtp_cb_t *oldcb, void **oldctx)
{
/* For _lmtp_ structure, register a _verb_ and associate a callback
* function _cb_ to it. A context can be specified which will be passed
* to the callback function for every call. Consider the context being
* user data. The library itself does not care about the context except
* passing it along. If the verb was registered previously, the
* registration is replaced and if _oldcb_ and/or _oldctx_ is given, the
* previous registration is returned. Calling the previously registered
* callbacks from within the newly registered callback effectively allows
* hooking or chaining to a previous registered callback. The _ctx_,
* _oldcb_ and _oldctx_ are optional and might be passed as NULL in case
* you don't care. Setting _cb_ to NULL means to check only for a
* previous registration;
*/
lmtp_rc_t rc = LMTP_OK;
int overload = 0; /* overload (replacement) detected has to return old oldcb
and/or oldctx, no overload requires growth of dispatch table */
int i;
if (cb == NULL) { /* checking for existing callback only */
i = verbindex(lmtp, verb);
if (oldcb != NULL)
*oldcb = (i == -1) ? NULL : lmtp->dispatch[i]->cb;
if (oldctx != NULL)
*oldctx = (i == -1) ? NULL : lmtp->dispatch[i]->ctx;
return LMTP_OK;
}
for (i = 0; lmtp->dispatch[i] != NULL; i++) {
if (strcasecmp(verb, lmtp->dispatch[i]->verb) == 0) {
overload = 1;
if (oldcb != NULL)
*oldcb = lmtp->dispatch[i]->cb;
if (oldctx != NULL)
*oldctx = lmtp->dispatch[i]->ctx;
break;
}
}
if (i > LMTP_MAXVERBS-2)
return LMTP_ERR_OVERFLOW;
if (!overload) {
if ((lmtp->dispatch[i] =
(lmtp_dispatch_t *)malloc(sizeof(lmtp_dispatch_t))) == NULL)
return LMTP_ERR_MEM;
lmtp->dispatch[i+1] = NULL;
if (oldcb != NULL)
*oldcb = NULL;
if (oldctx != NULL)
*oldctx = NULL;
}
lmtp->dispatch[i]->verb = strdup(verb);
lmtp->dispatch[i]->cb = cb;
lmtp->dispatch[i]->ctx = ctx;
return rc;
}
lmtp_rc_t lmtp_loop(lmtp_t *lmtp)
{
/* Print a welcome message then execute a request/ dispatch loop until
* request signals no more data. Each request is checked to contain a
* registered verb and if a verb is found the correspondig callback is
* executed. The lmtp_create() function usually cares to register a
* default callback in order to handle unregistered verbs. The psoudoverb
* for default is the empty string "" and the callback for this verb can
* be overridden.
*/
lmtp_rc_t rc = LMTP_OK;
lmtp_req_t req;
lmtp_res_t res;
char *verb;
int i;
req.verb = "";
req.msg = NULL;
res.statuscode = "220";
res.dsncode = NULL;
res.statusmsg = "LMTP Service ready.";
if ((rc = lmtp_response(lmtp, &res)) != LMTP_OK)
return rc;
while ((rc = lmtp_request(lmtp, &req)) == LMTP_OK) {
verb = req.verb;
if ((i = verbindex(lmtp, verb)) != -1) {
rc = lmtp->dispatch[i]->cb(lmtp, &lmtp->io, &req, lmtp->dispatch[i]->ctx);
if (req.msg != NULL)
free(req.msg);
req.verb = "";
req.msg = NULL;
if (rc != LMTP_OK)
break;
}
}
return rc;
}