/* ** OSSP lmtp2nntp - Mail to News Gateway ** Copyright (c) 2001-2003 Ralf S. Engelschall ** Copyright (c) 2001-2003 The OSSP Project ** Copyright (c) 2001-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_lmtp.c: Local Mail Transfer Protocol (LMTP) server library */ #include #include #include #include #include #include #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 * if parameters follow and otherwise. * 4.1.2. COMMAND SYNTAX * ::= 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; }