/*++
/* NAME
/*	lmtp 8
/* SUMMARY
/*	Postfix local delivery via LMTP
/* SYNOPSIS
/*	\fBlmtp\fR [generic Postfix daemon options]
/* DESCRIPTION
/*	The LMTP client processes message delivery requests from
/*	the queue manager. Each request specifies a queue file, a sender
/*	address, a domain or host to deliver to, and recipient information.
/*	This program expects to be run from the \fBmaster\fR(8) process
/*	manager.
/*
/*	The LMTP client updates the queue file and marks recipients
/*	as finished, or it informs the queue manager that delivery should
/*	be tried again at a later time. Delivery problem reports are sent
/*	to the \fBbounce\fR(8) or \fBdefer\fR(8) daemon as appropriate.
/*
/*	The LMTP client connects to the destination specified in the message
/*	delivery request. The destination, usually specified in the Postfix
/*	\fBtransport\fR(5) table, has the form:
/* .IP \fBunix\fR:\fIpathname\fR
/*	Connect to the local UNIX-domain server that is bound to the specified
/*	\fIpathname\fR. If the process runs chrooted, an absolute pathname
/*	is interpreted relative to the changed root directory.
/* .IP "\fBinet\fR:\fIhost\fR, \fBinet\fB:\fIhost\fR:\fIport\fR (symbolic host)"
/* .IP "\fBinet\fR:[\fIaddr\fR], \fBinet\fR:[\fIaddr\fR]:\fIport\fR (numeric host)"
/*	Connect to the specified IPV4 TCP port on the specified local or
/*	remote host. If no port is specified, connect to the port defined as
/*	\fBlmtp\fR in \fBservices\fR(4).
/*	If no such service is found, the \fBlmtp_tcp_port\fR configuration
/*	parameter (default value of 24) will be used.
/*
/*	The LMTP client does not perform MX (mail exchanger) lookups since
/*	those are defined only for mail delivery via SMTP.
/* .PP
/*	If neither \fBunix:\fR nor \fBinet:\fR are specified, \fBinet:\fR
/*	is assumed.
/* SECURITY
/* .ad
/* .fi
/*	The LMTP client is moderately security-sensitive. It talks to LMTP
/*	servers and to DNS servers on the network. The LMTP client can be
/*	run chrooted at fixed low privilege.
/* STANDARDS
/*	RFC 821 (SMTP protocol)
/*	RFC 1651 (SMTP service extensions)
/*	RFC 1652 (8bit-MIME transport)
/*	RFC 1870 (Message Size Declaration)
/*	RFC 2033 (LMTP protocol)
/*	RFC 2554 (AUTH command)
/*	RFC 2821 (SMTP protocol)
/*	RFC 2920 (SMTP Pipelining)
/* DIAGNOSTICS
/*	Problems and transactions are logged to \fBsyslogd\fR(8).
/*	Corrupted message files are marked so that the queue manager can
/*	move them to the \fBcorrupt\fR queue for further inspection.
/*
/*	Depending on the setting of the \fBnotify_classes\fR parameter,
/*	the postmaster is notified of bounces, protocol problems, and of
/*	other trouble.
/* BUGS
/* CONFIGURATION PARAMETERS
/* .ad
/* .fi
/*	The following \fBmain.cf\fR parameters are especially relevant to
/*	this program. See the Postfix \fBmain.cf\fR file for syntax details
/*	and for default values. Use the \fBpostfix reload\fR command after
/*	a configuration change.
/* .SH Miscellaneous
/* .ad
/* .fi
/* .IP \fBdebug_peer_level\fR
/*	Verbose logging level increment for hosts that match a
/*	pattern in the \fBdebug_peer_list\fR parameter.
/* .IP \fBdebug_peer_list\fR
/*	List of domain or network patterns. When a remote host matches
/*	a pattern, increase the verbose logging level by the amount
/*	specified in the \fBdebug_peer_level\fR parameter.
/* .IP \fBerror_notice_recipient\fR
/*	Recipient of protocol/policy/resource/software error notices.
/* .IP \fBnotify_classes\fR
/*	When this parameter includes the \fBprotocol\fR class, send mail to the
/*	postmaster with transcripts of LMTP sessions with protocol errors.
/* .IP \fBlmtp_skip_quit_response\fR
/*	Do not wait for the server response after sending QUIT.
/* .IP \fBlmtp_tcp_port\fR
/*	The TCP port to be used when connecting to a LMTP server.  Used as
/*	backup if the \fBlmtp\fR service is not found in \fBservices\fR(4).
/* .SH "Authentication controls"
/* .IP \fBlmtp_sasl_auth_enable\fR
/*	Enable per-session authentication as per RFC 2554 (SASL).
/*	By default, Postfix is built without SASL support.
/* .IP \fBlmtp_sasl_password_maps\fR
/*	Lookup tables with per-host or domain \fIname\fR:\fIpassword\fR entries.
/*	No entry for a host means no attempt to authenticate.
/* .IP \fBlmtp_sasl_security_options\fR
/*	Zero or more of the following.
/* .RS
/* .IP \fBnoplaintext\fR
/*	Disallow authentication methods that use plaintext passwords.
/* .IP \fBnoactive\fR
/*	Disallow authentication methods that are vulnerable to non-dictionary
/*	active attacks.
/* .IP \fBnodictionary\fR
/*	Disallow authentication methods that are vulnerable to passive
/*	dictionary attack.
/* .IP \fBnoanonymous\fR
/*	Disallow anonymous logins.
/* .RE
/* .SH "Resource controls"
/* .ad
/* .fi
/* .IP \fBlmtp_cache_connection\fR
/*	Should we cache the connection to the LMTP server? The effectiveness
/*	of cached connections will be determined by the number of LMTP servers
/*	in use, and the concurrency limit specified for the LMTP client.
/*	Cached connections are closed under any of the following conditions:
/* .RS
/* .IP \(bu
/*	The LMTP client idle time limit is reached. This limit is specified
/*	with the Postfix \fBmax_idle\fR configuration parameter.
/* .IP \(bu
/*	A delivery request specifies a different destination than the one
/*	currently cached.
/* .IP \(bu
/*	The per-process limit on the number of delivery requests is reached.
/*	This limit is specified with the Postfix \fBmax_use\fR configuration
/*	parameter.
/* .IP \(bu
/*	Upon the onset of another delivery request, the LMTP server associated
/*	with the current session does not respond to the \fBRSET\fR command.
/* .RE
/* .IP \fItransport_\fBdestination_concurrency_limit\fR
/*	Limit the number of parallel deliveries to the same destination
/*	via this mail delivery transport. \fItransport\fR is the name
/*	of the service as specified in the \fBmaster.cf\fR file.
/*	The default limit is taken from the
/*	\fBdefault_destination_concurrency_limit\fR parameter.
/* .IP \fItransport_\fBdestination_recipient_limit\fR
/*	Limit the number of recipients per message delivery via this mail
/*	delivery transport. \fItransport\fR is the name
/*	of the service as specified in the \fBmaster.cf\fR file.
/*	The default limit is taken from the
/*	\fBdefault_destination_recipient_limit\fR parameter.
/*
/*	This parameter becomes significant if the LMTP client is used
/*	for local delivery.  Some LMTP servers can optimize delivery of
/*	the same message to multiple recipients. The default limit for
/*	local mail delivery is 1.
/*
/*	Setting this parameter to 0 will lead to an unbounded number of
/*	recipients per delivery.  However, this could be risky since it may
/*	make the machine vulnerable to running out of resources if messages
/*	are encountered with an inordinate number of recipients.  Exercise
/*	care when setting this parameter.
/* .SH "Timeout controls"
/* .ad
/* .fi
/* .PP
/*	The default time unit is seconds; an explicit time unit can
/*	be specified by appending a one-letter suffix to the value:
/*	s (seconds), m (minutes), h (hours), d (days) or w (weeks).
/* .IP \fBlmtp_connect_timeout\fR
/*	Timeout for opening a connection to the LMTP server.
/*	If no connection can be made within the deadline, the message
/*	is deferred.
/* .IP \fBlmtp_lhlo_timeout\fR
/*	Timeout for sending the \fBLHLO\fR command, and for
/*	receiving the server response.
/* .IP \fBlmtp_mail_timeout\fR
/*	Timeout for sending the \fBMAIL FROM\fR command, and for
/*	receiving the server response.
/* .IP \fBlmtp_rcpt_timeout\fR
/*	Timeout for sending the \fBRCPT TO\fR command, and for
/*	receiving the server response.
/* .IP \fBlmtp_data_init_timeout\fR
/*	Timeout for sending the \fBDATA\fR command, and for
/*	receiving the server response.
/* .IP \fBlmtp_data_xfer_timeout\fR
/*	Timeout for sending the message content.
/* .IP \fBlmtp_data_done_timeout\fR
/*	Timeout for sending the "\fB.\fR" command, and for
/*	receiving the server response. When no response is received, a
/*	warning is logged that the mail may be delivered multiple times.
/* .IP \fBlmtp_rset_timeout\fR
/*	Timeout for sending the \fBRSET\fR command, and for
/*	receiving the server response.
/* .IP \fBlmtp_quit_timeout\fR
/*	Timeout for sending the \fBQUIT\fR command, and for
/*	receiving the server response.
/* SEE ALSO
/*	bounce(8) non-delivery status reports
/*	local(8) local mail delivery
/*	master(8) process manager
/*	qmgr(8) queue manager
/*	services(4) Internet services and aliases
/*	spawn(8) auxiliary command spawner
/*	syslogd(8) system logging
/* LICENSE
/* .ad
/* .fi
/*	The Secure Mailer license must be distributed with this software.
/* AUTHOR(S)
/*	Wietse Venema
/*	IBM T.J. Watson Research
/*	P.O. Box 704
/*	Yorktown Heights, NY 10598, USA
/*
/*	Alterations for LMTP by:
/*	Philip A. Prindeville
/*	Mirapoint, Inc.
/*	USA.
/*
/*	Additional work on LMTP by:
/*	Amos Gouaux
/*	University of Texas at Dallas
/*	P.O. Box 830688, MC34
/*	Richardson, TX 75083, USA
/*--*/

/* System library. */

#include <sys_defs.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <dict.h>

/* Utility library. */

#include <msg.h>
#include <argv.h>
#include <mymalloc.h>
#include <name_mask.h>
#include <split_at.h>

/* Global library. */

#include <deliver_request.h>
#include <mail_queue.h>
#include <mail_params.h>
#include <mail_conf.h>
#include <debug_peer.h>
#include <mail_error.h>

/* Single server skeleton. */

#include <mail_server.h>

/* Application-specific. */

#include "lmtp.h"
#include "lmtp_sasl.h"

 /*
  * Tunable parameters. These have compiled-in defaults that can be overruled
  * by settings in the global Postfix configuration file.
  */
int     var_lmtp_tcp_port;
int     var_lmtp_conn_tmout;
int     var_lmtp_rset_tmout;
int     var_lmtp_lhlo_tmout;
int     var_lmtp_mail_tmout;
int     var_lmtp_rcpt_tmout;
int     var_lmtp_data0_tmout;
int     var_lmtp_data1_tmout;
int     var_lmtp_data2_tmout;
int     var_lmtp_quit_tmout;
int     var_lmtp_cache_conn;
int     var_lmtp_skip_quit_resp;
char   *var_notify_classes;
char   *var_error_rcpt;
char   *var_lmtp_sasl_opts;
char   *var_lmtp_sasl_passwd;
bool    var_lmtp_sasl_enable;

 /*
  * Global variables.
  * 
  * lmtp_errno is set by the address lookup routines and by the connection
  * management routines.
  * 
  * state is global for the connection caching to work.
  */
int     lmtp_errno;
static LMTP_STATE *state = 0;

/* deliver_message - deliver message with extreme prejudice */

static int deliver_message(DELIVER_REQUEST *request, char **unused_argv)
{
    char   *myname = "deliver_message";
    VSTRING *why;
    int     result;

    if (msg_verbose)
	msg_info("%s: from %s", myname, request->sender);

    /*
     * Sanity checks.
     */
    if (request->rcpt_list.len <= 0)
	msg_fatal("%s: recipient count: %d", myname, request->rcpt_list.len);

    /*
     * Initialize. Bundle all information about the delivery request, so that
     * we can produce understandable diagnostics when something goes wrong
     * many levels below. The alternative would be to make everything global.
     * 
     * Note: `state' was made global (to this file) so that we can cache
     * connections and so that we can close a cached connection via the
     * MAIL_SERVER_EXIT function (cleanup). The alloc for `state' is
     * performed in the MAIL_SERVER_PRE_INIT function (pre_init).
     * 
     */
    why = vstring_alloc(100);
    state->request = request;
    state->src = request->fp;

    /*
     * See if we can reuse an existing connection.
     */
    if (state->session != 0) {

	/*
	 * Disconnect if we're going to a different destination. Discard
	 * transcript and status information for sending QUIT.
	 * 
	 * XXX Should transform nexthop into canonical form (unix:/path or
	 * inet:host:port) before doing connection cache lookup. See also the
	 * connection cache updating code in lmtp_connect.c.
	 */
	if (strcasecmp(state->session->dest, request->nexthop) != 0) {
	    lmtp_quit(state);
	    lmtp_chat_reset(state);
	    state->session = lmtp_session_free(state->session);
	}

	/*
	 * Disconnect if RSET can't be sent over an existing connection.
	 * Discard transcript and status information for sending RSET.
	 */
	else if (lmtp_rset(state) != 0) {
	    lmtp_chat_reset(state);
	    state->session = lmtp_session_free(state->session);
	}

	/*
	 * Ready to go with another load. The reuse counter is logged for
	 * statistical analysis purposes. Given the Postfix architecture,
	 * connection cacheing makes sense only for dedicated transports.
	 * Logging the reuse count can help to convince people.
	 */
	else {
	    ++state->reuse;
	    if (msg_verbose)
		msg_info("%s: reusing (count %d) session with: %s",
			 myname, state->reuse, state->session->host);
	}
    }

    /*
     * See if we need to establish an LMTP connection.
     */
    if (state->session == 0) {

	/*
	 * Bounce or defer the recipients if no connection can be made.
	 */
	if ((state->session = lmtp_connect(request->nexthop, why)) == 0) {
	    lmtp_site_fail(state, lmtp_errno == LMTP_RETRY ? 450 : 550,
			   "%s", vstring_str(why));
	}

	/*
	 * Bounce or defer the recipients if the LMTP handshake fails.
	 */
	else if (lmtp_lhlo(state) != 0) {
	    state->session = lmtp_session_free(state->session);
	}

	/*
	 * Congratulations. We just established a new LMTP connection.
	 */
	else
	    state->reuse = 0;
    }

    /*
     * If a session exists, deliver this message to all requested recipients.
     */
    if (state->session != 0)
	lmtp_xfer(state);

    /*
     * Optionally, notify the postmaster of problems with establishing a
     * session or with delivering mail.
     */
    if (state->history != 0
     && (state->error_mask & name_mask(VAR_NOTIFY_CLASSES, mail_error_masks,
				       var_notify_classes)))
	lmtp_chat_notify(state);

    /*
     * Disconnect if we're not cacheing connections. The pipelined protocol
     * state machine knows if it should have sent a QUIT command.
     */
    if (state->session != 0 && !var_lmtp_cache_conn)
	state->session = lmtp_session_free(state->session);

    /*
     * Clean up.
     */
    vstring_free(why);
    result = state->status;
    lmtp_chat_reset(state);

    /*
     * XXX State persists until idle timeout, but these fields will be
     * dangling pointers. Nuke them.
     */
    state->request = 0;
    state->src = 0;

    return (result);
}

/* lmtp_service - perform service for client */

static void lmtp_service(VSTREAM *client_stream, char *unused_service, char **argv)
{
    DELIVER_REQUEST *request;
    int     status;

    /*
     * Sanity check. This service takes no command-line arguments.
     */
    if (argv[0])
	msg_fatal("unexpected command-line argument: %s", argv[0]);

    /*
     * This routine runs whenever a client connects to the UNIX-domain socket
     * dedicated to remote LMTP delivery service. What we see below is a
     * little protocol to (1) tell the queue manager that we are ready, (2)
     * read a request from the queue manager, and (3) report the completion
     * status of that request. All connection-management stuff is handled by
     * the common code in single_server.c.
     */
    if ((request = deliver_request_read(client_stream)) != 0) {
	status = deliver_message(request, argv);
	deliver_request_done(client_stream, request, status);
    }
}

/* post_init - post-jail initialization */

static void post_init(char *unused_name, char **unused_argv)
{
    state = lmtp_state_alloc();
}

/* pre_init - pre-jail initialization */

static void pre_init(char *unused_name, char **unused_argv)
{
    debug_peer_init();
    if (var_lmtp_sasl_enable)
#ifdef USE_SASL_AUTH
	lmtp_sasl_initialize();
#else
	msg_warn("%s is true, but SASL support is not compiled in",
		 VAR_LMTP_SASL_ENABLE);
#endif
}

/* cleanup - close any open connections, etc. */

static void cleanup(void)
{
    if (state->session != 0) {
	lmtp_quit(state);
	lmtp_chat_reset(state);
	state->session = lmtp_session_free(state->session);
	if (msg_verbose)
	    msg_info("cleanup: just closed down session");
    }
    lmtp_state_free(state);
#ifdef USE_SASL_AUTH
    if (var_lmtp_sasl_enable)
	sasl_done();
#endif
}

/* pre_accept - see if tables have changed */

static void pre_accept(char *unused_name, char **unused_argv)
{
    if (dict_changed()) {
	msg_info("table has changed -- exiting");
	cleanup();
	exit(0);
    }
}

/* main - pass control to the single-threaded skeleton */

int     main(int argc, char **argv)
{
    static CONFIG_STR_TABLE str_table[] = {
	VAR_NOTIFY_CLASSES, DEF_NOTIFY_CLASSES, &var_notify_classes, 0, 0,
	VAR_ERROR_RCPT, DEF_ERROR_RCPT, &var_error_rcpt, 1, 0,
	VAR_LMTP_SASL_PASSWD, DEF_LMTP_SASL_PASSWD, &var_lmtp_sasl_passwd, 0, 0,
	VAR_LMTP_SASL_OPTS, DEF_LMTP_SASL_OPTS, &var_lmtp_sasl_opts, 0, 0,
	0,
    };
    static CONFIG_INT_TABLE int_table[] = {
	VAR_LMTP_TCP_PORT, DEF_LMTP_TCP_PORT, &var_lmtp_tcp_port, 0, 0,
	0,
    };
    static CONFIG_TIME_TABLE time_table[] = {
	VAR_LMTP_CONN_TMOUT, DEF_LMTP_CONN_TMOUT, &var_lmtp_conn_tmout, 0, 0,
	VAR_LMTP_RSET_TMOUT, DEF_LMTP_RSET_TMOUT, &var_lmtp_rset_tmout, 1, 0,
	VAR_LMTP_LHLO_TMOUT, DEF_LMTP_LHLO_TMOUT, &var_lmtp_lhlo_tmout, 1, 0,
	VAR_LMTP_MAIL_TMOUT, DEF_LMTP_MAIL_TMOUT, &var_lmtp_mail_tmout, 1, 0,
	VAR_LMTP_RCPT_TMOUT, DEF_LMTP_RCPT_TMOUT, &var_lmtp_rcpt_tmout, 1, 0,
	VAR_LMTP_DATA0_TMOUT, DEF_LMTP_DATA0_TMOUT, &var_lmtp_data0_tmout, 1, 0,
	VAR_LMTP_DATA1_TMOUT, DEF_LMTP_DATA1_TMOUT, &var_lmtp_data1_tmout, 1, 0,
	VAR_LMTP_DATA2_TMOUT, DEF_LMTP_DATA2_TMOUT, &var_lmtp_data2_tmout, 1, 0,
	VAR_LMTP_QUIT_TMOUT, DEF_LMTP_QUIT_TMOUT, &var_lmtp_quit_tmout, 1, 0,
	0,
    };
    static CONFIG_BOOL_TABLE bool_table[] = {
	VAR_LMTP_CACHE_CONN, DEF_LMTP_CACHE_CONN, &var_lmtp_cache_conn,
	VAR_LMTP_SKIP_QUIT_RESP, DEF_LMTP_SKIP_QUIT_RESP, &var_lmtp_skip_quit_resp,
	VAR_LMTP_SASL_ENABLE, DEF_LMTP_SASL_ENABLE, &var_lmtp_sasl_enable,
	0,
    };

    single_server_main(argc, argv, lmtp_service,
		       MAIL_SERVER_INT_TABLE, int_table,
		       MAIL_SERVER_STR_TABLE, str_table,
		       MAIL_SERVER_BOOL_TABLE, bool_table,
		       MAIL_SERVER_TIME_TABLE, time_table,
		       MAIL_SERVER_PRE_INIT, pre_init,
		       MAIL_SERVER_POST_INIT, post_init,
		       MAIL_SERVER_PRE_ACCEPT, pre_accept,
		       MAIL_SERVER_EXIT, cleanup,
		       0);
}
