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