/* $Cambridge: hermes/src/prayer/cmd/cmd_send.c,v 1.14 2009/08/13 09:45:18 dpc22 Exp $ */
/************************************************
 *    Prayer - a Webmail Interface              *
 ************************************************/

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

#include "prayer_session.h"

struct channel {
    struct iostream *output;
    pid_t child;
};

static struct channel *channel_open(struct session *session,
                                    struct pool *p, char *cmd)
{
    struct request *request = session->request;
    pid_t childpid;
    int pipefd[2];
    struct channel *channel = pool_alloc(p, sizeof(struct channel));
    char **argv;
    char *path;
    int argc;
    char *s, *t;

    /* Local scratch copy of cmd */
    cmd = pool_strdup(p, cmd);

    /* Count number of tokens in cmd */
    argc = 0;
    s = cmd;
    while ((string_skip_token(&s)))
        argc++;

    /* Allocate (temporary) argv array based on this count */
    argv = pool_alloc(p, (argc + 1) * sizeof(char *));

    /* Split up cmd (destructive) */
    argc = 0;
    while ((t = string_get_token(&cmd)))
        argv[argc++] = t;
    argv[argc] = NIL;

    /* Set up child process */
    if (pipe(pipefd) < 0) {
        session_log(session, "[cmd_send] pipe(): %s", strerror(errno));
        return (NIL);
    }

    if ((childpid = fork()) < 0) {
        session_log(session, "[cmd_send] fork(): %s", strerror(errno));
        return (NIL);
    }

    if (childpid == 0) {
        /* Redirect stdin for child process */
        close(pipefd[1]);
        dup2(pipefd[0], 0);
        close(pipefd[0]);

        /* Redirect stdout and stderr child to /dev/null */
        close(1);
        open("/dev/null", O_WRONLY | O_CREAT | O_APPEND, 0666);
        dup2(1, 2);

        path = argv[0];
        if ((s = strrchr(path, '/')))   /* Should be leave path intact? */
            argv[0] = s + 1;

        execv(path, argv);
        session_fatal(session, "[cmd_send] exec(sendmail): %s",
		      strerror(errno));
    }

    /* Parent */
    close(pipefd[0]);

    channel->child = childpid;
    channel->output = iostream_create(request->pool, pipefd[1], 0);

    return (channel);
}

static int channel_close(struct channel *channel)
{
    int status = 0;
    int rc = 0;

    iostream_close(channel->output);

    do {
        rc = waitpid(channel->child, &status, 0);
    } while ((rc < 0) && (errno == EINTR));

    if ((rc >= 0) && WIFEXITED(status) && (WEXITSTATUS(status) == 0))
        return(T);

    return(NIL);
}

static void
touch_file(char *path)
{
    FILE *file;

    if (path && path[0]) {
        if ((file = fopen(path, "a+")))
            fclose(file);
    }
}

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

void cmd_send(struct session *session)
{
    struct config *config = session->config;
    struct request *request = session->request;
    struct draft *draft = session->draft;
    MAILSTREAM *stream = session->stream;
    struct options *options = session->options;
    struct prefs *prefs = options->prefs;
    struct pool *pool = request->pool;
    struct buffer *cb = buffer_create(pool, 1024);
    char *s;
    int c;
    unsigned long offset;
    char *recips, *msg;
    STRING ms;
    char *command;
    struct channel *channel;
    struct iostream *output;
    unsigned long msg_len = 0L;
    char *sendmail;
    unsigned long recips_count = 0;

    if (config->sendmail_path && config->sendmail_path[0])
        sendmail = config->sendmail_path;
    else
        sendmail = "/usr/lib/sendmail";

    if (!(recips = draft_make_recipients(draft, NIL, &recips_count))) {
        session_redirect(session, request, "compose");
        return;
    }

    if ((config->recips_max_msg > 0) &&
        (recips_count > config->recips_max_msg)) {
        session_message(session, "Too many recipients");
        session_log(session, "Too many recipients");
        session_redirect(session, request, "compose");
        return;
    }

    /* Block sending altogether if per session limit reached */
    session->recips_count_session += recips_count;
    if (!session->sending_allow &&
        (config->recips_max_session > 0) &&
        (session->recips_count_session > config->recips_max_session)) {
        struct stat sbuf;
        char *allow_path = NIL;

        if (config->sending_allow_dir && session->username) {
            allow_path = pool_strcat3(request->pool,
                                      config->sending_allow_dir, "/",
                                      session->username);
        }
        if (allow_path && (stat(allow_path, &sbuf) == 0)) {
            session->sending_allow = T;
        } else {
            session->sending_block = T;

            /* Stop subsequent logins from sending either */
            if (config->sending_block_dir && session->username) {
                touch_file(pool_strcat3(request->pool,
                                        config->sending_block_dir, "/",
                                        session->username));
            }
        }
    } else if (config->sending_block_dir) {
        struct stat sbuf;
        char *path  = pool_strcat3(request->pool,
                                   config->sending_block_dir, "/",
                                   session->username);

        if (stat(path, &sbuf) == 0) {
            session->sending_block = T;
        }
    }

    if (session->sending_block) {
        session_message(session,
                        "Outgoing email disabled (compromised account?)");
        session_log(session,
                        "Outgoing email disabled (compromised account?)");
        session_redirect(session, request, "compose");
        return;
    }

    if (!(recips && recips[0])) {
        session_message(session, "No recipients for message");
        session_redirect(session, request, "compose");
        return;
    }

    if (prefs->line_wrap_advanced) {
        if (draft->line_wrap)
            draft_line_wrap_body(draft);
    } else if (prefs->line_wrap_on_send)
        draft_line_wrap_body(draft);

    if (!(msg = draft_make_msg(draft, NIL, &msg_len))) {
        session_redirect(session, request, "compose");
        return;
    }

    if (draft->save_copy && draft->fcc && draft->fcc[0]) {
        char *fcc_name;

        if (prefs->maildir && prefs->maildir[0])
            fcc_name =
                pool_strcat3(request->pool, prefs->maildir, session->hiersep,
                             draft->fcc);
        else
            fcc_name = draft->fcc;

        /* Convert simple "char *" string into c-client "STRING *" string */
        INIT(&ms, mail_string, msg, msg_len);

        ml_clear_error();
        ml_clear_have_close();

        /* Append message to end of sent-mail folder */
        ml_append(session, stream,
                  session_mailbox(session, request->pool, fcc_name), &ms);

        if (ml_have_close()) {
            session_redirect(session, request, "restart");
            return;
        } else if (ml_have_error()) {
            session_alert(session,
                          "Unable to append message to %s folder: %s",
                          draft->fcc, ml_errmsg());
            session_log(session,
                        "[cmd_send] Unable to write to %s folder: %s",
                        draft->fcc, ml_errmsg());
            session_redirect(session, request, "compose");
            return;
        }
    }

    /* -oi important: allows lines with single '.' in message body */
    if (strchr(session->username, '@')) {
        bprintf(cb, "%s -oi -f %s %s", sendmail, session->username, recips);
    } else {
        bprintf(cb, "%s -oi -f %s@%s %s", sendmail, 
                session->username, config->return_path_domain, recips);
    }

    command = buffer_fetch(cb, 0, buffer_size(cb), NIL);

    if ((channel = channel_open(session, request->pool, command)) == NIL) {
        session_alert(session,
                      "Failed to send message (system problem?)");
        session_log(session,
                    "[cmd_send] Failed to send message: failed to start %s",
                    command);

        session_redirect(session, request, "compose");
        return;
    }

    output = channel->output;
    s = msg;
    offset = 0;

    /* Copy hdr replacing CRLF, CR or LF with LF. Remove Bcc headers */
    while ((c = *s)) {
        if ((offset == 0) && !strncasecmp(s, "Bcc:", strlen("Bcc:"))) {
            /* Skip over Bcc header (may be folded over several lines) */
            while ((c = *s++)) {
                if ((c == '\015') || (c == '\012')) {
                    if ((c == '\015') && (*s == '\012'))
                        s++;

                    if (((c = *s) != ' ') && (c != '\t'))
                        break;
                }
            }
            continue;
        }

        if ((c == '\015') || (c == '\012')) {
            /* Replace CRLF with single LF */
            s += ((s[0] == '\015') && (s[1] == '\012')) ? 2 : 1;
            ioputc('\n', output);
            if (offset == 0)
                break;
            offset = 0;
            continue;
        }

        ioputc(c, output);
        s++;
        offset++;
    }

    /* Copy body replacing CRLF, CR or LF with LF */
    while ((c = *s)) {
        if ((c == '\015') || (c == '\012')) {
            /* Replace CRLF with single LF */
            s += ((s[0] == '\015') && (s[1] == '\012')) ? 2 : 1;
            ioputc('\n', output);
            continue;
        }

        ioputc(c, output);
        s++;
        offset++;
    }

    if (!channel_close(channel)) {
        session_alert(session,
                      "Failure sending message (system problem)");
        session_log(session,
                    "[cmd_send] Failed to send message: failure reported by %s",
                    command);

        session_redirect(session, request, "compose");
        return;
    }

    session_message(session, "Message sent");
    session_log(session, "[cmd_send] Message sent (%lu recipients)",
                recips_count);

    if (draft_is_reply(draft))
        draft_flag_answered(draft);

    /* Finished with this draft */
    draft_clear_atts(session->draft);
    draft_free(session->draft);
    draft_free(session->draft0);

    session_redirect(session, request, session->compose_parent_cmd);
}
