/*
 * connection.c:
 * deal with connections from clients
 *
 * Copyright (c) 2000 Chris Lightfoot. All rights reserved.
 *
 */

static const char rcsid[] = "$Id: connection.c,v 1.52 2003/11/06 01:19:27 chris Exp $";

#ifdef HAVE_CONFIG_H
#include "configuration.h"
#endif /* HAVE_CONFIG_H */

#include <sys/types.h>

#include <errno.h>
#include <fcntl.h>
#include <pwd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <time.h>
#include <unistd.h>

#include <netinet/in.h>
#include <arpa/inet.h>

#include <sys/mman.h>
#include <sys/socket.h>
#include <sys/utsname.h>

#include "buffer.h"
#include "connection.h"
#include "listener.h"
#include "util.h"

extern int verbose;

/* make_timestamp:
 * Create a timestamp string. */
#define TIMESTAMP_LEN   32
static char hex[] = "0123456789abcdef";

static char *make_timestamp(const char *domain) {
    int fd;
    unsigned char buffer[TIMESTAMP_LEN / 2], *q;
    struct utsname u;
    char *s, *p;
    size_t l;
    ssize_t n = 0;

    if (!domain) {
        if (uname(&u) == -1) return NULL;
        domain = u.nodename;
    }

    s = xmalloc(l = 1 + TIMESTAMP_LEN + 1 + strlen(domain) + 2);
    if (!s) return NULL;
    memset(s, 0, l);
    *s = '<';
    
    /* Get random "timestamp" data. */
    fd = open("/dev/urandom", O_RDONLY);
    if (fd != -1) {
        do
            n = read(fd, buffer, sizeof(buffer));
        while (n == -1 && errno == EINTR);
        close(fd);
    }
        
    if (n != sizeof(buffer)) {
        /* OK, we need to get some pseudo-random data from rand(3).
         * FIXME This is bad from a security PoV, and should be replaced by
         * hashing some rapidly-changing data. */
        unsigned char *p;
        for (p = buffer; p < buffer + sizeof(buffer); ++p)
            *p = (unsigned char)(rand() & 0xff);
    }
    
    for (p = s + 1, q = buffer; q < buffer + sizeof(buffer); ++q) {
        *p++ = hex[(((int)*q) >> 4) & 0x0f];
        *p++ = hex[((int)*q) & 0x0f];
    }
    strcat(s, "@");
    strcat(s, domain);
    strcat(s, ">");

    return s;
}

/* connection_new:
 * Create a connection object from a socket. */
connection connection_new(int s, const struct sockaddr_in *sin, listener L) {
    int n;
    connection c = NULL;

    alloc_struct(_connection, c);

    c->s = s;
    c->sin = *sin;

    n = sizeof(c->sin_local);
    if (getsockname(s, (struct sockaddr*)&(c->sin_local), &n) < 0) {
        log_print(LOG_WARNING, "connection_new: getsockname: %m");
        goto fail;
    }

    c->remote_ip = xstrdup(inet_ntoa(c->sin.sin_addr));
    c->local_ip = xstrdup(inet_ntoa(c->sin_local.sin_addr));

#ifdef MASS_HOSTING
    if (L->have_re)
        c->domain = listener_obtain_domain(L, s);
#endif
    if (!c->domain) {
        if (L->domain)
            c->domain = xstrdup(L->domain);
        else
            c->domain = xstrdup(c->local_ip);
    }

    c->idstr = xmalloc(strlen(c->remote_ip) + 1 + (c->domain ? strlen(c->domain) : 0) + 16);
    if (c->domain) sprintf(c->idstr, "[%d]%s/%s", s, c->remote_ip, c->domain);
    else sprintf(c->idstr, "[%d]%s", s, c->remote_ip);

    /* Read and write buffers */
    c->rdb = buffer_new(1024);
    c->wrb = buffer_new(1024);

    c->timestamp = make_timestamp(c->domain);
    if (!c->timestamp) goto fail;

    /* I/O abstraction layer */
#ifdef USE_TLS
    if (L->tls.mode == immediate) {
        if (!(c->io = (struct ioabs*)ioabs_tls_create(c, L))) {
            log_print(LOG_ERR, _("connection_new: could not set up TLS I/O abstraction layer for `%s'"), c->idstr);
            goto fail;
        }
    } else
#endif
    c->io = (struct ioabs*)ioabs_tcp_create();
    
    c->state = authorisation;

    c->idlesince = time(NULL);
    c->frozenuntil = 0;

    if (!connection_sendresponse(c, 1, c->timestamp)) {
        log_print(LOG_ERR, "connection_new: could not send timestamp to `%s'", c->idstr);
        goto fail;
    }

    c->l = L;

    return c;

fail:
    connection_delete(c);
    return NULL;
}

/* connection_delete:
 * Delete a connection and disconnect the peer. */
void connection_delete(connection c) {
    if (!c) return;

    if (c->s != -1) {
        /* This is a forced shutdown of the underlying socket connection. 
         * Calling code should normally ensure that the connection is properly
         * shut down and that c->s is set to -1 before calling
         * connection_delete. */
        shutdown(c->s, 2);
        close(c->s);
    }

    if (c->a) authcontext_delete(c->a);
    if (c->m) (c->m)->delete(c->m);

    if (c->domain)     xfree(c->domain);
    if (c->remote_ip)  xfree(c->remote_ip);
    if (c->local_ip)   xfree(c->local_ip);
    if (c->idstr)      xfree(c->idstr);
    if (c->rdb)        buffer_delete(c->rdb);
    if (c->wrb)        buffer_delete(c->wrb);
    if (c->io)         c->io->destroy(c);
    if (c->timestamp)  xfree(c->timestamp);
    if (c->user)       xfree(c->user);
    if (c->pass)       xfree(c->pass);
    xfree(c);
}

/* connection_isfrozen CONNECTION
 * Is CONNECTION frozen? */
int connection_isfrozen(connection c) {
    return c->frozenuntil && c->frozenuntil > time(NULL);
}

/* connection_shutdown CONNECTION
 * Immediate or delayed shutdown of CONNECTION. If the connection is frozen or
 * if there are data still to be written, then simply set a flag for later
 * real shutdown. */
int connection_shutdown(connection c) {
    if (connection_isfrozen(c) || buffer_available(c->wrb) > 0) {
        c->do_shutdown = 1;
        return IOABS_WOULDBLOCK;
    } else return c->io->shutdown(c);
}

/* connection_send_now CONNECTION DATA COUNT
 * Send COUNT bytes of DATA to CONNECTION immediately, if possible. Returns
 * the number of bytes written on success, IOABS_WOULDBLOCK if the write would
 * block, or IOABS_ERROR on error. Does not interact with the write buffer at
 * all, and must be called only when it is empty. */
static ssize_t connection_send_now(connection c, const char *data, const size_t len) {
    if (!c->io->immediate_write || connection_isfrozen(c))
        return 0;
    return c->io->immediate_write(c, data, len);
}

/* connection_send CONNECTION DATA COUNT
 * Send COUNT bytes of DATA to CONNECTION, either immediately if possible or
 * inserting it into the buffer otherwise. Returns 1 on success or 0 on
 * failure. */
ssize_t connection_send(connection c, const char *data, const size_t l) {
    size_t len;
    ssize_t n;
    len = l;
    if (buffer_available(c->wrb) == 0) {
        n = connection_send_now(c, data, len);
        if (n == len) return 1;
        else if (n == IOABS_ERROR) return 0;
        else if (n > 0) {
            data += n;
            len -= n;
        } /* else IOABS_WOULDBLOCK */
    }

    buffer_push_data(c->wrb, data, len);
        /* XXX should try a write from the buffer now...? */
    return 1;
}

/*
static void dump(const char *s, size_t l) {
    const char *p;
    for (p = s; p < s + l; ++p) {
        if (*p < 32)
            switch(*p) {
            case '\t': fprintf(stderr, "\\t"); break;
            case '\r': fprintf(stderr, "\\r"); break;
            case '\n': fprintf(stderr, "\\n"); break;
            default:   fprintf(stderr, "\\x%02x", *p);
            }
        else fprintf(stderr, "%c", (int)*p);
    }
    fprintf(stderr, "\n");
}
*/

/* connection_freeze:
 * Mark a connection as frozen. */
void connection_freeze(connection c) {
    c->frozenuntil = time(NULL) + 3;
}

/* pop3_commands:
 * Commands the server supports. */
struct {
    char *s;
    enum pop3_command_code cmd;
} pop3_commands[] =
    {{"APOP", APOP},
     {"CAPA", CAPA},
     {"DELE", DELE},
     {"LIST", LIST},
     {"NOOP", NOOP},
     {"PASS", PASS},
     {"QUIT", QUIT},
     {"RETR", RETR},
     {"RSET", RSET},
     {"STAT", STAT},
     {"STLS", STLS},
     {"TOP",  TOP },
     {"UIDL", UIDL},
     {"USER", USER},
     {"LAST", LAST},
     {NULL,   UNKNOWN}}; /* last command MUST have s = NULL */

/* connection_parsecommand CONNECTION
 * Parse a command from CONNECTION, returning NULL if none is available. */
pop3command connection_parsecommand(connection c) {
    static char *line; /* static buffer so we don't spend all day reallocating things... */
    static size_t llen;
    size_t i;
    char *p;
    pop3command pc = NULL;

    /* Some clients send \r\n, some send \n, others send a mixture. In the
     * latter case we must be careful not to interpret command1\ncommand2\r\n
     * as a single command. So always use \n as the line ending and strip off
     * any trailing \r. */
    if (!(line = buffer_consume_to_mark(c->rdb, "\n", 1, line, &llen)))
        return NULL;

    /* remove trailing eol */
    for (i = llen - 1; i > 0 && strchr("\r\n", line[i]); --i)
        line[i] = 0;
    p = line + strspn(line, " \t"); /* skip leading whitespace */
    
    if (verbose) {
        if  (strncasecmp(p, "PASS", 4))
            log_print(LOG_DEBUG, "connection_parsecommand: client %s: received `%s'", c->idstr, p);
        else
            log_print(LOG_DEBUG, "connection_parsecommand: client %s: received `%.4s [...]'", c->idstr, p);
    }

    pc = pop3command_new(p);

    return pc;
}

/* pop3command_new:
 * Create a new pop3command object. */
pop3command pop3command_new(const char *s) {
    pop3command p;
    const char *q;
    int i;
    
    alloc_struct(_pop3command, p);

    /* Ugly. PASS is a special case, because we permit a password to contain
     * spaces. */
    q = s + strspn(s, " \t");
    if (strncasecmp(q, "PASS ", 5) == 0) {
        /* Manual parsing. */
        p->cmd = PASS;
        p->toks = xcalloc(sizeof *p->toks, 1);
        
        p->toks->str = xstrdup(q);
        chomp(p->toks->str);
        p->toks->str[4] = 0;
        
        p->toks->toks = xcalloc(sizeof(char*), 2);
        p->toks->toks[0] = p->toks->str;
        p->toks->toks[1] = p->toks->str + 5;
        
        p->toks->num = 2;

        return p;
    }

    p->cmd = UNKNOWN;
    p->toks = tokens_new(s, " \t");

    /* Does this command have a sane structure? */
    if (p->toks->num < 1 || p->toks->num > 3)
        return p;

    /* Try to identify the command. */
    for (i = 0; pop3_commands[i].s; ++i)
        if (!strcasecmp((char*)(p->toks->toks[0]), pop3_commands[i].s)) {
            p->cmd = pop3_commands[i].cmd;
            break;
        }

    return p;
}

/* pop3command_delete:
 * Free a command returned by pop3command_new. */
void pop3command_delete(pop3command p) {
    if (!p) return;
    if (p->toks) tokens_delete(p->toks);
    xfree(p);
}

/* connection_sendresponse:
 * Send a +OK... / -ERR... response to a message. Returns 1 on success or 0 on
 * failure. */
int connection_sendresponse(connection c, const int success, const char *s) {
    /*
     * For efficiency's sake, we should send this bit-by-bit, avoiding another
     * buffer copy. But unfortunately, there are POP3 clients in the world
     * so stupid that they assume a whole response will arrive in a single TCP
     * segment. Particular examples include POP3 virus-scanning proxies, such
     * as Norman ASA's, which was evidently written by somebody very lazy.
     * 
     * Obviously there's no way to guarantee how the packets in a TCP stream
     * are disposed, in general, but we can increase the probability of success
     * by trying to ensure here that our response is contained in a single
     * write call. It still might get split up by the ioabs layer, but we have
     * to take our chances....
     */
    static char *buf;
    static size_t buflen;
    size_t l;
    
    l = (success ? 6 : 7) + strlen(s);

    if (!buf || buflen < l + 1)
        buf = xrealloc(buf, buflen = l + 1);
    
    sprintf(buf, "%s %s\r\n", success ? "+OK" : "-ERR", s);
    if (connection_send(c, buf, l)) {
        if (verbose)
            log_print(LOG_DEBUG, _("connection_sendresponse: client %s: sent `%s %s'"), c->idstr, success ? "+OK" : "-ERR", s);
        return 1;
    } else
        return 0;
}

/* connection_sendline:
 * Send an arbitrary line to a connected peer. Returns 1 on success or 0 on
 * failure. Used to send multiline responses. */
int connection_sendline(connection c, const char *s) {
    return connection_send(c, s, strlen(s)) && connection_send(c, "\r\n", 2);
}

/* connection_sendmessage:
 * Send to the connected peer a +OK response followed by the header and up to n
 * lines of the body of a message which begins at offset msgoffset + skip in
 * the file referenced by fd, which is assumed to be a mappable object. Lines
 * which begin . are escaped as required by RFC1939, and each line is
 * terminated with `\r\n'. If n is -1, the whole message is sent.
 *
 * RFC1939 doesn't define what a server which encounters an error half-way
 * through sending a message should do. In any case it's clear that we mustn't
 * send the final ., since that would result in the user obtaining a truncated
 * message.  So we return -1 if the message could not be sent but a -ERR
 * response was transmitted to the client, -2 if sending failed after a +OK
 * response was sent, or the number of bytes written on success.
 *
 * Assumes the message on disk uses only `\n' to indicate EOL. */
int connection_sendmessage(connection c, int fd, size_t msgoffset, size_t skip, size_t msglength, int n) {
    char *filemem;
    char *p, *q, *r;
    size_t length, offset;
    ssize_t nwritten = 0;
    
    offset = msgoffset - (msgoffset % PAGESIZE);
    length = (msgoffset + msglength + PAGESIZE) ;
    length -= length % PAGESIZE;

    filemem = mmap(0, length, PROT_READ, MAP_PRIVATE, fd, offset);
    if (filemem == MAP_FAILED) {
        log_print(LOG_ERR, "connection_sendmessage: mmap: %m");
        connection_sendresponse(c, 0, _("Cannot send message"));
        return -1; /* Failure before +OK sent. */
    }

    connection_sendresponse(c, 1, _("Message follows"));
    
    /* Find the beginning of the message headers */
    p = filemem + (msgoffset % PAGESIZE);
    r = p + msglength;
    p += skip;

    /* Send the message headers */
    while (p < r && *p != '\n') {
        q = memchr(p, '\n', r - p);
        if (!q) q = r;
        errno = 0;
        
        /* Escape a leading ., if present. */
        if (*p == '.' && !connection_send(c, ".", 1))
            goto write_failure;
        ++nwritten;
        
        /* Send line itself. */
        if (!connection_send(c, p, q - p) || !connection_send(c, "\r\n", 2))
            goto write_failure;
        nwritten += q - p + 2;

        p = q + 1;
    }

    ++p;

    errno = 0;
    if (!connection_send(c, "\r\n", 2)) {
        log_print(LOG_ERR, _("connection_sendmessage: send failure"));
        munmap(filemem, length);
        return -2;
    }
    
    /* Now send the message itself */
    while (p < r && n) {
        if (n > 0) --n;

        q = memchr(p, '\n', r - p);
        if (!q) q = r;
        errno = 0;

        /* Escape a leading ., if present. */
        if (*p == '.' && !connection_send(c, ".", 1))
            goto write_failure;
        ++nwritten;
        
        /* Send line itself. */
        if (!connection_send(c, p, q - p) || !connection_send(c, "\r\n", 2))
            goto write_failure;
        nwritten += q - p + 2;

        p = q + 1;
    }
    if (munmap(filemem, length) == -1)
        log_print(LOG_ERR, "connection_sendmessage: munmap: %m");
    
    errno = 0;

    if (!connection_send(c, ".\r\n", 3)) {
        log_print(LOG_ERR, _("connection_sendmessage: send failure"));
        return -2;
    } else return nwritten + 3;

write_failure:
    log_print(LOG_ERR, _("connection_sendmessage: send failure"));
    munmap(filemem, length);
    return -2;
}



syntax highlighted by Code2HTML, v. 0.9.1