/*
 *  lmtp.c: LMTP library (implementation)
 *
 *  Copyright (c) 2001 The OSSP Project (http://www.ossp.org/)
 *  Copyright (c) 2001 Cable & Wireless Deutschland (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.com/pkg/lmtp2nntp/.
 *
 *  Permission to use, copy, modify, and distribute this software for
 *  any purpose with or without fee is hereby granted, provided that
 *  the above copyright notice and this permission notice appear in all
 *  copies.
 *
 *  THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 *  WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 *  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 *  IN NO EVENT SHALL THE AUTHORS AND COPYRIGHT HOLDERS AND THEIR
 *  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 *  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 *  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 *  USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 *  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 *  OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 *  SUCH DAMAGE.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <ctype.h>
#include <errno.h>

#include "lmtp.h"

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#if defined(HAVE_DMALLOC_H) && defined(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 */
    int               rfd;      /* file descriptor for reading */
    int               wfd;      /* file descriptor for writing */
};

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(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() 
     *  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) {
        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] = NUL;
    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;

    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->rfd, 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 */

    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 ((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(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';
        do {
            rv = lmtp->io.write(lmtp->wfd, formatbuf, len);
        } while (rv == -1 && errno == EINTR);
        if (rv == -1)
            return LMTP_ERR_SYSTEM;
        cpS = cpE+1;
    }
    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;
}

