int main(int unused_argc, char **argv) { VSTRING *buffer = vstring_alloc(1); msg_vstream_init(argv[0], VSTREAM_ERR); msg_verbose = 1; while (vstring_fgets_nonl(buffer, VSTREAM_IN)) { msg_info("testing: \"%s\"", vstring_str(buffer)); if (vstring_str(buffer)[0] == '[') valid_mailhost_literal(vstring_str(buffer), DO_GRIPE); else valid_mailhost_addr(vstring_str(buffer), DO_GRIPE); } exit(0); }
int main(int argc, char **argv) { struct stat st; char *slash; int c; int fd; int mode = PQ_MODE_DEFAULT; char *site_to_flush = 0; char *id_to_flush = 0; ARGV *import_env; int bad_site; /* * Fingerprint executables and core dumps. */ MAIL_VERSION_STAMP_ALLOCATE; /* * Be consistent with file permissions. */ umask(022); /* * To minimize confusion, make sure that the standard file descriptors * are open before opening anything else. XXX Work around for 44BSD where * fstat can return EBADF on an open file descriptor. */ for (fd = 0; fd < 3; fd++) if (fstat(fd, &st) == -1 && (close(fd), open("/dev/null", O_RDWR, 0)) != fd) msg_fatal_status(EX_UNAVAILABLE, "open /dev/null: %m"); /* * Initialize. Set up logging, read the global configuration file and * extract configuration information. Set up signal handlers so that we * can clean up incomplete output. */ if ((slash = strrchr(argv[0], '/')) != 0 && slash[1]) argv[0] = slash + 1; msg_vstream_init(argv[0], VSTREAM_ERR); msg_cleanup(unavailable); msg_syslog_init(mail_task("postqueue"), LOG_PID, LOG_FACILITY); set_mail_conf_str(VAR_PROCNAME, var_procname = mystrdup(argv[0])); /* * Check the Postfix library version as soon as we enable logging. */ MAIL_VERSION_CHECK; /* * Parse JCL. This program is set-gid and must sanitize all command-line * parameters. The configuration directory argument is validated by the * mail configuration read routine. Don't do complex things until we have * completed initializations. */ while ((c = GETOPT(argc, argv, "c:fi:ps:v")) > 0) { switch (c) { case 'c': /* non-default configuration */ if (setenv(CONF_ENV_PATH, optarg, 1) < 0) msg_fatal_status(EX_UNAVAILABLE, "out of memory"); break; case 'f': /* flush queue */ if (mode != PQ_MODE_DEFAULT) usage(); mode = PQ_MODE_FLUSH_QUEUE; break; case 'i': /* flush queue file */ if (mode != PQ_MODE_DEFAULT) usage(); mode = PQ_MODE_FLUSH_FILE; id_to_flush = optarg; break; case 'p': /* traditional mailq */ if (mode != PQ_MODE_DEFAULT) usage(); mode = PQ_MODE_MAILQ_LIST; break; case 's': /* flush site */ if (mode != PQ_MODE_DEFAULT) usage(); mode = PQ_MODE_FLUSH_SITE; site_to_flush = optarg; break; case 'v': if (geteuid() == 0) msg_verbose++; break; default: usage(); } } if (argc > optind) usage(); /* * Further initialization... */ mail_conf_read(); /* Re-evaluate mail_task() after reading main.cf. */ msg_syslog_init(mail_task("postqueue"), LOG_PID, LOG_FACILITY); mail_dict_init(); /* proxy, sql, ldap */ get_mail_conf_str_table(str_table); /* * This program is designed to be set-gid, which makes it a potential * target for attack. If not running as root, strip the environment so we * don't have to trust the C library. If running as root, don't strip the * environment so that showq can receive non-default configuration * directory info when the mail system is down. */ if (geteuid() != 0) { import_env = mail_parm_split(VAR_IMPORT_ENVIRON, var_import_environ); clean_env(import_env->argv); argv_free(import_env); } if (chdir(var_queue_dir)) msg_fatal_status(EX_UNAVAILABLE, "chdir %s: %m", var_queue_dir); signal(SIGPIPE, SIG_IGN); /* End of initializations. */ /* * Further input validation. */ if (site_to_flush != 0) { bad_site = 0; if (*site_to_flush == '[') { bad_site = !valid_mailhost_literal(site_to_flush, DONT_GRIPE); } else { bad_site = !valid_hostname(site_to_flush, DONT_GRIPE); } if (bad_site) msg_fatal_status(EX_USAGE, "Cannot flush mail queue - invalid destination: \"%.100s%s\"", site_to_flush, strlen(site_to_flush) > 100 ? "..." : ""); } if (id_to_flush != 0) { if (!mail_queue_id_ok(id_to_flush)) msg_fatal_status(EX_USAGE, "Cannot flush queue ID - invalid name: \"%.100s%s\"", id_to_flush, strlen(id_to_flush) > 100 ? "..." : ""); } /* * Start processing. */ switch (mode) { default: msg_panic("unknown operation mode: %d", mode); /* NOTREACHED */ case PQ_MODE_MAILQ_LIST: show_queue(); exit(0); break; case PQ_MODE_FLUSH_SITE: flush_site(site_to_flush); exit(0); break; case PQ_MODE_FLUSH_FILE: flush_file(id_to_flush); exit(0); break; case PQ_MODE_FLUSH_QUEUE: flush_queue(); exit(0); break; case PQ_MODE_DEFAULT: usage(); /* NOTREACHED */ } }
int main(int argc, char **argv) { SESSION *session; char *host; char *port; char *path; int path_len; int sessions = 1; int ch; int i; char *buf; const char *parse_err; struct addrinfo *res; int aierr; const char *protocols = INET_PROTO_NAME_ALL; INET_PROTO_INFO *proto_info; char *message_file = 0; /* * Fingerprint executables and core dumps. */ MAIL_VERSION_STAMP_ALLOCATE; signal(SIGPIPE, SIG_IGN); msg_vstream_init(argv[0], VSTREAM_ERR); /* * Parse JCL. */ while ((ch = GETOPT(argc, argv, "46AcC:df:F:l:Lm:M:Nor:R:s:S:t:T:vw:")) > 0) { switch (ch) { case '4': protocols = INET_PROTO_NAME_IPV4; break; case '6': protocols = INET_PROTO_NAME_IPV6; break; case 'A': allow_reject = 1; break; case 'c': count++; break; case 'C': if ((connect_count = atoi(optarg)) <= 0) msg_fatal("bad connection count: %s", optarg); break; case 'd': disconnect = 0; break; case 'f': sender = optarg; break; case 'F': if (message_file == 0 && message_length > 0) msg_fatal("-l option cannot be used with -F"); message_file = optarg; break; case 'l': if (message_file != 0) msg_fatal("-l option cannot be used with -F"); if ((message_length = atoi(optarg)) <= 0) msg_fatal("bad message length: %s", optarg); break; case 'L': talk_lmtp = 1; break; case 'm': if ((message_count = atoi(optarg)) <= 0) msg_fatal("bad message count: %s", optarg); break; case 'M': if (*optarg == '[') { if (!valid_mailhost_literal(optarg, DO_GRIPE)) msg_fatal("bad address literal: %s", optarg); } else { if (!valid_hostname(optarg, DO_GRIPE)) msg_fatal("bad hostname: %s", optarg); } var_myhostname = optarg; break; case 'N': number_rcpts = 1; break; case 'o': send_helo_first = 0; send_headers = 0; break; case 'r': if ((recipients = atoi(optarg)) <= 0) msg_fatal("bad recipient count: %s", optarg); break; case 'R': if (fixed_delay > 0) msg_fatal("do not use -w and -R options at the same time"); if ((random_delay = atoi(optarg)) <= 0) msg_fatal("bad random delay: %s", optarg); break; case 's': if ((sessions = atoi(optarg)) <= 0) msg_fatal("bad session count: %s", optarg); break; case 'S': subject = optarg; break; case 't': recipient = optarg; break; case 'T': if ((inet_windowsize = atoi(optarg)) <= 0) msg_fatal("bad TCP window size: %s", optarg); break; case 'v': msg_verbose++; break; case 'w': if (random_delay > 0) msg_fatal("do not use -w and -R options at the same time"); if ((fixed_delay = atoi(optarg)) <= 0) msg_fatal("bad fixed delay: %s", optarg); break; default: usage(argv[0]); } } if (argc - optind != 1) usage(argv[0]); if (random_delay > 0) srand(getpid()); /* * Initialize the message content, SMTP encoded. smtp_fputs() will append * another \r\n but we don't care. */ if (message_file != 0) { VSTREAM *fp; VSTRING *buf = vstring_alloc(100); VSTRING *msg = vstring_alloc(100); if ((fp = vstream_fopen(message_file, O_RDONLY, 0)) == 0) msg_fatal("open %s: %m", message_file); while (vstring_get_nonl(buf, fp) != VSTREAM_EOF) { if (*vstring_str(buf) == '.') VSTRING_ADDCH(msg, '.'); vstring_memcat(msg, vstring_str(buf), VSTRING_LEN(buf)); vstring_memcat(msg, "\r\n", 2); } if (vstream_ferror(fp)) msg_fatal("read %s: %m", message_file); vstream_fclose(fp); vstring_free(buf); message_length = VSTRING_LEN(msg); message_data = vstring_export(msg); send_headers = 0; } else if (message_length > 0) { message_data = mymalloc(message_length); memset(message_data, 'X', message_length); for (i = 80; i < message_length; i += 80) { message_data[i - 80] = "0123456789"[(i / 80) % 10]; message_data[i - 2] = '\r'; message_data[i - 1] = '\n'; } } /* * Translate endpoint address to internal form. */ proto_info = inet_proto_init("protocols", protocols); if (strncmp(argv[optind], "unix:", 5) == 0) { path = argv[optind] + 5; path_len = strlen(path); if (path_len >= (int) sizeof(sun.sun_path)) msg_fatal("unix-domain name too long: %s", path); memset((char *) &sun, 0, sizeof(sun)); sun.sun_family = AF_UNIX; #ifdef HAS_SUN_LEN sun.sun_len = path_len + 1; #endif memcpy(sun.sun_path, path, path_len); sa = (struct sockaddr *) & sun; sa_length = sizeof(sun); } else { if (strncmp(argv[optind], "inet:", 5) == 0) argv[optind] += 5; buf = mystrdup(argv[optind]); if ((parse_err = host_port(buf, &host, (char *) 0, &port, "smtp")) != 0) msg_fatal("%s: %s", argv[optind], parse_err); if ((aierr = hostname_to_sockaddr(host, port, SOCK_STREAM, &res)) != 0) msg_fatal("%s: %s", argv[optind], MAI_STRERROR(aierr)); myfree(buf); sa = (struct sockaddr *) & ss; if (res->ai_addrlen > sizeof(ss)) msg_fatal("address length %d > buffer length %d", (int) res->ai_addrlen, (int) sizeof(ss)); memcpy((char *) sa, res->ai_addr, res->ai_addrlen); sa_length = res->ai_addrlen; #ifdef HAS_SA_LEN sa->sa_len = sa_length; #endif freeaddrinfo(res); } /* * Make sure the SMTP server cannot run us out of memory by sending * never-ending lines of text. */ if (buffer == 0) { buffer = vstring_alloc(100); vstring_ctl(buffer, VSTRING_CTL_MAXLEN, (ssize_t) var_line_limit, 0); } /* * Make sure we have sender and recipient addresses. */ if (var_myhostname == 0) var_myhostname = get_hostname(); if (sender == 0 || recipient == 0) { vstring_sprintf(buffer, "foo@%s", var_myhostname); defaddr = mystrdup(vstring_str(buffer)); if (sender == 0) sender = defaddr; if (recipient == 0) recipient = defaddr; } /* * Start sessions. */ while (sessions-- > 0) { session = (SESSION *) mymalloc(sizeof(*session)); session->stream = 0; session->xfer_count = 0; session->connect_count = connect_count; session->next = 0; session_count++; startup(session); } for (;;) { event_loop(-1); if (session_count <= 0 && message_count <= 0) { if (count) { VSTREAM_PUTC('\n', VSTREAM_OUT); vstream_fflush(VSTREAM_OUT); } exit(0); } } }
static void resolve_addr(RES_CONTEXT *rp, char *sender, char *addr, VSTRING *channel, VSTRING *nexthop, VSTRING *nextrcpt, int *flags) { const char *myname = "resolve_addr"; VSTRING *addr_buf = vstring_alloc(100); TOK822 *tree = 0; TOK822 *saved_domain = 0; TOK822 *domain = 0; char *destination; const char *blame = 0; const char *rcpt_domain; ssize_t addr_len; ssize_t loop_count; ssize_t loop_max; char *local; char *oper; char *junk; const char *relay; const char *xport; const char *sender_key; int rc; *flags = 0; vstring_strcpy(channel, "CHANNEL NOT UPDATED"); vstring_strcpy(nexthop, "NEXTHOP NOT UPDATED"); vstring_strcpy(nextrcpt, "NEXTRCPT NOT UPDATED"); /* * The address is in internalized (unquoted) form. * * In an ideal world we would parse the externalized address form as given * to us by the sender. * * However, in the real world we have to look for routing characters like * %@! in the address local-part, even when that information is quoted * due to the presence of special characters or whitespace. Although * technically incorrect, this is needed to stop user@domain@domain relay * attempts when forwarding mail to a Sendmail MX host. * * This suggests that we parse the address in internalized (unquoted) form. * Unfortunately, if we do that, the unparser generates incorrect white * space between adjacent non-operator tokens. Example: ``first last'' * needs white space, but ``stuff[stuff]'' does not. This is is not a * problem when unparsing the result from parsing externalized forms, * because the parser/unparser were designed for valid externalized forms * where ``stuff[stuff]'' does not happen. * * As a workaround we start with the quoted form and then dequote the * local-part only where needed. This will do the right thing in most * (but not all) cases. */ addr_len = strlen(addr); quote_822_local(addr_buf, addr); tree = tok822_scan_addr(vstring_str(addr_buf)); /* * The optimizer will eliminate tests that always fail, and will replace * multiple expansions of this macro by a GOTO to a single instance. */ #define FREE_MEMORY_AND_RETURN { \ if (saved_domain) \ tok822_free_tree(saved_domain); \ if(tree) \ tok822_free_tree(tree); \ if (addr_buf) \ vstring_free(addr_buf); \ return; \ } /* * Preliminary resolver: strip off all instances of the local domain. * Terminate when no destination domain is left over, or when the * destination domain is remote. * * XXX To whom it may concern. If you change the resolver loop below, or * quote_822_local.c, or tok822_parse.c, be sure to re-run the tests * under "make resolve_clnt_test" in the global directory. */ #define RESOLVE_LOCAL(domain) \ resolve_local(STR(tok822_internalize(addr_buf, domain, TOK822_STR_DEFL))) for (loop_count = 0, loop_max = addr_len + 100; /* void */ ; loop_count++) { /* * XXX Should never happen, but if this happens with some * pathological address, then that is not sufficient reason to * disrupt the operation of an MTA. */ if (loop_count > loop_max) { msg_warn("resolve_addr: <%s>: giving up after %ld iterations", addr, (long) loop_count); *flags |= RESOLVE_FLAG_FAIL; FREE_MEMORY_AND_RETURN; break; } /* * Strip trailing dot at end of domain, but not dot-dot or at-dot. * This merely makes diagnostics more accurate by leaving bogus * addresses alone. */ if (tree->tail && tree->tail->type == '.' && tok822_rfind_type(tree->tail, '@') != 0 && tree->tail->prev->type != '.' && tree->tail->prev->type != '@') tok822_free_tree(tok822_sub_keep_before(tree, tree->tail)); /* * Strip trailing @. */ if (var_resolve_nulldom && tree->tail && tree->tail->type == '@') tok822_free_tree(tok822_sub_keep_before(tree, tree->tail)); /* * Strip (and save) @domain if local. * * Grr. resolve_local() table lookups may fail. It may be OK for local * file lookup code to abort upon failure, but with network-based * tables it is preferable to return an error indication to the * requestor. */ if ((domain = tok822_rfind_type(tree->tail, '@')) != 0) { if (domain->next && (rc = RESOLVE_LOCAL(domain->next)) <= 0) { if (rc < 0) { *flags |= RESOLVE_FLAG_FAIL; FREE_MEMORY_AND_RETURN; } break; } tok822_sub_keep_before(tree, domain); if (saved_domain) tok822_free_tree(saved_domain); saved_domain = domain; domain = 0; /* safety for future change */ } /* * After stripping the local domain, if any, replace foo%bar by * foo@bar, site!user by user@site, rewrite to canonical form, and * retry. */ if (tok822_rfind_type(tree->tail, '@') || (var_swap_bangpath && tok822_rfind_type(tree->tail, '!')) || (var_percent_hack && tok822_rfind_type(tree->tail, '%'))) { rewrite_tree(&local_context, tree); continue; } /* * If the local-part is a quoted string, crack it open when we're * permitted to do so and look for routing operators. This is * technically incorrect, but is needed to stop relaying problems. * * XXX Do another feeble attempt to keep local-part info quoted. */ if (var_resolve_dequoted && tree->head && tree->head == tree->tail && tree->head->type == TOK822_QSTRING && ((oper = strrchr(local = STR(tree->head->vstr), '@')) != 0 || (var_percent_hack && (oper = strrchr(local, '%')) != 0) || (var_swap_bangpath && (oper = strrchr(local, '!')) != 0))) { if (*oper == '%') *oper = '@'; tok822_internalize(addr_buf, tree->head, TOK822_STR_DEFL); if (*oper == '@') { junk = mystrdup(STR(addr_buf)); quote_822_local(addr_buf, junk); myfree(junk); } tok822_free(tree->head); tree->head = tok822_scan(STR(addr_buf), &tree->tail); rewrite_tree(&local_context, tree); continue; } /* * An empty local-part or an empty quoted string local-part becomes * the local MAILER-DAEMON, for consistency with our own From: * message headers. */ if (tree->head && tree->head == tree->tail && tree->head->type == TOK822_QSTRING && VSTRING_LEN(tree->head->vstr) == 0) { tok822_free(tree->head); tree->head = 0; } /* XXX Re-resolve the surrogate, in case already in user@domain form. */ if (tree->head == 0) { tree->head = tok822_scan(var_empty_addr, &tree->tail); continue; } /* XXX Re-resolve with @$myhostname for backwards compatibility. */ if (domain == 0 && saved_domain == 0) { tok822_sub_append(tree, tok822_alloc('@', (char *) 0)); tok822_sub_append(tree, tok822_scan(var_myhostname, (TOK822 **) 0)); continue; } /* * We're done. There are no domains left to strip off the address, * and all null local-part information is sanitized. */ domain = 0; break; } vstring_free(addr_buf); addr_buf = 0; /* * Make sure the resolved envelope recipient has the user@domain form. If * no domain was specified in the address, assume the local machine. See * above for what happens with an empty address. */ if (domain == 0) { if (saved_domain) { tok822_sub_append(tree, saved_domain); saved_domain = 0; } else { tok822_sub_append(tree, tok822_alloc('@', (char *) 0)); tok822_sub_append(tree, tok822_scan(var_myhostname, (TOK822 **) 0)); } } /* * Transform the recipient address back to internal form. * * XXX This may produce incorrect results if we cracked open a quoted * local-part with routing operators; see discussion above at the top of * the big loop. * * XXX We explicitly disallow domain names in bare network address form. A * network address destination should be formatted according to RFC 2821: * it should be enclosed in [], and an IPv6 address should have an IPv6: * prefix. */ tok822_internalize(nextrcpt, tree, TOK822_STR_DEFL); rcpt_domain = strrchr(STR(nextrcpt), '@') + 1; if (rcpt_domain == 0) msg_panic("no @ in address: \"%s\"", STR(nextrcpt)); if (*rcpt_domain == '[') { if (!valid_mailhost_literal(rcpt_domain, DONT_GRIPE)) *flags |= RESOLVE_FLAG_ERROR; } else if (!valid_utf8_hostname(var_smtputf8_enable, rcpt_domain, DONT_GRIPE)) { if (var_resolve_num_dom && valid_hostaddr(rcpt_domain, DONT_GRIPE)) { vstring_insert(nextrcpt, rcpt_domain - STR(nextrcpt), "[", 1); vstring_strcat(nextrcpt, "]"); rcpt_domain = strrchr(STR(nextrcpt), '@') + 1; if ((rc = resolve_local(rcpt_domain)) > 0) /* XXX */ domain = 0; else if (rc < 0) { *flags |= RESOLVE_FLAG_FAIL; FREE_MEMORY_AND_RETURN; } } else { *flags |= RESOLVE_FLAG_ERROR; } } tok822_free_tree(tree); tree = 0; /* * XXX Short-cut invalid address forms. */ if (*flags & RESOLVE_FLAG_ERROR) { *flags |= RESOLVE_CLASS_DEFAULT; FREE_MEMORY_AND_RETURN; } /* * Recognize routing operators in the local-part, even when we do not * recognize ! or % as valid routing operators locally. This is needed to * prevent backup MX hosts from relaying third-party destinations through * primary MX hosts, otherwise the backup host could end up on black * lists. Ignore local swap_bangpath and percent_hack settings because we * can't know how the next MX host is set up. */ if (strcmp(STR(nextrcpt) + strcspn(STR(nextrcpt), "@!%") + 1, rcpt_domain)) *flags |= RESOLVE_FLAG_ROUTED; /* * With local, virtual, relay, or other non-local destinations, give the * highest precedence to transport associated nexthop information. * * Otherwise, with relay or other non-local destinations, the relayhost * setting overrides the recipient domain name, and the sender-dependent * relayhost overrides both. * * XXX Nag if the recipient domain is listed in multiple domain lists. The * result is implementation defined, and may break when internals change. * * For now, we distinguish only a fixed number of address classes. * Eventually this may become extensible, so that new classes can be * configured with their own domain list, delivery transport, and * recipient table. */ #define STREQ(x,y) (strcmp((x), (y)) == 0) if (domain != 0) { /* * Virtual alias domain. */ if (virt_alias_doms && string_list_match(virt_alias_doms, rcpt_domain)) { if (var_helpful_warnings) { if (virt_mailbox_doms && string_list_match(virt_mailbox_doms, rcpt_domain)) msg_warn("do not list domain %s in BOTH %s and %s", rcpt_domain, VAR_VIRT_ALIAS_DOMS, VAR_VIRT_MAILBOX_DOMS); if (relay_domains && domain_list_match(relay_domains, rcpt_domain)) msg_warn("do not list domain %s in BOTH %s and %s", rcpt_domain, VAR_VIRT_ALIAS_DOMS, VAR_RELAY_DOMAINS); #if 0 if (strcasecmp(rcpt_domain, var_myorigin) == 0) msg_warn("do not list $%s (%s) in %s", VAR_MYORIGIN, var_myorigin, VAR_VIRT_ALIAS_DOMS); #endif } vstring_strcpy(channel, MAIL_SERVICE_ERROR); vstring_sprintf(nexthop, "5.1.1 User unknown%s", var_show_unk_rcpt_table ? " in virtual alias table" : ""); *flags |= RESOLVE_CLASS_ALIAS; } else if (virt_alias_doms && virt_alias_doms->error != 0) { msg_warn("%s lookup failure", VAR_VIRT_ALIAS_DOMS); *flags |= RESOLVE_FLAG_FAIL; FREE_MEMORY_AND_RETURN; } /* * Virtual mailbox domain. */ else if (virt_mailbox_doms && string_list_match(virt_mailbox_doms, rcpt_domain)) { if (var_helpful_warnings) { if (relay_domains && domain_list_match(relay_domains, rcpt_domain)) msg_warn("do not list domain %s in BOTH %s and %s", rcpt_domain, VAR_VIRT_MAILBOX_DOMS, VAR_RELAY_DOMAINS); } vstring_strcpy(channel, RES_PARAM_VALUE(rp->virt_transport)); vstring_strcpy(nexthop, rcpt_domain); blame = rp->virt_transport_name; *flags |= RESOLVE_CLASS_VIRTUAL; } else if (virt_mailbox_doms && virt_mailbox_doms->error != 0) { msg_warn("%s lookup failure", VAR_VIRT_MAILBOX_DOMS); *flags |= RESOLVE_FLAG_FAIL; FREE_MEMORY_AND_RETURN; } else { /* * Off-host relay destination. */ if (relay_domains && domain_list_match(relay_domains, rcpt_domain)) { vstring_strcpy(channel, RES_PARAM_VALUE(rp->relay_transport)); blame = rp->relay_transport_name; *flags |= RESOLVE_CLASS_RELAY; } else if (relay_domains && relay_domains->error != 0) { msg_warn("%s lookup failure", VAR_RELAY_DOMAINS); *flags |= RESOLVE_FLAG_FAIL; FREE_MEMORY_AND_RETURN; } /* * Other off-host destination. */ else { if (rp->snd_def_xp_info && (xport = mail_addr_find(rp->snd_def_xp_info, sender_key = (*sender ? sender : var_null_def_xport_maps_key), (char **) 0)) != 0) { if (*xport == 0) { msg_warn("%s: ignoring null lookup result for %s", rp->snd_def_xp_maps_name, sender_key); xport = "DUNNO"; } vstring_strcpy(channel, strcasecmp(xport, "DUNNO") == 0 ? RES_PARAM_VALUE(rp->def_transport) : xport); blame = rp->snd_def_xp_maps_name; } else if (rp->snd_def_xp_info && rp->snd_def_xp_info->error != 0) { msg_warn("%s lookup failure", rp->snd_def_xp_maps_name); *flags |= RESOLVE_FLAG_FAIL; FREE_MEMORY_AND_RETURN; } else { vstring_strcpy(channel, RES_PARAM_VALUE(rp->def_transport)); blame = rp->def_transport_name; } *flags |= RESOLVE_CLASS_DEFAULT; } /* * With off-host delivery, sender-dependent or global relayhost * override the recipient domain. */ if (rp->snd_relay_info && (relay = mail_addr_find(rp->snd_relay_info, sender_key = (*sender ? sender : var_null_relay_maps_key), (char **) 0)) != 0) { if (*relay == 0) { msg_warn("%s: ignoring null lookup result for %s", rp->snd_relay_maps_name, sender_key); relay = "DUNNO"; } vstring_strcpy(nexthop, strcasecmp(relay, "DUNNO") == 0 ? rcpt_domain : relay); } else if (rp->snd_relay_info && rp->snd_relay_info->error != 0) { msg_warn("%s lookup failure", rp->snd_relay_maps_name); *flags |= RESOLVE_FLAG_FAIL; FREE_MEMORY_AND_RETURN; } else if (*RES_PARAM_VALUE(rp->relayhost)) vstring_strcpy(nexthop, RES_PARAM_VALUE(rp->relayhost)); else vstring_strcpy(nexthop, rcpt_domain); } } /* * Local delivery. * * XXX Nag if the domain is listed in multiple domain lists. The effect is * implementation defined, and may break when internals change. */ else { if (var_helpful_warnings) { if (virt_alias_doms && string_list_match(virt_alias_doms, rcpt_domain)) msg_warn("do not list domain %s in BOTH %s and %s", rcpt_domain, VAR_MYDEST, VAR_VIRT_ALIAS_DOMS); if (virt_mailbox_doms && string_list_match(virt_mailbox_doms, rcpt_domain)) msg_warn("do not list domain %s in BOTH %s and %s", rcpt_domain, VAR_MYDEST, VAR_VIRT_MAILBOX_DOMS); } vstring_strcpy(channel, RES_PARAM_VALUE(rp->local_transport)); vstring_strcpy(nexthop, rcpt_domain); blame = rp->local_transport_name; *flags |= RESOLVE_CLASS_LOCAL; } /* * An explicit main.cf transport:nexthop setting overrides the nexthop. * * XXX We depend on this mechanism to enforce per-recipient concurrencies * for local recipients. With "local_transport = local:$myhostname" we * force mail for any domain in $mydestination/${proxy,inet}_interfaces * to share the same queue. */ if ((destination = split_at(STR(channel), ':')) != 0 && *destination) vstring_strcpy(nexthop, destination); /* * Sanity checks. */ if (*STR(channel) == 0) { if (blame == 0) msg_panic("%s: null blame", myname); msg_warn("file %s/%s: parameter %s: null transport is not allowed", var_config_dir, MAIN_CONF_FILE, blame); *flags |= RESOLVE_FLAG_FAIL; FREE_MEMORY_AND_RETURN; } if (*STR(nexthop) == 0) msg_panic("%s: null nexthop", myname); /* * The transport map can selectively override any transport and/or * nexthop host info that is set up above. Unfortunately, the syntax for * nexthop information is transport specific. We therefore need sane and * intuitive semantics for transport map entries that specify a channel * but no nexthop. * * With non-error transports, the initial nexthop information is the * recipient domain. However, specific main.cf transport definitions may * specify a transport-specific destination, such as a host + TCP socket, * or the pathname of a UNIX-domain socket. With less precedence than * main.cf transport definitions, a main.cf relayhost definition may also * override nexthop information for off-host deliveries. * * With the error transport, the nexthop information is free text that * specifies the reason for non-delivery. * * Because nexthop syntax is transport specific we reset the nexthop * information to the recipient domain when the transport table specifies * a transport without also specifying the nexthop information. * * Subtle note: reset nexthop even when the transport table does not change * the transport. Otherwise it is hard to get rid of main.cf specified * nexthop information. * * XXX Don't override the virtual alias class (error:User unknown) result. */ if (rp->transport_info && !(*flags & RESOLVE_CLASS_ALIAS)) { if (transport_lookup(rp->transport_info, STR(nextrcpt), rcpt_domain, channel, nexthop) == 0 && rp->transport_info->transport_path->error != 0) { msg_warn("%s lookup failure", rp->transport_maps_name); *flags |= RESOLVE_FLAG_FAIL; FREE_MEMORY_AND_RETURN; } } /* * Bounce recipients that have moved, regardless of domain address class. * We do this last, in anticipation of transport maps that can override * the recipient address. * * The downside of not doing this in delivery agents is that this table has * no effect on local alias expansion results. Such mail will have to * make almost an entire iteration through the mail system. */ #define IGNORE_ADDR_EXTENSION ((char **) 0) if (relocated_maps != 0) { const char *newloc; if ((newloc = mail_addr_find(relocated_maps, STR(nextrcpt), IGNORE_ADDR_EXTENSION)) != 0) { vstring_strcpy(channel, MAIL_SERVICE_ERROR); /* 5.1.6 is the closest match, but not perfect. */ vstring_sprintf(nexthop, "5.1.6 User has moved to %s", newloc); } else if (relocated_maps->error != 0) { msg_warn("%s lookup failure", VAR_RELOCATED_MAPS); *flags |= RESOLVE_FLAG_FAIL; FREE_MEMORY_AND_RETURN; } } /* * Bounce recipient addresses that start with `-'. External commands may * misinterpret such addresses as command-line options. * * In theory I could say people should always carefully set up their * master.cf pipe mailer entries with `--' before the first non-option * argument, but mistakes will happen regardless. * * Therefore the protection is put in place here, where it cannot be * bypassed. */ if (var_allow_min_user == 0 && STR(nextrcpt)[0] == '-') { *flags |= RESOLVE_FLAG_ERROR; FREE_MEMORY_AND_RETURN; } /* * Clean up. */ FREE_MEMORY_AND_RETURN; }
int main(int argc, char **argv) { SESSION *session; char *host; char *port; char *path; int path_len; int sessions = 1; int ch; ssize_t len; int n; int i; char *buf; const char *parse_err; struct addrinfo *res; int aierr; const char *protocols = INET_PROTO_NAME_ALL; INET_PROTO_INFO *proto_info; /* * Fingerprint executables and core dumps. */ MAIL_VERSION_STAMP_ALLOCATE; signal(SIGPIPE, SIG_IGN); msg_vstream_init(argv[0], VSTREAM_ERR); /* * Parse JCL. */ while ((ch = GETOPT(argc, argv, "46cC:f:l:m:M:r:R:s:t:vw:")) > 0) { switch (ch) { case '4': protocols = INET_PROTO_NAME_IPV4; break; case '6': protocols = INET_PROTO_NAME_IPV6; break; case 'c': count++; break; case 'C': if ((connect_count = atoi(optarg)) <= 0) usage(argv[0]); break; case 'f': sender = optarg; break; case 'l': if ((message_length = atoi(optarg)) <= 0) usage(argv[0]); break; case 'm': if ((message_count = atoi(optarg)) <= 0) usage(argv[0]); break; case 'M': if (*optarg == '[') { if (!valid_mailhost_literal(optarg, DO_GRIPE)) msg_fatal("bad address literal: %s", optarg); } else { if (!valid_hostname(optarg, DO_GRIPE)) msg_fatal("bad hostname: %s", optarg); } var_myhostname = optarg; break; case 'r': if ((recipients = atoi(optarg)) <= 0) usage(argv[0]); break; case 'R': if (fixed_delay > 0 || (random_delay = atoi(optarg)) <= 0) usage(argv[0]); break; case 's': if ((sessions = atoi(optarg)) <= 0) usage(argv[0]); break; case 't': recipient = optarg; break; case 'v': msg_verbose++; break; case 'w': if (random_delay > 0 || (fixed_delay = atoi(optarg)) <= 0) usage(argv[0]); break; default: usage(argv[0]); } } if (argc - optind != 1) usage(argv[0]); if (random_delay > 0) srand(getpid()); /* * Translate endpoint address to internal form. */ proto_info = inet_proto_init("protocols", protocols); if (strncmp(argv[optind], "unix:", 5) == 0) { path = argv[optind] + 5; path_len = strlen(path); if (path_len >= (int) sizeof(sun.sun_path)) msg_fatal("unix-domain name too long: %s", path); memset((char *) &sun, 0, sizeof(sun)); sun.sun_family = AF_UNIX; #ifdef HAS_SUN_LEN sun.sun_len = path_len + 1; #endif memcpy(sun.sun_path, path, path_len); sa = (struct sockaddr *) & sun; sa_length = sizeof(sun); } else { if (strncmp(argv[optind], "inet:", 5) == 0) argv[optind] += 5; buf = mystrdup(argv[optind]); if ((parse_err = host_port(buf, &host, (char *) 0, &port, "628")) != 0) msg_fatal("%s: %s", argv[optind], parse_err); if ((aierr = hostname_to_sockaddr(host, port, SOCK_STREAM, &res)) != 0) msg_fatal("%s: %s", argv[optind], MAI_STRERROR(aierr)); myfree(buf); sa = (struct sockaddr *) & ss; if (res->ai_addrlen > sizeof(ss)) msg_fatal("address length %d > buffer length %d", (int) res->ai_addrlen, (int) sizeof(ss)); memcpy((char *) sa, res->ai_addr, res->ai_addrlen); sa_length = res->ai_addrlen; #ifdef HAS_SA_LEN sa->sa_len = sa_length; #endif freeaddrinfo(res); } /* * Allocate space for temporary buffer. */ buffer = vstring_alloc(100); /* * Make sure we have sender and recipient addresses. */ if (var_myhostname == 0) var_myhostname = get_hostname(); if (sender == 0 || recipient == 0) { vstring_sprintf(buffer, "foo@%s", var_myhostname); defaddr = mystrdup(vstring_str(buffer)); if (sender == 0) sender = defaddr; if (recipient == 0) recipient = defaddr; } /* * Prepare some results that may be used multiple times: the message * content netstring, the sender netstring, and the recipient netstrings. */ mydate = mail_date(time((time_t *) 0)); mypid = getpid(); message_buffer = vstring_alloc(message_length + 200); vstring_sprintf(buffer, "From: <%s>\nTo: <%s>\nDate: %s\nMessage-Id: <%d@%s>\n\n", sender, recipient, mydate, mypid, var_myhostname); for (n = 1; LEN(buffer) < message_length; n++) { for (i = 0; i < n && i < 79; i++) VSTRING_ADDCH(buffer, 'X'); VSTRING_ADDCH(buffer, '\n'); } STR(buffer)[message_length - 1] = '\n'; netstring_memcpy(message_buffer, STR(buffer), message_length); len = strlen(sender); sender_buffer = vstring_alloc(len); netstring_memcpy(sender_buffer, sender, len); if (recipients == 1) { len = strlen(recipient); recipient_buffer = vstring_alloc(len); netstring_memcpy(recipient_buffer, recipient, len); } else { recipient_buffer = vstring_alloc(100); for (n = 0; n < recipients; n++) { vstring_sprintf(buffer, "%d%s", n, recipient); netstring_memcat(recipient_buffer, STR(buffer), LEN(buffer)); } } /* * Start sessions. */ while (sessions-- > 0) { session = (SESSION *) mymalloc(sizeof(*session)); session->stream = 0; session->xfer_count = 0; session->connect_count = connect_count; session->next = 0; session_count++; startup(session); } for (;;) { event_loop(-1); if (session_count <= 0 && message_count <= 0) { if (count) { VSTREAM_PUTC('\n', VSTREAM_OUT); vstream_fflush(VSTREAM_OUT); } exit(0); } } }