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