/* $Cambridge: hermes/src/prayer/accountd/mail.c,v 1.2 2008/09/16 09:59:54 dpc22 Exp $ */
/************************************************
 *    Prayer - a Webmail Interface              *
 ************************************************/

/* Copyright (c) University of Cambridge 2000 - 2002 */
/* See the file NOTICE for conditions of use and distribution. */

#include "accountd.h"

/* mail_string_add() *****************************************************
 *
 * Copy string to ptr, add pointer over string
 *   Up to client to worry about bounds checking and buffer overruns.
 *   mail_fake_simple is the only client
 *
 *  sp: Targ string. Updated to point to end of copied string.
 *   s: String to add
 ************************************************************************/

static void mail_string_add(char **sp, char *s)
{
    strcpy(*sp, s);
    *sp += strlen(s);
}

/* mail_fake_simple() ****************************************************
 *
 * Convert simple format .forward files into equivalent MSforward so that
 * client doesn't have to worry about the distinguistion.
 *    stream: iostream connection to client
 *      file: .forward file to test
 *
 * Returns:
 *   NIL => input file was not simple .forward file
 *   T   => .forward file was simple format. OK {literal} to output.
 ************************************************************************/

static BOOL mail_fake_simple(struct iostream *stream, FILE * file)
{
    static char buffer[MAXLENGTH];
    static char tmp[2 * MAXLENGTH];     /* Safe upper bound */
    char *s;
    int c, i, count;
    int copy, vacation;
    char *address;

    /* Get first line from file */
    if (!fgets(buffer, MAXLENGTH - 1, file))
        return (NIL);

    buffer[strlen(buffer) - 1] = '\0';  /* Remove trailing \n */

    /* Can't fake Exim filter files */
    if (!strncasecmp(buffer, "# Exim filter ", strlen("# Exim filter ")))
        return (NIL);

    /* Can't fake multiline files */
    while ((c = getc(file)) != EOF)
        if ((c != '\015') && (c != '\015') && (c != ' ') && (c != '\t'))
            return (NIL);

    /* Old style formats that we want to support:
     *
     * \user, vacation-user   ==> VACATION action
     * \user, address         ==> REDIRECT action
     */

    copy = NIL;
    vacation = NIL;
    address = NIL;

    s = buffer;

    /* Skip leading whitespace */
    while (*s == ' ')
        s++;

    if (*s == '\\') {
        /* "\dpc22, something" */
        copy = T;
        if (!(s = strchr(s, ',')))
            return (NIL);       /* Something funny going on */

        s++;
    } else
        s = buffer;

    /* Skip leading whitespace */
    while (*s == ' ')
        s++;

    if (!strncmp(s, "vacation-", strlen("vacation-"))) {
        if (strchr(s, '@'))     /* Catch vacation-foo@some.remote.site */
            return (NIL);

        vacation = T;
        address = NIL;
    } else
        address = s;

    /* .forward file passes our regex: convert to equivalent MSforward */
    s = tmp;
    mail_string_add(&s, "# MSforward file created by prayer\n");

    if (vacation)
        mail_string_add(&s, "vacation\n");

    if (address && address[0]) {
        mail_string_add(&s, "redirect\n");
        mail_string_add(&s, "   address\t");
        mail_string_add(&s, address);
        mail_string_add(&s, "\n");
        if (copy)
            mail_string_add(&s, "   copy true\n");
    }

    count = (int) (s - tmp);
    ioprintf(stream, "OK {%d}" CRLF, count);

    for (i = 0; i < count; i++)
        ioputc(tmp[i], stream);

    ioputs(stream, "" CRLF);
    ioflush(stream);

    return (T);
}

/* ====================================================================== */

/* mail_status() *********************************************************
 *
 * MAILSTATUS command.
 *    config: Accountd configuration
 *    stream: iostream connection to client
 *      line: Arguments to MAILSTATUS: should be empty
 *
 * Returns: T on success, NIL otherwise
 *
 * Output:
 *   OK {literal}        => Current mail status as MSforward format
 *   NO [Error message]  => Couldn't determine mail status
 *  BAD [Error message]  => Protocol error
 ************************************************************************/

BOOL
mail_status(struct config * config, struct iostream * stream, char *line)
{
    unsigned long existing, current;
    FILE *file;
    struct stat sbuf;
    int c;

    if (!checksum_test(config, &existing, &current)) {
        ioputs(stream, "OK {0}" CRLF);  /* No mail forwarding in place */
        ioflush(stream);
        return (T);
    }

    if (existing != 0) {
        /* .forward file exists and contains checksum */
        if (existing != current) {
            ioputs(stream,
                   "NO Checksum mismatch: manually maintained .forward file?"
                   CRLF);
            ioflush(stream);
            return (T);
        }

        if (stat(config->msforward_name, &sbuf) ||
            ((file = fopen(config->msforward_name, "r")) == NULL)) {
            ioputs(stream,
                   "NO Inconsistent state: Couldn't open .MSforward file"
                   CRLF);
            ioflush(stream);
            return (T);
        }

        ioprintf(stream, "OK {%lu}" CRLF, (unsigned long) sbuf.st_size);
        while ((c = getc(file)) != EOF)
            ioputc(c, stream);

        ioputs(stream, "" CRLF);
        ioflush(stream);
        fclose(file);
        return (T);
    }

    /* No checksum in .forward file.  Check for trivial case that we can fake
     * up as an automatically managed file */

    if ((file = fopen(config->forward_name, "r")) == NULL) {
        /* Worked a second ago! */
        ioputs(stream,
               "NO Checksum mismatch: manually maintained .forward file?"
               CRLF);
        ioflush(stream);
        return (T);
    }

    if (!mail_fake_simple(stream, file)) {
        ioputs(stream, "NO Manually maintained .forward file?" CRLF);
        ioflush(stream);
    }
    fclose(file);

    return (T);
}

/* ====================================================================== */

/* XXX Should really update temporary files, rename */

/* mail_change() *********************************************************
 *
 * MAILCHANGE command
 *     config: accountd configuration
 *     stream: iostream connection to client
 *       line: Arguments to MAILCHANGE. Literal containing MSforward
 * restricted: T => filters are restricted to "mail" subdirectory
 *
 * Returns: T on success, NIL otherwise
 *
 * Output:
 *   OK [text]. MSforward file uploaded, filter filter generated
 *   NO [text]. Upload or translation failed
 *  BAD [text]. Protocol error
 ************************************************************************/

BOOL
mail_change(struct config * config, struct iostream * stream, char *line)
{
    char *size;
    FILE *file;
    int c, len;
    unsigned char *text = NIL;
    unsigned char *s;

    if (!((size = string_get_token(&line)) && (size[0]))) {
        ioputs(stream, "BAD No file size provided" CRLF);
        ioflush(stream);
        return (T);
    }

    if (((len = strlen(size)) < 2) ||
        (size[0] != '{') || (size[len - 1] != '}')) {
        ioputs(stream, "BAD Invalid file size" CRLF);
        ioflush(stream);
        return (T);
    }

    /* Check that size[1] -> size[len-1] all digits? */
    size[len - 1] = '\0';       /* Probably not needed */

    len = atoi(size + 1);

    /* Copy filter value to string */
    if ((text = malloc(len + 1)) == NIL) {
        ioputs(stream, "NO Out of memory" CRLF);
        ioflush(stream);
        return (T);
    }

    s = text;
    while ((len > 0) && (c = iogetc(stream))) {
        *s++ = (unsigned char) c;
        len--;
    }
    *s = '\0';

    if (len > 0) {
        free(text);
        ioprintf(stream, "BAD %d bytes missing from input" CRLF, len);
        ioflush(stream);
        return (T);
    }

    /* Check whether filter file actually contains actions */
    if (filter_empty((char *) text)) {
        free(text);
        unlink(config->msforward_name);
        unlink(config->forward_name);
        ioputs(stream, "OK Filter file disabled" CRLF);
        ioflush(stream);
        return (T);
    }

    /* Push filter value into .MSforward file */
    if ((file = fopen(config->msforward_name, "w")) == NIL) {
        /* Swallow unwanted text */
        while ((len > 0) && (c = iogetc(stream)))
            len--;
        fclose(file);

        ioputs(stream, "NO Couldn't open file" CRLF);
        ioflush(stream);
        return (T);
    }

    for (s = text; *s; s++)
        putc(*s, file);

    if (fclose(file) != 0) {
        free(text);
        ioputs(stream,
               "NO Failed to update filter file. Quota problems?" CRLF);
        ioflush(stream);
        return (T);
    }

    /* File successfully uploaded. Run update script */

    if (filter_write_forward(config, (char *) text))
        ioputs(stream, "OK Filter file uploaded" CRLF);
    else
        ioputs(stream,
               "NO Failed to update filter file. Quota problems?" CRLF);

    ioflush(stream);
    free(text);
    return (T);
}

/* ====================================================================== */

/* mail_vacclear() *******************************************************
 *
 * Clear vacation log file
 *    config: Accountd configuration
 *    stream: iostream connection to client
 *      line: Arguments for VACATION_CLEAR command. Should be empty
 *
 * Returns: T  in all situations
 * Output:  OK in all situations
 ************************************************************************/

BOOL
mail_vacclear(struct config * config, struct iostream * stream, char *line)
{
    unlink("vacation.log");
    unlink("vacation.once.dir");
    unlink("vacation.once.pag");

    ioputs(stream, "OK Vacation Log cleared" CRLF);
    ioflush(stream);
    return (T);
}
