xref: /netbsd-src/external/ibm-public/postfix/dist/src/milter/milter.c (revision 8450a7c42673d65e3b1f6560d3b6ecd317a6cbe8)
1 /*	$NetBSD: milter.c,v 1.1.1.4 2015/01/24 18:08:26 tron Exp $	*/
2 
3 /*++
4 /* NAME
5 /*	milter 3
6 /* SUMMARY
7 /*	generic MTA-side mail filter interface
8 /* SYNOPSIS
9 /*	#include <milter.h>
10 /*
11 /*	MILTERS	*milter_create(milter_names, conn_timeout, cmd_timeout,
12 /*					msg_timeout, protocol, def_action,
13 /*					conn_macros, helo_macros,
14 /*					mail_macros, rcpt_macros,
15 /*					data_macros, eoh_macros,
16 /*					eod_macros, unk_macros)
17 /*	const char *milter_names;
18 /*	int	conn_timeout;
19 /*	int	cmd_timeout;
20 /*	int	msg_timeout;
21 /*	const char *protocol;
22 /*	const char *def_action;
23 /*	const char *conn_macros;
24 /*	const char *helo_macros;
25 /*	const char *mail_macros;
26 /*	const char *rcpt_macrps;
27 /*	const char *data_macros;
28 /*	const char *eoh_macros;
29 /*	const char *eod_macros;
30 /*	const char *unk_macros;
31 /*
32 /*	void	milter_free(milters)
33 /*	MILTERS	*milters;
34 /*
35 /*	void	milter_macro_callback(milters, mac_lookup, mac_context)
36 /*	const char *(*mac_lookup)(const char *name, void *context);
37 /*	void	*mac_context;
38 /*
39 /*	void	milter_edit_callback(milters, add_header, upd_header,
40 /*					ins_header, del_header, chg_from,
41 /*					add_rcpt, add_rcpt_par, del_rcpt,
42 /*					repl_body, context)
43 /*	MILTERS	*milters;
44 /*	MILTER_ADD_HEADER_FN add_header;
45 /*	MILTER_EDIT_HEADER_FN upd_header;
46 /*	MILTER_EDIT_HEADER_FN ins_header;
47 /*	MILTER_DEL_HEADER_FN del_header;
48 /*	MILTER_EDIT_FROM_FN chg_from;
49 /*	MILTER_EDIT_RCPT_FN add_rcpt;
50 /*	MILTER_EDIT_RCPT_PAR_FN add_rcpt_par;
51 /*	MILTER_EDIT_RCPT_FN del_rcpt;
52 /*	MILTER_EDIT_BODY_FN repl_body;
53 /*	void	*context;
54 /*
55 /*	const char *milter_conn_event(milters, client_name, client_addr,
56 /*					client_port, addr_family)
57 /*	MILTERS	*milters;
58 /*	const char *client_name;
59 /*	const char *client_addr;
60 /*	const char *client_port;
61 /*	int	addr_family;
62 /*
63 /*	const char *milter_disc_event(milters)
64 /*	MILTERS	*milters;
65 /*
66 /*	const char *milter_helo_event(milters, helo_name, esmtp_flag)
67 /*	MILTERS	*milters;
68 /*	const char *helo_name;
69 /*	int	esmtp_flag;
70 /*
71 /*	const char *milter_mail_event(milters, argv)
72 /*	MILTERS	*milters;
73 /*	const char **argv;
74 /*
75 /*	const char *milter_rcpt_event(milters, flags, argv)
76 /*	MILTERS	*milters;
77 /*	int	flags;
78 /*	const char **argv;
79 /*
80 /*	const char *milter_data_event(milters)
81 /*	MILTERS	*milters;
82 /*
83 /*	const char *milter_unknown_event(milters, command)
84 /*	MILTERS	*milters;
85 /*	const char *command;
86 /*
87 /*	const char *milter_other_event(milters)
88 /*	MILTERS	*milters;
89 /*
90 /*	const char *milter_message(milters, qfile, data_offset, auto_hdrs)
91 /*	MILTERS	*milters;
92 /*	VSTREAM *qfile;
93 /*	off_t	data_offset;
94 /*	ARGV	*auto_hdrs;
95 /*
96 /*	const char *milter_abort(milters)
97 /*	MILTERS	*milters;
98 /*
99 /*	int	milter_send(milters, fp)
100 /*	MILTERS	*milters;
101 /*	VSTREAM *fp;
102 /*
103 /*	MILTERS	*milter_receive(fp, count)
104 /*	VSTREAM	*fp;
105 /*	int	count;
106 /*
107 /*	int	milter_dummy(milters, fp)
108 /*	MILTERS	*milters;
109 /*	VSTREAM *fp;
110 /* DESCRIPTION
111 /*	The functions in this module manage one or more milter (mail
112 /*	filter) clients. Currently, only the Sendmail 8 filter
113 /*	protocol is supported.
114 /*
115 /*	The functions that inspect content or envelope commands
116 /*	return either an SMTP reply ([45]XX followed by enhanced
117 /*	status code and text), "D" (discard), "H" (quarantine),
118 /*	"S" (shutdown connection), or a null pointer, which means
119 /*	"no news is good news".
120 /*
121 /*	milter_create() instantiates the milter clients specified
122 /*	with the milter_names argument.  The conn_macros etc.
123 /*	arguments specify the names of macros that are sent to the
124 /*	mail filter applications upon a connect etc. event. This
125 /*	function should be called during process initialization,
126 /*	before entering a chroot jail. The timeout parameters specify
127 /*	time limits for the completion of the specified request
128 /*	classes. The protocol parameter specifies a protocol version
129 /*	and optional extensions.  When the milter application is
130 /*	unavailable, the milter client will go into a suitable error
131 /*	state as specified with the def_action parameter (i.e.
132 /*	reject, tempfail or accept all subsequent events).
133 /*
134 /*	milter_free() disconnects from the milter instances that
135 /*	are still opened, and destroys the data structures created
136 /*	by milter_create(). This function is safe to call at any
137 /*	point after milter_create().
138 /*
139 /*	milter_macro_callback() specifies a call-back function and
140 /*	context for macro lookup. This function must be called
141 /*	before milter_conn_event().
142 /*
143 /*	milter_edit_callback() specifies call-back functions and
144 /*	context for editing the queue file after the end-of-data
145 /*	is received. This function must be called before milter_message();
146 /*
147 /*	milter_conn_event() reports an SMTP client connection event
148 /*	to the specified milter instances, after sending the macros
149 /*	specified with the milter_create() conn_macros argument.
150 /*	This function must be called before reporting any other
151 /*	events.
152 /*
153 /*	milter_disc_event() reports an SMTP client disconnection
154 /*	event to the specified milter instances. No events can
155 /*	reported after this call. To simplify usage, redundant calls
156 /*	of this function are NO-OPs and don't raise a run-time
157 /*	error.
158 /*
159 /*	milter_helo_event() reports a HELO or EHLO event to the
160 /*	specified milter instances, after sending the macros that
161 /*	were specified with the milter_create() helo_macros argument.
162 /*
163 /*	milter_mail_event() reports a MAIL FROM event to the specified
164 /*	milter instances, after sending the macros that were specified
165 /*	with the milter_create() mail_macros argument.
166 /*
167 /*	milter_rcpt_event() reports an RCPT TO event to the specified
168 /*	milter instances, after sending the macros that were specified
169 /*	with the milter_create() rcpt_macros argument. The flags
170 /*	argument supports the following:
171 /* .IP MILTER_FLAG_WANT_RCPT_REJ
172 /*	When this flag is cleared, invoke all milters.  When this
173 /*	flag is set, invoke only milters that want to receive
174 /*	rejected recipients; with Sendmail V8 Milters, {rcpt_mailer}
175 /*	is set to "error", {rcpt_host} is set to an enhanced status
176 /*	code, and {rcpt_addr} is set to descriptive text.
177 /* .PP
178 /*	milter_data_event() reports a DATA event to the specified
179 /*	milter instances, after sending the macros that were specified
180 /*	with the milter_create() data_macros argument.
181 /*
182 /*	milter_unknown_event() reports an unknown command event to
183 /*	the specified milter instances, after sending the macros
184 /*	that were specified with the milter_create() unk_macros
185 /*	argument.
186 /*
187 /*	milter_other_event() returns the current default mail filter
188 /*	reply for the current SMTP connection state; it does not
189 /*	change milter states. A null pointer result means that all
190 /*	is well. This function can be used for SMTP commands such
191 /*	as AUTH, STARTTLS that don't have their own milter event
192 /*	routine.
193 /*
194 /*	milter_message() sends the message header and body to the
195 /*	to the specified milter instances, and sends the macros
196 /*	specified with the milter_create() eoh_macros after the
197 /*	message header, and with the eod_macros argument at
198 /*	the end.  Each milter sees the result of any changes made
199 /*	by a preceding milter. This function must be called with
200 /*	as argument an open Postfix queue file.
201 /*
202 /*	milter_abort() cancels a mail transaction in progress.  To
203 /*	simplify usage, redundant calls of this function are NO-OPs
204 /*	and don't raise a run-time error.
205 /*
206 /*	milter_send() sends a list of mail filters over the specified
207 /*	stream. When given a null list pointer, a "no filter"
208 /*	indication is sent.  The result is non-zero in case of
209 /*	error.
210 /*
211 /*	milter_receive() receives the specified number of mail
212 /*	filters over the specified stream. The result is a null
213 /*	pointer when no milters were sent, or when an error happened.
214 /*
215 /*	milter_dummy() is like milter_send(), except that it sends
216 /*	a dummy, but entirely valid, mail filter list.
217 /* SEE ALSO
218 /*	milter8(3) Sendmail 8 Milter protocol
219 /* DIAGNOSTICS
220 /*	Panic: interface violation.
221 /*	Fatal errors: memory allocation problem.
222 /* LICENSE
223 /* .ad
224 /* .fi
225 /*	The Secure Mailer license must be distributed with this software.
226 /* AUTHOR(S)
227 /*	Wietse Venema
228 /*	IBM T.J. Watson Research
229 /*	P.O. Box 704
230 /*	Yorktown Heights, NY 10598, USA
231 /*--*/
232 
233 /* System library. */
234 
235 #include <sys_defs.h>
236 
237 /* Utility library. */
238 
239 #include <msg.h>
240 #include <mymalloc.h>
241 #include <stringops.h>
242 #include <argv.h>
243 #include <attr.h>
244 
245 /* Global library. */
246 
247 #include <mail_proto.h>
248 #include <record.h>
249 #include <rec_type.h>
250 
251 /* Postfix Milter library. */
252 
253 #include <milter.h>
254 
255 /* Application-specific. */
256 
257  /*
258   * SLMs.
259   */
260 #define STR(x)	vstring_str(x)
261 
262 /* milter_macro_lookup - look up macros */
263 
264 static ARGV *milter_macro_lookup(MILTERS *milters, const char *macro_names)
265 {
266     const char *myname = "milter_macro_lookup";
267     char   *saved_names = mystrdup(macro_names);
268     char   *cp = saved_names;
269     ARGV   *argv = argv_alloc(10);
270     const char *value;
271     const char *name;
272 
273     while ((name = mystrtok(&cp, ", \t\r\n")) != 0) {
274 	if (msg_verbose)
275 	    msg_info("%s: \"%s\"", myname, name);
276 	if ((value = milters->mac_lookup(name, milters->mac_context)) != 0) {
277 	    if (msg_verbose)
278 		msg_info("%s: result \"%s\"", myname, value);
279 	    argv_add(argv, name, value, (char *) 0);
280 	}
281     }
282     myfree(saved_names);
283     return (argv);
284 }
285 
286 /* milter_macro_callback - specify macro lookup */
287 
288 void    milter_macro_callback(MILTERS *milters,
289 		           const char *(*mac_lookup) (const char *, void *),
290 			              void *mac_context)
291 {
292     milters->mac_lookup = mac_lookup;
293     milters->mac_context = mac_context;
294 }
295 
296 /* milter_edit_callback - specify queue file edit call-back information */
297 
298 void    milter_edit_callback(MILTERS *milters,
299 			             MILTER_ADD_HEADER_FN add_header,
300 			             MILTER_EDIT_HEADER_FN upd_header,
301 			             MILTER_EDIT_HEADER_FN ins_header,
302 			             MILTER_DEL_HEADER_FN del_header,
303 			             MILTER_EDIT_FROM_FN chg_from,
304 			             MILTER_EDIT_RCPT_FN add_rcpt,
305 			             MILTER_EDIT_RCPT_PAR_FN add_rcpt_par,
306 			             MILTER_EDIT_RCPT_FN del_rcpt,
307 			             MILTER_EDIT_BODY_FN repl_body,
308 			             void *chg_context)
309 {
310     milters->add_header = add_header;
311     milters->upd_header = upd_header;
312     milters->ins_header = ins_header;
313     milters->del_header = del_header;
314     milters->chg_from = chg_from;
315     milters->add_rcpt = add_rcpt;
316     milters->add_rcpt_par = add_rcpt_par;
317     milters->del_rcpt = del_rcpt;
318     milters->repl_body = repl_body;
319     milters->chg_context = chg_context;
320 }
321 
322 /* milter_conn_event - report connect event */
323 
324 const char *milter_conn_event(MILTERS *milters,
325 			              const char *client_name,
326 			              const char *client_addr,
327 			              const char *client_port,
328 			              unsigned addr_family)
329 {
330     const char *resp;
331     MILTER *m;
332     ARGV   *global_macros = 0;
333     ARGV   *any_macros;
334 
335 #define MILTER_MACRO_EVAL(global_macros, m, milters, member) \
336 	((m->macros && m->macros->member[0]) ? \
337 	    milter_macro_lookup(milters, m->macros->member) : \
338 		global_macros ? global_macros : \
339 		    (global_macros = \
340 		         milter_macro_lookup(milters, milters->macros->member)))
341 
342     if (msg_verbose)
343 	msg_info("report connect to all milters");
344     for (resp = 0, m = milters->milter_list; resp == 0 && m != 0; m = m->next) {
345 	any_macros = MILTER_MACRO_EVAL(global_macros, m, milters, conn_macros);
346 	resp = m->conn_event(m, client_name, client_addr, client_port,
347 			     addr_family, any_macros);
348 	if (any_macros != global_macros)
349 	    argv_free(any_macros);
350     }
351     if (global_macros)
352 	argv_free(global_macros);
353     return (resp);
354 }
355 
356 /* milter_helo_event - report helo event */
357 
358 const char *milter_helo_event(MILTERS *milters, const char *helo_name,
359 			              int esmtp_flag)
360 {
361     const char *resp;
362     MILTER *m;
363     ARGV   *global_macros = 0;
364     ARGV   *any_macros;
365 
366     if (msg_verbose)
367 	msg_info("report helo to all milters");
368     for (resp = 0, m = milters->milter_list; resp == 0 && m != 0; m = m->next) {
369 	any_macros = MILTER_MACRO_EVAL(global_macros, m, milters, helo_macros);
370 	resp = m->helo_event(m, helo_name, esmtp_flag, any_macros);
371 	if (any_macros != global_macros)
372 	    argv_free(any_macros);
373     }
374     if (global_macros)
375 	argv_free(global_macros);
376     return (resp);
377 }
378 
379 /* milter_mail_event - report mail from event */
380 
381 const char *milter_mail_event(MILTERS *milters, const char **argv)
382 {
383     const char *resp;
384     MILTER *m;
385     ARGV   *global_macros = 0;
386     ARGV   *any_macros;
387 
388     if (msg_verbose)
389 	msg_info("report sender to all milters");
390     for (resp = 0, m = milters->milter_list; resp == 0 && m != 0; m = m->next) {
391 	any_macros = MILTER_MACRO_EVAL(global_macros, m, milters, mail_macros);
392 	resp = m->mail_event(m, argv, any_macros);
393 	if (any_macros != global_macros)
394 	    argv_free(any_macros);
395     }
396     if (global_macros)
397 	argv_free(global_macros);
398     return (resp);
399 }
400 
401 /* milter_rcpt_event - report rcpt to event */
402 
403 const char *milter_rcpt_event(MILTERS *milters, int flags, const char **argv)
404 {
405     const char *resp;
406     MILTER *m;
407     ARGV   *global_macros = 0;
408     ARGV   *any_macros;
409 
410     if (msg_verbose)
411 	msg_info("report recipient to all milters (flags=0x%x)", flags);
412     for (resp = 0, m = milters->milter_list; resp == 0 && m != 0; m = m->next) {
413 	if ((flags & MILTER_FLAG_WANT_RCPT_REJ) == 0
414 	    || (m->flags & MILTER_FLAG_WANT_RCPT_REJ) != 0) {
415 	    any_macros =
416 		MILTER_MACRO_EVAL(global_macros, m, milters, rcpt_macros);
417 	    resp = m->rcpt_event(m, argv, any_macros);
418 	    if (any_macros != global_macros)
419 		argv_free(any_macros);
420 	}
421     }
422     if (global_macros)
423 	argv_free(global_macros);
424     return (resp);
425 }
426 
427 /* milter_data_event - report data event */
428 
429 const char *milter_data_event(MILTERS *milters)
430 {
431     const char *resp;
432     MILTER *m;
433     ARGV   *global_macros = 0;
434     ARGV   *any_macros;
435 
436     if (msg_verbose)
437 	msg_info("report data to all milters");
438     for (resp = 0, m = milters->milter_list; resp == 0 && m != 0; m = m->next) {
439 	any_macros = MILTER_MACRO_EVAL(global_macros, m, milters, data_macros);
440 	resp = m->data_event(m, any_macros);
441 	if (any_macros != global_macros)
442 	    argv_free(any_macros);
443     }
444     if (global_macros)
445 	argv_free(global_macros);
446     return (resp);
447 }
448 
449 /* milter_unknown_event - report unknown command */
450 
451 const char *milter_unknown_event(MILTERS *milters, const char *command)
452 {
453     const char *resp;
454     MILTER *m;
455     ARGV   *global_macros = 0;
456     ARGV   *any_macros;
457 
458     if (msg_verbose)
459 	msg_info("report unknown command to all milters");
460     for (resp = 0, m = milters->milter_list; resp == 0 && m != 0; m = m->next) {
461 	any_macros = MILTER_MACRO_EVAL(global_macros, m, milters, unk_macros);
462 	resp = m->unknown_event(m, command, any_macros);
463 	if (any_macros != global_macros)
464 	    argv_free(any_macros);
465     }
466     if (global_macros)
467 	argv_free(global_macros);
468     return (resp);
469 }
470 
471 /* milter_other_event - other SMTP event */
472 
473 const char *milter_other_event(MILTERS *milters)
474 {
475     const char *resp;
476     MILTER *m;
477 
478     if (msg_verbose)
479 	msg_info("query milter states for other event");
480     for (resp = 0, m = milters->milter_list; resp == 0 && m != 0; m = m->next)
481 	resp = m->other_event(m);
482     return (resp);
483 }
484 
485 /* milter_message - inspect message content */
486 
487 const char *milter_message(MILTERS *milters, VSTREAM *fp, off_t data_offset,
488 			           ARGV *auto_hdrs)
489 {
490     const char *resp;
491     MILTER *m;
492     ARGV   *global_eoh_macros = 0;
493     ARGV   *global_eod_macros = 0;
494     ARGV   *any_eoh_macros;
495     ARGV   *any_eod_macros;
496 
497     if (msg_verbose)
498 	msg_info("inspect content by all milters");
499     for (resp = 0, m = milters->milter_list; resp == 0 && m != 0; m = m->next) {
500 	any_eoh_macros = MILTER_MACRO_EVAL(global_eoh_macros, m, milters, eoh_macros);
501 	any_eod_macros = MILTER_MACRO_EVAL(global_eod_macros, m, milters, eod_macros);
502 	resp = m->message(m, fp, data_offset, any_eoh_macros, any_eod_macros,
503 			  auto_hdrs);
504 	if (any_eoh_macros != global_eoh_macros)
505 	    argv_free(any_eoh_macros);
506 	if (any_eod_macros != global_eod_macros)
507 	    argv_free(any_eod_macros);
508     }
509     if (global_eoh_macros)
510 	argv_free(global_eoh_macros);
511     if (global_eod_macros)
512 	argv_free(global_eod_macros);
513     return (resp);
514 }
515 
516 /* milter_abort - cancel message receiving state, all milters */
517 
518 void    milter_abort(MILTERS *milters)
519 {
520     MILTER *m;
521 
522     if (msg_verbose)
523 	msg_info("abort all milters");
524     for (m = milters->milter_list; m != 0; m = m->next)
525 	m->abort(m);
526 }
527 
528 /* milter_disc_event - report client disconnect event to all milters */
529 
530 void    milter_disc_event(MILTERS *milters)
531 {
532     MILTER *m;
533 
534     if (msg_verbose)
535 	msg_info("disconnect event to all milters");
536     for (m = milters->milter_list; m != 0; m = m->next)
537 	m->disc_event(m);
538 }
539 
540 /* milter_new - create milter list */
541 
542 MILTERS *milter_new(const char *names,
543 		            int conn_timeout,
544 		            int cmd_timeout,
545 		            int msg_timeout,
546 		            const char *protocol,
547 		            const char *def_action,
548 		            MILTER_MACROS *macros)
549 {
550     MILTERS *milters;
551     MILTER *head = 0;
552     MILTER *tail = 0;
553     char   *name;
554     MILTER *milter;
555     const char *sep = ", \t\r\n";
556 
557     /*
558      * Parse the milter list.
559      */
560     milters = (MILTERS *) mymalloc(sizeof(*milters));
561     if (names != 0) {
562 	char   *saved_names = mystrdup(names);
563 	char   *cp = saved_names;
564 
565 	while ((name = mystrtok(&cp, sep)) != 0) {
566 	    milter = milter8_create(name, conn_timeout, cmd_timeout,
567 				    msg_timeout, protocol, def_action,
568 				    milters);
569 	    if (head == 0) {
570 		head = milter;
571 	    } else {
572 		tail->next = milter;
573 	    }
574 	    tail = milter;
575 	}
576 	myfree(saved_names);
577     }
578     milters->milter_list = head;
579     milters->mac_lookup = 0;
580     milters->mac_context = 0;
581     milters->macros = macros;
582     milters->add_header = 0;
583     milters->upd_header = milters->ins_header = 0;
584     milters->del_header = 0;
585     milters->add_rcpt = milters->del_rcpt = 0;
586     milters->repl_body = 0;
587     milters->chg_context = 0;
588     return (milters);
589 }
590 
591 /* milter_free - destroy all milters */
592 
593 void    milter_free(MILTERS *milters)
594 {
595     MILTER *m;
596     MILTER *next;
597 
598     if (msg_verbose)
599 	msg_info("free all milters");
600     for (m = milters->milter_list; m != 0; m = next)
601 	next = m->next, m->free(m);
602     if (milters->macros)
603 	milter_macros_free(milters->macros);
604     myfree((char *) milters);
605 }
606 
607 /* milter_dummy - send empty milter list */
608 
609 int     milter_dummy(MILTERS *milters, VSTREAM *stream)
610 {
611     MILTERS dummy = *milters;
612 
613     dummy.milter_list = 0;
614     return (milter_send(&dummy, stream));
615 }
616 
617 /* milter_send - send Milter instances over stream */
618 
619 int     milter_send(MILTERS *milters, VSTREAM *stream)
620 {
621     MILTER *m;
622     int     status = 0;
623     int     count = 0;
624 
625     /*
626      * XXX Optimization: send only the filters that are actually used in the
627      * remote process. No point sending a filter that looks at HELO commands
628      * to a cleanup server. For now we skip only the filters that are known
629      * to be disabled (either in global error state or in global accept
630      * state).
631      *
632      * XXX We must send *some* information, even when there are no active
633      * filters, otherwise the cleanup server would try to apply its own
634      * non_smtpd_milters settings.
635      */
636     if (milters != 0)
637 	for (m = milters->milter_list; m != 0; m = m->next)
638 	    if (m->active(m))
639 		count++;
640     (void) rec_fprintf(stream, REC_TYPE_MILT_COUNT, "%d", count);
641 
642     if (msg_verbose)
643 	msg_info("send %d milters", count);
644 
645     /*
646      * XXX Optimization: don't send or receive further information when there
647      * aren't any active filters.
648      */
649     if (count <= 0)
650 	return (0);
651 
652     /*
653      * Send the filter macro name lists.
654      */
655     (void) attr_print(stream, ATTR_FLAG_MORE,
656 		      ATTR_TYPE_FUNC, milter_macros_print,
657 		      (void *) milters->macros,
658 		      ATTR_TYPE_END);
659 
660     /*
661      * Send the filter instances.
662      */
663     for (m = milters->milter_list; m != 0; m = m->next)
664 	if (m->active(m) && (status = m->send(m, stream)) != 0)
665 	    break;
666 
667     /*
668      * Over to you.
669      */
670     if (status != 0
671 	|| attr_scan(stream, ATTR_FLAG_STRICT,
672 		     ATTR_TYPE_INT, MAIL_ATTR_STATUS, &status,
673 		     ATTR_TYPE_END) != 1
674 	|| status != 0) {
675 	msg_warn("cannot send milters to service %s", VSTREAM_PATH(stream));
676 	return (-1);
677     }
678     return (0);
679 }
680 
681 /* milter_receive - receive milters from stream */
682 
683 MILTERS *milter_receive(VSTREAM *stream, int count)
684 {
685     MILTERS *milters;
686     MILTER *head = 0;
687     MILTER *tail = 0;
688     MILTER *milter = 0;
689 
690     if (msg_verbose)
691 	msg_info("receive %d milters", count);
692 
693     /*
694      * XXX We must instantiate a MILTERS structure even when the sender has
695      * no active filters, otherwise the cleanup server would try to use its
696      * own non_smtpd_milters settings.
697      */
698 #define NO_MILTERS	((char *) 0)
699 #define NO_TIMEOUTS	0, 0, 0
700 #define NO_PROTOCOL	((char *) 0)
701 #define NO_ACTION	((char *) 0)
702 #define NO_MACROS	((MILTER_MACROS *) 0)
703 
704     milters = milter_new(NO_MILTERS, NO_TIMEOUTS, NO_PROTOCOL, NO_ACTION,
705 			 NO_MACROS);
706 
707     /*
708      * XXX Optimization: don't send or receive further information when there
709      * aren't any active filters.
710      */
711     if (count <= 0)
712 	return (milters);
713 
714     /*
715      * Receive the global macro name lists.
716      */
717     milters->macros = milter_macros_alloc(MILTER_MACROS_ALLOC_ZERO);
718     if (attr_scan(stream, ATTR_FLAG_STRICT | ATTR_FLAG_MORE,
719 		  ATTR_TYPE_FUNC, milter_macros_scan,
720 		  (void *) milters->macros,
721 		  ATTR_TYPE_END) != 1) {
722 	milter_free(milters);
723 	return (0);
724     }
725 
726     /*
727      * Receive the filters.
728      */
729     for (; count > 0; count--) {
730 	if ((milter = milter8_receive(stream, milters)) == 0) {
731 	    msg_warn("cannot receive milters via service %s socket",
732 		     VSTREAM_PATH(stream));
733 	    milter_free(milters);
734 	    return (0);
735 	}
736 	if (head == 0) {
737 	    /* Coverity: milter_free() depends on milters->milter_list. */
738 	    milters->milter_list = head = milter;
739 	} else {
740 	    tail->next = milter;
741 	}
742 	tail = milter;
743     }
744 
745     /*
746      * Over to you.
747      */
748     (void) attr_print(stream, ATTR_FLAG_NONE,
749 		      ATTR_TYPE_INT, MAIL_ATTR_STATUS, 0,
750 		      ATTR_TYPE_END);
751     return (milters);
752 }
753 
754 #ifdef TEST
755 
756  /*
757   * Proof-of-concept test program. This can be used interactively, but is
758   * typically used for automated regression tests from a script.
759   */
760 
761 /* System library. */
762 
763 #include <sys/socket.h>
764 #include <stdlib.h>
765 #include <string.h>
766 
767 /* Utility library. */
768 
769 #include "msg_vstream.h"
770 #include "vstring_vstream.h"
771 
772 /* Global library. */
773 
774 #include <mail_params.h>
775 
776 int     var_milt_conn_time = 10;
777 int     var_milt_cmd_time = 10;
778 int     var_milt_msg_time = 100;
779 char   *var_milt_protocol = DEF_MILT_PROTOCOL;
780 char   *var_milt_def_action = DEF_MILT_DEF_ACTION;
781 
782 static void usage(void)
783 {
784     vstream_fprintf(VSTREAM_ERR, "usage: \n"
785 		    "	create names...		create and connect\n"
786 #if 0
787 		    "	conn_macros names...	define connect macros\n"
788 		    "	helo_macros names...	define helo command macros\n"
789 		    "	mail_macros names...	define mail command macros\n"
790 		    "	rcpt_macros names...	define rcpt command macros\n"
791 		    "	data_macros names...	define data command macros\n"
792 		    "	unk_macros names...	unknown command macros\n"
793 		    "	message_macros names...	define message macros\n"
794 #endif
795 		    "	free			disconnect and destroy\n"
796 		    "	connect name addr port family\n"
797 		    "	helo hostname\n"
798 		    "	ehlo hostname\n"
799 		    "	mail from sender...\n"
800 		    "	rcpt to recipient...\n"
801 		    "	data\n"
802 		    "	disconnect\n"
803 		    "	unknown command\n");
804     vstream_fflush(VSTREAM_ERR);
805 }
806 
807 int     main(int argc, char **argv)
808 {
809     MILTERS *milters = 0;
810     char   *conn_macros, *helo_macros, *mail_macros, *rcpt_macros;
811     char   *data_macros, *eoh_macros, *eod_macros, *unk_macros;
812     VSTRING *inbuf = vstring_alloc(100);
813     char   *bufp;
814     char   *cmd;
815     int     ch;
816     int     istty = isatty(vstream_fileno(VSTREAM_IN));
817 
818     conn_macros = helo_macros = mail_macros = rcpt_macros = data_macros
819 	= eoh_macros = eod_macros = unk_macros = "";
820 
821     msg_vstream_init(argv[0], VSTREAM_ERR);
822     while ((ch = GETOPT(argc, argv, "V:v")) > 0) {
823 	switch (ch) {
824 	default:
825 	    msg_fatal("usage: %s [-a action] [-p protocol] [-v]", argv[0]);
826 	case 'a':
827 	    var_milt_def_action = optarg;
828 	    break;
829 	case 'p':
830 	    var_milt_protocol = optarg;
831 	    break;
832 	case 'v':
833 	    msg_verbose++;
834 	    break;
835 	}
836     }
837     optind = OPTIND;
838 
839     for (;;) {
840 	const char *resp = 0;
841 	ARGV   *argv;
842 	char  **args;
843 
844 	if (istty) {
845 	    vstream_printf("- ");
846 	    vstream_fflush(VSTREAM_OUT);
847 	}
848 	if (vstring_fgets_nonl(inbuf, VSTREAM_IN) <= 0)
849 	    break;
850 	bufp = vstring_str(inbuf);
851 	if (!istty) {
852 	    vstream_printf("> %s\n", bufp);
853 	    vstream_fflush(VSTREAM_OUT);
854 	}
855 	if (*bufp == '#')
856 	    continue;
857 	cmd = mystrtok(&bufp, " ");
858 	if (cmd == 0) {
859 	    usage();
860 	    continue;
861 	}
862 	argv = argv_split(bufp, " ");
863 	args = argv->argv;
864 	if (strcmp(cmd, "create") == 0 && argv->argc == 1) {
865 	    if (milters != 0) {
866 		msg_warn("deleting existing milters");
867 		milter_free(milters);
868 	    }
869 	    milters = milter_create(args[0], var_milt_conn_time,
870 				    var_milt_cmd_time, var_milt_msg_time,
871 				    var_milt_protocol, var_milt_def_action,
872 				    conn_macros, helo_macros, mail_macros,
873 				    rcpt_macros, data_macros, eoh_macros,
874 				    eod_macros, unk_macros);
875 	} else if (strcmp(cmd, "free") == 0 && argv->argc == 0) {
876 	    if (milters == 0) {
877 		msg_warn("no milters");
878 		continue;
879 	    }
880 	    milter_free(milters);
881 	    milters = 0;
882 	} else if (strcmp(cmd, "connect") == 0 && argv->argc == 4) {
883 	    if (milters == 0) {
884 		msg_warn("no milters");
885 		continue;
886 	    }
887 	    resp = milter_conn_event(milters, args[0], args[1], args[2],
888 				 strcmp(args[3], "AF_INET") == 0 ? AF_INET :
889 			       strcmp(args[3], "AF_INET6") == 0 ? AF_INET6 :
890 				 strcmp(args[3], "AF_UNIX") == 0 ? AF_UNIX :
891 				     AF_UNSPEC);
892 	} else if (strcmp(cmd, "helo") == 0 && argv->argc == 1) {
893 	    if (milters == 0) {
894 		msg_warn("no milters");
895 		continue;
896 	    }
897 	    resp = milter_helo_event(milters, args[0], 0);
898 	} else if (strcmp(cmd, "ehlo") == 0 && argv->argc == 1) {
899 	    if (milters == 0) {
900 		msg_warn("no milters");
901 		continue;
902 	    }
903 	    resp = milter_helo_event(milters, args[0], 1);
904 	} else if (strcmp(cmd, "mail") == 0 && argv->argc > 0) {
905 	    if (milters == 0) {
906 		msg_warn("no milters");
907 		continue;
908 	    }
909 	    resp = milter_mail_event(milters, (const char **) args);
910 	} else if (strcmp(cmd, "rcpt") == 0 && argv->argc > 0) {
911 	    if (milters == 0) {
912 		msg_warn("no milters");
913 		continue;
914 	    }
915 	    resp = milter_rcpt_event(milters, 0, (const char **) args);
916 	} else if (strcmp(cmd, "unknown") == 0 && argv->argc > 0) {
917 	    if (milters == 0) {
918 		msg_warn("no milters");
919 		continue;
920 	    }
921 	    resp = milter_unknown_event(milters, args[0]);
922 	} else if (strcmp(cmd, "data") == 0 && argv->argc == 0) {
923 	    if (milters == 0) {
924 		msg_warn("no milters");
925 		continue;
926 	    }
927 	    resp = milter_data_event(milters);
928 	} else if (strcmp(cmd, "disconnect") == 0 && argv->argc == 0) {
929 	    if (milters == 0) {
930 		msg_warn("no milters");
931 		continue;
932 	    }
933 	    milter_disc_event(milters);
934 	} else {
935 	    usage();
936 	}
937 	if (resp != 0)
938 	    msg_info("%s", resp);
939 	argv_free(argv);
940     }
941     if (milters != 0)
942 	milter_free(milters);
943     vstring_free(inbuf);
944     return (0);
945 }
946 
947 #endif
948