--- lmtp.c 2001/07/24 11:32:07 1.3
+++ lmtp.c 2001/07/25 11:29:38 1.4
@@ -41,14 +41,19 @@
}
/*************************************************************************/
-/*
- * 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
- */
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;
@@ -91,6 +96,15 @@
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)
@@ -123,12 +137,33 @@
void lmtp_destroy(lmtp_t *lmtp)
{
- free(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);
@@ -140,13 +175,19 @@
lmtp_rc_t lmtp_readmsg(lmtp_t *lmtp, char **cppBuf, size_t maxlen)
{
- /* read lines until end of message, unescape dots
+ /* read lines until end of message, unescape dots
*
- * RFC821 "Simple Mail Transfer Protocol" [...] 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. [...]
+ * 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;
@@ -158,48 +199,39 @@
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;
- cpPtr = cpBuf;
- cpLine = cpBuf;
+ 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) {
-// printf("DEBUG: cpPtr-cpBuf=%d, nBuf=%d, nBuf-(cpPtr-cpBuf)=%d\n", cpPtr-cpBuf, nBuf, nBuf-(cpPtr-cpBuf));
rc = lmtp_readline(lmtp, cpPtr, nBuf-(cpPtr-cpBuf));
-// printf("DEBUG: lmtp_readline()=***%s***, rc=%d\n", cpPtr, rc);
if (rc == LMTP_ERR_OVERFLOW) {
- if (nBuf == maxlen)
- return LMTP_ERR_OVERFLOW;
- offset = nBuf-1;
- offsetline = cpLine - cpBuf;
- nBuf *= 2;
- if (nBuf > maxlen)
- nBuf = maxlen;
+ 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);
+ free(cpBuf); //FIXME double check isn't this a destroy() task? */
return LMTP_ERR_MEM;
}
- *cppBuf = cpBuf;
- cpPtr = cpBuf + offset;
- cpLine = cpBuf + offsetline;
+ *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) {
-// printf("DEBUG: \".\" found ***%s***\n", cpLine);
- *cpLine = '\0';
+ if (strcmp(cpLine, ".") == 0) { /* dot alone is end of message */
+ *cpLine = '\0'; /* hide dot from caller */
break;
}
- if (*cpLine == '.')
- memmove(cpLine, cpLine+1, strlen(cpLine+1)+1); /* escaped dot */
- cpPtr += strlen(cpPtr);
- *cpPtr++ = '\n';
- *cpPtr = '\0';
- cpLine = cpPtr;
- }
- else {
- /* rc == LMTP_ERR_SYSTEM, etc. */
- 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;
}
@@ -210,10 +242,17 @@
* 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
- * LMTP_ERR_VERB req->msg set, req->verb NULL means no verb seen
+ *
+ * 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;
@@ -230,18 +269,21 @@
return rc;
for (i = 0; (i < LMTP_MAXVERBS) && (lmtp->dispatch[i] != NULL); i++) {
- if ((verb = lmtp->dispatch[i]->verb) != NULL) {
- if ((verblen = strlen(verb)) == 0)
- continue;
- if( (strlen(req->msg) >= verblen) //FIXME check for verb\s
+ 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;
}
}
}
- return LMTP_ERR_VERB;
+ req->verb = "";
+ return LMTP_OK;
}
lmtp_rc_t lmtp_response(lmtp_t *lmtp, lmtp_res_t *res)
@@ -292,13 +334,15 @@
return rc;
}
-char **lmtp_message(lmtp_t *lmtp, char *verb)
+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;
- char **cpp = NULL;
- if ((i = verbindex(lmtp, verb)) >= 0)
- cpp = lmtp->dispatch[i]->msg;
+ lmtp_msg_t *cpp = NULL;
+ if ((i = verbindex(lmtp, verb)) >= 0) cpp = lmtp->dispatch[i]->msg;
return cpp;
}
@@ -309,21 +353,56 @@
char *lmtp_error(lmtp_t *lmtp, lmtp_rc_t rc)
{
- char *str = NULL;
+ /* 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 returns old, no overload increases table */
+ 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;
+ if (oldcb != NULL) *oldcb = lmtp->dispatch[i]->cb;
+ if (oldctx != NULL) *oldctx = lmtp->dispatch[i]->ctx;
break;
}
}
@@ -334,10 +413,9 @@
(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;
+ 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;
@@ -348,9 +426,16 @@
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;
- lmtp_rc_t rc = LMTP_OK;
char *verb;
int i;
@@ -359,15 +444,12 @@
res.statusmsg = "LMTP server ready (lmtp2nntp)";
if ((rc = lmtp_response(lmtp, &res)) != LMTP_OK) return rc;
- while ((rc = lmtp_request(lmtp, &req)) == LMTP_OK || (rc == LMTP_ERR_VERB)) {
+ while ((rc = lmtp_request(lmtp, &req)) == LMTP_OK) {
verb = req.verb;
- if (rc == LMTP_ERR_VERB)
- verb = "";
- if ((i = verbindex(lmtp, verb)) == -1)
- return LMTP_ERR_VERB;
- rc = lmtp->dispatch[i]->cb(lmtp, &lmtp->io, &req, lmtp->dispatch[i]->ctx);
- if (rc != LMTP_OK)
- break;
+ 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;
}
|