1 /*        $NetBSD: test-milter.c,v 1.4 2025/02/25 19:15:46 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|quarantine \fItext\fR|\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_REJECT;
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 *quarantine_reason;
145 
146 static char *reply_code;
147 static char *reply_dsn;
148 static char *reply_message;
149 
150 #ifdef SMFIR_CHGFROM
151 static char *chg_from;
152 
153 #endif
154 
155 #ifdef SMFIR_INSHEADER
156 static char *ins_hdr;
157 static int ins_idx;
158 static char *ins_val;
159 
160 #endif
161 
162 #ifdef SMFIR_CHGHEADER
163 static char *chg_hdr;
164 static int chg_idx;
165 static char *chg_val;
166 
167 #endif
168 
169 #ifdef SMFIR_REPLBODY
170 static char *body_file;
171 
172 #endif
173 
174 #define MAX_RCPT    10
175 int     add_rcpt_count = 0;
176 char   *add_rcpt[MAX_RCPT];
177 int     del_rcpt_count = 0;
178 char   *del_rcpt[MAX_RCPT];
179 
180 static const char *macro_names[] = {
181     "_",
182     "i",
183     "j",
184     "v",
185     "{auth_authen}",
186     "{auth_author}",
187     "{auth_type}",
188     "{cert_issuer}",
189     "{cert_subject}",
190     "{cipher}",
191     "{cipher_bits}",
192     "{client_addr}",
193     "{client_connections}",
194     "{client_name}",
195     "{client_port}",
196     "{client_ptr}",
197     "{client_resolve}",
198     "{daemon_addr}",
199     "{daemon_name}",
200     "{daemon_port}",
201     "{if_addr}",
202     "{if_name}",
203     "{mail_addr}",
204     "{mail_host}",
205     "{mail_mailer}",
206     "{rcpt_addr}",
207     "{rcpt_host}",
208     "{rcpt_mailer}",
209     "{tls_version}",
210     0,
211 };
212 
test_reply(SMFICTX * ctx,int code)213 static int test_reply(SMFICTX *ctx, int code)
214 {
215     const char **cpp;
216     const char *symval;
217 
218     for (cpp = macro_names; *cpp; cpp++)
219           if ((symval = smfi_getsymval(ctx, (char *) *cpp)) != 0)
220               printf("macro: %s=\"%s\"\n", *cpp, symval);
221     (void) fflush(stdout);                        /* In case output redirected. */
222 
223     if (code == SMFIR_REPLYCODE) {
224           if (smfi_setmlreply(ctx, reply_code, reply_dsn, reply_message, reply_message, (char *) 0) == MI_FAILURE)
225               fprintf(stderr, "smfi_setmlreply failed\n");
226           printf("test_reply %s\n\n", reply_code);
227           return (reply_code[0] == '4' ? SMFIS_TEMPFAIL : SMFIS_REJECT);
228     } else {
229           printf("test_reply %d\n\n", code);
230           return (code);
231     }
232 }
233 
test_connect(SMFICTX * ctx,char * name,struct sockaddr * sa)234 static sfsistat test_connect(SMFICTX *ctx, char *name, struct sockaddr *sa)
235 {
236     const char *print_addr;
237     char    buf[BUFSIZ];
238 
239     printf("test_connect %s ", name);
240     switch (sa->sa_family) {
241     case AF_INET:
242           {
243               struct sockaddr_in *sin = (struct sockaddr_in *) sa;
244 
245               print_addr = inet_ntop(AF_INET, &sin->sin_addr, buf, sizeof(buf));
246               if (print_addr == 0)
247                     print_addr = strerror(errno);
248               printf("AF_INET (%s:%d)\n", print_addr, ntohs(sin->sin_port));
249           }
250           break;
251 #ifdef HAS_IPV6
252     case AF_INET6:
253           {
254               struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *) sa;
255 
256               print_addr = inet_ntop(AF_INET, &sin6->sin6_addr, buf, sizeof(buf));
257               if (print_addr == 0)
258                     print_addr = strerror(errno);
259               printf("AF_INET6 (%s:%d)\n", print_addr, ntohs(sin6->sin6_port));
260           }
261           break;
262 #endif
263     case AF_UNIX:
264           {
265 #undef sun
266               struct sockaddr_un *sun = (struct sockaddr_un *) sa;
267 
268               printf("AF_UNIX (%s)\n", sun->sun_path);
269           }
270           break;
271     default:
272           printf(" [unknown address family]\n");
273           break;
274     }
275     return (test_reply(ctx, test_connect_reply));
276 }
277 
test_helo(SMFICTX * ctx,char * arg)278 static sfsistat test_helo(SMFICTX *ctx, char *arg)
279 {
280     printf("test_helo \"%s\"\n", arg ? arg : "NULL");
281     return (test_reply(ctx, test_helo_reply));
282 }
283 
test_mail(SMFICTX * ctx,char ** argv)284 static sfsistat test_mail(SMFICTX *ctx, char **argv)
285 {
286     char  **cpp;
287 
288     printf("test_mail");
289     for (cpp = argv; *cpp; cpp++)
290           printf(" \"%s\"", *cpp);
291     printf("\n");
292     return (test_reply(ctx, test_mail_reply));
293 }
294 
test_rcpt(SMFICTX * ctx,char ** argv)295 static sfsistat test_rcpt(SMFICTX *ctx, char **argv)
296 {
297     char  **cpp;
298 
299     printf("test_rcpt");
300     for (cpp = argv; *cpp; cpp++)
301           printf(" \"%s\"", *cpp);
302     printf("\n");
303     return (test_reply(ctx, test_rcpt_reply));
304 }
305 
306 
test_header(SMFICTX * ctx,char * name,char * value)307 sfsistat test_header(SMFICTX *ctx, char *name, char *value)
308 {
309     printf("test_header \"%s\" \"%s\"\n", name, value);
310     return (test_reply(ctx, test_header_reply));
311 }
312 
test_eoh(SMFICTX * ctx)313 static sfsistat test_eoh(SMFICTX *ctx)
314 {
315     printf("test_eoh\n");
316     return (test_reply(ctx, test_eoh_reply));
317 }
318 
test_body(SMFICTX * ctx,unsigned char * data,size_t data_len)319 static sfsistat test_body(SMFICTX *ctx, unsigned char *data, size_t data_len)
320 {
321     if (verbose == 0)
322           printf("test_body %ld bytes\n", (long) data_len);
323     else
324           printf("%.*s", (int) data_len, data);
325     return (test_reply(ctx, test_body_reply));
326 }
327 
test_eom(SMFICTX * ctx)328 static sfsistat test_eom(SMFICTX *ctx)
329 {
330     printf("test_eom\n");
331 #ifdef SMFIR_REPLBODY
332     if (body_file) {
333           char    buf[BUFSIZ + 2];
334           FILE   *fp;
335           size_t  len;
336           int     count;
337 
338           if ((fp = fopen(body_file, "r")) == 0) {
339               perror(body_file);
340           } else {
341               printf("replace body with content of %s\n", body_file);
342               for (count = 0; fgets(buf, BUFSIZ, fp) != 0; count++) {
343                     len = strcspn(buf, "\n");
344                     buf[len + 0] = '\r';
345                     buf[len + 1] = '\n';
346                     if (smfi_replacebody(ctx, buf, len + 2) == MI_FAILURE) {
347                         fprintf(stderr, "body replace failure\n");
348                         exit(1);
349                     }
350                     if (verbose)
351                         printf("%.*s\n", (int) len, buf);
352               }
353               if (count == 0)
354                     perror("fgets");
355               (void) fclose(fp);
356           }
357     }
358 #endif
359 #ifdef SMFIR_CHGFROM
360     if (chg_from != 0 && smfi_chgfrom(ctx, chg_from, "whatever") == MI_FAILURE)
361           fprintf(stderr, "smfi_chgfrom failed\n");
362 #endif
363 #ifdef SMFIR_INSHEADER
364     if (ins_hdr && smfi_insheader(ctx, ins_idx, ins_hdr, ins_val) == MI_FAILURE)
365           fprintf(stderr, "smfi_insheader failed\n");
366 #endif
367 #ifdef SMFIR_CHGHEADER
368     if (chg_hdr && smfi_chgheader(ctx, chg_hdr, chg_idx, chg_val) == MI_FAILURE)
369           fprintf(stderr, "smfi_chgheader failed\n");
370 #endif
371     {
372           int     count;
373           char   *args;
374 
375           for (count = 0; count < add_rcpt_count; count++) {
376               if ((args = strchr(add_rcpt[count], ' ')) != 0) {
377                     *args++ = 0;
378                     if (smfi_addrcpt_par(ctx, add_rcpt[count], args) == MI_FAILURE)
379                         fprintf(stderr, "smfi_addrcpt_par `%s' `%s' failed\n",
380                                   add_rcpt[count], args);
381               } else {
382                     if (smfi_addrcpt(ctx, add_rcpt[count]) == MI_FAILURE)
383                         fprintf(stderr, "smfi_addrcpt `%s' failed\n",
384                                   add_rcpt[count]);
385               }
386           }
387 
388           for (count = 0; count < del_rcpt_count; count++)
389               if (smfi_delrcpt(ctx, del_rcpt[count]) == MI_FAILURE)
390                     fprintf(stderr, "smfi_delrcpt `%s' failed\n", del_rcpt[count]);
391     }
392     if (quarantine_reason) {
393           if (smfi_quarantine(ctx, quarantine_reason) == MI_FAILURE)
394               fprintf(stderr, "smfi_quarantine failed\n");
395           printf("quarantine '%s'\n", quarantine_reason);
396     }
397     return (test_reply(ctx, test_eom_reply));
398 }
399 
test_abort(SMFICTX * ctx)400 static sfsistat test_abort(SMFICTX *ctx)
401 {
402     printf("test_abort\n");
403     return (test_reply(ctx, test_abort_reply));
404 }
405 
test_close(SMFICTX * ctx)406 static sfsistat test_close(SMFICTX *ctx)
407 {
408     printf("test_close\n");
409     if (verbose)
410           printf("conn_count %d\n", conn_count);
411     if (conn_count > 0 && --conn_count == 0)
412           exit(0);
413     return (test_reply(ctx, test_close_reply));
414 }
415 
416 #if SMFI_VERSION > 3
417 
test_data(SMFICTX * ctx)418 static sfsistat test_data(SMFICTX *ctx)
419 {
420     printf("test_data\n");
421     return (test_reply(ctx, test_data_reply));
422 }
423 
424 #endif
425 
426 #if SMFI_VERSION > 2
427 
test_unknown(SMFICTX * ctx,const char * what)428 static sfsistat test_unknown(SMFICTX *ctx, const char *what)
429 {
430     printf("test_unknown %s\n", what);
431     return (test_reply(ctx, test_unknown_reply));
432 }
433 
434 #endif
435 
436 #if SMFI_VERSION > 5
437 
438 static sfsistat test_negotiate(SMFICTX *, unsigned long, unsigned long,
439                                              unsigned long, unsigned long,
440                                              unsigned long *, unsigned long *,
441                                              unsigned long *, unsigned long *);
442 
443 #endif
444 
445 #ifndef SMFIF_CHGFROM
446 #define SMFIF_CHGFROM 0
447 #endif
448 #ifndef SMFIP_HDR_LEADSPC
449 #define SMFIP_HDR_LEADSPC 0
450 #define misc_mask 0
451 #endif
452 
453 static struct smfiDesc smfilter =
454 {
455     "test-milter",
456     SMFI_VERSION,
457     SMFIF_ADDRCPT | SMFIF_DELRCPT | SMFIF_ADDHDRS | SMFIF_CHGHDRS | SMFIF_CHGBODY | SMFIF_CHGFROM | SMFIF_QUARANTINE,
458     test_connect,
459     test_helo,
460     test_mail,
461     test_rcpt,
462     test_header,
463     test_eoh,
464     test_body,
465     test_eom,
466     test_abort,
467     test_close,
468 #if SMFI_VERSION > 2
469     test_unknown,
470 #endif
471 #if SMFI_VERSION > 3
472     test_data,
473 #endif
474 #if SMFI_VERSION > 5
475     test_negotiate,
476 #endif
477 };
478 
479 #if SMFI_VERSION > 5
480 
481 static const char *macro_states[] = {
482     "connect",                                    /* SMFIM_CONNECT */
483     "helo",                                       /* SMFIM_HELO */
484     "mail",                                       /* SMFIM_ENVFROM */
485     "rcpt",                                       /* SMFIM_ENVRCPT */
486     "data",                                       /* SMFIM_DATA */
487     "eom",                                        /* SMFIM_EOM < SMFIM_EOH */
488     "eoh",                                        /* SMFIM_EOH > SMFIM_EOM */
489     0,
490 };
491 
492 static int set_macro_state;
493 static char *set_macro_list;
494 
495 typedef sfsistat (*FILTER_ACTION) ();
496 
497 struct noproto_map {
498     const char *name;
499     int     send_mask;
500     int     reply_mask;
501     int    *reply;
502     FILTER_ACTION *action;
503 };
504 
505 static const struct noproto_map noproto_map[] = {
506     "connect", SMFIP_NOCONNECT, SMFIP_NR_CONN, &test_connect_reply, &smfilter.xxfi_connect,
507     "helo", SMFIP_NOHELO, SMFIP_NR_HELO, &test_helo_reply, &smfilter.xxfi_helo,
508     "mail", SMFIP_NOMAIL, SMFIP_NR_MAIL, &test_mail_reply, &smfilter.xxfi_envfrom,
509     "rcpt", SMFIP_NORCPT, SMFIP_NR_RCPT, &test_rcpt_reply, &smfilter.xxfi_envrcpt,
510     "data", SMFIP_NODATA, SMFIP_NR_DATA, &test_data_reply, &smfilter.xxfi_data,
511     "header", SMFIP_NOHDRS, SMFIP_NR_HDR, &test_header_reply, &smfilter.xxfi_header,
512     "eoh", SMFIP_NOEOH, SMFIP_NR_EOH, &test_eoh_reply, &smfilter.xxfi_eoh,
513     "body", SMFIP_NOBODY, SMFIP_NR_BODY, &test_body_reply, &smfilter.xxfi_body,
514     "unknown", SMFIP_NOUNKNOWN, SMFIP_NR_UNKN, &test_unknown_reply, &smfilter.xxfi_unknown,
515     0,
516 };
517 
518 static int nosend_mask;
519 static int noreply_mask;
520 static int misc_mask;
521 
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)522 static sfsistat test_negotiate(SMFICTX *ctx,
523                                              unsigned long f0,
524                                              unsigned long f1,
525                                              unsigned long f2,
526                                              unsigned long f3,
527                                              unsigned long *pf0,
528                                              unsigned long *pf1,
529                                              unsigned long *pf2,
530                                              unsigned long *pf3)
531 {
532     if (set_macro_list) {
533           if (verbose)
534               printf("set symbol list %s to \"%s\"\n",
535                        macro_states[set_macro_state], set_macro_list);
536           smfi_setsymlist(ctx, set_macro_state, set_macro_list);
537     }
538     if (verbose)
539           printf("negotiate f0=%lx *pf0 = %lx f1=%lx *pf1=%lx nosend=%lx noreply=%lx misc=%lx\n",
540                  f0, *pf0, f1, *pf1, (long) nosend_mask, (long) noreply_mask, (long) misc_mask);
541     *pf0 = f0;
542     *pf1 = f1 & (nosend_mask | noreply_mask | misc_mask);
543     return (SMFIS_CONTINUE);
544 }
545 
546 #endif
547 
parse_hdr_info(const char * optarg,int * idx,char ** hdr,char ** value)548 static void parse_hdr_info(const char *optarg, int *idx,
549                                          char **hdr, char **value)
550 {
551     int     len;
552 
553     len = strlen(optarg) + 1;
554     if ((*hdr = malloc(len)) == 0 || (*value = malloc(len)) == 0) {
555           fprintf(stderr, "out of memory\n");
556           exit(1);
557     }
558     if ((misc_mask & SMFIP_HDR_LEADSPC) == 0 ?
559           sscanf(optarg, "%d %s %[^\n]", idx, *hdr, *value) != 3 :
560           sscanf(optarg, "%d %[^ ]%[^\n]", idx, *hdr, *value) != 3) {
561           fprintf(stderr, "bad header info: %s\n", optarg);
562           exit(1);
563     }
564 }
565 
main(int argc,char ** argv)566 int     main(int argc, char **argv)
567 {
568     char   *action = 0;
569     char   *command = 0;
570     const struct command_map *cp;
571     int     ch;
572     int     code;
573     const char **cpp;
574     char   *set_macro_state_arg = 0;
575     char   *nosend = 0;
576     char   *noreply = 0;
577     const struct noproto_map *np;
578 
579     while ((ch = getopt(argc, argv, "a:A:b:c:C:d:D:f:h:i:lm:M:n:N:p:rv")) > 0) {
580           switch (ch) {
581           case 'a':
582               if (action != 0)
583                     fprintf(stderr, "ignoring extra -a option\n");
584               else
585                     action = optarg;
586               break;
587           case 'A':
588               if (add_rcpt_count >= MAX_RCPT) {
589                     fprintf(stderr, "too many -A options\n");
590                     exit(1);
591               }
592               add_rcpt[add_rcpt_count++] = optarg;
593               break;
594           case 'b':
595 #ifdef SMFIR_REPLBODY
596               if (body_file) {
597                     fprintf(stderr, "too many -b options\n");
598                     exit(1);
599               }
600               body_file = optarg;
601 #else
602               fprintf(stderr, "no libmilter support to replace body\n");
603 #endif
604               break;
605           case 'c':
606               command = optarg;
607               break;
608           case 'd':
609               if (smfi_setdbg(atoi(optarg)) == MI_FAILURE) {
610                     fprintf(stderr, "smfi_setdbg failed\n");
611                     exit(1);
612               }
613               break;
614           case 'D':
615               if (del_rcpt_count >= MAX_RCPT) {
616                     fprintf(stderr, "too many -D options\n");
617                     exit(1);
618               }
619               del_rcpt[del_rcpt_count++] = optarg;
620               break;
621           case 'f':
622 #ifdef SMFIR_CHGFROM
623               if (chg_from) {
624                     fprintf(stderr, "too many -f options\n");
625                     exit(1);
626               }
627               chg_from = optarg;
628 #else
629               fprintf(stderr, "no libmilter support to change sender\n");
630               exit(1);
631 #endif
632               break;
633           case 'h':
634 #ifdef SMFIR_CHGHEADER
635               if (chg_hdr) {
636                     fprintf(stderr, "too many -h options\n");
637                     exit(1);
638               }
639               parse_hdr_info(optarg, &chg_idx, &chg_hdr, &chg_val);
640 #else
641               fprintf(stderr, "no libmilter support to change header\n");
642               exit(1);
643 #endif
644               break;
645           case 'i':
646 #ifdef SMFIR_INSHEADER
647               if (ins_hdr) {
648                     fprintf(stderr, "too many -i options\n");
649                     exit(1);
650               }
651               parse_hdr_info(optarg, &ins_idx, &ins_hdr, &ins_val);
652 #else
653               fprintf(stderr, "no libmilter support to insert header\n");
654               exit(1);
655 #endif
656               break;
657           case 'l':
658 #if SMFI_VERSION > 5
659               if (ins_hdr || chg_hdr) {
660                     fprintf(stderr, "specify -l before -i or -r\n");
661                     exit(1);
662               }
663               misc_mask |= SMFIP_HDR_LEADSPC;
664 #else
665               fprintf(stderr, "no libmilter support for leading space\n");
666               exit(1);
667 #endif
668               break;
669           case 'm':
670 #if SMFI_VERSION > 5
671               if (set_macro_state_arg) {
672                     fprintf(stderr, "too many -m options\n");
673                     exit(1);
674               }
675               set_macro_state_arg = optarg;
676 #else
677               fprintf(stderr, "no libmilter support to specify macro list\n");
678               exit(1);
679 #endif
680               break;
681           case 'M':
682 #if SMFI_VERSION > 5
683               if (set_macro_list) {
684                     fprintf(stderr, "too many -M options\n");
685                     exit(1);
686               }
687               set_macro_list = optarg;
688 #else
689               fprintf(stderr, "no libmilter support to specify macro list\n");
690 #endif
691               break;
692           case 'n':
693 #if SMFI_VERSION > 5
694               if (nosend) {
695                     fprintf(stderr, "too many -n options\n");
696                     exit(1);
697               }
698               nosend = optarg;
699 #else
700               fprintf(stderr, "no libmilter support for negotiate callback\n");
701 #endif
702               break;
703           case 'N':
704 #if SMFI_VERSION > 5
705               if (noreply) {
706                     fprintf(stderr, "too many -n options\n");
707                     exit(1);
708               }
709               noreply = optarg;
710 #else
711               fprintf(stderr, "no libmilter support for negotiate callback\n");
712 #endif
713               break;
714           case 'p':
715               if (smfi_setconn(optarg) == MI_FAILURE) {
716                     fprintf(stderr, "smfi_setconn failed\n");
717                     exit(1);
718               }
719               break;
720           case 'r':
721 #ifdef SMFIP_RCPT_REJ
722               misc_mask |= SMFIP_RCPT_REJ;
723 #else
724               fprintf(stderr, "no libmilter support for rejected recipients\n");
725 #endif
726               break;
727           case 'v':
728               verbose++;
729               break;
730           case 'C':
731               conn_count = atoi(optarg);
732               break;
733           default:
734               fprintf(stderr,
735                         "usage: %s [-dv] \n"
736                         "\t[-a action]              non-default action\n"
737                         "\t[-b body_text]           replace body\n"
738                         "\t[-c command]             non-default action trigger\n"
739                         "\t[-h 'index label value'] replace header\n"
740                         "\t[-i 'index label value'] insert header\n"
741                         "\t[-m macro_state]                 non-default macro state\n"
742                         "\t[-M macro_list]                  non-default macro list\n"
743                         "\t[-n events]            don't receive these events\n"
744                       "\t[-N events]              don't reply to these events\n"
745                         "\t-p port                  milter application\n"
746                       "\t-r                       request rejected recipients\n"
747                         "\t[-C conn_count]          when to exit\n",
748                         argv[0]);
749               exit(1);
750           }
751     }
752     if (command) {
753           for (cp = command_map; /* see below */ ; cp++) {
754               if (cp->name == 0) {
755                     fprintf(stderr, "bad -c argument: %s\n", command);
756                     exit(1);
757               }
758               if (strcmp(command, cp->name) == 0)
759                     break;
760           }
761     }
762     if (action) {
763           if (command == 0)
764               cp = command_map;
765           if (strcmp(action, "tempfail") == 0) {
766               cp->reply[0] = SMFIS_TEMPFAIL;
767           } else if (strcmp(action, "reject") == 0) {
768               cp->reply[0] = SMFIS_REJECT;
769           } else if (strcmp(action, "accept") == 0) {
770               cp->reply[0] = SMFIS_ACCEPT;
771           } else if (strcmp(action, "discard") == 0) {
772               cp->reply[0] = SMFIS_DISCARD;
773           } else if (strncmp(action, "quarantine ", 11) == 0) {
774               if (strcmp(command, "eom") != 0) {
775                     fprintf(stderr, "quarantine action requires '-c eom'\n");
776                     exit(1);
777               }
778               quarantine_reason = action + 11;
779               quarantine_reason += strspn(quarantine_reason, " ");
780 #ifdef SMFIS_SKIP
781           } else if (strcmp(action, "skip") == 0) {
782               cp->reply[0] = SMFIS_SKIP;
783 #endif
784           } else if ((code = atoi(action)) >= 400
785                        && code <= 599
786                        && action[3] == ' ') {
787               cp->reply[0] = SMFIR_REPLYCODE;
788               reply_code = action;
789               reply_dsn = action + 3;
790               if (*reply_dsn != 0) {
791                     *reply_dsn++ = 0;
792                     reply_dsn += strspn(reply_dsn, " ");
793               }
794               if (*reply_dsn == 0) {
795                     reply_dsn = reply_message = 0;
796               } else {
797                     reply_message = reply_dsn + strcspn(reply_dsn, " ");
798                     if (*reply_message != 0) {
799                         *reply_message++ = 0;
800                         reply_message += strspn(reply_message, " ");
801                     }
802                     if (*reply_message == 0)
803                         reply_message = 0;
804               }
805           } else {
806               fprintf(stderr, "bad -a argument: %s\n", action);
807               exit(1);
808           }
809           if (verbose) {
810               printf("command %s action %d\n", cp->name, cp->reply[0]);
811               if (reply_code)
812                     printf("reply code %s dsn %s message %s\n",
813                            reply_code, reply_dsn ? reply_dsn : "(null)",
814                            reply_message ? reply_message : "(null)");
815               if (quarantine_reason)
816                     printf("quarantine reason %s\n", quarantine_reason);
817           }
818     }
819 #if SMFI_VERSION > 5
820     if (set_macro_state_arg) {
821           for (cpp = macro_states; /* see below */ ; cpp++) {
822               if (*cpp == 0) {
823                     fprintf(stderr, "bad -m argument: %s\n", set_macro_state_arg);
824                     exit(1);
825               }
826               if (strcmp(set_macro_state_arg, *cpp) == 0)
827                     break;
828           }
829           set_macro_state = cpp - macro_states;
830     }
831     if (nosend) {
832           for (np = noproto_map; /* see below */ ; np++) {
833               if (np->name == 0) {
834                     fprintf(stderr, "bad -n argument: %s\n", nosend);
835                     exit(1);
836               }
837               if (strcmp(nosend, np->name) == 0)
838                     break;
839           }
840           nosend_mask = np->send_mask;
841           np->action[0] = 0;
842     }
843     if (noreply) {
844           for (np = noproto_map; /* see below */ ; np++) {
845               if (np->name == 0) {
846                     fprintf(stderr, "bad -N argument: %s\n", noreply);
847                     exit(1);
848               }
849               if (strcmp(noreply, np->name) == 0)
850                     break;
851           }
852           noreply_mask = np->reply_mask;
853           *np->reply = SMFIS_NOREPLY;
854     }
855 #endif
856     if (smfi_register(smfilter) == MI_FAILURE) {
857           fprintf(stderr, "smfi_register failed\n");
858           exit(1);
859     }
860     return (smfi_main());
861 }
862