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);
}
Example #2
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 */
    }
}
Example #3
0
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);
        }
    }
}
Example #4
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;
}
Example #5
0
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);
	}
    }
}