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