ossp-pkg/lmtp2nntp/lmtp.c
1.4
/* standard headers */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <ctype.h>
/* third-party headers */
// #include "str.h"
/* own headers */
#include "lmtp.h"
#include "lmtp_p.h"
int verbindex(lmtp_t *lmtp, char *verb);
lmtp_rc_t lmtp_cb_default(lmtp_t *lmtp, lmtp_io_t *io, lmtp_req_t *req, void *ctx);
void lmtp_debug_dumplmtp(lmtp_t *lmtp)
{
int i;
printf("lmtp = %ld \n", (long)lmtp);
printf("io.select = %ld \n", (long)lmtp->io.select);
printf("io.read = %ld \n", (long)lmtp->io.read);
printf("io.write = %ld \n", (long)lmtp->io.write);
printf("rl.cnt = %d \n", lmtp->rl.rl_cnt);
printf("rl.bufptr =*%39s*\n", lmtp->rl.rl_bufptr);
printf("rl.buf =*%39s*\n", lmtp->rl.rl_buf);
for (i = 0; (i < LMTP_MAXVERBS) && (lmtp->dispatch[i] != NULL); i++) {
printf("dispatch[i].verb =*%39s*\n", lmtp->dispatch[i]->verb);
printf("dispatch[i].cb = %ld \n", (long)lmtp->dispatch[i]->cb);
printf("dispatch[i].ctx = %ld \n", (long)lmtp->dispatch[i]->ctx);
// printf("dispatch[i].msg =*%39s*\n", lmtp->dispatch[i]->msg);
}
printf("rfd = %d \n", lmtp->rfd);
printf("wfd = %d \n", lmtp->wfd);
return;
}
/*************************************************************************/
static int readline(lmtp_t *lmtp, char *buf, size_t buflen)
{
/* read a line
*
* NOTE: the underlying readline() already reduces any CR/LF combination
* to a string terminating zero.
*
* return >= 0 number of chars read, zero is empty line
* return = -1 end of file, no chars inside
* return = -2 buffer overrun, chars inside but no newline seen
* return = -3 io error (errno), buffer content undefined
*/
size_t n;
char c;
lmtp_readline_t *rl = &lmtp->rl;
for (n = 0; n < buflen-1;) {
/* fetch one character (but read more) */
if (rl->rl_cnt <= 0) {
if ((rl->rl_cnt = lmtp->io.read(lmtp->rfd, rl->rl_buf, LMTP_LINE_MAXLEN)) < 0)
return -3; /* error see errno */
if (rl->rl_cnt == 0)
return -1; /* 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] = '\0'; /* string termination */
return (n == (buflen-1)) ? -2 : n;
}
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;
}
lmtp_t *lmtp_create(int rfd, int wfd, 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() and
* select() 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 = NULL;
if ((lmtp = (lmtp_t *)malloc(sizeof(lmtp_t))) == NULL)
return NULL;
if(io == NULL) {
lmtp->io.select = select;
lmtp->io.read = read;
lmtp->io.write = write;
} else {
lmtp->io.select = io->select ? io->select : select;
lmtp->io.read = io->read ? io->read : read;
lmtp->io.write = io->write ? io->write : write;
}
lmtp->rl.rl_cnt = 0;
lmtp->rl.rl_bufptr = NULL;
lmtp->rl.rl_buf[0] = '\0';
lmtp->rfd = rfd;
lmtp->wfd = wfd;
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;
lmtp_msg_t *msg;
lmtp_msg_t *next;
//_readmsg : if ((cpBuf = (char *)malloc(nBuf)) == NULL)
for (i = 0; (i < LMTP_MAXVERBS) && (lmtp->dispatch[i] != NULL); i++) {
msg = lmtp->dispatch[i]->msg;
do {
next = lmtp->dispatch[i]->msg->next;
free(msg); /* linked messages */
msg = next;
} while(next != NULL);
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
*
* NOTE: the underlying readline() already reduces any CR/LF combination
* to a string terminating zero.
*/
lmtp_rc_t rc;
rc = readline(lmtp, buf, buflen);
if(rc == -3) return LMTP_ERR_SYSTEM; /* io error (errno), buffer content undefined */
if(rc == -2) return LMTP_ERR_OVERFLOW; /* buffer overrun, chars inside but no newline seen */
if(rc == -1) return LMTP_EOF; /* end of file, no chars inside */
return LMTP_OK;
}
lmtp_rc_t lmtp_readmsg(lmtp_t *lmtp, char **cppBuf, size_t maxlen)
{
/* read lines until end of message, unescape dots
*
* NOTE: the lmtp_readline()'s underlying 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.
*
* RFC821 "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 *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 */
nBuf = 4096;
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 ((cpBuf = (char *)realloc(cpBuf, nBuf)) == NULL) {
free(cpBuf); //FIXME double check isn't this a destroy() task? */
return LMTP_ERR_MEM;
}
*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 = '\0'; /* 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 = '\0'; /* 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
*
* RFC821 "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] == '\0')
|| (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 dash;
int len;
char *cpS;
char *cpE;
char formatbuf[LMTP_LINE_MAXLEN];
if ( strlen(res->statuscode) != 3
|| !isdigit(res->statuscode[0])
|| !isdigit(res->statuscode[1])
|| !isdigit(res->statuscode[2])
) return LMTP_ERR_ARG;
if (res->dsncode != NULL)
if ( (strlen(res->dsncode) != 5)
|| !isdigit(res->dsncode[0])
|| (res->dsncode[1] != '.')
|| !isdigit(res->dsncode[2])
|| (res->dsncode[3] != '.')
|| !isdigit(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 ? '-' : ' ');
strncpy(formatbuf+len, cpS, cpE-cpS);
len += (cpE-cpS);
formatbuf[len++] = '\n';
lmtp->io.write(lmtp->wfd, formatbuf, len);
cpS = cpE+1;
}
return rc;
}
lmtp_msg_t *lmtp_message(lmtp_t *lmtp, char *verb)
{
/* get the first message attached to a verb's dispatch structure. The
* messages are fifo linked lists.
*/
int i;
lmtp_msg_t *cpp = NULL;
if ((i = verbindex(lmtp, verb)) >= 0) cpp = lmtp->dispatch[i]->msg;
return cpp;
}
void lmtp_reset(lmtp_t *lmtp)
{
return;
}
char *lmtp_error(lmtp_t *lmtp, 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";
if (rc == LMTP_EOF ) str = "LMTP: eof";
if (rc == LMTP_ERR_SYSTEM ) str = "LMTP: see errno";
if (rc == LMTP_ERR_MEM ) str = "LMTP: dynamic memory allocation failed";
if (rc == LMTP_ERR_OVERFLOW) str = "LMTP: static allocated memory exhausted";
if (rc == LMTP_ERR_ARG ) str = "LMTP: invalid arg was passed to function";
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;
lmtp->dispatch[i]->msg = NULL;
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 create() function usually cares to register a default
* callback in order to handle unregistered verbs. The psoudoverb for
* default is the empty string "".
*/
lmtp_rc_t rc = LMTP_OK;
lmtp_req_t req;
lmtp_res_t res;
char *verb;
int i;
res.statuscode = "220";
res.dsncode = NULL;
res.statusmsg = "LMTP server ready (lmtp2nntp)";
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 (rc != LMTP_OK) break;
}
}
return rc;
}
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;
}