view src/deliver.c @ 250:a41c013c8458

moved the queue manipulation code to an new function
author markus schnalke <meillo@marmaro.de>
date Thu, 04 Nov 2010 12:32:11 -0300 (2010-11-04)
parents 5f9f3a65032e
children 72e377210d5e
line wrap: on
line source
/*  MasqMail
    Copyright (C) 1999-2002 Oliver Kurth
    Copyright (C) 2008, 2010 markus schnalke <meillo@marmaro.de>

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/

#include <fnmatch.h>
#include <sysexits.h>
#include <netdb.h>

#include "masqmail.h"
#include "smtp_out.h"

/* collect failed/defered rcpts for failure/warning messages */
/* returns TRUE if either there are no failures or a failure message has been successfully sent */
gboolean
delivery_failures(message * msg, GList * rcpt_list, gchar * err_fmt, ...)
{
	gboolean ok_fail = TRUE, ok_warn = TRUE;
	time_t now = time(NULL);

	GList *failed_list = NULL, *defered_list = NULL, *rcpt_node;
	va_list args;
	va_start(args, err_fmt);

	foreach(rcpt_list, rcpt_node) {
		address *rcpt = (address *) (rcpt_node->data);

		if (addr_is_defered(rcpt)) {
			if ((now - msg->received_time) >= conf.max_defer_time) {
				addr_mark_failed(rcpt);
			} else
				defered_list = g_list_prepend(defered_list, rcpt);
		}
		if (addr_is_failed(rcpt))
			failed_list = g_list_prepend(failed_list, rcpt);
	}
	if (failed_list != NULL) {
		ok_fail = fail_msg(msg, conf.errmsg_file, failed_list, err_fmt, args);
		g_list_free(failed_list);
	}
	if (defered_list != NULL) {
		ok_warn = warn_msg(msg, conf.warnmsg_file, defered_list, err_fmt, args);
		g_list_free(defered_list);
	}
	va_end(args);
	return ok_fail && ok_warn;
}

static gint
_g_list_strcasecmp(gconstpointer a, gconstpointer b)
{
	return (gint) strcasecmp(a, b);
}

gboolean
deliver_local(msg_out * msgout)
{
	message *msg = msgout->msg;
	GList *rcpt_list = msgout->rcpt_list;
	GList *rcpt_node;
	gboolean ok = TRUE, flag = FALSE, ok_fail = FALSE;

	DEBUG(5) debugf("deliver_local entered\n");

	flag = (msg->data_list == NULL);
	if (flag) {
		if (!(ok = spool_read_data(msg))) {
			logwrite(LOG_ALERT, "could not open data spool file for %s\n", msg->uid);
		}
	}
	if (!ok)
		return FALSE;

	ok = FALSE;
	for (rcpt_node = g_list_first(rcpt_list); rcpt_node; rcpt_node = g_list_next(rcpt_node)) {
		GList *hdr_list;
		address *rcpt = (address *) (rcpt_node->data);
		address *env_addr = addr_find_ancestor(rcpt);
		address *ret_path = msg->return_path;
		header *retpath_hdr, *envto_hdr;

		/* we need a private copy of the hdr list because we add headers here that belong to the rcpt only.
		   g_list_copy copies only the nodes, so it is safe to g_list_free it */
		hdr_list = g_list_copy(msg->hdr_list);
		retpath_hdr = create_header(HEAD_ENVELOPE_TO, "Envelope-to: %s\n", addr_string(env_addr));
		envto_hdr = create_header(HEAD_RETURN_PATH, "Return-path: %s\n", addr_string(ret_path));

		hdr_list = g_list_prepend(hdr_list, envto_hdr);
		hdr_list = g_list_prepend(hdr_list, retpath_hdr);

		if (rcpt->local_part[0] == '|') {
			DEBUG(1) debugf("attempting to deliver %s with pipe\n", msg->uid);
			if (pipe_out(msg, hdr_list, rcpt, &(rcpt->local_part[1]),
			    (conf.pipe_fromline ? MSGSTR_FROMLINE : 0)
			    | (conf.pipe_fromhack ? MSGSTR_FROMHACK : 0))) {
				logwrite(LOG_NOTICE, "%s => %s <%s@%s> with pipe\n",
				         msg->uid, rcpt->local_part, env_addr->local_part, env_addr->domain);
				addr_mark_delivered(rcpt);
				ok = TRUE;
			} else {
				if ((errno != (1024 + EX_TEMPFAIL)) && (errno != EAGAIN)) {
					addr_mark_failed(rcpt);
				} else {
					addr_mark_defered(rcpt);  /* has no effect yet, except that mail remains in spool */
				}
			}
		} else {
			/* figure out which mailbox type should be used for this user */
			gchar *user = rcpt->local_part;
			gchar *mbox_type = conf.mbox_default;

			if (g_list_find_custom (conf.mbox_users, user, _g_list_strcasecmp) != NULL)
				mbox_type = "mbox";
			else if (g_list_find_custom (conf.mda_users, user, _g_list_strcasecmp) != NULL)
				mbox_type = "mda";

			if (strcmp(mbox_type, "mbox") == 0) {
				DEBUG(1) debugf("attempting to deliver %s with mbox\n", msg->uid);
				if (append_file(msg, hdr_list, rcpt->local_part)) {
					if (env_addr != rcpt) {
						logwrite(LOG_NOTICE, "%s => %s@%s <%s@%s> with mbox\n",
						         msg->uid, rcpt->local_part, rcpt->domain,
						         env_addr->local_part, env_addr->domain);
					} else {
						logwrite(LOG_NOTICE, "%s => <%s@%s> with mbox\n",
						         msg->uid, rcpt->local_part, rcpt->domain);
					}
					addr_mark_delivered(rcpt);
					ok = TRUE;
				} else {
					if (errno != EAGAIN) {  /* prevents 'Resource temporarily unavailable (11)' */
						addr_mark_failed(rcpt);
					} else {
						addr_mark_defered(rcpt);
					}
				}

			} else if (strcmp(mbox_type, "mda") == 0) {
				if (conf.mda) {
					gchar *cmd = g_malloc(256);
					GList *var_table = var_table_rcpt(var_table_msg(NULL, msg), rcpt);

					DEBUG(1) debugf("attempting to deliver %s with mda\n", msg->uid);

					if (expand(var_table, conf.mda, cmd, 256)) {

						if (pipe_out(msg, hdr_list, rcpt, cmd, (conf.mda_fromline ? MSGSTR_FROMLINE : 0)
						    | (conf.mda_fromhack ? MSGSTR_FROMHACK : 0))) {
							logwrite(LOG_NOTICE, "%s => %s@%s with mda (cmd = '%s')\n",
							         msg->uid, rcpt->local_part, rcpt->domain, cmd);
							addr_mark_delivered(rcpt);
							ok = TRUE;
						} else {
							if ((errno != (1024 + EX_TEMPFAIL)) && (errno != EAGAIN)) {
								addr_mark_failed(rcpt);
							} else {
								addr_mark_defered(rcpt);  /* has no effect yet, except that mail remains in spool */
							}
						}
					} else
						logwrite(LOG_ALERT, "could not expand string %s\n", conf.mda);

					destroy_table(var_table);
				} else
					logwrite(LOG_ALERT, "mbox type is mda, but no mda command given in configuration\n");
			} else
				logwrite(LOG_ALERT, "unknown mbox type '%s'\n", mbox_type);
		}

		destroy_header(retpath_hdr);
		destroy_header(envto_hdr);

		g_list_free(hdr_list);
	}
	ok_fail = delivery_failures(msg, rcpt_list, "%s (%d)", ext_strerror(errno), errno);

	if (flag)
		msg_free_data(msg);
	if (ok || ok_fail)
		deliver_finish(msgout);

	return ok;
}

/* make a list of rcpt's of a message that are local return a new copy of the list */
void
msg_rcptlist_local(GList * rcpt_list, GList ** p_local_list, GList ** p_nonlocal_list)
{
	GList *rcpt_node;

	foreach(rcpt_list, rcpt_node) {
		address *rcpt = (address *) (rcpt_node->data);
		GList *dom_node;

		DEBUG(5) debugf("checking address %s\n", rcpt->address);

		/* search for local host list: */
		foreach(conf.local_hosts, dom_node) {
			if (strcasecmp(dom_node->data, rcpt->domain) == 0) {
				*p_local_list = g_list_append(*p_local_list, rcpt);
				DEBUG(5) debugf("<%s@%s> is local\n", rcpt->local_part, rcpt->domain);
				break;
			} else {
				*p_nonlocal_list = g_list_append(*p_nonlocal_list, rcpt);
			}
		}
	}
}

gboolean
deliver_msglist_host_pipe(connect_route * route, GList * msgout_list, gchar * host, GList * res_list)
{
	gboolean ok = TRUE;
	GList *msgout_node;

	DEBUG(5) debugf("deliver_msglist_host_pipe entered\n");

	if (route->pipe == NULL) {
		logwrite(LOG_ALERT, "no pipe command given for route (protocol is pipe!)\n");
		return FALSE;
	}

	foreach(msgout_list, msgout_node) {
		msg_out *msgout = (msg_out *) (msgout_node->data);
		gboolean flag, ok_msg = TRUE, ok_fail = FALSE;
		message *msg = msgout->msg;
		GList *rcpt_node, *rcpt_list = msgout->rcpt_list;

		DEBUG(1) debugf("attempting to deliver %s with pipe\n", msg->uid);

		flag = (msg->data_list == NULL);
		if (flag) {
			if (!(ok_msg = spool_read_data(msg))) {
				logwrite(LOG_ALERT, "could not open data spool file for %s\n", msg->uid);
			}
		}
		if (!ok_msg)
			continue;

		ok = FALSE;
		foreach(rcpt_list, rcpt_node) {
			address *rcpt = (address *) (rcpt_node->data);
			gchar *cmd = g_malloc(256);
			GList *var_table = var_table_rcpt(var_table_msg(NULL, msg), rcpt);

			DEBUG(1) debugf("attempting to deliver %s to %s@%s with pipe\n", msg->uid, rcpt->local_part, rcpt->domain);

			if (expand(var_table, route->pipe, cmd, 256)) {

				if (pipe_out(msg, msg->hdr_list, rcpt, cmd, (route->pipe_fromline ? MSGSTR_FROMLINE : 0)
				    | (route->pipe_fromhack ? MSGSTR_FROMHACK : 0))) {
					logwrite(LOG_NOTICE, "%s => %s@%s with pipe (cmd = '%s')\n",
					         msg->uid, rcpt->local_part, rcpt->domain, cmd);
					addr_mark_delivered(rcpt);
					ok = TRUE;
				} else {
					logwrite(LOG_ALERT, "pipe_out '%s' failed\n", route->pipe);

					if (route->connect_error_fail) {
						addr_mark_failed(rcpt);
					} else {
						addr_mark_defered(rcpt);
					}
				}
			} else
				logwrite(LOG_ALERT, "could not expand string %s\n", route->pipe);

			destroy_table(var_table);
		}
		ok_fail = delivery_failures(msg, rcpt_list, "%s", strerror(errno));

		if (flag)
			msg_free_data(msg);

		if (ok || ok_fail)
			deliver_finish(msgout);
	}

	return ok;
}

/* deliver list of messages to one host and finishes them if the message was delivered to at least one rcpt.
   Returns TRUE if at least one msg was delivered to at least one rcpt.
*/
gboolean
deliver_msglist_host_smtp(connect_route * route, GList * msgout_list, gchar * host, GList * res_list)
{
	gboolean ok = FALSE;
	GList *msgout_node;
	smtp_base *psb;
	gint port = 25;

	/* paranoid check: */
	if (msgout_list == NULL) {
		logwrite(LOG_ALERT, "Ooops: empty list of messages in deliver_msglist_host()\n");
		return FALSE;
	}

	if (host == NULL) {
		/* XXX: what if mail_host isn't set? Is this possible? */
		host = route->mail_host->address;
		port = route->mail_host->port;
	}

	if ((psb = (route->wrapper ? smtp_out_open_child(route->wrapper) : smtp_out_open(host, port, res_list)))) {

		if (route->wrapper) {
			/* it seems as if the remote_host is only set for logging
			/* XXX: this could probably be moved into smtp_out_open_child() */
			psb->remote_host = host;
		}

		set_heloname(psb, route->helo_name ? route->helo_name : conf.host_name, route->do_correct_helo);

#ifdef ENABLE_AUTH
		if ((route->auth_name) && (route->auth_login) && (route->auth_secret))
			set_auth(psb, route->auth_name, route->auth_login, route->auth_secret);
#endif
		if (smtp_out_init(psb, route->instant_helo)) {

			if (!route->do_pipelining)
				psb->use_pipelining = FALSE;

			foreach(msgout_list, msgout_node) {
				msg_out *msgout = (msg_out *) (msgout_node->data);
				gboolean flag, ok_msg = FALSE, ok_fail = FALSE;
				message *msg = msgout->msg;

				/* we may have to read the data at this point and remember if we did */
				flag = (msg->data_list == NULL);
				if (flag) {
					if (!spool_read_data(msg)) {
						logwrite(LOG_ALERT, "could not open data spool file %s\n", msg->uid);
						break;
					}
				}

				smtp_out_msg(psb, msg, msgout->return_path, msgout->rcpt_list, msgout->hdr_list);

				ok_fail = delivery_failures(msg, msgout->rcpt_list,
				                            "while connected with %s, the server replied\n\t%s", host, psb->buffer);

				if ((psb->error == smtp_eof)
				    || (psb->error == smtp_timeout)) {
					/* connection lost */
					break;
				} else if (psb->error != smtp_ok) {
					if (g_list_next(msgout_node) != NULL)
						if (!smtp_out_rset(psb))
							break;
				}
				ok_msg = (psb->error == smtp_ok);

				if (flag)
					msg_free_data(msg);
				if (ok_msg)
					ok = TRUE;
				if (ok_msg || ok_fail) {
					deliver_finish(msgout);
				}
			}
			if (psb->error == smtp_ok || (psb->error == smtp_fail)
			    || (psb->error == smtp_trylater) || (psb->error == smtp_syntax)) {
				smtp_out_quit(psb);
			}
		} else {
			/* smtp_out_init() failed */
			if ((psb->error == smtp_fail) || (psb->error == smtp_trylater) || (psb->error == smtp_syntax)) {
				smtp_out_quit(psb);

				foreach(msgout_list, msgout_node) {
					msg_out *msgout = (msg_out *) (msgout_node->data);
					smtp_out_mark_rcpts(psb, msgout->rcpt_list);

					if (delivery_failures(msgout->msg, msgout->rcpt_list,
					    "while connected with %s, the server replied\n\t%s", host, psb->buffer))
						deliver_finish(msgout);
				}
			}
		}
		destroy_smtpbase(psb);
	} else {
		/* smtp_out_open() failed */
		foreach(msgout_list, msgout_node) {
			msg_out *msgout = (msg_out *) (msgout_node->data);
			GList *rcpt_node;

			for (rcpt_node = g_list_first(msgout->rcpt_list); rcpt_node; rcpt_node = g_list_next(rcpt_node)) {
				address *rcpt = (address *) (rcpt_node->data);

				addr_unmark_delivered(rcpt);
				if (route->connect_error_fail) {
					addr_mark_failed(rcpt);
				} else {
					addr_mark_defered(rcpt);
				}
				if (route->wrapper
				    ? delivery_failures(msgout->msg, msgout->rcpt_list, "could not open wrapper:\n\t%s",
				                        strerror(errno))
				    : delivery_failures(msgout->msg, msgout->rcpt_list, "could not open connection to %s:%d :\n\t%s",
				                        host, port, h_errno != 0 ? hstrerror(h_errno) : strerror(errno)))
					deliver_finish(msgout);
			}
		}
	}
	return ok;
}

gboolean
deliver_msglist_host(connect_route * route, GList * msgout_list, gchar * host, GList * res_list)
{
	DEBUG(5) debugf("protocol = %s\n", route->protocol);

	if (strcmp(route->protocol, "pipe") == 0) {
		return deliver_msglist_host_pipe(route, msgout_list, host, res_list);
	} else {
		return deliver_msglist_host_smtp(route, msgout_list, host, res_list);
	}
}

/*
  delivers messages in msgout_list using route
*/
gboolean
deliver_route_msgout_list(connect_route * route, GList * msgout_list)
{
	gboolean ok = FALSE;

	DEBUG(5) debugf("deliver_route_msgout_list entered, route->name = %s\n", route->name);

	if (route->mail_host) {
		/* this is easy... deliver everything to a smart host for relay */
		if (deliver_msglist_host(route, msgout_list, NULL, route->resolve_list))
			ok = TRUE;

	} else {
		/* this is not easy... */
		GList *mo_ph_list;

		mo_ph_list = route_msgout_list(route, msgout_list);
		/* okay, now we have ordered our messages by the hosts. */
		if (mo_ph_list != NULL) {
			GList *mo_ph_node;
			/* TODO: It would be nice to be able to fork for each host.
			   We cannot do that yet because of complications with finishing the
			   messages. Threads could be a solution because they use the same
			   memory. But we are not thread safe yet...
			 */
			foreach(mo_ph_list, mo_ph_node) {
				msgout_perhost *mo_ph = (msgout_perhost *) (mo_ph_node->data);
				if (deliver_msglist_host (route, mo_ph->msgout_list, mo_ph->host, route->resolve_list))
					ok = TRUE;

				destroy_msgout_perhost(mo_ph);
			}
			g_list_free(mo_ph_list);
		}
	}
	return ok;
}

/*
  calls route_prepare_msg()
  delivers messages in msg_list using route by calling deliver_route_msgout_list()
*/
gboolean
deliver_route_msg_list(connect_route * route, GList * msgout_list)
{
	GList *msgout_list_deliver = NULL;
	GList *msgout_node;
	gboolean ok = TRUE;

	DEBUG(6) debugf("deliver_route_msg_list()\n");

	foreach(msgout_list, msgout_node) {
		msg_out *msgout = (msg_out *) (msgout_node->data);
		msg_out *msgout_cloned = clone_msg_out(msgout);
		GList *rcpt_list_non_delivered = NULL;
		GList *rcpt_node;

		/* we have to delete already delivered rcpt's because a previous route may have delivered to it */
		foreach(msgout_cloned->rcpt_list, rcpt_node) {
			address *rcpt = (address *) (rcpt_node->data);
			/* failed addresses already have been bounced - there should be a better way to handle those. */
			if (!addr_is_delivered(rcpt) && !addr_is_failed(rcpt)
			    && !(rcpt->flags & ADDR_FLAG_LAST_ROUTE))
				rcpt_list_non_delivered = g_list_append(rcpt_list_non_delivered, rcpt);
		}
		g_list_free(msgout_cloned->rcpt_list);
		msgout_cloned->rcpt_list = rcpt_list_non_delivered;

		if (msgout_cloned->rcpt_list) {
			if (route_is_allowed_mail_local(route, msgout->msg->return_path)
			   && route_is_allowed_return_path(route, msgout->msg-> return_path)) {
				GList *rcpt_list_allowed = NULL, *rcpt_list_notallowed = NULL;
				msg_rcptlist_route(route, msgout_cloned->rcpt_list, &rcpt_list_allowed, &rcpt_list_notallowed);

				if (rcpt_list_allowed != NULL) {
					logwrite(LOG_NOTICE, "%s using '%s'\n", msgout->msg->uid, route->name);

					g_list_free(msgout_cloned->rcpt_list);
					msgout_cloned->rcpt_list = rcpt_list_allowed;

					if (route->last_route) {
						GList *rcpt_node;
						foreach(msgout_cloned->rcpt_list, rcpt_node) {
							address *rcpt = (address *) (rcpt_node->data);
							rcpt->flags |= ADDR_FLAG_LAST_ROUTE;
						}
					}

					route_prepare_msgout(route, msgout_cloned);
					msgout_list_deliver = g_list_append(msgout_list_deliver, msgout_cloned);
				} else
					destroy_msg_out(msgout_cloned);
			} else
				destroy_msg_out(msgout_cloned);
		} else
			destroy_msg_out(msgout_cloned);
	}

	if (msgout_list_deliver != NULL) {
		if (deliver_route_msgout_list(route, msgout_list_deliver))
			ok = TRUE;
		destroy_msg_out_list(msgout_list_deliver);
	}
	return ok;
}

/* copy pointers of delivered addresses to the msg's non_rcpt_list,
   to make sure that they will not be delivered again.
*/
void
update_non_rcpt_list(msg_out * msgout)
{
	GList *rcpt_node;
	message *msg = msgout->msg;

	foreach(msgout->rcpt_list, rcpt_node) {
		address *rcpt = (address *) (rcpt_node->data);
		if (addr_is_delivered(rcpt) || addr_is_failed(rcpt))
			msg->non_rcpt_list = g_list_append(msg->non_rcpt_list, rcpt);
	}
}

/* after delivery attempts, we check if there are any rcpt addresses left in the message.
   If all addresses have been completed, the spool files will be deleted,
   otherwise the header spool will be written back.
   We never changed the data spool, so there is no need to write that back.

   returns TRUE if all went well.
*/
gboolean
deliver_finish(msg_out * msgout)
{
	GList *rcpt_node;
	gboolean ok = FALSE;
	message *msg = msgout->msg;
	gboolean finished = TRUE;

	update_non_rcpt_list(msgout);

	/* we NEVER made copies of the addresses, flags affecting addresses
	   were always set on the original address structs */
	foreach(msg->rcpt_list, rcpt_node) {
		address *rcpt = (address *) (rcpt_node->data);
		if (!addr_is_finished_children(rcpt))
			finished = FALSE;
		else {
			/* if ALL children have been delivered, mark parent as delivered.
			   if there is one or more not delivered, it must have failed, we mark the parent as failed as well.
			 */
			if (addr_is_delivered_children(rcpt)) {
				addr_mark_delivered(rcpt);
			} else {
				addr_mark_failed(rcpt);
			}
		}
	}

	if (!finished) {
		/* one not delivered address was found */
		if (spool_write(msg, FALSE)) {
			ok = TRUE;
			DEBUG(2) debugf("spool header for %s written back.\n", msg->uid);
		} else
			logwrite(LOG_ALERT, "could not write back spool header for %s\n", msg->uid);
	} else {
		ok = spool_delete_all(msg);
		if (ok)
			logwrite(LOG_NOTICE, "%s completed.\n", msg->uid);
	}
	return ok;
}

gboolean
deliver_finish_list(GList * msgout_list)
{
	gboolean ok = TRUE;
	GList *msgout_node;
	foreach(msgout_list, msgout_node) {
		msg_out *msgout = (msg_out *) (msgout_node->data);
		if (!deliver_finish(msgout))
			ok = FALSE;
	}
	return ok;
}

gboolean
deliver_msgout_list_online(GList * msgout_list)
{
	GList *rf_list = NULL;
	gchar *connect_name = detect_online();
	gboolean ok = FALSE;

	if (connect_name != NULL) {
		logwrite(LOG_NOTICE, "detected online configuration %s\n", connect_name);
		/* we are online! */
		rf_list = (GList *) table_find(conf.connect_routes, connect_name);
		if (rf_list != NULL) {
			GList *route_list = read_route_list(rf_list, FALSE);
			if (route_list) {
				GList *route_node;
				foreach(route_list, route_node) {
					connect_route *route = (connect_route *) (route_node->data);
					ok = deliver_route_msg_list(route, msgout_list);
				}
				destroy_route_list(route_list);
			} else
				logwrite(LOG_ALERT, "could not read route list '%s'\n", connect_name);
		} else {
			logwrite(LOG_ALERT, "route list with name '%s' not found.\n", connect_name);
		}
	}
	return ok;
}

gboolean
deliver_msg_list(GList * msg_list, guint flags)
{
	GList *msgout_list = create_msg_out_list(msg_list);
	GList *local_msgout_list = NULL, *localnet_msgout_list = NULL, *other_msgout_list = NULL;
	GList *msgout_node;
	GList *alias_table = NULL;
	gboolean ok = TRUE;

	if (conf.alias_file) {
		alias_table = table_read(conf.alias_file, ':');
	}

	/* sort messages for different deliveries */
	foreach(msgout_list, msgout_node) {
		msg_out *msgout = (msg_out *) (msgout_node->data);
		GList *rcpt_list;
		GList *local_rcpt_list = NULL;
		GList *localnet_rcpt_list = NULL;
		GList *other_rcpt_list = NULL;

		if (!spool_lock(msgout->msg->uid)) {
			DEBUG(5) debugf("spool_lock(%s) failed.\n", msgout->msg->uid);
			continue;
		}
		DEBUG(5) debugf("spool_lock(%s)\n", msgout->msg->uid);

		rcpt_list = g_list_copy(msgout->msg->rcpt_list);
		if (conf.log_user) {
			address *addr = create_address_qualified(conf.log_user, TRUE, conf.host_name);
			if (addr)
				rcpt_list = g_list_prepend(rcpt_list, addr);
		}
		if (alias_table) {
			GList *aliased_rcpt_list;
			aliased_rcpt_list = alias_expand(alias_table, rcpt_list, msgout->msg->non_rcpt_list);
			g_list_free(rcpt_list);
			rcpt_list = aliased_rcpt_list;
		}

		split_rcpts(rcpt_list, conf.local_nets, &local_rcpt_list, &localnet_rcpt_list, &other_rcpt_list);
		g_list_free(rcpt_list);

		/* local recipients */
		if ((flags & DLVR_LOCAL) && local_rcpt_list) {
			msg_out *local_msgout = clone_msg_out(msgout);
			local_msgout->rcpt_list = local_rcpt_list;
			local_msgout_list = g_list_append(local_msgout_list, local_msgout);
		}

		/* local net recipients */
		if ((flags & DLVR_LAN) && localnet_rcpt_list) {
			msg_out *localnet_msgout = clone_msg_out(msgout);
			localnet_msgout->rcpt_list = localnet_rcpt_list;
			localnet_msgout_list = g_list_append(localnet_msgout_list, localnet_msgout);
		}

		/* remote recipients (the rest), requires online delivery  */
		if ((flags & DLVR_ONLINE) && other_rcpt_list) {
			msg_out *other_msgout = clone_msg_out(msgout);
			other_msgout->rcpt_list = other_rcpt_list;
			other_msgout_list = g_list_append(other_msgout_list, other_msgout);
		}
	}

	if (alias_table)
		destroy_table(alias_table);

	/* actual delivery */

	if (local_msgout_list) {
		DEBUG(5) debugf("local_msgout_list\n");
		foreach(local_msgout_list, msgout_node) {
			msg_out *msgout = (msg_out *) (msgout_node->data);
			if (!deliver_local(msgout))
				ok = FALSE;
		}
		destroy_msg_out_list(local_msgout_list);
	}

	if (localnet_msgout_list) {
		GList *route_list = NULL;
		GList *route_node;

		DEBUG(5) debugf("localnet_msgout_list\n");
		if (conf.local_net_routes)
			route_list = read_route_list(conf.local_net_routes, TRUE);
		else
			route_list = g_list_append(NULL, create_local_route());

		foreach(route_list, route_node) {
			connect_route *route = (connect_route *) (route_node->data);
			if (!deliver_route_msg_list(route, localnet_msgout_list))
				ok = FALSE;
		}
		destroy_msg_out_list(localnet_msgout_list);
		destroy_route_list(route_list);
	}

	if (other_msgout_list) {
		DEBUG(5) debugf("other_msgout_list\n");
		if (!deliver_msgout_list_online(other_msgout_list))
			ok = FALSE;
		destroy_msg_out_list(other_msgout_list);
	}

	foreach(msgout_list, msgout_node) {
		msg_out *msgout = (msg_out *) (msgout_node->data);
		if (spool_unlock(msgout->msg->uid)) {
			DEBUG(5) debugf("spool_unlock(%s)\n", msgout->msg->uid);
		} else {
			DEBUG(5) debugf("spool_unlock(%s) failed.\n", msgout->msg->uid);
		}

	}

	destroy_msg_out_list(msgout_list);

	return ok;
}

/* This function searches in the list of rcpt addresses
   for local and 'local net' addresses. Remote addresses
   which are reachable only when online are treated specially
   in another function.

   deliver() is called when a message has just been received and should
   be delivered immediately.
*/
gboolean
deliver(message * msg)
{
	gboolean ok;
	GList *msg_list = g_list_append(NULL, msg);

	ok = deliver_msg_list(msg_list, DLVR_ALL);
	g_list_free(msg_list);

	return ok;
}