/* deliver.c -- deliver shell - just calls lmtpd
 * Tim Martin
 *
 * Copyright (c) 1994-2008 Carnegie Mellon University.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The name "Carnegie Mellon University" must not be used to
 *    endorse or promote products derived from this software without
 *    prior written permission. For permission or any legal
 *    details, please contact
 *      Carnegie Mellon University
 *      Center for Technology Transfer and Enterprise Creation
 *      4615 Forbes Avenue
 *      Suite 302
 *      Pittsburgh, PA  15213
 *      (412) 268-7393, fax: (412) 268-7395
 *      innovation@andrew.cmu.edu
 *
 * 4. Redistributions of any form whatsoever must retain the following
 *    acknowledgment:
 *    "This product includes software developed by Computing Services
 *     at Carnegie Mellon University (http://www.cmu.edu/computing/)."
 *
 * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
 * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
 * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
 * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 * $Id: deliver.c,v 1.183 2010/01/06 17:01:31 murch Exp $
 */

#include <config.h>

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <syslog.h>
#include <errno.h>
#include <pwd.h>
#include <sys/types.h>

#include <netdb.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/un.h>

#include "global.h"
#include "exitcodes.h"
#include "imap_err.h"
#include "xmalloc.h"
#include "xstrlcpy.h"
#include "xstrlcat.h"
#include "lmtpengine.h"
#include "prot.h"
#include "proxy.h"
#include "version.h"

/* config.c stuff */
const int config_need_data = CONFIG_NEED_PARTITION_DATA;

extern int optind;
extern char *optarg;

static int logdebug = 0;

static struct protstream *deliver_out, *deliver_in;

static const char *sockaddr;

static struct protocol_t lmtp_protocol =
{ "lmtp", "lmtp",
  { 0, "220 " },
  { "LHLO", "deliver", "250 ", NULL,
    { { "AUTH ", CAPA_AUTH },
      { "STARTTLS", CAPA_STARTTLS },
      { "PIPELINING", CAPA_PIPELINING },
      { "IGNOREQUOTA", CAPA_IGNOREQUOTA },
      { NULL, 0 } } },
  { "STARTTLS", "220", "454", 0 },
  { "AUTH", 512, 0, "235", "5", "334 ", "*", NULL, 0 },
  { NULL, NULL, NULL },
  { "NOOP", NULL, "250" },
  { "QUIT", NULL, "221" }
};

/* unused for deliver.c, but needed to make lmtpengine.c happy */
int deliver_logfd = -1;

/* forward declarations */

static int deliver_msg(char *return_path, char *authuser, int ignorequota,
		       char **users, int numusers, char *mailbox);
static struct backend *init_net(const char *sockaddr);

static void usage()
{
    fprintf(stderr, 
	    "421-4.3.0 usage: deliver [-C <alt_config> ] [-m mailbox]"
	    " [-a auth] [-r return_path] [-l] [-D]\r\n");
    fprintf(stderr, "421 4.3.0 %s\n", cyrus_version());
    exit(EC_USAGE);
}

void fatal(const char* s, int code)
{
    static int recurse_code = 0;
    
    if(recurse_code) exit(code);
    else recurse_code = 0;
    
    prot_printf(deliver_out,"421 4.3.0 deliver: %s\r\n", s);
    prot_flush(deliver_out);
    cyrus_done();
    exit(code);
}

/*
 * Here we're just an intermediatory piping stdin to lmtp socket
 * and lmtp socket to stdout
 */
void pipe_through(struct backend *conn)
{
    struct protgroup *protin = protgroup_new(2);

    protgroup_insert(protin, deliver_in);
    protgroup_insert(protin, conn->in);

    do {
	/* Flush any buffered output */
	prot_flush(deliver_out);
	prot_flush(conn->out);

    } while (!proxy_check_input(protin, deliver_in, deliver_out,
				conn->in, conn->out, 0));

    /* ok, we're done. */
    protgroup_free(protin);

    return;
}

int main(int argc, char **argv)
{
    int r = 0;
    int opt;
    int lmtpflag = 0;
    int ignorequota = 0;
    char *mailboxname = NULL;
    char *authuser = NULL;
    char *return_path = NULL;
    char buf[1024];
    char *alt_config = NULL;

    while ((opt = getopt(argc, argv, "C:df:r:m:a:F:eE:lqD")) != EOF) {
	switch(opt) {
	case 'C': /* alt config file */
	    alt_config = optarg;
	    break;

	case 'd':
	    /* Ignore -- /bin/mail compatibility flags */
	    break;

        case 'D':
	    logdebug = 1;
	    break;

	case 'r':
	case 'f':
	    return_path = optarg;
	    break;

	case 'm':
	    if (mailboxname) {
		fprintf(stderr, "deliver: multiple -m options\n");
		usage();
		/* NOTREACHED */
	    }
	    if (*optarg) mailboxname = optarg;
	    break;

	case 'a':
	    if (authuser) {
		fprintf(stderr, "deliver: multiple -a options\n");
		usage();
		/* NOTREACHED */
	    }
	    authuser = optarg;
	    break;

	case 'F': /* set IMAP flag. we no longer support this */
	    fprintf(stderr,"deliver: 'F' option no longer supported\n");
	    usage();
	    break;

	case 'e':
	    /* duplicate delivery. ignore */
	    break;

	case 'E':
	    fprintf(stderr,"deliver: 'E' option no longer supported\n");
	    usage();
	    break;

	case 'l':
	    lmtpflag = 1;
	    break;

	case 'q':
	    ignorequota = 1;
	    break;

	default:
	    usage();
	    /* NOTREACHED */
	}
    }

    deliver_in = prot_new(0, 0);
    deliver_out = prot_new(1, 1);
    prot_setflushonread(deliver_in, deliver_out);
    prot_settimeout(deliver_in, 300);

    cyrus_init(alt_config, "deliver", CYRUSINIT_NODB);

    sockaddr = config_getstring(IMAPOPT_LMTPSOCKET);
    if (!sockaddr) {	
	strlcpy(buf, config_dir, sizeof(buf));
	strlcat(buf, "/socket/lmtp", sizeof(buf));
	sockaddr = buf;
    }

    if (lmtpflag == 1) {
	struct backend *conn = init_net(sockaddr);

	pipe_through(conn);

	backend_disconnect(conn);
	free(conn);
    }
    else {
	if (return_path == NULL) {
	    uid_t me = getuid();
	    struct passwd *p = getpwuid(me);
	    return_path = p->pw_name;
	}

	/* deliver to users or global mailbox */
	r = deliver_msg(return_path,authuser, ignorequota,
			argv+optind, argc - optind, mailboxname);
    }

    cyrus_done();

    return r;
}

void just_exit(const char *msg)
{
    com_err(msg, 0, "%s", error_message(errno));

    fatal(msg, EC_CONFIG);
}

/* initialize the network 
 * we talk on unix sockets
 */
static struct backend *init_net(const char *unixpath)
{
  int lmtpdsock;
  struct sockaddr_un addr;
  struct backend *conn;

  if ((lmtpdsock = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
      just_exit("socket failed");
  }

  addr.sun_family = AF_UNIX;
  strlcpy(addr.sun_path, unixpath, sizeof(addr.sun_path));

  if (connect(lmtpdsock, (struct sockaddr *) &addr, 
	      sizeof(addr.sun_family) + strlen(addr.sun_path) + 1) < 0) {
      just_exit("connect failed");
  }

  conn = xzmalloc(sizeof(struct backend));
  conn->timeout = NULL;
  conn->in = prot_new(lmtpdsock, 0);
  conn->out = prot_new(lmtpdsock, 1);
  conn->sock = lmtpdsock;
  prot_setflushonread(conn->in, conn->out);
  conn->prot = &lmtp_protocol;

  return conn;
}

static int deliver_msg(char *return_path, char *authuser, int ignorequota,
		       char **users, int numusers, char *mailbox)
{
    int r;
    struct backend *conn;
    struct lmtp_txn *txn = LMTP_TXN_ALLOC(numusers ? numusers : 1);
    int j;
    int ml = 0;

    /* must have either some users or a mailbox */
    if (!numusers && !mailbox) {
	usage();
    }

    /* connect */
    conn = backend_connect(NULL, sockaddr, &lmtp_protocol,
			   "", NULL, NULL);
    if (!conn) {
	just_exit("couldn't connect to lmtpd");
    }

    /* setup txn */
    txn->from = return_path;
    txn->auth = authuser;
    txn->data = deliver_in;
    txn->isdotstuffed = 0;
    txn->tempfail_unknown_mailbox = 0;
    txn->rcpt_num = numusers ? numusers : 1;
    if (mailbox) ml = strlen(mailbox);
    if (numusers == 0) {
	/* just deliver to mailbox 'mailbox' */
	const char *BB = config_getstring(IMAPOPT_POSTUSER);
	txn->rcpt[0].addr = (char *) xmalloc(ml + strlen(BB) + 2); /* xxx leaks! */
	sprintf(txn->rcpt[0].addr, "%s+%s", BB, mailbox);
	txn->rcpt[0].ignorequota = ignorequota;
    } else {
	/* setup each recipient */
	for (j = 0; j < numusers; j++) {
	    if (mailbox) {
		size_t ulen;

		txn->rcpt[j].addr = 
		    (char *) xmalloc(strlen(users[j]) + ml + 2);

		/* find the length of the userid minus the domain */
		ulen = strcspn(users[j], "@");
		sprintf(txn->rcpt[j].addr, "%.*s+%s",
			(int) ulen, users[j], mailbox);

		/* add the domain if we have one */
		if (ulen < strlen(users[j]))
		    strcat(txn->rcpt[j].addr, users[j]+ulen);
	    } else {
		txn->rcpt[j].addr = xstrdup(users[j]);
	    }
	    txn->rcpt[j].ignorequota = ignorequota;
	}
    }

    /* run txn */
    r = lmtp_runtxn(conn, txn);

    /* disconnect */
    backend_disconnect(conn);
    free(conn);

    /* examine txn for error state */
    r = 0;
    for (j = 0; j < txn->rcpt_num; j++) {
	switch (txn->rcpt[j].result) {
	case RCPT_GOOD:
	    break;

	case RCPT_TEMPFAIL:
	    r = EC_TEMPFAIL;
	    break;

	case RCPT_PERMFAIL:
	    /* we just need any permanent failure, though we should
	       probably return data from the client-side LMTP info */
	    printf("%s: %s\n", 
		   txn->rcpt[j].addr, error_message(txn->rcpt[j].r));
	    if (r != EC_TEMPFAIL) {
		r = EC_DATAERR;
	    }
	    break;
	}
	free(txn->rcpt[j].addr);
    }

    free(txn);

    /* return appropriately */
    return r;
}
