1/*	$NetBSD: test-milter.c,v 1.3 2020/03/18 19:05:17 christos Exp $	*/
2
3/*++
4/* NAME
5/*	test-milter 1
6/* SUMMARY
7/*	Simple test mail filter program.
8/* SYNOPSIS
9/* .fi
10/*	\fBtest-milter\fR [\fIoptions\fR] -p \fBinet:\fIport\fB@\fIhost\fR
11/*
12/*	\fBtest-milter\fR [\fIoptions\fR] -p \fBunix:\fIpathname\fR
13/* DESCRIPTION
14/*	\fBtest-milter\fR is a Milter (mail filter) application that
15/*	exercises selected features.
16/*
17/*	Note: this is an unsupported test program. No attempt is made
18/*	to maintain compatibility between successive versions.
19/*
20/*	Arguments (multiple alternatives are separated by "\fB|\fR"):
21/* .IP "\fB-a accept|tempfail|reject|discard|skip|\fIddd x.y.z text\fR"
22/*	Specifies a non-default reply for the MTA command specified
23/*	with \fB-c\fR. The default is \fBtempfail\fR. The \fItext\fR
24/*	is repeated once, to produce multi-line reply text.
25/* .IP "\fB-A address\fR"
26/*	Add the specified recipient address (specify ESMTP parameters
27/*	separated by space). Multiple -A options are supported.
28/* .IP "\fB-b pathname\fR"
29/*	Replace the message body by the content of the specified file.
30/* .IP "\fB-c connect|helo|mail|rcpt|data|header|eoh|body|eom|unknown|close|abort\fR"
31/*	When to send the non-default reply specified with \fB-a\fR.
32/*	The default protocol stage is \fBconnect\fR.
33/* .IP "\fB-C\fI count\fR"
34/*	Terminate after \fIcount\fR connections.
35/* .IP "\fB-d\fI level\fR"
36/*	Enable libmilter debugging at the specified level.
37/* .IP "\fB-D\fI address\fR"
38/*	Delete the specified recipient address. Multiple -D options
39/*	are supported.
40/* .IP "\fB-f \fIsender\fR"
41/*	Replace the sender by the specified address.
42/* .IP "\fB-h \fI'index header-label header-value'\fR"
43/*	Replace the message header at the specified position.
44/* .IP "\fB-i \fI'index header-label header-value'\fR"
45/*	Insert header at specified position.
46/* .IP "\fB-l\fR"
47/*	Header values include leading space. Specify this option
48/*	before \fB-i\fR or \fB-h\fR.
49/* .IP "\fB-m connect|helo|mail|rcpt|data|eoh|eom\fR"
50/*	The protocol stage that receives the list of macros specified
51/*	with \fB-M\fR.  The default protocol stage is \fBconnect\fR.
52/* .IP "\fB-M \fIset_macro_list\fR"
53/*	A non-default list of macros that the MTA should send at
54/*	the protocol stage specified with \fB-m\fR.
55/* .IP "\fB-n connect|helo|mail|rcpt|data|header|eoh|body|eom|unknown\fR"
56/*	The event that the MTA should not send.
57/* .IP "\fB-N connect|helo|mail|rcpt|data|header|eoh|body|eom|unknown\fR"
58/*	The event for which the filter will not reply.
59/* .IP "\fB-p inet:\fIport\fB@\fIhost\fB|unix:\fIpathname\fR"
60/*	The mail filter listen endpoint.
61/* .IP "\fB-r\fR"
62/*	Request rejected recipients from the MTA.
63/* .IP "\fB-v\fR"
64/*	Make the program more verbose.
65/* LICENSE
66/* .ad
67/* .fi
68/*	The Secure Mailer license must be distributed with this software.
69/* AUTHOR(S)
70/*	Wietse Venema
71/*	IBM T.J. Watson Research
72/*	P.O. Box 704
73/*	Yorktown Heights, NY 10598, USA
74/*
75/*	Wietse Venema
76/*	Google, Inc.
77/*	111 8th Avenue
78/*	New York, NY 10011, USA
79/*--*/
80
81#include <sys/types.h>
82#include <sys/socket.h>
83#include <netinet/in.h>
84#include <sys/un.h>
85#include <arpa/inet.h>
86#include <errno.h>
87#include <stdio.h>
88#include <stdlib.h>
89#include <unistd.h>
90#include <string.h>
91
92#include "libmilter/mfapi.h"
93#include "libmilter/mfdef.h"
94
95static int conn_count;
96static int verbose;
97
98static int test_connect_reply = SMFIS_CONTINUE;
99static int test_helo_reply = SMFIS_CONTINUE;
100static int test_mail_reply = SMFIS_CONTINUE;
101static int test_rcpt_reply = SMFIS_CONTINUE;
102
103#if SMFI_VERSION > 3
104static int test_data_reply = SMFIS_CONTINUE;
105
106#endif
107static int test_header_reply = SMFIS_CONTINUE;
108static int test_eoh_reply = SMFIS_CONTINUE;
109static int test_body_reply = SMFIS_CONTINUE;
110static int test_eom_reply = SMFIS_CONTINUE;
111
112#if SMFI_VERSION > 2
113static int test_unknown_reply = SMFIS_CONTINUE;
114
115#endif
116static int test_close_reply = SMFIS_CONTINUE;
117static int test_abort_reply = SMFIS_CONTINUE;
118
119struct command_map {
120    const char *name;
121    int    *reply;
122};
123
124static const struct command_map command_map[] = {
125    "connect", &test_connect_reply,
126    "helo", &test_helo_reply,
127    "mail", &test_mail_reply,
128    "rcpt", &test_rcpt_reply,
129    "header", &test_header_reply,
130    "eoh", &test_eoh_reply,
131    "body", &test_body_reply,
132    "eom", &test_eom_reply,
133    "abort", &test_abort_reply,
134    "close", &test_close_reply,
135#if SMFI_VERSION > 2
136    "unknown", &test_unknown_reply,
137#endif
138#if SMFI_VERSION > 3
139    "data", &test_data_reply,
140#endif
141    0, 0,
142};
143
144static char *reply_code;
145static char *reply_dsn;
146static char *reply_message;
147
148#ifdef SMFIR_CHGFROM
149static char *chg_from;
150
151#endif
152
153#ifdef SMFIR_INSHEADER
154static char *ins_hdr;
155static int ins_idx;
156static char *ins_val;
157
158#endif
159
160#ifdef SMFIR_CHGHEADER
161static char *chg_hdr;
162static int chg_idx;
163static char *chg_val;
164
165#endif
166
167#ifdef SMFIR_REPLBODY
168static char *body_file;
169
170#endif
171
172#define MAX_RCPT	10
173int     add_rcpt_count = 0;
174char   *add_rcpt[MAX_RCPT];
175int     del_rcpt_count = 0;
176char   *del_rcpt[MAX_RCPT];
177
178static const char *macro_names[] = {
179    "_",
180    "i",
181    "j",
182    "v",
183    "{auth_authen}",
184    "{auth_author}",
185    "{auth_type}",
186    "{cert_issuer}",
187    "{cert_subject}",
188    "{cipher}",
189    "{cipher_bits}",
190    "{client_addr}",
191    "{client_connections}",
192    "{client_name}",
193    "{client_port}",
194    "{client_ptr}",
195    "{client_resolve}",
196    "{daemon_addr}",
197    "{daemon_name}",
198    "{daemon_port}",
199    "{if_addr}",
200    "{if_name}",
201    "{mail_addr}",
202    "{mail_host}",
203    "{mail_mailer}",
204    "{rcpt_addr}",
205    "{rcpt_host}",
206    "{rcpt_mailer}",
207    "{tls_version}",
208    0,
209};
210
211static int test_reply(SMFICTX *ctx, int code)
212{
213    const char **cpp;
214    const char *symval;
215
216    for (cpp = macro_names; *cpp; cpp++)
217	if ((symval = smfi_getsymval(ctx, (char *) *cpp)) != 0)
218	    printf("macro: %s=\"%s\"\n", *cpp, symval);
219    (void) fflush(stdout);			/* In case output redirected. */
220
221    if (code == SMFIR_REPLYCODE) {
222	if (smfi_setmlreply(ctx, reply_code, reply_dsn, reply_message, reply_message, (char *) 0) == MI_FAILURE)
223	    fprintf(stderr, "smfi_setmlreply failed\n");
224	printf("test_reply %s\n\n", reply_code);
225	return (reply_code[0] == '4' ? SMFIS_TEMPFAIL : SMFIS_REJECT);
226    } else {
227	printf("test_reply %d\n\n", code);
228	return (code);
229    }
230}
231
232static sfsistat test_connect(SMFICTX *ctx, char *name, struct sockaddr * sa)
233{
234    const char *print_addr;
235    char    buf[BUFSIZ];
236
237    printf("test_connect %s ", name);
238    switch (sa->sa_family) {
239    case AF_INET:
240	{
241	    struct sockaddr_in *sin = (struct sockaddr_in *) sa;
242
243	    print_addr = inet_ntop(AF_INET, &sin->sin_addr, buf, sizeof(buf));
244	    if (print_addr == 0)
245		print_addr = strerror(errno);
246	    printf("AF_INET (%s:%d)\n", print_addr, ntohs(sin->sin_port));
247	}
248	break;
249#ifdef HAS_IPV6
250    case AF_INET6:
251	{
252	    struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *) sa;
253
254	    print_addr = inet_ntop(AF_INET, &sin6->sin6_addr, buf, sizeof(buf));
255	    if (print_addr == 0)
256		print_addr = strerror(errno);
257	    printf("AF_INET6 (%s:%d)\n", print_addr, ntohs(sin6->sin6_port));
258	}
259	break;
260#endif
261    case AF_UNIX:
262	{
263#undef sun
264	    struct sockaddr_un *sun = (struct sockaddr_un *) sa;
265
266	    printf("AF_UNIX (%s)\n", sun->sun_path);
267	}
268	break;
269    default:
270	printf(" [unknown address family]\n");
271	break;
272    }
273    return (test_reply(ctx, test_connect_reply));
274}
275
276static sfsistat test_helo(SMFICTX *ctx, char *arg)
277{
278    printf("test_helo \"%s\"\n", arg ? arg : "NULL");
279    return (test_reply(ctx, test_helo_reply));
280}
281
282static sfsistat test_mail(SMFICTX *ctx, char **argv)
283{
284    char  **cpp;
285
286    printf("test_mail");
287    for (cpp = argv; *cpp; cpp++)
288	printf(" \"%s\"", *cpp);
289    printf("\n");
290    return (test_reply(ctx, test_mail_reply));
291}
292
293static sfsistat test_rcpt(SMFICTX *ctx, char **argv)
294{
295    char  **cpp;
296
297    printf("test_rcpt");
298    for (cpp = argv; *cpp; cpp++)
299	printf(" \"%s\"", *cpp);
300    printf("\n");
301    return (test_reply(ctx, test_rcpt_reply));
302}
303
304
305sfsistat test_header(SMFICTX *ctx, char *name, char *value)
306{
307    printf("test_header \"%s\" \"%s\"\n", name, value);
308    return (test_reply(ctx, test_header_reply));
309}
310
311static sfsistat test_eoh(SMFICTX *ctx)
312{
313    printf("test_eoh\n");
314    return (test_reply(ctx, test_eoh_reply));
315}
316
317static sfsistat test_body(SMFICTX *ctx, unsigned char *data, size_t data_len)
318{
319    if (verbose == 0)
320	printf("test_body %ld bytes\n", (long) data_len);
321    else
322	printf("%.*s", (int) data_len, data);
323    return (test_reply(ctx, test_body_reply));
324}
325
326static sfsistat test_eom(SMFICTX *ctx)
327{
328    printf("test_eom\n");
329#ifdef SMFIR_REPLBODY
330    if (body_file) {
331	char    buf[BUFSIZ + 2];
332	FILE   *fp;
333	size_t  len;
334	int     count;
335
336	if ((fp = fopen(body_file, "r")) == 0) {
337	    perror(body_file);
338	} else {
339	    printf("replace body with content of %s\n", body_file);
340	    for (count = 0; fgets(buf, BUFSIZ, fp) != 0; count++) {
341		len = strcspn(buf, "\n");
342		buf[len + 0] = '\r';
343		buf[len + 1] = '\n';
344		if (smfi_replacebody(ctx, buf, len + 2) == MI_FAILURE) {
345		    fprintf(stderr, "body replace failure\n");
346		    exit(1);
347		}
348		if (verbose)
349		    printf("%.*s\n", (int) len, buf);
350	    }
351	    if (count == 0)
352		perror("fgets");
353	    (void) fclose(fp);
354	}
355    }
356#endif
357#ifdef SMFIR_CHGFROM
358    if (chg_from != 0 && smfi_chgfrom(ctx, chg_from, "whatever") == MI_FAILURE)
359	fprintf(stderr, "smfi_chgfrom failed\n");
360#endif
361#ifdef SMFIR_INSHEADER
362    if (ins_hdr && smfi_insheader(ctx, ins_idx, ins_hdr, ins_val) == MI_FAILURE)
363	fprintf(stderr, "smfi_insheader failed\n");
364#endif
365#ifdef SMFIR_CHGHEADER
366    if (chg_hdr && smfi_chgheader(ctx, chg_hdr, chg_idx, chg_val) == MI_FAILURE)
367	fprintf(stderr, "smfi_chgheader failed\n");
368#endif
369    {
370	int     count;
371	char   *args;
372
373	for (count = 0; count < add_rcpt_count; count++) {
374	    if ((args = strchr(add_rcpt[count], ' ')) != 0) {
375		*args++ = 0;
376		if (smfi_addrcpt_par(ctx, add_rcpt[count], args) == MI_FAILURE)
377		    fprintf(stderr, "smfi_addrcpt_par `%s' `%s' failed\n",
378			    add_rcpt[count], args);
379	    } else {
380		if (smfi_addrcpt(ctx, add_rcpt[count]) == MI_FAILURE)
381		    fprintf(stderr, "smfi_addrcpt `%s' failed\n",
382			    add_rcpt[count]);
383	    }
384	}
385
386	for (count = 0; count < del_rcpt_count; count++)
387	    if (smfi_delrcpt(ctx, del_rcpt[count]) == MI_FAILURE)
388		fprintf(stderr, "smfi_delrcpt `%s' failed\n", del_rcpt[count]);
389    }
390    return (test_reply(ctx, test_eom_reply));
391}
392
393static sfsistat test_abort(SMFICTX *ctx)
394{
395    printf("test_abort\n");
396    return (test_reply(ctx, test_abort_reply));
397}
398
399static sfsistat test_close(SMFICTX *ctx)
400{
401    printf("test_close\n");
402    if (verbose)
403	printf("conn_count %d\n", conn_count);
404    if (conn_count > 0 && --conn_count == 0)
405	exit(0);
406    return (test_reply(ctx, test_close_reply));
407}
408
409#if SMFI_VERSION > 3
410
411static sfsistat test_data(SMFICTX *ctx)
412{
413    printf("test_data\n");
414    return (test_reply(ctx, test_data_reply));
415}
416
417#endif
418
419#if SMFI_VERSION > 2
420
421static sfsistat test_unknown(SMFICTX *ctx, const char *what)
422{
423    printf("test_unknown %s\n", what);
424    return (test_reply(ctx, test_unknown_reply));
425}
426
427#endif
428
429#if SMFI_VERSION > 5
430
431static sfsistat test_negotiate(SMFICTX *, unsigned long, unsigned long,
432			               unsigned long, unsigned long,
433			               unsigned long *, unsigned long *,
434			               unsigned long *, unsigned long *);
435
436#endif
437
438#ifndef SMFIF_CHGFROM
439#define SMFIF_CHGFROM 0
440#endif
441#ifndef SMFIP_HDR_LEADSPC
442#define SMFIP_HDR_LEADSPC 0
443#define misc_mask 0
444#endif
445
446static struct smfiDesc smfilter =
447{
448    "test-milter",
449    SMFI_VERSION,
450    SMFIF_ADDRCPT | SMFIF_DELRCPT | SMFIF_ADDHDRS | SMFIF_CHGHDRS | SMFIF_CHGBODY | SMFIF_CHGFROM,
451    test_connect,
452    test_helo,
453    test_mail,
454    test_rcpt,
455    test_header,
456    test_eoh,
457    test_body,
458    test_eom,
459    test_abort,
460    test_close,
461#if SMFI_VERSION > 2
462    test_unknown,
463#endif
464#if SMFI_VERSION > 3
465    test_data,
466#endif
467#if SMFI_VERSION > 5
468    test_negotiate,
469#endif
470};
471
472#if SMFI_VERSION > 5
473
474static const char *macro_states[] = {
475    "connect",				/* SMFIM_CONNECT */
476    "helo",				/* SMFIM_HELO */
477    "mail",				/* SMFIM_ENVFROM */
478    "rcpt",				/* SMFIM_ENVRCPT */
479    "data",				/* SMFIM_DATA */
480    "eom",				/* SMFIM_EOM < SMFIM_EOH */
481    "eoh",				/* SMFIM_EOH > SMFIM_EOM */
482    0,
483};
484
485static int set_macro_state;
486static char *set_macro_list;
487
488typedef sfsistat (*FILTER_ACTION) ();
489
490struct noproto_map {
491    const char *name;
492    int     send_mask;
493    int     reply_mask;
494    int    *reply;
495    FILTER_ACTION *action;
496};
497
498static const struct noproto_map noproto_map[] = {
499    "connect", SMFIP_NOCONNECT, SMFIP_NR_CONN, &test_connect_reply, &smfilter.xxfi_connect,
500    "helo", SMFIP_NOHELO, SMFIP_NR_HELO, &test_helo_reply, &smfilter.xxfi_helo,
501    "mail", SMFIP_NOMAIL, SMFIP_NR_MAIL, &test_mail_reply, &smfilter.xxfi_envfrom,
502    "rcpt", SMFIP_NORCPT, SMFIP_NR_RCPT, &test_rcpt_reply, &smfilter.xxfi_envrcpt,
503    "data", SMFIP_NODATA, SMFIP_NR_DATA, &test_data_reply, &smfilter.xxfi_data,
504    "header", SMFIP_NOHDRS, SMFIP_NR_HDR, &test_header_reply, &smfilter.xxfi_header,
505    "eoh", SMFIP_NOEOH, SMFIP_NR_EOH, &test_eoh_reply, &smfilter.xxfi_eoh,
506    "body", SMFIP_NOBODY, SMFIP_NR_BODY, &test_body_reply, &smfilter.xxfi_body,
507    "unknown", SMFIP_NOUNKNOWN, SMFIP_NR_UNKN, &test_connect_reply, &smfilter.xxfi_unknown,
508    0,
509};
510
511static int nosend_mask;
512static int noreply_mask;
513static int misc_mask;
514
515static sfsistat test_negotiate(SMFICTX *ctx,
516			               unsigned long f0,
517			               unsigned long f1,
518			               unsigned long f2,
519			               unsigned long f3,
520			               unsigned long *pf0,
521			               unsigned long *pf1,
522			               unsigned long *pf2,
523			               unsigned long *pf3)
524{
525    if (set_macro_list) {
526	if (verbose)
527	    printf("set symbol list %s to \"%s\"\n",
528		   macro_states[set_macro_state], set_macro_list);
529	smfi_setsymlist(ctx, set_macro_state, set_macro_list);
530    }
531    if (verbose)
532	printf("negotiate f0=%lx *pf0 = %lx f1=%lx *pf1=%lx nosend=%lx noreply=%lx misc=%lx\n",
533	       f0, *pf0, f1, *pf1, (long) nosend_mask, (long) noreply_mask, (long) misc_mask);
534    *pf0 = f0;
535    *pf1 = f1 & (nosend_mask | noreply_mask | misc_mask);
536    return (SMFIS_CONTINUE);
537}
538
539#endif
540
541static void parse_hdr_info(const char *optarg, int *idx,
542			           char **hdr, char **value)
543{
544    int     len;
545
546    len = strlen(optarg) + 1;
547    if ((*hdr = malloc(len)) == 0 || (*value = malloc(len)) == 0) {
548	fprintf(stderr, "out of memory\n");
549	exit(1);
550    }
551    if ((misc_mask & SMFIP_HDR_LEADSPC) == 0 ?
552	sscanf(optarg, "%d %s %[^\n]", idx, *hdr, *value) != 3 :
553	sscanf(optarg, "%d %[^ ]%[^\n]", idx, *hdr, *value) != 3) {
554	fprintf(stderr, "bad header info: %s\n", optarg);
555	exit(1);
556    }
557}
558
559int     main(int argc, char **argv)
560{
561    char   *action = 0;
562    char   *command = 0;
563    const struct command_map *cp;
564    int     ch;
565    int     code;
566    const char **cpp;
567    char   *set_macro_state_arg = 0;
568    char   *nosend = 0;
569    char   *noreply = 0;
570    const struct noproto_map *np;
571
572    while ((ch = getopt(argc, argv, "a:A:b:c:C:d:D:f:h:i:lm:M:n:N:p:rv")) > 0) {
573	switch (ch) {
574	case 'a':
575	    action = optarg;
576	    break;
577	case 'A':
578	    if (add_rcpt_count >= MAX_RCPT) {
579		fprintf(stderr, "too many -A options\n");
580		exit(1);
581	    }
582	    add_rcpt[add_rcpt_count++] = optarg;
583	    break;
584	case 'b':
585#ifdef SMFIR_REPLBODY
586	    if (body_file) {
587		fprintf(stderr, "too many -b options\n");
588		exit(1);
589	    }
590	    body_file = optarg;
591#else
592	    fprintf(stderr, "no libmilter support to replace body\n");
593#endif
594	    break;
595	case 'c':
596	    command = optarg;
597	    break;
598	case 'd':
599	    if (smfi_setdbg(atoi(optarg)) == MI_FAILURE) {
600		fprintf(stderr, "smfi_setdbg failed\n");
601		exit(1);
602	    }
603	    break;
604	case 'D':
605	    if (del_rcpt_count >= MAX_RCPT) {
606		fprintf(stderr, "too many -D options\n");
607		exit(1);
608	    }
609	    del_rcpt[del_rcpt_count++] = optarg;
610	    break;
611	case 'f':
612#ifdef SMFIR_CHGFROM
613	    if (chg_from) {
614		fprintf(stderr, "too many -f options\n");
615		exit(1);
616	    }
617	    chg_from = optarg;
618#else
619	    fprintf(stderr, "no libmilter support to change sender\n");
620	    exit(1);
621#endif
622	    break;
623	case 'h':
624#ifdef SMFIR_CHGHEADER
625	    if (chg_hdr) {
626		fprintf(stderr, "too many -h options\n");
627		exit(1);
628	    }
629	    parse_hdr_info(optarg, &chg_idx, &chg_hdr, &chg_val);
630#else
631	    fprintf(stderr, "no libmilter support to change header\n");
632	    exit(1);
633#endif
634	    break;
635	case 'i':
636#ifdef SMFIR_INSHEADER
637	    if (ins_hdr) {
638		fprintf(stderr, "too many -i options\n");
639		exit(1);
640	    }
641	    parse_hdr_info(optarg, &ins_idx, &ins_hdr, &ins_val);
642#else
643	    fprintf(stderr, "no libmilter support to insert header\n");
644	    exit(1);
645#endif
646	    break;
647	case 'l':
648#if SMFI_VERSION > 5
649	    if (ins_hdr || chg_hdr) {
650		fprintf(stderr, "specify -l before -i or -r\n");
651		exit(1);
652	    }
653	    misc_mask |= SMFIP_HDR_LEADSPC;
654#else
655	    fprintf(stderr, "no libmilter support for leading space\n");
656	    exit(1);
657#endif
658	    break;
659	case 'm':
660#if SMFI_VERSION > 5
661	    if (set_macro_state_arg) {
662		fprintf(stderr, "too many -m options\n");
663		exit(1);
664	    }
665	    set_macro_state_arg = optarg;
666#else
667	    fprintf(stderr, "no libmilter support to specify macro list\n");
668	    exit(1);
669#endif
670	    break;
671	case 'M':
672#if SMFI_VERSION > 5
673	    if (set_macro_list) {
674		fprintf(stderr, "too many -M options\n");
675		exit(1);
676	    }
677	    set_macro_list = optarg;
678#else
679	    fprintf(stderr, "no libmilter support to specify macro list\n");
680#endif
681	    break;
682	case 'n':
683#if SMFI_VERSION > 5
684	    if (nosend) {
685		fprintf(stderr, "too many -n options\n");
686		exit(1);
687	    }
688	    nosend = optarg;
689#else
690	    fprintf(stderr, "no libmilter support for negotiate callback\n");
691#endif
692	    break;
693	case 'N':
694#if SMFI_VERSION > 5
695	    if (noreply) {
696		fprintf(stderr, "too many -n options\n");
697		exit(1);
698	    }
699	    noreply = optarg;
700#else
701	    fprintf(stderr, "no libmilter support for negotiate callback\n");
702#endif
703	    break;
704	case 'p':
705	    if (smfi_setconn(optarg) == MI_FAILURE) {
706		fprintf(stderr, "smfi_setconn failed\n");
707		exit(1);
708	    }
709	    break;
710	case 'r':
711#ifdef SMFIP_RCPT_REJ
712	    misc_mask |= SMFIP_RCPT_REJ;
713#else
714	    fprintf(stderr, "no libmilter support for rejected recipients\n");
715#endif
716	    break;
717	case 'v':
718	    verbose++;
719	    break;
720	case 'C':
721	    conn_count = atoi(optarg);
722	    break;
723	default:
724	    fprintf(stderr,
725		    "usage: %s [-dv] \n"
726		    "\t[-a action]              non-default action\n"
727		    "\t[-b body_text]           replace body\n"
728		    "\t[-c command]             non-default action trigger\n"
729		    "\t[-h 'index label value'] replace header\n"
730		    "\t[-i 'index label value'] insert header\n"
731		    "\t[-m macro_state]		non-default macro state\n"
732		    "\t[-M macro_list]		non-default macro list\n"
733		    "\t[-n events]		don't receive these events\n"
734		  "\t[-N events]		don't reply to these events\n"
735		    "\t-p port                  milter application\n"
736		  "\t-r                       request rejected recipients\n"
737		    "\t[-C conn_count]          when to exit\n",
738		    argv[0]);
739	    exit(1);
740	}
741    }
742    if (command) {
743	for (cp = command_map; /* see below */ ; cp++) {
744	    if (cp->name == 0) {
745		fprintf(stderr, "bad -c argument: %s\n", command);
746		exit(1);
747	    }
748	    if (strcmp(command, cp->name) == 0)
749		break;
750	}
751    }
752    if (action) {
753	if (command == 0)
754	    cp = command_map;
755	if (strcmp(action, "tempfail") == 0) {
756	    cp->reply[0] = SMFIS_TEMPFAIL;
757	} else if (strcmp(action, "reject") == 0) {
758	    cp->reply[0] = SMFIS_REJECT;
759	} else if (strcmp(action, "accept") == 0) {
760	    cp->reply[0] = SMFIS_ACCEPT;
761	} else if (strcmp(action, "discard") == 0) {
762	    cp->reply[0] = SMFIS_DISCARD;
763#ifdef SMFIS_SKIP
764	} else if (strcmp(action, "skip") == 0) {
765	    cp->reply[0] = SMFIS_SKIP;
766#endif
767	} else if ((code = atoi(action)) >= 400
768		   && code <= 599
769		   && action[3] == ' ') {
770	    cp->reply[0] = SMFIR_REPLYCODE;
771	    reply_code = action;
772	    reply_dsn = action + 3;
773	    if (*reply_dsn != 0) {
774		*reply_dsn++ = 0;
775		reply_dsn += strspn(reply_dsn, " ");
776	    }
777	    if (*reply_dsn == 0) {
778		reply_dsn = reply_message = 0;
779	    } else {
780		reply_message = reply_dsn + strcspn(reply_dsn, " ");
781		if (*reply_message != 0) {
782		    *reply_message++ = 0;
783		    reply_message += strspn(reply_message, " ");
784		}
785		if (*reply_message == 0)
786		    reply_message = 0;
787	    }
788	} else {
789	    fprintf(stderr, "bad -a argument: %s\n", action);
790	    exit(1);
791	}
792	if (verbose) {
793	    printf("command %s action %d\n", cp->name, cp->reply[0]);
794	    if (reply_code)
795		printf("reply code %s dsn %s message %s\n",
796		       reply_code, reply_dsn ? reply_dsn : "(null)",
797		       reply_message ? reply_message : "(null)");
798	}
799    }
800#if SMFI_VERSION > 5
801    if (set_macro_state_arg) {
802	for (cpp = macro_states; /* see below */ ; cpp++) {
803	    if (*cpp == 0) {
804		fprintf(stderr, "bad -m argument: %s\n", set_macro_state_arg);
805		exit(1);
806	    }
807	    if (strcmp(set_macro_state_arg, *cpp) == 0)
808		break;
809	}
810	set_macro_state = cpp - macro_states;
811    }
812    if (nosend) {
813	for (np = noproto_map; /* see below */ ; np++) {
814	    if (np->name == 0) {
815		fprintf(stderr, "bad -n argument: %s\n", nosend);
816		exit(1);
817	    }
818	    if (strcmp(nosend, np->name) == 0)
819		break;
820	}
821	nosend_mask = np->send_mask;
822	np->action[0] = 0;
823    }
824    if (noreply) {
825	for (np = noproto_map; /* see below */ ; np++) {
826	    if (np->name == 0) {
827		fprintf(stderr, "bad -N argument: %s\n", noreply);
828		exit(1);
829	    }
830	    if (strcmp(noreply, np->name) == 0)
831		break;
832	}
833	noreply_mask = np->reply_mask;
834	*np->reply = SMFIS_NOREPLY;
835    }
836#endif
837    if (smfi_register(smfilter) == MI_FAILURE) {
838	fprintf(stderr, "smfi_register failed\n");
839	exit(1);
840    }
841    return (smfi_main());
842}
843