1/*	$NetBSD$	*/
2
3/*++
4/* NAME
5/*	post_mail 3
6/* SUMMARY
7/*	convenient mail posting interface
8/* SYNOPSIS
9/*	#include <post_mail.h>
10/*
11/*	VSTREAM	*post_mail_fopen(sender, recipient, filter_class, trace_flags,
12/*		queue_id)
13/*	const char *sender;
14/*	const char *recipient;
15/*	int	filter_class;
16/*	int	trace_flags;
17/*	VSTRING *queue_id;
18/*
19/*	VSTREAM	*post_mail_fopen_nowait(sender, recipient,
20/*					filter_class, trace_flags, queue_id)
21/*	const char *sender;
22/*	const char *recipient;
23/*	int	filter_class;
24/*	int	trace_flags;
25/*	VSTRING *queue_id;
26/*
27/*	void	post_mail_fopen_async(sender, recipient,
28/*					filter_class, trace_flags,
29/*					queue_id, notify, context)
30/*	const char *sender;
31/*	const char *recipient;
32/*	int	filter_class;
33/*	int	trace_flags;
34/*	VSTRING *queue_id;
35/*	void	(*notify)(VSTREAM *stream, char *context);
36/*	char	*context;
37/*
38/*	int	post_mail_fprintf(stream, format, ...)
39/*	VSTREAM	*stream;
40/*	const char *format;
41/*
42/*	int	post_mail_fputs(stream, str)
43/*	VSTREAM	*stream;
44/*	const char *str;
45/*
46/*	int	post_mail_buffer(stream, buf, len)
47/*	VSTREAM	*stream;
48/*	const char *buffer;
49/*
50/*	int	POST_MAIL_BUFFER(stream, buf)
51/*	VSTREAM	*stream;
52/*	VSTRING	*buffer;
53/*
54/*	int	post_mail_fclose(stream)
55/*	VSTREAM	*STREAM;
56/* DESCRIPTION
57/*	This module provides a convenient interface for the most
58/*	common case of sending one message to one recipient. It
59/*	allows the application to concentrate on message content,
60/*	without having to worry about queue file structure details.
61/*
62/*	post_mail_fopen() opens a connection to the cleanup service
63/*	and waits until the service is available, does some option
64/*	negotiation, generates message envelope records, and generates
65/*	Received: and Date: message headers.  The result is a stream
66/*	handle that can be used for sending message records.
67/*
68/*	post_mail_fopen_nowait() tries to contact the cleanup service
69/*	only once, and does not wait until the cleanup service is
70/*	available.  Otherwise it is identical to post_mail_fopen().
71/*
72/*	post_mail_fopen_async() contacts the cleanup service and
73/*	invokes the caller-specified notify routine, with the
74/*	open stream and the caller-specified context when the
75/*	service responds, or with a null stream and the caller-specified
76/*	context when the request could not be completed. It is the
77/*	responsability of the application to close an open stream.
78/*
79/*	post_mail_fprintf() formats message content (header or body)
80/*	and sends it to the cleanup service.
81/*
82/*	post_mail_fputs() sends pre-formatted content (header or body)
83/*	to the cleanup service.
84/*
85/*	post_mail_buffer() sends a pre-formatted buffer to the
86/*	cleanup service.
87/*
88/*	POST_MAIL_BUFFER() is a wrapper for post_mail_buffer() that
89/*	evaluates its buffer argument more than once.
90/*
91/*	post_mail_fclose() completes the posting of a message.
92/*
93/*	Arguments:
94/* .IP sender
95/*	The sender envelope address. It is up to the application
96/*	to produce From: headers.
97/* .IP recipient
98/*	The recipient envelope address. It is up to the application
99/*	to produce To: headers.
100/* .IP filter_class
101/*	The internal mail filtering class, as defined in
102/*	\fB<int_filt.h>\fR.  Depending on the setting of the
103/*	internal_mail_filter_classes parameter the message will or
104/*	won't be subject to content inspection.
105/* .IP trace_flags
106/*	Message tracing flags as specified in \fB<deliver_request.h>\fR.
107/* .IP queue_id
108/*	Null pointer, or pointer to buffer that receives the queue
109/*	ID of the new message.
110/* .IP stream
111/*	A stream opened by mail_post_fopen().
112/* .IP notify
113/*	Application call-back routine.
114/* .IP context
115/*	Application call-back context.
116/* DIAGNOSTICS
117/*	post_mail_fopen_nowait() returns a null pointer when the
118/*	cleanup service is not available immediately.
119/*
120/*	post_mail_fopen_async() returns a null pointer when the
121/*	attempt to contact the cleanup service fails immediately.
122/*
123/*	post_mail_fprintf(), post_mail_fputs() post_mail_fclose(),
124/*	and post_mail_buffer() return the binary OR of the error
125/*	status codes defined in \fI<cleanup_user.h>\fR.
126/*
127/*	Fatal errors: cleanup initial handshake errors. This means
128/*	the client and server speak incompatible protocols.
129/* SEE ALSO
130/*	cleanup_user(3h) cleanup options and results
131/*	cleanup_strerror(3) translate results to text
132/*	cleanup(8) cleanup service
133/* LICENSE
134/* .ad
135/* .fi
136/*	The Secure Mailer license must be distributed with this software.
137/* AUTHOR(S)
138/*	Wietse Venema
139/*	IBM T.J. Watson Research
140/*	P.O. Box 704
141/*	Yorktown Heights, NY 10598, USA
142/*--*/
143
144/* System library. */
145
146#include <sys_defs.h>
147#include <sys/time.h>
148#include <stdlib.h>			/* 44BSD stdarg.h uses abort() */
149#include <stdarg.h>
150#include <string.h>
151
152/* Utility library. */
153
154#include <msg.h>
155#include <vstream.h>
156#include <vstring.h>
157#include <mymalloc.h>
158#include <events.h>
159
160/* Global library. */
161
162#include <mail_params.h>
163#include <record.h>
164#include <rec_type.h>
165#include <mail_proto.h>
166#include <cleanup_user.h>
167#include <post_mail.h>
168#include <mail_date.h>
169
170 /*
171  * Call-back state for asynchronous connection requests.
172  */
173typedef struct {
174    char   *sender;
175    char   *recipient;
176    int     filter_class;
177    int     trace_flags;
178    POST_MAIL_NOTIFY notify;
179    void   *context;
180    VSTREAM *stream;
181    VSTRING *queue_id;
182} POST_MAIL_STATE;
183
184/* post_mail_init - initial negotiations */
185
186static void post_mail_init(VSTREAM *stream, const char *sender,
187			           const char *recipient,
188			           int filter_class, int trace_flags,
189			           VSTRING *queue_id)
190{
191    VSTRING *id = queue_id ? queue_id : vstring_alloc(100);
192    struct timeval now;
193    const char *date;
194    int cleanup_flags =
195	int_filt_flags(filter_class) | CLEANUP_FLAG_MASK_INTERNAL;
196
197    GETTIMEOFDAY(&now);
198    date = mail_date(now.tv_sec);
199
200    /*
201     * Negotiate with the cleanup service. Give up if we can't agree.
202     */
203    if (attr_scan(stream, ATTR_FLAG_STRICT,
204		  ATTR_TYPE_STR, MAIL_ATTR_QUEUEID, id,
205		  ATTR_TYPE_END) != 1
206	|| attr_print(stream, ATTR_FLAG_NONE,
207		      ATTR_TYPE_INT, MAIL_ATTR_FLAGS, cleanup_flags,
208		      ATTR_TYPE_END) != 0)
209	msg_fatal("unable to contact the %s service", var_cleanup_service);
210
211    /*
212     * Generate a minimal envelope section. The cleanup service will add a
213     * size record.
214     */
215    rec_fprintf(stream, REC_TYPE_TIME, REC_TYPE_TIME_FORMAT,
216		REC_TYPE_TIME_ARG(now));
217    rec_fprintf(stream, REC_TYPE_ATTR, "%s=%s",
218		MAIL_ATTR_LOG_ORIGIN, MAIL_ATTR_ORG_LOCAL);
219    rec_fprintf(stream, REC_TYPE_ATTR, "%s=%d",
220		MAIL_ATTR_TRACE_FLAGS, trace_flags);
221    rec_fputs(stream, REC_TYPE_FROM, sender);
222    rec_fputs(stream, REC_TYPE_RCPT, recipient);
223    rec_fputs(stream, REC_TYPE_MESG, "");
224
225    /*
226     * Do the Received: and Date: header lines. This allows us to shave a few
227     * cycles by using the expensive date conversion result for both.
228     */
229    post_mail_fprintf(stream, "Received: by %s (%s)",
230		      var_myhostname, var_mail_name);
231    post_mail_fprintf(stream, "\tid %s; %s", vstring_str(id), date);
232    post_mail_fprintf(stream, "Date: %s", date);
233    if (queue_id == 0)
234	vstring_free(id);
235}
236
237/* post_mail_fopen - prepare for posting a message */
238
239VSTREAM *post_mail_fopen(const char *sender, const char *recipient,
240			         int filter_class, int trace_flags,
241			         VSTRING *queue_id)
242{
243    VSTREAM *stream;
244
245    stream = mail_connect_wait(MAIL_CLASS_PUBLIC, var_cleanup_service);
246    post_mail_init(stream, sender, recipient, filter_class, trace_flags,
247		   queue_id);
248    return (stream);
249}
250
251/* post_mail_fopen_nowait - prepare for posting a message */
252
253VSTREAM *post_mail_fopen_nowait(const char *sender, const char *recipient,
254				        int filter_class, int trace_flags,
255				        VSTRING *queue_id)
256{
257    VSTREAM *stream;
258
259    if ((stream = mail_connect(MAIL_CLASS_PUBLIC, var_cleanup_service,
260			       BLOCKING)) != 0)
261	post_mail_init(stream, sender, recipient, filter_class, trace_flags,
262		       queue_id);
263    return (stream);
264}
265
266/* post_mail_open_event - handle asynchronous connection events */
267
268static void post_mail_open_event(int event, char *context)
269{
270    POST_MAIL_STATE *state = (POST_MAIL_STATE *) context;
271    const char *myname = "post_mail_open_event";
272
273    switch (event) {
274
275	/*
276	 * Initial server reply. Stop the watchdog timer, disable further
277	 * read events that end up calling this function, and notify the
278	 * requestor.
279	 */
280    case EVENT_READ:
281	if (msg_verbose)
282	    msg_info("%s: read event", myname);
283	event_cancel_timer(post_mail_open_event, context);
284	event_disable_readwrite(vstream_fileno(state->stream));
285	non_blocking(vstream_fileno(state->stream), BLOCKING);
286	post_mail_init(state->stream, state->sender,
287		       state->recipient, state->filter_class,
288		       state->trace_flags, state->queue_id);
289	myfree(state->sender);
290	myfree(state->recipient);
291	state->notify(state->stream, state->context);
292	myfree((char *) state);
293	return;
294
295	/*
296	 * No connection or no initial reply within a conservative time
297	 * limit. The system is broken and we give up.
298	 */
299    case EVENT_TIME:
300	if (state->stream) {
301	    msg_warn("timeout connecting to service: %s", var_cleanup_service);
302	    event_disable_readwrite(vstream_fileno(state->stream));
303	    vstream_fclose(state->stream);
304	} else {
305	    msg_warn("connect to service: %s: %m", var_cleanup_service);
306	}
307	myfree(state->sender);
308	myfree(state->recipient);
309	state->notify((VSTREAM *) 0, state->context);
310	myfree((char *) state);
311	return;
312
313	/*
314	 * Some exception.
315	 */
316    case EVENT_XCPT:
317	msg_warn("error connecting to service: %s", var_cleanup_service);
318	event_cancel_timer(post_mail_open_event, context);
319	event_disable_readwrite(vstream_fileno(state->stream));
320	vstream_fclose(state->stream);
321	myfree(state->sender);
322	myfree(state->recipient);
323	state->notify((VSTREAM *) 0, state->context);
324	myfree((char *) state);
325	return;
326
327	/*
328	 * Broken software or hardware.
329	 */
330    default:
331	msg_panic("%s: unknown event type %d", myname, event);
332    }
333}
334
335/* post_mail_fopen_async - prepare for posting a message */
336
337void    post_mail_fopen_async(const char *sender, const char *recipient,
338			              int filter_class, int trace_flags,
339			              VSTRING *queue_id,
340			              void (*notify) (VSTREAM *, void *),
341			              void *context)
342{
343    VSTREAM *stream;
344    POST_MAIL_STATE *state;
345
346    stream = mail_connect(MAIL_CLASS_PUBLIC, var_cleanup_service, NON_BLOCKING);
347    state = (POST_MAIL_STATE *) mymalloc(sizeof(*state));
348    state->sender = mystrdup(sender);
349    state->recipient = mystrdup(recipient);
350    state->filter_class = filter_class;
351    state->trace_flags = trace_flags;
352    state->notify = notify;
353    state->context = context;
354    state->stream = stream;
355    state->queue_id = queue_id;
356
357    /*
358     * To keep interfaces as simple as possible we report all errors via the
359     * same interface as all successes.
360     */
361    if (stream != 0) {
362	event_enable_read(vstream_fileno(stream), post_mail_open_event,
363			   (void *) state);
364	event_request_timer(post_mail_open_event, (void *) state,
365			    var_daemon_timeout);
366    } else {
367	event_request_timer(post_mail_open_event, (void *) state, 0);
368    }
369}
370
371/* post_mail_fprintf - format and send message content */
372
373int     post_mail_fprintf(VSTREAM *cleanup, const char *format,...)
374{
375    int     status;
376    va_list ap;
377
378    va_start(ap, format);
379    status = rec_vfprintf(cleanup, REC_TYPE_NORM, format, ap);
380    va_end(ap);
381    return (status != REC_TYPE_NORM ? CLEANUP_STAT_WRITE : 0);
382}
383
384/* post_mail_buffer - send pre-formatted buffer */
385
386int     post_mail_buffer(VSTREAM *cleanup, const char *buf, int len)
387{
388    return (rec_put(cleanup, REC_TYPE_NORM, buf, len) != REC_TYPE_NORM ?
389	    CLEANUP_STAT_WRITE : 0);
390}
391
392/* post_mail_fputs - send pre-formatted message content */
393
394int     post_mail_fputs(VSTREAM *cleanup, const char *str)
395{
396    ssize_t len = str ? strlen(str) : 0;
397
398    return (rec_put(cleanup, REC_TYPE_NORM, str, len) != REC_TYPE_NORM ?
399	    CLEANUP_STAT_WRITE : 0);
400}
401
402/* post_mail_fclose - finish posting of message */
403
404int     post_mail_fclose(VSTREAM *cleanup)
405{
406    int     status = 0;
407
408    /*
409     * Send the message end marker only when there were no errors.
410     */
411    if (vstream_ferror(cleanup) != 0) {
412	status = CLEANUP_STAT_WRITE;
413    } else {
414	rec_fputs(cleanup, REC_TYPE_XTRA, "");
415	rec_fputs(cleanup, REC_TYPE_END, "");
416	if (vstream_fflush(cleanup)
417	    || attr_scan(cleanup, ATTR_FLAG_MISSING,
418			 ATTR_TYPE_INT, MAIL_ATTR_STATUS, &status,
419			 ATTR_TYPE_END) != 1)
420	    status = CLEANUP_STAT_WRITE;
421    }
422    (void) vstream_fclose(cleanup);
423    return (status);
424}
425