7 /* Simple test mail filter program.
10 /* \fBtest-milter\fR [\fIoptions\fR] -p \fBinet:\fIport\fB@\fIhost\fR
12 /* \fBtest-milter\fR [\fIoptions\fR] -p \fBunix:\fIpathname\fR
14 /* \fBtest-milter\fR is a Milter (mail filter) application that
15 /* exercises selected features.
17 /* Note: this is an unsupported test program. No attempt is made
18 /* to maintain compatibility between successive versions.
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
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.
43 /* Header values include leading space. Specify this option
44 /* before \fB-i\fR or \fB-r\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.
58 /* Request rejected recipients from the MTA.
60 /* Make the program more verbose.
64 /* The Secure Mailer license must be distributed with this software.
67 /* IBM T.J. Watson Research
69 /* Yorktown Heights, NY 10598, USA
72 #include <sys/types.h>
73 #include <sys/socket.h>
74 #include <netinet/in.h>
76 #include <arpa/inet.h>
83 #include "libmilter/mfapi.h"
84 #include "libmilter/mfdef.h"
86 static int conn_count
;
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
;
95 static int test_data_reply
= SMFIS_CONTINUE
;
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
;
104 static int test_unknown_reply
= SMFIS_CONTINUE
;
107 static int test_close_reply
= SMFIS_CONTINUE
;
108 static int test_abort_reply
= SMFIS_CONTINUE
;
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
,
127 "unknown", &test_unknown_reply
,
130 "data", &test_data_reply
,
135 static char *reply_code
;
136 static char *reply_dsn
;
137 static char *reply_message
;
140 static char *chg_from
;
144 #ifdef SMFIR_INSHEADER
145 static char *ins_hdr
;
147 static char *ins_val
;
151 #ifdef SMFIR_CHGHEADER
152 static char *chg_hdr
;
154 static char *chg_val
;
158 #ifdef SMFIR_REPLBODY
159 static char *body_file
;
165 char *rcpt_addr
[MAX_RCPT
];
167 static const char *macro_names
[] = {
180 "{client_connections}",
198 static int test_reply(SMFICTX
*ctx
, int code
)
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. */
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
);
214 printf("test_reply %d\n", code
);
219 static sfsistat
test_connect(SMFICTX
*ctx
, char *name
, struct sockaddr
* sa
)
221 const char *print_addr
;
224 printf("test_connect %s ", name
);
225 switch (sa
->sa_family
) {
228 struct sockaddr_in
*sin
= (struct sockaddr_in
*) sa
;
230 print_addr
= inet_ntop(AF_INET
, &sin
->sin_addr
, buf
, sizeof(buf
));
232 print_addr
= strerror(errno
);
233 printf("AF_INET (%s:%d)\n", print_addr
, ntohs(sin
->sin_port
));
239 struct sockaddr_in6
*sin6
= (struct sockaddr_in6
*) sa
;
241 print_addr
= inet_ntop(AF_INET
, &sin6
->sin6_addr
, buf
, sizeof(buf
));
243 print_addr
= strerror(errno
);
244 printf("AF_INET6 (%s:%d)\n", print_addr
, ntohs(sin6
->sin6_port
));
251 struct sockaddr_un
*sun
= (struct sockaddr_un
*) sa
;
253 printf("AF_UNIX (%s)\n", sun
->sun_path
);
257 printf(" [unknown address family]\n");
260 return (test_reply(ctx
, test_connect_reply
));
263 static sfsistat
test_helo(SMFICTX
*ctx
, char *arg
)
265 printf("test_helo \"%s\"\n", arg
? arg
: "NULL");
266 return (test_reply(ctx
, test_helo_reply
));
269 static sfsistat
test_mail(SMFICTX
*ctx
, char **argv
)
274 for (cpp
= argv
; *cpp
; cpp
++)
275 printf(" \"%s\"", *cpp
);
277 return (test_reply(ctx
, test_mail_reply
));
280 static sfsistat
test_rcpt(SMFICTX
*ctx
, char **argv
)
285 for (cpp
= argv
; *cpp
; cpp
++)
286 printf(" \"%s\"", *cpp
);
288 return (test_reply(ctx
, test_rcpt_reply
));
292 sfsistat
test_header(SMFICTX
*ctx
, char *name
, char *value
)
294 printf("test_header \"%s\" \"%s\"\n", name
, value
);
295 return (test_reply(ctx
, test_header_reply
));
298 static sfsistat
test_eoh(SMFICTX
*ctx
)
300 printf("test_eoh\n");
301 return (test_reply(ctx
, test_eoh_reply
));
304 static sfsistat
test_body(SMFICTX
*ctx
, unsigned char *data
, size_t data_len
)
307 printf("test_body %ld bytes\n", (long) data_len
);
309 printf("%.*s", (int) data_len
, data
);
310 return (test_reply(ctx
, test_body_reply
));
313 static sfsistat
test_eom(SMFICTX
*ctx
)
315 printf("test_eom\n");
316 #ifdef SMFIR_REPLBODY
318 char buf
[BUFSIZ
+ 2];
323 if ((fp
= fopen(body_file
, "r")) == 0) {
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");
331 if (smfi_replacebody(ctx
, buf
, len
+ 2) == MI_FAILURE
) {
332 fprintf(stderr
, "body replace failure\n");
336 printf("%.*s\n", (int) len
, buf
);
345 if (chg_from
!= 0 && smfi_chgfrom(ctx
, chg_from
, "whatever") == MI_FAILURE
)
346 fprintf(stderr
, "smfi_chgfrom failed\n");
348 printf("smfi_chgfrom OK\n");
350 #ifdef SMFIR_INSHEADER
351 if (ins_hdr
&& smfi_insheader(ctx
, ins_idx
, ins_hdr
, ins_val
) == MI_FAILURE
)
352 fprintf(stderr
, "smfi_insheader failed\n");
354 #ifdef SMFIR_CHGHEADER
355 if (chg_hdr
&& smfi_chgheader(ctx
, chg_hdr
, chg_idx
, chg_val
) == MI_FAILURE
)
356 fprintf(stderr
, "smfi_chgheader failed\n");
361 for (count
= 0; count
< rcpt_count
; count
++)
362 if (smfi_addrcpt(ctx
, rcpt_addr
[count
]) == MI_FAILURE
)
363 fprintf(stderr
, "smfi_addrcpt `%s' failed\n", rcpt_addr
[count
]);
365 return (test_reply(ctx
, test_eom_reply
));
368 static sfsistat
test_abort(SMFICTX
*ctx
)
370 printf("test_abort\n");
371 return (test_reply(ctx
, test_abort_reply
));
374 static sfsistat
test_close(SMFICTX
*ctx
)
376 printf("test_close\n");
378 printf("conn_count %d\n", conn_count
);
379 if (conn_count
> 0 && --conn_count
== 0)
381 return (test_reply(ctx
, test_close_reply
));
386 static sfsistat
test_data(SMFICTX
*ctx
)
388 printf("test_data\n");
389 return (test_reply(ctx
, test_data_reply
));
396 static sfsistat
test_unknown(SMFICTX
*ctx
, const char *what
)
398 printf("test_unknown %s\n", what
);
399 return (test_reply(ctx
, test_unknown_reply
));
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 *);
409 static struct smfiDesc smfilter
=
413 SMFIF_ADDRCPT
| SMFIF_DELRCPT
| SMFIF_ADDHDRS
| SMFIF_CHGHDRS
| SMFIF_CHGBODY
| SMFIF_CHGFROM
,
437 static const char *macro_states
[] = {
438 "connect", /* SMFIM_CONNECT */
439 "helo", /* SMFIM_HELO */
440 "mail", /* SMFIM_ENVFROM */
441 "rcpt", /* SMFIM_ENVRCPT */
442 "data", /* SMFIM_DATA */
443 "eom", /* SMFIM_EOM < SMFIM_EOH */
444 "eoh", /* SMFIM_EOH > SMFIM_EOM */
448 static int set_macro_state
;
449 static char *set_macro_list
;
451 typedef sfsistat (*FILTER_ACTION
) ();
458 FILTER_ACTION
*action
;
461 static const struct noproto_map noproto_map
[] = {
462 "connect", SMFIP_NOCONNECT
, SMFIP_NR_CONN
, &test_connect_reply
, &smfilter
.xxfi_connect
,
463 "helo", SMFIP_NOHELO
, SMFIP_NR_HELO
, &test_helo_reply
, &smfilter
.xxfi_helo
,
464 "mail", SMFIP_NOMAIL
, SMFIP_NR_MAIL
, &test_mail_reply
, &smfilter
.xxfi_envfrom
,
465 "rcpt", SMFIP_NORCPT
, SMFIP_NR_RCPT
, &test_rcpt_reply
, &smfilter
.xxfi_envrcpt
,
466 "data", SMFIP_NODATA
, SMFIP_NR_DATA
, &test_data_reply
, &smfilter
.xxfi_data
,
467 "header", SMFIP_NOHDRS
, SMFIP_NR_HDR
, &test_header_reply
, &smfilter
.xxfi_header
,
468 "eoh", SMFIP_NOEOH
, SMFIP_NR_EOH
, &test_eoh_reply
, &smfilter
.xxfi_eoh
,
469 "body", SMFIP_NOBODY
, SMFIP_NR_BODY
, &test_body_reply
, &smfilter
.xxfi_body
,
470 "unknown", SMFIP_NOUNKNOWN
, SMFIP_NR_UNKN
, &test_connect_reply
, &smfilter
.xxfi_unknown
,
474 static int nosend_mask
;
475 static int noreply_mask
;
476 static int misc_mask
;
478 static sfsistat
test_negotiate(SMFICTX
*ctx
,
488 if (set_macro_list
) {
490 printf("set symbol list %s to \"%s\"\n",
491 macro_states
[set_macro_state
], set_macro_list
);
492 smfi_setsymlist(ctx
, set_macro_state
, set_macro_list
);
495 printf("negotiate f0=%lx *pf0 = %lx f1=%lx *pf1=%lx nosend=%lx noreply=%lx misc=%lx\n",
496 f0
, *pf0
, f1
, *pf1
, (long) nosend_mask
, (long) noreply_mask
, (long) misc_mask
);
498 *pf1
= f1
& (nosend_mask
| noreply_mask
| misc_mask
);
499 return (SMFIS_CONTINUE
);
504 static void parse_hdr_info(const char *optarg
, int *idx
,
505 char **hdr
, char **value
)
509 len
= strlen(optarg
) + 1;
510 if ((*hdr
= malloc(len
)) == 0 || (*value
= malloc(len
)) == 0) {
511 fprintf(stderr
, "out of memory\n");
514 if ((misc_mask
& SMFIP_HDR_LEADSPC
) == 0 ?
515 sscanf(optarg
, "%d %s %[^\n]", idx
, *hdr
, *value
) != 3 :
516 sscanf(optarg
, "%d %[^ ]%[^\n]", idx
, *hdr
, *value
) != 3) {
517 fprintf(stderr
, "bad header info: %s\n", optarg
);
522 int main(int argc
, char **argv
)
526 const struct command_map
*cp
;
530 char *set_macro_state_arg
= 0;
533 const struct noproto_map
*np
;
535 while ((ch
= getopt(argc
, argv
, "a:A:b:c:C:d:f:h:i:lm:M:n:N:p:rv")) > 0) {
541 if (rcpt_count
>= MAX_RCPT
) {
542 fprintf(stderr
, "too many -A options\n");
545 rcpt_addr
[rcpt_count
++] = optarg
;
548 #ifdef SMFIR_REPLBODY
550 fprintf(stderr
, "too many -b options\n");
555 fprintf(stderr
, "no libmilter support to replace body\n");
562 if (smfi_setdbg(atoi(optarg
)) == MI_FAILURE
) {
563 fprintf(stderr
, "smfi_setdbg failed\n");
570 fprintf(stderr
, "too many -f options\n");
575 fprintf(stderr
, "no libmilter support to change sender\n");
580 #ifdef SMFIR_CHGHEADER
582 fprintf(stderr
, "too many -h options\n");
585 parse_hdr_info(optarg
, &chg_idx
, &chg_hdr
, &chg_val
);
587 fprintf(stderr
, "no libmilter support to change header\n");
592 #ifdef SMFIR_INSHEADER
594 fprintf(stderr
, "too many -i options\n");
597 parse_hdr_info(optarg
, &ins_idx
, &ins_hdr
, &ins_val
);
599 fprintf(stderr
, "no libmilter support to insert header\n");
605 if (ins_hdr
|| chg_hdr
) {
606 fprintf(stderr
, "specify -l before -i or -r\n");
609 misc_mask
|= SMFIP_HDR_LEADSPC
;
611 fprintf(stderr
, "no libmilter support for leading space\n");
617 if (set_macro_state_arg
) {
618 fprintf(stderr
, "too many -m options\n");
621 set_macro_state_arg
= optarg
;
623 fprintf(stderr
, "no libmilter support to specify macro list\n");
629 if (set_macro_list
) {
630 fprintf(stderr
, "too many -M options\n");
633 set_macro_list
= optarg
;
635 fprintf(stderr
, "no libmilter support to specify macro list\n");
641 fprintf(stderr
, "too many -n options\n");
646 fprintf(stderr
, "no libmilter support for negotiate callback\n");
652 fprintf(stderr
, "too many -n options\n");
657 fprintf(stderr
, "no libmilter support for negotiate callback\n");
661 if (smfi_setconn(optarg
) == MI_FAILURE
) {
662 fprintf(stderr
, "smfi_setconn failed\n");
667 #ifdef SMFIP_RCPT_REJ
668 misc_mask
|= SMFIP_RCPT_REJ
;
670 fprintf(stderr
, "no libmilter support for rejected recipients\n");
677 conn_count
= atoi(optarg
);
682 "\t[-a action] non-default action\n"
683 "\t[-b body_text] replace body\n",
684 "\t[-c command] non-default action trigger\n"
685 "\t[-h 'index label value'] replace header\n"
686 "\t[-i 'index label value'] insert header\n"
687 "\t[-m macro_state] non-default macro state\n"
688 "\t[-M macro_list] non-default macro list\n"
689 "\t[-n events] don't receive these events\n"
690 "\t[-N events] don't reply to these events\n"
691 "\t-p port milter application\n"
692 "\t-r request rejected recipients\n"
693 "\t[-C conn_count] when to exit\n",
699 for (cp
= command_map
; /* see below */ ; cp
++) {
701 fprintf(stderr
, "bad -c argument: %s\n", command
);
704 if (strcmp(command
, cp
->name
) == 0)
711 if (strcmp(action
, "tempfail") == 0) {
712 cp
->reply
[0] = SMFIS_TEMPFAIL
;
713 } else if (strcmp(action
, "reject") == 0) {
714 cp
->reply
[0] = SMFIS_REJECT
;
715 } else if (strcmp(action
, "accept") == 0) {
716 cp
->reply
[0] = SMFIS_ACCEPT
;
717 } else if (strcmp(action
, "discard") == 0) {
718 cp
->reply
[0] = SMFIS_DISCARD
;
720 } else if (strcmp(action
, "skip") == 0) {
721 cp
->reply
[0] = SMFIS_SKIP
;
723 } else if ((code
= atoi(action
)) >= 400
725 && action
[3] == ' ') {
726 cp
->reply
[0] = SMFIR_REPLYCODE
;
728 reply_dsn
= action
+ 3;
729 if (*reply_dsn
!= 0) {
731 reply_dsn
+= strspn(reply_dsn
, " ");
733 if (*reply_dsn
== 0) {
734 reply_dsn
= reply_message
= 0;
736 reply_message
= reply_dsn
+ strcspn(reply_dsn
, " ");
737 if (*reply_message
!= 0) {
738 *reply_message
++ = 0;
739 reply_message
+= strspn(reply_message
, " ");
741 if (*reply_message
== 0)
745 fprintf(stderr
, "bad -a argument: %s\n", action
);
749 printf("command %s action %d\n", cp
->name
, cp
->reply
[0]);
751 printf("reply code %s dsn %s message %s\n",
752 reply_code
, reply_dsn
? reply_dsn
: "(null)",
753 reply_message
? reply_message
: "(null)");
757 if (set_macro_state_arg
) {
758 for (cpp
= macro_states
; /* see below */ ; cpp
++) {
760 fprintf(stderr
, "bad -m argument: %s\n", set_macro_state_arg
);
763 if (strcmp(set_macro_state_arg
, *cpp
) == 0)
766 set_macro_state
= cpp
- macro_states
;
769 for (np
= noproto_map
; /* see below */ ; np
++) {
771 fprintf(stderr
, "bad -n argument: %s\n", nosend
);
774 if (strcmp(nosend
, np
->name
) == 0)
777 nosend_mask
= np
->send_mask
;
781 for (np
= noproto_map
; /* see below */ ; np
++) {
783 fprintf(stderr
, "bad -N argument: %s\n", noreply
);
786 if (strcmp(noreply
, np
->name
) == 0)
789 noreply_mask
= np
->reply_mask
;
790 *np
->reply
= SMFIS_NOREPLY
;
793 if (smfi_register(smfilter
) == MI_FAILURE
) {
794 fprintf(stderr
, "smfi_register failed\n");
797 return (smfi_main());