summaryrefslogtreecommitdiffstats
path: root/src/global/post_mail.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/global/post_mail.c571
1 files changed, 571 insertions, 0 deletions
diff --git a/src/global/post_mail.c b/src/global/post_mail.c
new file mode 100644
index 0000000..e7a9a67
--- /dev/null
+++ b/src/global/post_mail.c
@@ -0,0 +1,571 @@
+/*++
+/* NAME
+/* post_mail 3
+/* SUMMARY
+/* convenient mail posting interface
+/* SYNOPSIS
+/* #include <post_mail.h>
+/*
+/* VSTREAM *post_mail_fopen(sender, recipient, source_class, trace_flags,
+/* utf8_flags, queue_id)
+/* const char *sender;
+/* const char *recipient;
+/* int source_class;
+/* int trace_flags;
+/* int utf8_flags;
+/* VSTRING *queue_id;
+/*
+/* VSTREAM *post_mail_fopen_nowait(sender, recipient, source_class,
+/* trace_flags, utf8_flags, queue_id)
+/* const char *sender;
+/* const char *recipient;
+/* int source_class;
+/* int trace_flags;
+/* int utf8_flags;
+/* VSTRING *queue_id;
+/*
+/* void post_mail_fopen_async(sender, recipient, source_class,
+/* trace_flags, utf8_flags,
+/* queue_id, notify, context)
+/* const char *sender;
+/* const char *recipient;
+/* int source_class;
+/* int trace_flags;
+/* int utf8_flags;
+/* VSTRING *queue_id;
+/* void (*notify)(VSTREAM *stream, void *context);
+/* void *context;
+/*
+/* int post_mail_fprintf(stream, format, ...)
+/* VSTREAM *stream;
+/* const char *format;
+/*
+/* int post_mail_fputs(stream, str)
+/* VSTREAM *stream;
+/* const char *str;
+/*
+/* int post_mail_buffer(stream, buf, len)
+/* VSTREAM *stream;
+/* const char *buffer;
+/*
+/* int POST_MAIL_BUFFER(stream, buf)
+/* VSTREAM *stream;
+/* VSTRING *buffer;
+/*
+/* int post_mail_fclose(stream)
+/* VSTREAM *STREAM;
+/*
+/* void post_mail_fclose_async(stream, notify, context)
+/* VSTREAM *stream;
+/* void (*notify)(int status, void *context);
+/* void *context;
+/* DESCRIPTION
+/* This module provides a convenient interface for the most
+/* common case of sending one message to one recipient. It
+/* allows the application to concentrate on message content,
+/* without having to worry about queue file structure details.
+/*
+/* post_mail_fopen() opens a connection to the cleanup service
+/* and waits until the service is available, does some option
+/* negotiation, generates message envelope records, and generates
+/* Received: and Date: message headers. The result is a stream
+/* handle that can be used for sending message records.
+/*
+/* post_mail_fopen_nowait() tries to contact the cleanup service
+/* only once, and does not wait until the cleanup service is
+/* available. Otherwise it is identical to post_mail_fopen().
+/*
+/* post_mail_fopen_async() contacts the cleanup service and
+/* invokes the caller-specified notify routine, with the
+/* open stream and the caller-specified context when the
+/* service responds, or with a null stream and the caller-specified
+/* context when the request could not be completed. It is the
+/* responsibility of the application to close an open stream.
+/*
+/* post_mail_fprintf() formats message content (header or body)
+/* and sends it to the cleanup service.
+/*
+/* post_mail_fputs() sends pre-formatted content (header or body)
+/* to the cleanup service.
+/*
+/* post_mail_buffer() sends a pre-formatted buffer to the
+/* cleanup service.
+/*
+/* POST_MAIL_BUFFER() is a wrapper for post_mail_buffer() that
+/* evaluates its buffer argument more than once.
+/*
+/* post_mail_fclose() completes the posting of a message.
+/*
+/* post_mail_fclose_async() completes the posting of a message
+/* and upon completion invokes the caller-specified notify
+/* routine, with the cleanup status and caller-specified context
+/* as arguments.
+/*
+/* Arguments:
+/* .IP sender
+/* The sender envelope address. It is up to the application
+/* to produce From: headers.
+/* .IP recipient
+/* The recipient envelope address. It is up to the application
+/* to produce To: headers.
+/* .IP source_class
+/* The message source class, as defined in \fB<mail_proto.h>\fR.
+/* Depending on the setting of the internal_mail_source_classes
+/* and smtputf8_autodetect_classes parameters, the message
+/* will or won't be subject to content inspection or SMTPUTF8
+/* autodetection.
+/* .IP trace_flags
+/* Message tracing flags as specified in \fB<deliver_request.h>\fR.
+/* .IP utf8_flags
+/* Flags defined in <smtputf8.h>. Flags other than
+/* SMTPUTF8_FLAG_REQUESTED are ignored.
+/* .IP queue_id
+/* Null pointer, or pointer to buffer that receives the queue
+/* ID of the new message.
+/* .IP stream
+/* A stream opened by mail_post_fopen().
+/* .IP notify
+/* Application call-back routine.
+/* .IP context
+/* Application call-back context.
+/* DIAGNOSTICS
+/* post_mail_fopen_nowait() returns a null pointer when the
+/* cleanup service is not available immediately.
+/*
+/* post_mail_fopen_async() returns a null pointer when the
+/* attempt to contact the cleanup service fails immediately.
+/*
+/* post_mail_fprintf(), post_mail_fputs() post_mail_fclose(),
+/* and post_mail_buffer() return the binary OR of the error
+/* status codes defined in \fI<cleanup_user.h>\fR.
+/*
+/* Fatal errors: cleanup initial handshake errors. This means
+/* the client and server speak incompatible protocols.
+/* SEE ALSO
+/* cleanup_user(3h) cleanup options and results
+/* cleanup_strerror(3) translate results to text
+/* cleanup(8) cleanup service
+/* 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
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <sys/time.h>
+#include <stdlib.h> /* 44BSD stdarg.h uses abort() */
+#include <stdarg.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstream.h>
+#include <vstring.h>
+#include <mymalloc.h>
+#include <events.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <record.h>
+#include <rec_type.h>
+#include <mail_proto.h>
+#include <cleanup_user.h>
+#include <post_mail.h>
+#include <mail_date.h>
+
+ /*
+ * Call-back state for asynchronous connection requests.
+ */
+typedef struct {
+ char *sender;
+ char *recipient;
+ int source_class;
+ int trace_flags;
+ int utf8_flags;
+ POST_MAIL_NOTIFY notify;
+ void *context;
+ VSTREAM *stream;
+ VSTRING *queue_id;
+} POST_MAIL_STATE;
+
+ /*
+ * Call-back state for asynchronous close requests.
+ */
+typedef struct {
+ int status;
+ VSTREAM *stream;
+ POST_MAIL_FCLOSE_NOTIFY notify;
+ void *context;
+} POST_MAIL_FCLOSE_STATE;
+
+/* post_mail_init - initial negotiations */
+
+static void post_mail_init(VSTREAM *stream, const char *sender,
+ const char *recipient,
+ int source_class, int trace_flags,
+ int utf8_flags, VSTRING *queue_id)
+{
+ VSTRING *id = queue_id ? queue_id : vstring_alloc(100);
+ struct timeval now;
+ const char *date;
+ int cleanup_flags =
+ int_filt_flags(source_class) | CLEANUP_FLAG_MASK_INTERNAL
+ | smtputf8_autodetect(source_class)
+ | ((utf8_flags & SMTPUTF8_FLAG_REQUESTED) ? CLEANUP_FLAG_SMTPUTF8 : 0);
+
+ GETTIMEOFDAY(&now);
+ date = mail_date(now.tv_sec);
+
+ /*
+ * The comment in the next paragraph is likely obsolete. Fix 20030610
+ * changed the verify server to use asynchronous submission of mail
+ * probes, to avoid blocking the post_mail client for in_flow_delay
+ * seconds when the cleanup service receives email messages faster than
+ * they are delivered. Instead, the post_mail client waits until the
+ * cleanup server announces its availability to receive input. A similar
+ * change was made at the end of submission, to avoid blocking the
+ * post_mail client for up to trigger_timeout seconds when the cleanup
+ * server attempts to notify a queue manager that is overwhelmed.
+ *
+ * XXX Don't flush buffers while sending the initial message records. That
+ * would cause deadlock between verify(8) and cleanup(8) servers.
+ */
+ vstream_control(stream, VSTREAM_CTL_BUFSIZE, 2 * VSTREAM_BUFSIZE,
+ VSTREAM_CTL_END);
+
+ /*
+ * Negotiate with the cleanup service. Give up if we can't agree.
+ */
+ if (attr_scan(stream, ATTR_FLAG_STRICT,
+ RECV_ATTR_STREQ(MAIL_ATTR_PROTO, MAIL_ATTR_PROTO_CLEANUP),
+ RECV_ATTR_STR(MAIL_ATTR_QUEUEID, id),
+ ATTR_TYPE_END) != 1
+ || attr_print(stream, ATTR_FLAG_NONE,
+ SEND_ATTR_INT(MAIL_ATTR_FLAGS, cleanup_flags),
+ ATTR_TYPE_END) != 0)
+ msg_fatal("unable to contact the %s service", var_cleanup_service);
+
+ /*
+ * Generate a minimal envelope section. The cleanup service will add a
+ * size record.
+ */
+ rec_fprintf(stream, REC_TYPE_TIME, REC_TYPE_TIME_FORMAT,
+ REC_TYPE_TIME_ARG(now));
+ rec_fprintf(stream, REC_TYPE_ATTR, "%s=%s",
+ MAIL_ATTR_LOG_ORIGIN, MAIL_ATTR_ORG_LOCAL);
+ rec_fprintf(stream, REC_TYPE_ATTR, "%s=%d",
+ MAIL_ATTR_TRACE_FLAGS, trace_flags);
+ rec_fputs(stream, REC_TYPE_FROM, sender);
+ rec_fputs(stream, REC_TYPE_RCPT, recipient);
+ rec_fputs(stream, REC_TYPE_MESG, "");
+
+ /*
+ * Do the Received: and Date: header lines. This allows us to shave a few
+ * cycles by using the expensive date conversion result for both.
+ */
+ post_mail_fprintf(stream, "Received: by %s (%s)",
+ var_myhostname, var_mail_name);
+ post_mail_fprintf(stream, "\tid %s; %s", vstring_str(id), date);
+ post_mail_fprintf(stream, "Date: %s", date);
+ if (queue_id == 0)
+ vstring_free(id);
+}
+
+/* post_mail_fopen - prepare for posting a message */
+
+VSTREAM *post_mail_fopen(const char *sender, const char *recipient,
+ int source_class, int trace_flags,
+ int utf8_flags, VSTRING *queue_id)
+{
+ VSTREAM *stream;
+
+ stream = mail_connect_wait(MAIL_CLASS_PUBLIC, var_cleanup_service);
+ post_mail_init(stream, sender, recipient, source_class, trace_flags,
+ utf8_flags, queue_id);
+ return (stream);
+}
+
+/* post_mail_fopen_nowait - prepare for posting a message */
+
+VSTREAM *post_mail_fopen_nowait(const char *sender, const char *recipient,
+ int source_class, int trace_flags,
+ int utf8_flags, VSTRING *queue_id)
+{
+ VSTREAM *stream;
+
+ if ((stream = mail_connect(MAIL_CLASS_PUBLIC, var_cleanup_service,
+ BLOCKING)) != 0)
+ post_mail_init(stream, sender, recipient, source_class, trace_flags,
+ utf8_flags, queue_id);
+ else
+ msg_warn("connect to %s/%s: %m",
+ MAIL_CLASS_PUBLIC, var_cleanup_service);
+ return (stream);
+}
+
+/* post_mail_open_event - handle asynchronous connection events */
+
+static void post_mail_open_event(int event, void *context)
+{
+ POST_MAIL_STATE *state = (POST_MAIL_STATE *) context;
+ const char *myname = "post_mail_open_event";
+
+ switch (event) {
+
+ /*
+ * Initial server reply. Stop the watchdog timer, disable further
+ * read events that end up calling this function, and notify the
+ * requestor.
+ */
+ case EVENT_READ:
+ if (msg_verbose)
+ msg_info("%s: read event", myname);
+ event_cancel_timer(post_mail_open_event, context);
+ event_disable_readwrite(vstream_fileno(state->stream));
+ non_blocking(vstream_fileno(state->stream), BLOCKING);
+ post_mail_init(state->stream, state->sender,
+ state->recipient, state->source_class,
+ state->trace_flags, state->utf8_flags,
+ state->queue_id);
+ myfree(state->sender);
+ myfree(state->recipient);
+ state->notify(state->stream, state->context);
+ myfree((void *) state);
+ return;
+
+ /*
+ * No connection or no initial reply within a conservative time
+ * limit. The system is broken and we give up.
+ */
+ case EVENT_TIME:
+ if (state->stream) {
+ msg_warn("timeout connecting to service: %s", var_cleanup_service);
+ event_disable_readwrite(vstream_fileno(state->stream));
+ vstream_fclose(state->stream);
+ } else {
+ msg_warn("connect to service: %s: %m", var_cleanup_service);
+ }
+ myfree(state->sender);
+ myfree(state->recipient);
+ state->notify((VSTREAM *) 0, state->context);
+ myfree((void *) state);
+ return;
+
+ /*
+ * Some exception.
+ */
+ case EVENT_XCPT:
+ msg_warn("error connecting to service: %s", var_cleanup_service);
+ event_cancel_timer(post_mail_open_event, context);
+ event_disable_readwrite(vstream_fileno(state->stream));
+ vstream_fclose(state->stream);
+ myfree(state->sender);
+ myfree(state->recipient);
+ state->notify((VSTREAM *) 0, state->context);
+ myfree((void *) state);
+ return;
+
+ /*
+ * Broken software or hardware.
+ */
+ default:
+ msg_panic("%s: unknown event type %d", myname, event);
+ }
+}
+
+/* post_mail_fopen_async - prepare for posting a message */
+
+void post_mail_fopen_async(const char *sender, const char *recipient,
+ int source_class, int trace_flags,
+ int utf8_flags, VSTRING *queue_id,
+ void (*notify) (VSTREAM *, void *),
+ void *context)
+{
+ VSTREAM *stream;
+ POST_MAIL_STATE *state;
+
+ stream = mail_connect(MAIL_CLASS_PUBLIC, var_cleanup_service, NON_BLOCKING);
+ state = (POST_MAIL_STATE *) mymalloc(sizeof(*state));
+ state->sender = mystrdup(sender);
+ state->recipient = mystrdup(recipient);
+ state->source_class = source_class;
+ state->trace_flags = trace_flags;
+ state->utf8_flags = utf8_flags;
+ state->notify = notify;
+ state->context = context;
+ state->stream = stream;
+ state->queue_id = queue_id;
+
+ /*
+ * To keep interfaces as simple as possible we report all errors via the
+ * same interface as all successes.
+ */
+ if (stream != 0) {
+ event_enable_read(vstream_fileno(stream), post_mail_open_event,
+ (void *) state);
+ event_request_timer(post_mail_open_event, (void *) state,
+ var_daemon_timeout);
+ } else {
+ event_request_timer(post_mail_open_event, (void *) state, 0);
+ }
+}
+
+/* post_mail_fprintf - format and send message content */
+
+int post_mail_fprintf(VSTREAM *cleanup, const char *format,...)
+{
+ int status;
+ va_list ap;
+
+ va_start(ap, format);
+ status = rec_vfprintf(cleanup, REC_TYPE_NORM, format, ap);
+ va_end(ap);
+ return (status != REC_TYPE_NORM ? CLEANUP_STAT_WRITE : 0);
+}
+
+/* post_mail_buffer - send pre-formatted buffer */
+
+int post_mail_buffer(VSTREAM *cleanup, const char *buf, int len)
+{
+ return (rec_put(cleanup, REC_TYPE_NORM, buf, len) != REC_TYPE_NORM ?
+ CLEANUP_STAT_WRITE : 0);
+}
+
+/* post_mail_fputs - send pre-formatted message content */
+
+int post_mail_fputs(VSTREAM *cleanup, const char *str)
+{
+ ssize_t len = str ? strlen(str) : 0;
+
+ return (rec_put(cleanup, REC_TYPE_NORM, str, len) != REC_TYPE_NORM ?
+ CLEANUP_STAT_WRITE : 0);
+}
+
+/* post_mail_fclose - finish posting of message */
+
+int post_mail_fclose(VSTREAM *cleanup)
+{
+ int status = 0;
+
+ /*
+ * Send the message end marker only when there were no errors.
+ */
+ if (vstream_ferror(cleanup) != 0) {
+ status = CLEANUP_STAT_WRITE;
+ } else {
+ rec_fputs(cleanup, REC_TYPE_XTRA, "");
+ rec_fputs(cleanup, REC_TYPE_END, "");
+ if (vstream_fflush(cleanup)
+ || attr_scan(cleanup, ATTR_FLAG_MISSING,
+ RECV_ATTR_INT(MAIL_ATTR_STATUS, &status),
+ ATTR_TYPE_END) != 1)
+ status = CLEANUP_STAT_WRITE;
+ }
+ (void) vstream_fclose(cleanup);
+ return (status);
+}
+
+/* post_mail_fclose_event - event handler */
+
+static void post_mail_fclose_event(int event, void *context)
+{
+ POST_MAIL_FCLOSE_STATE *state = (POST_MAIL_FCLOSE_STATE *) context;
+ int status = state->status;
+
+ switch (event) {
+
+ /*
+ * Final server reply. Pick up the completion status.
+ */
+ case EVENT_READ:
+ if (status == 0) {
+ if (vstream_ferror(state->stream) != 0
+ || attr_scan(state->stream, ATTR_FLAG_MISSING,
+ ATTR_TYPE_INT, MAIL_ATTR_STATUS, &status,
+ ATTR_TYPE_END) != 1)
+ status = CLEANUP_STAT_WRITE;
+ }
+ break;
+
+ /*
+ * No response or error.
+ */
+ default:
+ msg_warn("error talking to service: %s", var_cleanup_service);
+ status = CLEANUP_STAT_WRITE;
+ break;
+ }
+
+ /*
+ * Stop the watchdog timer, and disable further read events that end up
+ * calling this function.
+ */
+ event_cancel_timer(post_mail_fclose_event, context);
+ event_disable_readwrite(vstream_fileno(state->stream));
+
+ /*
+ * Notify the requestor and clean up.
+ */
+ state->notify(status, state->context);
+ (void) vstream_fclose(state->stream);
+ myfree((void *) state);
+}
+
+/* post_mail_fclose_async - finish posting of message */
+
+void post_mail_fclose_async(VSTREAM *stream,
+ void (*notify) (int status, void *context),
+ void *context)
+{
+ POST_MAIL_FCLOSE_STATE *state;
+ int status = 0;
+
+
+ /*
+ * Send the message end marker only when there were no errors.
+ */
+ if (vstream_ferror(stream) != 0) {
+ status = CLEANUP_STAT_WRITE;
+ } else {
+ rec_fputs(stream, REC_TYPE_XTRA, "");
+ rec_fputs(stream, REC_TYPE_END, "");
+ if (vstream_fflush(stream))
+ status = CLEANUP_STAT_WRITE;
+ }
+
+ /*
+ * Bundle up the suspended state.
+ */
+ state = (POST_MAIL_FCLOSE_STATE *) mymalloc(sizeof(*state));
+ state->status = status;
+ state->stream = stream;
+ state->notify = notify;
+ state->context = context;
+
+ /*
+ * To keep interfaces as simple as possible we report all errors via the
+ * same interface as all successes.
+ */
+ if (status == 0) {
+ event_enable_read(vstream_fileno(stream), post_mail_fclose_event,
+ (void *) state);
+ event_request_timer(post_mail_fclose_event, (void *) state,
+ var_daemon_timeout);
+ } else {
+ event_request_timer(post_mail_fclose_event, (void *) state, 0);
+ }
+}