xref: /netbsd-src/external/ibm-public/postfix/dist/src/milter/test-milter.c (revision 33881f779a77dce6440bdc44610d94de75bebefe)
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 
95 static int conn_count;
96 static int verbose;
97 
98 static int test_connect_reply = SMFIS_CONTINUE;
99 static int test_helo_reply = SMFIS_CONTINUE;
100 static int test_mail_reply = SMFIS_CONTINUE;
101 static int test_rcpt_reply = SMFIS_CONTINUE;
102 
103 #if SMFI_VERSION > 3
104 static int test_data_reply = SMFIS_CONTINUE;
105 
106 #endif
107 static int test_header_reply = SMFIS_CONTINUE;
108 static int test_eoh_reply = SMFIS_CONTINUE;
109 static int test_body_reply = SMFIS_CONTINUE;
110 static int test_eom_reply = SMFIS_CONTINUE;
111 
112 #if SMFI_VERSION > 2
113 static int test_unknown_reply = SMFIS_CONTINUE;
114 
115 #endif
116 static int test_close_reply = SMFIS_CONTINUE;
117 static int test_abort_reply = SMFIS_CONTINUE;
118 
119 struct command_map {
120     const char *name;
121     int    *reply;
122 };
123 
124 static 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 
144 static char *reply_code;
145 static char *reply_dsn;
146 static char *reply_message;
147 
148 #ifdef SMFIR_CHGFROM
149 static char *chg_from;
150 
151 #endif
152 
153 #ifdef SMFIR_INSHEADER
154 static char *ins_hdr;
155 static int ins_idx;
156 static char *ins_val;
157 
158 #endif
159 
160 #ifdef SMFIR_CHGHEADER
161 static char *chg_hdr;
162 static int chg_idx;
163 static char *chg_val;
164 
165 #endif
166 
167 #ifdef SMFIR_REPLBODY
168 static char *body_file;
169 
170 #endif
171 
172 #define MAX_RCPT	10
173 int     add_rcpt_count = 0;
174 char   *add_rcpt[MAX_RCPT];
175 int     del_rcpt_count = 0;
176 char   *del_rcpt[MAX_RCPT];
177 
178 static 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 
test_reply(SMFICTX * ctx,int code)211 static 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 
test_connect(SMFICTX * ctx,char * name,struct sockaddr * sa)232 static 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 
test_helo(SMFICTX * ctx,char * arg)276 static 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 
test_mail(SMFICTX * ctx,char ** argv)282 static 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 
test_rcpt(SMFICTX * ctx,char ** argv)293 static 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 
test_header(SMFICTX * ctx,char * name,char * value)305 sfsistat 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 
test_eoh(SMFICTX * ctx)311 static sfsistat test_eoh(SMFICTX *ctx)
312 {
313     printf("test_eoh\n");
314     return (test_reply(ctx, test_eoh_reply));
315 }
316 
test_body(SMFICTX * ctx,unsigned char * data,size_t data_len)317 static 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 
test_eom(SMFICTX * ctx)326 static 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 
test_abort(SMFICTX * ctx)393 static sfsistat test_abort(SMFICTX *ctx)
394 {
395     printf("test_abort\n");
396     return (test_reply(ctx, test_abort_reply));
397 }
398 
test_close(SMFICTX * ctx)399 static 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 
test_data(SMFICTX * ctx)411 static 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 
test_unknown(SMFICTX * ctx,const char * what)421 static 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 
431 static 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 
446 static 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 
474 static 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 
485 static int set_macro_state;
486 static char *set_macro_list;
487 
488 typedef sfsistat (*FILTER_ACTION) ();
489 
490 struct noproto_map {
491     const char *name;
492     int     send_mask;
493     int     reply_mask;
494     int    *reply;
495     FILTER_ACTION *action;
496 };
497 
498 static 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 
511 static int nosend_mask;
512 static int noreply_mask;
513 static int misc_mask;
514 
test_negotiate(SMFICTX * ctx,unsigned long f0,unsigned long f1,unsigned long f2,unsigned long f3,unsigned long * pf0,unsigned long * pf1,unsigned long * pf2,unsigned long * pf3)515 static 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 
parse_hdr_info(const char * optarg,int * idx,char ** hdr,char ** value)541 static 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 
main(int argc,char ** argv)559 int     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