OSSP CVS Repository

ossp - ossp-pkg/lmtp2nntp/lmtp.c 1.4
Not logged in
[Honeypot]  [Browse]  [Directory]  [Home]  [Login
[Reports]  [Search]  [Ticket]  [Timeline
  [Raw

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;
}


CVSTrac 2.0.1