Exemple #1
0
int     mail_copy(const char *sender,
		          const char *orig_rcpt,
		          const char *delivered,
		          VSTREAM *src, VSTREAM *dst,
		          int flags, const char *eol, DSN_BUF *why)
{
    const char *myname = "mail_copy";
    VSTRING *buf;
    char   *bp;
    off_t   orig_length;
    int     read_error;
    int     write_error;
    int     corrupt_error = 0;
    time_t  now;
    int     type;
    int     prev_type;
    struct stat st;
    off_t   size_limit;

    /*
     * Workaround 20090114. This will hopefully get someone's attention. The
     * problem with file_size_limit < message_size_limit is that mail will be
     * delivered again and again until someone removes it from the queue by
     * hand, because Postfix cannot mark a recipient record as "completed".
     */
    if (fstat(vstream_fileno(src), &st) < 0)
	msg_fatal("fstat: %m");
    if ((size_limit = get_file_limit()) < st.st_size)
	msg_panic("file size limit %lu < message size %lu. This "
		  "causes large messages to be delivered repeatedly "
		  "after they were submitted with \"sendmail -t\" "
		  "or after recipients were added with the Milter "
		  "SMFIR_ADDRCPT request",
		  (unsigned long) size_limit,
		  (unsigned long) st.st_size);

    /*
     * Initialize.
     */
#ifndef NO_TRUNCATE
    if ((flags & MAIL_COPY_TOFILE) != 0)
	if ((orig_length = vstream_fseek(dst, (off_t) 0, SEEK_END)) < 0)
	    msg_fatal("seek file %s: %m", VSTREAM_PATH(dst));
#endif
    buf = vstring_alloc(100);

    /*
     * Prepend a bunch of headers to the message.
     */
    if (flags & (MAIL_COPY_FROM | MAIL_COPY_RETURN_PATH)) {
	if (sender == 0)
	    msg_panic("%s: null sender", myname);
	quote_822_local(buf, sender);
	if (flags & MAIL_COPY_FROM) {
	    time(&now);
	    vstream_fprintf(dst, "From %s  %.24s%s", *sender == 0 ?
			    MAIL_ADDR_MAIL_DAEMON : vstring_str(buf),
			    asctime(localtime(&now)), eol);
	}
	if (flags & MAIL_COPY_RETURN_PATH) {
	    vstream_fprintf(dst, "Return-Path: <%s>%s",
			    *sender ? vstring_str(buf) : "", eol);
	}
    }
    if (flags & MAIL_COPY_ORIG_RCPT) {
	if (orig_rcpt == 0)
	    msg_panic("%s: null orig_rcpt", myname);

	/*
	 * An empty original recipient record almost certainly means that
	 * original recipient processing was disabled.
	 */
	if (*orig_rcpt) {
	    quote_822_local(buf, orig_rcpt);
	    vstream_fprintf(dst, "X-Original-To: %s%s", vstring_str(buf), eol);
	}
    }
    if (flags & MAIL_COPY_DELIVERED) {
	if (delivered == 0)
	    msg_panic("%s: null delivered", myname);
	quote_822_local(buf, delivered);
	vstream_fprintf(dst, "Delivered-To: %s%s", vstring_str(buf), eol);
    }

    /*
     * Copy the message. Escape lines that could be confused with the ugly
     * From_ line. Make sure that there is a blank line at the end of the
     * message so that the next ugly From_ can be found by mail reading
     * software.
     * 
     * XXX Rely on the front-end services to enforce record size limits.
     */
#define VSTREAM_FWRITE_BUF(s,b) \
	vstream_fwrite((s),vstring_str(b),VSTRING_LEN(b))

    prev_type = REC_TYPE_NORM;
    while ((type = rec_get(src, buf, 0)) > 0) {
	if (type != REC_TYPE_NORM && type != REC_TYPE_CONT)
	    break;
	bp = vstring_str(buf);
	if (prev_type == REC_TYPE_NORM) {
	    if ((flags & MAIL_COPY_QUOTE) && *bp == 'F' && !strncmp(bp, "From ", 5))
		VSTREAM_PUTC('>', dst);
	    if ((flags & MAIL_COPY_DOT) && *bp == '.')
		VSTREAM_PUTC('.', dst);
	}
	if (VSTRING_LEN(buf) && VSTREAM_FWRITE_BUF(dst, buf) != VSTRING_LEN(buf))
	    break;
	if (type == REC_TYPE_NORM && vstream_fputs(eol, dst) == VSTREAM_EOF)
	    break;
	prev_type = type;
    }
    if (vstream_ferror(dst) == 0) {
	if (var_fault_inj_code == 1)
	    type = 0;
	if (type != REC_TYPE_XTRA) {
	    /* XXX Where is the queue ID? */
	    msg_warn("bad record type: %d in message content", type);
	    corrupt_error = mark_corrupt(src);
	}
	if (prev_type != REC_TYPE_NORM)
	    vstream_fputs(eol, dst);
	if (flags & MAIL_COPY_BLANK)
	    vstream_fputs(eol, dst);
    }
    vstring_free(buf);

    /*
     * Make sure we read and wrote all. Truncate the file to its original
     * length when the delivery failed. POSIX does not require ftruncate(),
     * so we may have a portability problem. Note that fclose() may fail even
     * while fflush and fsync() succeed. Think of remote file systems such as
     * AFS that copy the file back to the server upon close. Oh well, no
     * point optimizing the error case. XXX On systems that use flock()
     * locking, we must truncate the file file before closing it (and losing
     * the exclusive lock).
     */
    read_error = vstream_ferror(src);
    write_error = vstream_fflush(dst);
#ifdef HAS_FSYNC
    if ((flags & MAIL_COPY_TOFILE) != 0)
	write_error |= fsync(vstream_fileno(dst));
#endif
    if (var_fault_inj_code == 2) {
	read_error = 1;
	errno = ENOENT;
    }
    if (var_fault_inj_code == 3) {
	write_error = 1;
	errno = ENOENT;
    }
#ifndef NO_TRUNCATE
    if ((flags & MAIL_COPY_TOFILE) != 0)
	if (corrupt_error || read_error || write_error)
	    /* Complain about ignored "undo" errors? So sue me. */
	    (void) ftruncate(vstream_fileno(dst), orig_length);
#endif
    write_error |= vstream_fclose(dst);

    /*
     * Return the optional verbose error description.
     */
#define TRY_AGAIN_ERROR(errno) \
	(errno == EAGAIN || errno == ESTALE)

    if (why && read_error)
	dsb_unix(why, TRY_AGAIN_ERROR(errno) ? "4.3.0" : "5.3.0",
		 sys_exits_detail(EX_IOERR)->text,
		 "error reading message: %m");
    if (why && write_error)
	dsb_unix(why, mbox_dsn(errno, "5.3.0"),
		 sys_exits_detail(EX_IOERR)->text,
		 "error writing message: %m");

    /*
     * Use flag+errno description when the optional verbose description is
     * not desired.
     */
    return ((corrupt_error ? MAIL_COPY_STAT_CORRUPT : 0)
	    | (read_error ? MAIL_COPY_STAT_READ : 0)
	    | (write_error ? MAIL_COPY_STAT_WRITE : 0));
}
Exemple #2
0
static void cleanup_service(VSTREAM *src, char *unused_service, char **argv)
{
    VSTRING *buf = vstring_alloc(100);
    CLEANUP_STATE *state;
    int     flags;
    int     type = 0;
    int     status;

    /*
     * Sanity check. This service takes no command-line arguments.
     */
    if (argv[0])
	msg_fatal("unexpected command-line argument: %s", argv[0]);

    /*
     * Open a queue file and initialize state.
     */
    state = cleanup_open(src);

    /*
     * Send the queue id to the client. Read client processing options. If we
     * can't read the client processing options we can pretty much forget
     * about the whole operation.
     */
    attr_print(src, ATTR_FLAG_NONE,
	       SEND_ATTR_STR(MAIL_ATTR_QUEUEID, state->queue_id),
	       ATTR_TYPE_END);
    if (attr_scan(src, ATTR_FLAG_STRICT,
		  RECV_ATTR_INT(MAIL_ATTR_FLAGS, &flags),
		  ATTR_TYPE_END) != 1) {
	state->errs |= CLEANUP_STAT_BAD;
	flags = 0;
    }
    cleanup_control(state, flags);

    /*
     * XXX Rely on the front-end programs to enforce record size limits.
     * 
     * First, copy the envelope records to the queue file. Then, copy the
     * message content (headers and body). Finally, attach any information
     * extracted from message headers.
     */
    while (CLEANUP_OUT_OK(state)) {
	if ((type = rec_get_raw(src, buf, 0, REC_FLAG_NONE)) < 0) {
	    state->errs |= CLEANUP_STAT_BAD;
	    break;
	}
	if (REC_GET_HIDDEN_TYPE(type)) {
	    msg_warn("%s: record type %d not allowed - discarding this message",
		     state->queue_id, type);
	    state->errs |= CLEANUP_STAT_BAD;
	    break;
	}
	CLEANUP_RECORD(state, type, vstring_str(buf), VSTRING_LEN(buf));
	if (type == REC_TYPE_END)
	    break;
    }

    /*
     * Keep reading in case of problems, until the sender is ready to receive
     * our status report.
     */
    if (CLEANUP_OUT_OK(state) == 0 && type > 0) {
	while (type != REC_TYPE_END
	       && (type = rec_get_raw(src, buf, 0, REC_FLAG_NONE)) > 0) {
	    if (type == REC_TYPE_MILT_COUNT) {
		int     milter_count = atoi(vstring_str(buf));

		/* Avoid deadlock. */
		if (milter_count >= 0)
		    cleanup_milter_receive(state, milter_count);
	    }
	}
    }

    /*
     * Log something to make timeout errors easier to debug.
     */
    if (vstream_ftimeout(src))
	msg_warn("%s: read timeout on %s",
		 state->queue_id, VSTREAM_PATH(src));

    /*
     * Finish this message, and report the result status to the client.
     */
    status = cleanup_flush(state);		/* in case state is modified */
    attr_print(src, ATTR_FLAG_NONE,
	       SEND_ATTR_INT(MAIL_ATTR_STATUS, status),
	       SEND_ATTR_STR(MAIL_ATTR_WHY,
			     (state->flags & CLEANUP_FLAG_SMTP_REPLY)
			     && state->smtp_reply ? state->smtp_reply :
			     state->reason ? state->reason : ""),
	       ATTR_TYPE_END);
    cleanup_free(state);

    /*
     * Cleanup.
     */
    vstring_free(buf);
}
Exemple #3
0
static int tls_policy_lookup_one(SMTP_SESSION *session, int *site_level,
				         const char *site_name,
				         const char *site_class)
{
    const char *lookup;
    char   *policy;
    char   *saved_policy;
    char   *tok;
    const char *err;
    char   *name;
    char   *val;
    static VSTRING *cbuf;

#undef FREE_RETURN
#define FREE_RETURN(x) do { myfree(saved_policy); return (x); } while (0)

    if ((lookup = maps_find(tls_policy, site_name, 0)) == 0) {
	if (tls_policy->error) {
	    msg_fatal("%s: %s lookup error for %s",
		      session->state->request->queue_id,
		      tls_policy->title, site_name);
	    /* XXX session->stream has no longjmp context yet. */
	}
	return (0);
    }
    if (cbuf == 0)
	cbuf = vstring_alloc(10);

#define WHERE \
    vstring_str(vstring_sprintf(cbuf, "TLS policy table, %s \"%s\"", \
		site_class, site_name))

    saved_policy = policy = mystrdup(lookup);

    if ((tok = mystrtok(&policy, "\t\n\r ,")) == 0) {
	msg_warn("%s: invalid empty policy", WHERE);
	*site_level = TLS_LEV_INVALID;
	FREE_RETURN(1);				/* No further lookups */
    }
    *site_level = tls_level_lookup(tok);
    if (*site_level == TLS_LEV_INVALID) {
	/* tls_level_lookup() logs no warning. */
	msg_warn("%s: invalid security level \"%s\"", WHERE, tok);
	FREE_RETURN(1);				/* No further lookups */
    }

    /*
     * Warn about ignored attributes when TLS is disabled.
     */
    if (*site_level < TLS_LEV_MAY) {
	while ((tok = mystrtok(&policy, "\t\n\r ,")) != 0)
	    msg_warn("%s: ignoring attribute \"%s\" with TLS disabled",
		     WHERE, tok);
	FREE_RETURN(1);
    }

    /*
     * Errors in attributes may have security consequences, don't ignore
     * errors that can degrade security.
     */
    while ((tok = mystrtok(&policy, "\t\n\r ,")) != 0) {
	if ((err = split_nameval(tok, &name, &val)) != 0) {
	    *site_level = TLS_LEV_INVALID;
	    msg_warn("%s: malformed attribute/value pair \"%s\": %s",
		     WHERE, tok, err);
	    break;
	}
	/* Only one instance per policy. */
	if (!strcasecmp(name, "ciphers")) {
	    if (*val == 0) {
		msg_warn("%s: attribute \"%s\" has empty value", WHERE, name);
		*site_level = TLS_LEV_INVALID;
		break;
	    }
	    if (session->tls_grade) {
		msg_warn("%s: attribute \"%s\" is specified multiple times",
			 WHERE, name);
		*site_level = TLS_LEV_INVALID;
		break;
	    }
	    session->tls_grade = mystrdup(val);
	    continue;
	}
	/* Only one instance per policy. */
	if (!strcasecmp(name, "protocols")) {
	    if (session->tls_protocols) {
		msg_warn("%s: attribute \"%s\" is specified multiple times",
			 WHERE, name);
		*site_level = TLS_LEV_INVALID;
		break;
	    }
	    session->tls_protocols = mystrdup(val);
	    continue;
	}
	/* Multiple instance(s) per policy. */
	if (!strcasecmp(name, "match")) {
	    char   *delim = *site_level == TLS_LEV_FPRINT ? "|" : ":";

	    if (*site_level <= TLS_LEV_ENCRYPT) {
		msg_warn("%s: attribute \"%s\" invalid at security level \"%s\"",
			 WHERE, name, policy_name(*site_level));
		*site_level = TLS_LEV_INVALID;
		break;
	    }
	    if (*val == 0) {
		msg_warn("%s: attribute \"%s\" has empty value", WHERE, name);
		*site_level = TLS_LEV_INVALID;
		break;
	    }
	    if (session->tls_matchargv == 0)
		session->tls_matchargv = argv_split(val, delim);
	    else
		argv_split_append(session->tls_matchargv, val, delim);
	    continue;
	}
	/* Only one instance per policy. */
	if (!strcasecmp(name, "exclude")) {
	    if (session->tls_exclusions) {
		msg_warn("%s: attribute \"%s\" is specified multiple times",
			 WHERE, name);
		*site_level = TLS_LEV_INVALID;
		break;
	    }
	    session->tls_exclusions = vstring_strcpy(vstring_alloc(10), val);
	    continue;
	} else {
	    msg_warn("%s: invalid attribute name: \"%s\"", WHERE, name);
	    *site_level = TLS_LEV_INVALID;
	    break;
	}
    }
    FREE_RETURN(1);
}
Exemple #4
0
static NORETURN usage(const char *me)
{
    msg_fatal("usage: %s [-c config_dir] [-D (debug)] [-d (don't detach from terminal)] [-e exit_time] [-t (test)] [-v] [-w (wait for initialization)]", me);
}
Exemple #5
0
static void psc_service(VSTREAM *smtp_client_stream,
			        char *unused_service,
			        char **unused_argv)
{
    const char *myname = "psc_service";
    PSC_STATE *state;
    struct sockaddr_storage addr_storage;
    SOCKADDR_SIZE addr_storage_len = sizeof(addr_storage);
    MAI_HOSTADDR_STR smtp_client_addr;
    MAI_SERVPORT_STR smtp_client_port;
    MAI_HOSTADDR_STR smtp_server_addr;
    MAI_SERVPORT_STR smtp_server_port;
    int     aierr;
    const char *stamp_str;
    int     saved_flags;

    /*
     * For sanity, require that at least one of INET or INET6 is enabled.
     * Otherwise, we can't look up interface information, and we can't
     * convert names or addresses.
     */
    if (inet_proto_info()->ai_family_list[0] == 0)
	msg_fatal("all network protocols are disabled (%s = %s)",
		  VAR_INET_PROTOCOLS, var_inet_protocols);

    /*
     * This program handles all incoming connections, so it must not block.
     * We use event-driven code for all operations that introduce latency.
     * 
     * Note: instead of using VSTREAM-level timeouts, we enforce limits on the
     * total amount of time to receive a complete SMTP command line.
     */
    non_blocking(vstream_fileno(smtp_client_stream), NON_BLOCKING);

    /*
     * We use the event_server framework. This means we get already-accepted
     * connections so we have to invoke getpeername() to find out the remote
     * address and port.
     */

    /* Best effort - if this non-blocking write(2) fails, so be it. */
#define PSC_SERVICE_DISCONNECT_AND_RETURN(stream) do { \
	(void) write(vstream_fileno(stream), \
		     "421 4.3.2 No system resources\r\n", \
		     sizeof("421 4.3.2 No system resources\r\n") - 1); \
	event_server_disconnect(stream); \
	return; \
    } while (0);

    /*
     * Look up the remote SMTP client address and port.
     */
    if (getpeername(vstream_fileno(smtp_client_stream), (struct sockaddr *)
		    & addr_storage, &addr_storage_len) < 0) {
	msg_warn("getpeername: %m -- dropping this connection");
	PSC_SERVICE_DISCONNECT_AND_RETURN(smtp_client_stream);
    }

    /*
     * Convert the remote SMTP client address and port to printable form for
     * logging and access control.
     */
    if ((aierr = sockaddr_to_hostaddr((struct sockaddr *) & addr_storage,
				      addr_storage_len, &smtp_client_addr,
				      &smtp_client_port, 0)) != 0) {
	msg_warn("cannot convert client address/port to string: %s"
		 " -- dropping this connection",
		 MAI_STRERROR(aierr));
	PSC_SERVICE_DISCONNECT_AND_RETURN(smtp_client_stream);
    }
    if (strncasecmp("::ffff:", smtp_client_addr.buf, 7) == 0)
	memmove(smtp_client_addr.buf, smtp_client_addr.buf + 7,
		sizeof(smtp_client_addr.buf) - 7);
    if (msg_verbose > 1)
	msg_info("%s: sq=%d cq=%d connect from [%s]:%s",
		 myname, psc_post_queue_length, psc_check_queue_length,
		 smtp_client_addr.buf, smtp_client_port.buf);

    /*
     * Look up the local SMTP server address and port.
     */
    if (getsockname(vstream_fileno(smtp_client_stream), (struct sockaddr *)
		    & addr_storage, &addr_storage_len) < 0) {
	msg_warn("getsockname: %m -- dropping this connection");
	PSC_SERVICE_DISCONNECT_AND_RETURN(smtp_client_stream);
    }

    /*
     * Convert the local SMTP server address and port to printable form for
     * logging and access control.
     */
    if ((aierr = sockaddr_to_hostaddr((struct sockaddr *) & addr_storage,
				      addr_storage_len, &smtp_server_addr,
				      &smtp_server_port, 0)) != 0) {
	msg_warn("cannot convert server address/port to string: %s"
		 " -- dropping this connection",
		 MAI_STRERROR(aierr));
	PSC_SERVICE_DISCONNECT_AND_RETURN(smtp_client_stream);
    }
    if (strncasecmp("::ffff:", smtp_server_addr.buf, 7) == 0)
	memmove(smtp_server_addr.buf, smtp_server_addr.buf + 7,
		sizeof(smtp_server_addr.buf) - 7);

    msg_info("CONNECT from [%s]:%s to [%s]:%s",
	     smtp_client_addr.buf, smtp_client_port.buf,
	     smtp_server_addr.buf, smtp_server_port.buf);

    /*
     * Bundle up all the loose session pieces. This zeroes all flags and time
     * stamps.
     */
    state = psc_new_session_state(smtp_client_stream, smtp_client_addr.buf,
				  smtp_client_port.buf);

    /*
     * Reply with 421 when the client has too many open connections.
     */
    if (var_psc_cconn_limit > 0
	&& state->client_concurrency > var_psc_cconn_limit) {
	msg_info("NOQUEUE: reject: CONNECT from [%s]:%s: too many connections",
		 state->smtp_client_addr, state->smtp_client_port);
	PSC_DROP_SESSION_STATE(state,
			       "421 4.7.0 Error: too many connections\r\n");
	return;
    }

    /*
     * Reply with 421 when we can't forward more connections.
     */
    if (var_psc_post_queue_limit > 0
	&& psc_post_queue_length >= var_psc_post_queue_limit) {
	msg_info("NOQUEUE: reject: CONNECT from [%s]:%s: all server ports busy",
		 state->smtp_client_addr, state->smtp_client_port);
	PSC_DROP_SESSION_STATE(state,
			       "421 4.3.2 All server ports are busy\r\n");
	return;
    }

    /*
     * The permanent white/blacklist has highest precedence.
     */
    if (psc_acl != 0) {
	switch (psc_acl_eval(state, psc_acl, VAR_PSC_ACL)) {

	    /*
	     * Permanently blacklisted.
	     */
	case PSC_ACL_ACT_BLACKLIST:
	    msg_info("BLACKLISTED [%s]:%s", PSC_CLIENT_ADDR_PORT(state));
	    PSC_FAIL_SESSION_STATE(state, PSC_STATE_FLAG_BLIST_FAIL);
	    switch (psc_blist_action) {
	    case PSC_ACT_DROP:
		PSC_DROP_SESSION_STATE(state,
			     "521 5.3.2 Service currently unavailable\r\n");
		return;
	    case PSC_ACT_ENFORCE:
		PSC_ENFORCE_SESSION_STATE(state,
			     "550 5.3.2 Service currently unavailable\r\n");
		break;
	    case PSC_ACT_IGNORE:
		PSC_UNFAIL_SESSION_STATE(state, PSC_STATE_FLAG_BLIST_FAIL);

		/*
		 * Not: PSC_PASS_SESSION_STATE. Repeat this test the next
		 * time.
		 */
		break;
	    default:
		msg_panic("%s: unknown blacklist action value %d",
			  myname, psc_blist_action);
	    }
	    break;

	    /*
	     * Permanently whitelisted.
	     */
	case PSC_ACL_ACT_WHITELIST:
	    msg_info("WHITELISTED [%s]:%s", PSC_CLIENT_ADDR_PORT(state));
	    psc_conclude(state);
	    return;

	    /*
	     * Other: dunno (don't know) or error.
	     */
	default:
	    break;
	}
    }

    /*
     * The temporary whitelist (i.e. the postscreen cache) has the lowest
     * precedence. This cache contains information about the results of prior
     * tests. Whitelist the client when all enabled test results are still
     * valid.
     */
    if ((state->flags & PSC_STATE_MASK_ANY_FAIL) == 0
	&& psc_cache_map != 0
	&& (stamp_str = psc_cache_lookup(psc_cache_map, state->smtp_client_addr)) != 0) {
	saved_flags = state->flags;
	psc_parse_tests(state, stamp_str, event_time());
	state->flags |= saved_flags;
	if (msg_verbose)
	    msg_info("%s: cached + recent flags: %s",
		     myname, psc_print_state_flags(state->flags, myname));
	if ((state->flags & PSC_STATE_MASK_ANY_TODO_FAIL) == 0) {
	    msg_info("PASS OLD [%s]:%s", PSC_CLIENT_ADDR_PORT(state));
	    psc_conclude(state);
	    return;
	}
    } else {
	saved_flags = state->flags;
	psc_new_tests(state);
	state->flags |= saved_flags;
	if (msg_verbose)
	    msg_info("%s: new + recent flags: %s",
		     myname, psc_print_state_flags(state->flags, myname));
    }

    /*
     * Don't whitelist clients that connect to backup MX addresses. Fail
     * "closed" on error.
     */
    if (addr_match_list_match(psc_wlist_if, smtp_server_addr.buf) == 0) {
	state->flags |= (PSC_STATE_FLAG_WLIST_FAIL | PSC_STATE_FLAG_NOFORWARD);
	msg_info("WHITELIST VETO [%s]:%s", PSC_CLIENT_ADDR_PORT(state));
    }

    /*
     * Reply with 421 when we can't analyze more connections. That also means
     * no deep protocol tests when the noforward flag is raised.
     */
    if (var_psc_pre_queue_limit > 0
	&& psc_check_queue_length - psc_post_queue_length
	>= var_psc_pre_queue_limit) {
	msg_info("reject: connect from [%s]:%s: all screening ports busy",
		 state->smtp_client_addr, state->smtp_client_port);
	PSC_DROP_SESSION_STATE(state,
			       "421 4.3.2 All screening ports are busy\r\n");
	return;
    }

    /*
     * If the client has no up-to-date results for some tests, do those tests
     * first. Otherwise, skip the tests and hand off the connection.
     */
    if (state->flags & PSC_STATE_MASK_EARLY_TODO)
	psc_early_tests(state);
    else if (state->flags & (PSC_STATE_MASK_SMTPD_TODO | PSC_STATE_FLAG_NOFORWARD))
	psc_smtpd_tests(state);
    else
	psc_conclude(state);
}
Exemple #6
0
static int deliver_mailbox_file(LOCAL_STATE state, USER_ATTR usr_attr)
{
    const char *myname = "deliver_mailbox_file";
    char   *spool_dir;
    char   *mailbox;
    DSN_BUF *why = state.msg_attr.why;
    MBOX   *mp;
    int     mail_copy_status;
    int     deliver_status;
    int     copy_flags;
    VSTRING *biff;
    long    end;
    struct stat st;
    uid_t   spool_uid;
    gid_t   spool_gid;
    uid_t   chown_uid;
    gid_t   chown_gid;

    /*
     * Make verbose logging easier to understand.
     */
    state.level++;
    if (msg_verbose)
	MSG_LOG_STATE(myname, state);

    /*
     * Don't deliver trace-only requests.
     */
    if (DEL_REQ_TRACE_ONLY(state.request->flags)) {
	dsb_simple(why, "2.0.0", "delivers to mailbox");
	return (sent(BOUNCE_FLAGS(state.request), SENT_ATTR(state.msg_attr)));
    }

    /*
     * Initialize. Assume the operation will fail. Set the delivered
     * attribute to reflect the final recipient.
     */
    if (vstream_fseek(state.msg_attr.fp, state.msg_attr.offset, SEEK_SET) < 0)
	msg_fatal("seek message file %s: %m", VSTREAM_PATH(state.msg_attr.fp));
    if (var_frozen_delivered == 0)
	state.msg_attr.delivered = state.msg_attr.rcpt.address;
    mail_copy_status = MAIL_COPY_STAT_WRITE;
    if (*var_home_mailbox) {
	spool_dir = 0;
	mailbox = concatenate(usr_attr.home, "/", var_home_mailbox, (char *) 0);
    } else {
	spool_dir = var_mail_spool_dir;
	mailbox = concatenate(spool_dir, "/", state.msg_attr.user, (char *) 0);
    }

    /*
     * Mailbox delivery with least privilege. As long as we do not use root
     * privileges this code may also work over NFS.
     * 
     * If delivering to the recipient's home directory, perform all operations
     * (including file locking) as that user (Mike Muuss, Army Research
     * Laboratory, USA).
     * 
     * If delivering to the mail spool directory, and the spool directory is
     * world-writable, deliver as the recipient; if the spool directory is
     * group-writable, use the recipient user id and the mail spool group id.
     * 
     * Otherwise, use root privileges and chown the mailbox.
     */
    if (spool_dir == 0
	|| stat(spool_dir, &st) < 0
	|| (st.st_mode & S_IWOTH) != 0) {
	spool_uid = usr_attr.uid;
	spool_gid = usr_attr.gid;
    } else if ((st.st_mode & S_IWGRP) != 0) {
	spool_uid = usr_attr.uid;
	spool_gid = st.st_gid;
    } else {
	spool_uid = 0;
	spool_gid = 0;
    }
    if (spool_uid == usr_attr.uid) {
	chown_uid = -1;
	chown_gid = -1;
    } else {
	chown_uid = usr_attr.uid;
	chown_gid = usr_attr.gid;
    }
    if (msg_verbose)
	msg_info("spool_uid/gid %ld/%ld chown_uid/gid %ld/%ld",
		 (long) spool_uid, (long) spool_gid,
		 (long) chown_uid, (long) chown_gid);

    /*
     * Lock the mailbox and open/create the mailbox file. Depending on the
     * type of locking used, we lock first or we open first.
     * 
     * Write the file as the recipient, so that file quota work.
     */
    copy_flags = MAIL_COPY_MBOX;
    if ((local_deliver_hdr_mask & DELIVER_HDR_FILE) == 0)
	copy_flags &= ~MAIL_COPY_DELIVERED;

    set_eugid(spool_uid, spool_gid);
    mp = mbox_open(mailbox, O_APPEND | O_WRONLY | O_CREAT,
		   S_IRUSR | S_IWUSR, &st, chown_uid, chown_gid,
		   local_mbox_lock_mask, "5.2.0", why);
    if (mp != 0) {
	if (spool_uid != usr_attr.uid || spool_gid != usr_attr.gid)
	    set_eugid(usr_attr.uid, usr_attr.gid);
	if (S_ISREG(st.st_mode) == 0) {
	    vstream_fclose(mp->fp);
	    dsb_simple(why, "5.2.0",
		       "destination %s is not a regular file", mailbox);
	} else if (var_strict_mbox_owner && st.st_uid != usr_attr.uid) {
	    vstream_fclose(mp->fp);
	    dsb_simple(why, "4.2.0",
		       "destination %s is not owned by recipient", mailbox);
	    msg_warn("specify \"%s = no\" to ignore mailbox ownership mismatch",
		     VAR_STRICT_MBOX_OWNER);
	} else {
	    end = vstream_fseek(mp->fp, (off_t) 0, SEEK_END);
	    mail_copy_status = mail_copy(COPY_ATTR(state.msg_attr), mp->fp,
					 copy_flags, "\n", why);
	}
	if (spool_uid != usr_attr.uid || spool_gid != usr_attr.gid)
	    set_eugid(spool_uid, spool_gid);
	mbox_release(mp);
    }
    set_eugid(var_owner_uid, var_owner_gid);

    /*
     * As the mail system, bounce, defer delivery, or report success.
     */
    if (mail_copy_status & MAIL_COPY_STAT_CORRUPT) {
	deliver_status = DEL_STAT_DEFER;
    } else if (mail_copy_status != 0) {
	vstring_sprintf_prepend(why->reason,
				"cannot update mailbox %s for user %s. ",
				mailbox, state.msg_attr.user);
	deliver_status =
	    (STR(why->status)[0] == '4' ?
	     defer_append : bounce_append)
	    (BOUNCE_FLAGS(state.request), BOUNCE_ATTR(state.msg_attr));
    } else {
	dsb_simple(why, "2.0.0", "delivered to mailbox");
	deliver_status = sent(BOUNCE_FLAGS(state.request),
			      SENT_ATTR(state.msg_attr));
	if (var_biff) {
	    biff = vstring_alloc(100);
	    vstring_sprintf(biff, "%s@%ld", usr_attr.logname, (long) end);
	    biff_notify(STR(biff), VSTRING_LEN(biff) + 1);
	    vstring_free(biff);
	}
    }

    /*
     * Clean up.
     */
    myfree(mailbox);
    return (deliver_status);
}
Exemple #7
0
static void show_queue(void)
{
    char    buf[VSTREAM_BUFSIZE];
    VSTREAM *showq;
    int     n;

    /*
     * Connect to the show queue service. Terminate silently when piping into
     * a program that terminates early.
     */
    if ((showq = mail_connect(MAIL_CLASS_PUBLIC, var_showq_service, BLOCKING)) != 0) {
	while ((n = vstream_fread(showq, buf, sizeof(buf))) > 0) {
	    if (vstream_fwrite(VSTREAM_OUT, buf, n) != n
		|| vstream_fflush(VSTREAM_OUT) != 0) {
		if (errno == EPIPE)
		    break;
		msg_fatal("write error: %m");
	    }
	}
	if (vstream_fclose(showq) && errno != EPIPE)
	    msg_warn("close: %m");
    }

    /*
     * Don't assume that the mail system is down when the user has
     * insufficient permission to access the showq socket.
     */
    else if (errno == EACCES) {
	msg_fatal_status(EX_SOFTWARE,
			 "Connect to the %s %s service: %m",
			 var_mail_name, var_showq_service);
    }

    /*
     * When the mail system is down, the superuser can still access the queue
     * directly. Just run the showq program in stand-alone mode.
     */
    else if (geteuid() == 0) {
	ARGV   *argv;
	int     stat;

	msg_warn("Mail system is down -- accessing queue directly");
	argv = argv_alloc(6);
	argv_add(argv, var_showq_service, "-u", "-S", (char *) 0);
	for (n = 0; n < msg_verbose; n++)
	    argv_add(argv, "-v", (char *) 0);
	argv_terminate(argv);
	stat = mail_run_foreground(var_daemon_dir, argv->argv);
	argv_free(argv);
    }

    /*
     * When the mail system is down, unprivileged users are stuck, because by
     * design the mail system contains no set_uid programs. The only way for
     * an unprivileged user to cross protection boundaries is to talk to the
     * showq daemon.
     */
    else {
	msg_fatal_status(EX_UNAVAILABLE,
			 "Queue report unavailable - mail system is down");
    }
}
Exemple #8
0
DICT   *dict_pcre_open(const char *mapname, int open_flags, int dict_flags)
{
    DICT_PCRE *dict_pcre;
    VSTREAM *map_fp;
    struct stat st;
    VSTRING *line_buffer;
    DICT_PCRE_RULE *last_rule = 0;
    DICT_PCRE_RULE *rule;
    int     lineno = 0;
    int     nesting = 0;
    char   *p;

    /*
     * Sanity checks.
     */
    if (open_flags != O_RDONLY)
	return (dict_surrogate(DICT_TYPE_PCRE, mapname, open_flags, dict_flags,
			       "%s:%s map requires O_RDONLY access mode",
			       DICT_TYPE_PCRE, mapname));

    /*
     * Open the configuration file.
     */
    if ((map_fp = vstream_fopen(mapname, O_RDONLY, 0)) == 0)
	return (dict_surrogate(DICT_TYPE_PCRE, mapname, open_flags, dict_flags,
			       "open %s: %m", mapname));
    if (fstat(vstream_fileno(map_fp), &st) < 0)
	msg_fatal("fstat %s: %m", mapname);

    line_buffer = vstring_alloc(100);

    dict_pcre = (DICT_PCRE *) dict_alloc(DICT_TYPE_PCRE, mapname,
					 sizeof(*dict_pcre));
    dict_pcre->dict.lookup = dict_pcre_lookup;
    dict_pcre->dict.close = dict_pcre_close;
    dict_pcre->dict.flags = dict_flags | DICT_FLAG_PATTERN;
    if (dict_flags & DICT_FLAG_FOLD_MUL)
	dict_pcre->dict.fold_buf = vstring_alloc(10);
    dict_pcre->head = 0;
    dict_pcre->expansion_buf = 0;

    if (dict_pcre_init == 0) {
	pcre_malloc = (void *(*) (size_t)) mymalloc;
	pcre_free = (void (*) (void *)) myfree;
	dict_pcre_init = 1;
    }
    dict_pcre->dict.owner.uid = st.st_uid;
    dict_pcre->dict.owner.status = (st.st_uid != 0);

    /*
     * Parse the pcre table.
     */
    while (readlline(line_buffer, map_fp, &lineno)) {
	p = vstring_str(line_buffer);
	trimblanks(p, 0)[0] = 0;		/* Trim space at end */
	if (*p == 0)
	    continue;
	rule = dict_pcre_parse_rule(mapname, lineno, p, nesting, dict_flags);
	if (rule == 0)
	    continue;
	if (rule->op == DICT_PCRE_OP_IF) {
	    nesting++;
	} else if (rule->op == DICT_PCRE_OP_ENDIF) {
	    nesting--;
	}
	if (last_rule == 0)
	    dict_pcre->head = rule;
	else
	    last_rule->next = rule;
	last_rule = rule;
    }

    if (nesting)
	msg_warn("pcre map %s, line %d: more IFs than ENDIFs",
		 mapname, lineno);

    vstring_free(line_buffer);
    vstream_fclose(map_fp);

    return (DICT_DEBUG (&dict_pcre->dict));
}
Exemple #9
0
static void bounce_service(VSTREAM *client, char *service_name, char **argv)
{
    int     command;
    int     status;

    /*
     * Sanity check. This service takes no command-line arguments. The
     * service name should be usable as a subdirectory name.
     */
    if (argv[0])
	msg_fatal("unexpected command-line argument: %s", argv[0]);
    if (mail_queue_name_ok(service_name) == 0)
	msg_fatal("malformed service name: %s", service_name);

    /*
     * Read and validate the first parameter of the client request. Let the
     * request-specific protocol routines take care of the remainder.
     */
    if (attr_scan(client, ATTR_FLAG_STRICT | ATTR_FLAG_MORE,
		  ATTR_TYPE_INT, MAIL_ATTR_NREQ, &command, 0) != 1) {
	msg_warn("malformed request");
	status = -1;
    } else if (command == BOUNCE_CMD_VERP) {
	status = bounce_verp_proto(service_name, client);
    } else if (command == BOUNCE_CMD_FLUSH) {
	status = bounce_notify_proto(service_name, client,
				     bounce_notify_service);
    } else if (command == BOUNCE_CMD_WARN) {
	status = bounce_notify_proto(service_name, client,
				     bounce_warn_service);
    } else if (command == BOUNCE_CMD_TRACE) {
	status = bounce_notify_proto(service_name, client,
				     bounce_trace_service);
    } else if (command == BOUNCE_CMD_APPEND) {
	status = bounce_append_proto(service_name, client);
    } else if (command == BOUNCE_CMD_ONE) {
	status = bounce_one_proto(service_name, client);
    } else {
	msg_warn("unknown command: %d", command);
	status = -1;
    }

    /*
     * When the request has completed, send the completion status to the
     * client.
     */
    attr_print(client, ATTR_FLAG_NONE,
	       ATTR_TYPE_INT, MAIL_ATTR_STATUS, status,
	       ATTR_TYPE_END);
    vstream_fflush(client);

    /*
     * When a cleanup trap was set, delete the log file in case of error.
     * This includes errors while sending the completion status to the
     * client.
     */
    if (bounce_cleanup_path) {
	if (status || vstream_ferror(client))
	    bounce_cleanup_log();
	bounce_cleanup_unregister();
    }
}
Exemple #10
0
static void post_init(char *unused_name, char **unused_argv)
{
    static const NAME_MASK lookup_masks[] = {
	SMTP_HOST_LOOKUP_DNS, SMTP_HOST_FLAG_DNS,
	SMTP_HOST_LOOKUP_NATIVE, SMTP_HOST_FLAG_NATIVE,
	0,
    };
    static const NAME_MASK dns_res_opt_masks[] = {
	SMTP_DNS_RES_OPT_DEFNAMES, RES_DEFNAMES,
	SMTP_DNS_RES_OPT_DNSRCH, RES_DNSRCH,
	0,
    };
    static const NAME_CODE dns_support[] = {
	SMTP_DNS_SUPPORT_DISABLED, SMTP_DNS_DISABLED,
	SMTP_DNS_SUPPORT_ENABLED, SMTP_DNS_ENABLED,
#if (RES_USE_DNSSEC != 0) && (RES_USE_EDNS0 != 0)
	SMTP_DNS_SUPPORT_DNSSEC, SMTP_DNS_DNSSEC,
#endif
	0, SMTP_DNS_INVALID,
    };

    if (*var_smtp_dns_support == 0) {
	/* Backwards compatible empty setting */
	smtp_dns_support =
	    var_disable_dns ? SMTP_DNS_DISABLED : SMTP_DNS_ENABLED;
    } else {
	smtp_dns_support =
	    name_code(dns_support, NAME_CODE_FLAG_NONE, var_smtp_dns_support);
	if (smtp_dns_support == SMTP_DNS_INVALID)
	    msg_fatal("invalid %s: \"%s\"", SMTP_X(DNS_SUPPORT),
		      var_smtp_dns_support);
	var_disable_dns = (smtp_dns_support == SMTP_DNS_DISABLED);
    }

    /*
     * Select hostname lookup mechanisms.
     */
    if (smtp_dns_support == SMTP_DNS_DISABLED)
	smtp_host_lookup_mask = SMTP_HOST_FLAG_NATIVE;
    else
	smtp_host_lookup_mask =
	    name_mask(SMTP_X(HOST_LOOKUP), lookup_masks, var_smtp_host_lookup);
    if (msg_verbose)
	msg_info("host name lookup methods: %s",
		 str_name_mask(SMTP_X(HOST_LOOKUP), lookup_masks,
			       smtp_host_lookup_mask));

    /*
     * Session cache instance.
     */
    if (*var_smtp_cache_dest || var_smtp_cache_demand)
#if 0
	smtp_scache = scache_multi_create();
#else
	smtp_scache = scache_clnt_create(var_scache_service,
					 var_scache_proto_tmout,
					 var_ipc_idle_limit,
					 var_ipc_ttl_limit);
#endif

    /*
     * Select DNS query flags.
     */
    smtp_dns_res_opt = name_mask(SMTP_X(DNS_RES_OPT), dns_res_opt_masks,
				 var_smtp_dns_res_opt);
}
Exemple #11
0
static const char *dict_pcre_lookup(DICT *dict, const char *lookup_string)
{
    DICT_PCRE *dict_pcre = (DICT_PCRE *) dict;
    DICT_PCRE_RULE *rule;
    DICT_PCRE_IF_RULE *if_rule;
    DICT_PCRE_MATCH_RULE *match_rule;
    int     lookup_len = strlen(lookup_string);
    DICT_PCRE_EXPAND_CONTEXT ctxt;
    int     nesting = 0;

    dict->error = 0;

    if (msg_verbose)
	msg_info("dict_pcre_lookup: %s: %s", dict->name, lookup_string);

    /*
     * Optionally fold the key.
     */
    if (dict->flags & DICT_FLAG_FOLD_MUL) {
	if (dict->fold_buf == 0)
	    dict->fold_buf = vstring_alloc(10);
	vstring_strcpy(dict->fold_buf, lookup_string);
	lookup_string = lowercase(vstring_str(dict->fold_buf));
    }
    for (rule = dict_pcre->head; rule; rule = rule->next) {

	/*
	 * Skip rules inside failed IF/ENDIF.
	 */
	if (nesting < rule->nesting)
	    continue;

	switch (rule->op) {

	    /*
	     * Search for a matching expression.
	     */
	case DICT_PCRE_OP_MATCH:
	    match_rule = (DICT_PCRE_MATCH_RULE *) rule;
	    ctxt.matches = pcre_exec(match_rule->pattern, match_rule->hints,
				     lookup_string, lookup_len,
				     NULL_STARTOFFSET, NULL_EXEC_OPTIONS,
				     ctxt.offsets, PCRE_MAX_CAPTURE * 3);

	    if (ctxt.matches > 0) {
		if (!match_rule->match)
		    continue;			/* Negative rule matched */
	    } else if (ctxt.matches == PCRE_ERROR_NOMATCH) {
		if (match_rule->match)
		    continue;			/* Positive rule did not
						 * match */
	    } else {
		dict_pcre_exec_error(dict->name, rule->lineno, ctxt.matches);
		continue;			/* pcre_exec failed */
	    }

	    /*
	     * Skip $number substitutions when the replacement text contains
	     * no $number strings, as learned during the compile time
	     * pre-scan. The pre-scan already replaced $$ by $.
	     */
	    if (match_rule->max_sub == 0)
		return match_rule->replacement;

	    /*
	     * We've got a match. Perform substitution on replacement string.
	     */
	    if (dict_pcre->expansion_buf == 0)
		dict_pcre->expansion_buf = vstring_alloc(10);
	    VSTRING_RESET(dict_pcre->expansion_buf);
	    ctxt.dict_pcre = dict_pcre;
	    ctxt.match_rule = match_rule;
	    ctxt.lookup_string = lookup_string;

	    if (mac_parse(match_rule->replacement, dict_pcre_expand,
			  (char *) &ctxt) & MAC_PARSE_ERROR)
		msg_fatal("pcre map %s, line %d: bad replacement syntax",
			  dict->name, rule->lineno);

	    VSTRING_TERMINATE(dict_pcre->expansion_buf);
	    return (vstring_str(dict_pcre->expansion_buf));

	    /*
	     * Conditional. XXX We provide space for matched substring info
	     * because PCRE uses part of it as workspace for backtracking.
	     * PCRE will allocate memory if it runs out of backtracking
	     * storage.
	     */
	case DICT_PCRE_OP_IF:
	    if_rule = (DICT_PCRE_IF_RULE *) rule;
	    ctxt.matches = pcre_exec(if_rule->pattern, if_rule->hints,
				     lookup_string, lookup_len,
				     NULL_STARTOFFSET, NULL_EXEC_OPTIONS,
				     ctxt.offsets, PCRE_MAX_CAPTURE * 3);

	    if (ctxt.matches > 0) {
		if (!if_rule->match)
		    continue;			/* Negative rule matched */
	    } else if (ctxt.matches == PCRE_ERROR_NOMATCH) {
		if (if_rule->match)
		    continue;			/* Positive rule did not
						 * match */
	    } else {
		dict_pcre_exec_error(dict->name, rule->lineno, ctxt.matches);
		continue;			/* pcre_exec failed */
	    }
	    nesting++;
	    continue;

	    /*
	     * ENDIF after successful IF.
	     */
	case DICT_PCRE_OP_ENDIF:
	    nesting--;
	    continue;

	default:
	    msg_panic("dict_pcre_lookup: impossible operation %d", rule->op);
	}
    }
    return (0);
}
Exemple #12
0
static void pre_init(char *unused_name, char **unused_argv)
{
    int     use_tls;
    static const NAME_CODE addr_pref_map[] = {
	INET_PROTO_NAME_IPV6, SMTP_MISC_FLAG_PREF_IPV6,
	INET_PROTO_NAME_IPV4, SMTP_MISC_FLAG_PREF_IPV4,
	INET_PROTO_NAME_ANY, 0,
	0, -1,
    };

    /*
     * Turn on per-peer debugging.
     */
    debug_peer_init();

    /*
     * SASL initialization.
     */
    if (var_smtp_sasl_enable)
#ifdef USE_SASL_AUTH
	smtp_sasl_initialize();
#else
	msg_warn("%s is true, but SASL support is not compiled in",
		 SMTP_X(SASL_ENABLE));
#endif

    if (*var_smtp_tls_level != 0)
#ifdef USE_TLS
	switch (tls_level_lookup(var_smtp_tls_level)) {
	case TLS_LEV_SECURE:
	case TLS_LEV_VERIFY:
	case TLS_LEV_DANE_ONLY:
	case TLS_LEV_FPRINT:
	case TLS_LEV_ENCRYPT:
	    var_smtp_use_tls = var_smtp_enforce_tls = 1;
	    break;
	case TLS_LEV_DANE:
	case TLS_LEV_MAY:
	    var_smtp_use_tls = 1;
	    var_smtp_enforce_tls = 0;
	    break;
	case TLS_LEV_NONE:
	    var_smtp_use_tls = var_smtp_enforce_tls = 0;
	    break;
	default:
	    /* tls_level_lookup() logs no warning. */
	    /* session_tls_init() assumes that var_smtp_tls_level is sane. */
	    msg_fatal("Invalid TLS level \"%s\"", var_smtp_tls_level);
	}
#endif
    use_tls = (var_smtp_use_tls || var_smtp_enforce_tls);

    /*
     * Initialize the TLS data before entering the chroot jail
     */
    if (use_tls || var_smtp_tls_per_site[0] || var_smtp_tls_policy[0]) {
#ifdef USE_TLS
	TLS_CLIENT_INIT_PROPS props;

	/*
	 * We get stronger type safety and a cleaner interface by combining
	 * the various parameters into a single tls_client_props structure.
	 * 
	 * Large parameter lists are error-prone, so we emulate a language
	 * feature that C does not have natively: named parameter lists.
	 */
	smtp_tls_ctx =
	    TLS_CLIENT_INIT(&props,
			    log_param = SMTP_X(TLS_LOGLEVEL),
			    log_level = var_smtp_tls_loglevel,
			    verifydepth = var_smtp_tls_scert_vd,
			    cache_type = X_SMTP(TLS_MGR_SCACHE),
			    cert_file = var_smtp_tls_cert_file,
			    key_file = var_smtp_tls_key_file,
			    dcert_file = var_smtp_tls_dcert_file,
			    dkey_file = var_smtp_tls_dkey_file,
			    eccert_file = var_smtp_tls_eccert_file,
			    eckey_file = var_smtp_tls_eckey_file,
			    CAfile = var_smtp_tls_CAfile,
			    CApath = var_smtp_tls_CApath,
			    mdalg = var_smtp_tls_fpt_dgst);
	smtp_tls_list_init();
#else
	msg_warn("TLS has been selected, but TLS support is not compiled in");
#endif
    }

    /*
     * Flush client.
     */
    flush_init();

    /*
     * Session cache domain list.
     */
    if (*var_smtp_cache_dest)
	smtp_cache_dest = string_list_init(MATCH_FLAG_RETURN, var_smtp_cache_dest);

    /*
     * EHLO keyword filter.
     */
    if (*var_smtp_ehlo_dis_maps)
	smtp_ehlo_dis_maps = maps_create(SMTP_X(EHLO_DIS_MAPS),
					 var_smtp_ehlo_dis_maps,
					 DICT_FLAG_LOCK);

    /*
     * PIX bug workarounds.
     */
    if (*var_smtp_pix_bug_maps)
	smtp_pix_bug_maps = maps_create(SMTP_X(PIX_BUG_MAPS),
					var_smtp_pix_bug_maps,
					DICT_FLAG_LOCK);

    /*
     * Generic maps.
     */
    if (*var_prop_extension)
	smtp_ext_prop_mask =
	    ext_prop_mask(VAR_PROP_EXTENSION, var_prop_extension);
    if (*var_smtp_generic_maps)
	smtp_generic_maps =
	    maps_create(SMTP_X(GENERIC_MAPS), var_smtp_generic_maps,
			DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX);

    /*
     * Header/body checks.
     */
    smtp_header_checks = hbc_header_checks_create(
				      SMTP_X(HEAD_CHKS), var_smtp_head_chks,
				      SMTP_X(MIME_CHKS), var_smtp_mime_chks,
				      SMTP_X(NEST_CHKS), var_smtp_nest_chks,
						  smtp_hbc_callbacks);
    smtp_body_checks = hbc_body_checks_create(
				      SMTP_X(BODY_CHKS), var_smtp_body_chks,
					      smtp_hbc_callbacks);

    /*
     * Server reply filter.
     */
    if (*var_smtp_resp_filter)
	smtp_chat_resp_filter =
	    dict_open(var_smtp_resp_filter, O_RDONLY,
		      DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX);

    /*
     * Address family preference.
     */
    if (*var_smtp_addr_pref) {
	smtp_addr_pref = name_code(addr_pref_map, NAME_CODE_FLAG_NONE,
				   var_smtp_addr_pref);
	if (smtp_addr_pref < 0)
	    msg_fatal("bad %s value: %s", SMTP_X(ADDR_PREF), var_smtp_addr_pref);
    }
}
Exemple #13
0
void    smtpd_peer_init(SMTPD_STATE *state)
{
    const char *myname = "smtpd_peer_init";
    SOCKADDR_SIZE sa_length;
    struct sockaddr *sa;
    INET_PROTO_INFO *proto_info = inet_proto_info();

    sa = (struct sockaddr *) & (state->sockaddr);
    sa_length = sizeof(state->sockaddr);

    /*
     * Look up the peer address information.
     * 
     * XXX If we make local endpoint (getsockname) information available to
     * Milter applications as {if_name} and {if_addr}, then we also must be
     * able to provide this via the XCLIENT command for Milter testing.
     * 
     * XXX If we make local or remote port information available to policy
     * servers or Milter applications, then we must also make this testable
     * with the XCLIENT command, otherwise there will be confusion.
     * 
     * XXX If we make local or remote port information available via logging,
     * then we must also support these attributes with the XFORWARD command.
     * 
     * XXX If support were to be added for Milter applications in down-stream
     * MTAs, then consistency demands that we propagate a lot of Sendmail
     * macro information via the XFORWARD command. Otherwise we could end up
     * with a very confusing situation.
     */
    if (getpeername(vstream_fileno(state->client), sa, &sa_length) >= 0) {
	errno = 0;
    }

    /*
     * If peer went away, give up.
     */
    if (errno != 0 && errno != ENOTSOCK) {
	state->name = mystrdup(CLIENT_NAME_UNKNOWN);
	state->reverse_name = mystrdup(CLIENT_NAME_UNKNOWN);
	state->addr = mystrdup(CLIENT_ADDR_UNKNOWN);
	state->rfc_addr = mystrdup(CLIENT_ADDR_UNKNOWN);
	state->addr_family = AF_UNSPEC;
	state->name_status = SMTPD_PEER_CODE_PERM;
	state->reverse_name_status = SMTPD_PEER_CODE_PERM;
	state->port = mystrdup(CLIENT_PORT_UNKNOWN);
    }

    /*
     * Convert the client address to printable address and hostname.
     * 
     * XXX If we're given an IPv6 (or IPv4) connection from, e.g., inetd, while
     * Postfix IPv6 (or IPv4) support is turned off, don't (skip to the final
     * else clause, pretend the origin is localhost[127.0.0.1], and become an
     * open relay).
     */
    else if (errno == 0
	     && (sa->sa_family == AF_INET
#ifdef AF_INET6
		 || sa->sa_family == AF_INET6
#endif
		 )) {
	MAI_HOSTNAME_STR client_name;
	MAI_HOSTADDR_STR client_addr;
	MAI_SERVPORT_STR client_port;
	int     aierr;
	char   *colonp;

	/*
	 * Sanity check: we can't use sockets that we're not configured for.
	 */
	if (strchr((char *) proto_info->sa_family_list, sa->sa_family) == 0)
	    msg_fatal("cannot handle socket type %s with \"%s = %s\"",
#ifdef AF_INET6
		      sa->sa_family == AF_INET6 ? "AF_INET6" :
#endif
		      sa->sa_family == AF_INET ? "AF_INET" :
		      "other", VAR_INET_PROTOCOLS, var_inet_protocols);

	/*
	 * Sorry, but there are some things that we just cannot do while
	 * connected to the network.
	 */
	if (geteuid() != var_owner_uid || getuid() != var_owner_uid) {
	    msg_error("incorrect SMTP server privileges: uid=%lu euid=%lu",
		      (unsigned long) getuid(), (unsigned long) geteuid());
	    msg_fatal("the Postfix SMTP server must run with $%s privileges",
		      VAR_MAIL_OWNER);
	}

	/*
	 * Convert the client address to printable form.
	 */
	if ((aierr = sockaddr_to_hostaddr(sa, sa_length, &client_addr,
					  &client_port, 0)) != 0)
	    msg_fatal("%s: cannot convert client address/port to string: %s",
		      myname, MAI_STRERROR(aierr));
	state->port = mystrdup(client_port.buf);

	/*
	 * XXX Strip off the IPv6 datalink suffix to avoid false alarms with
	 * strict address syntax checks.
	 */
#ifdef HAS_IPV6
	(void) split_at(client_addr.buf, '%');
#endif

	/*
	 * We convert IPv4-in-IPv6 address to 'true' IPv4 address early on,
	 * but only if IPv4 support is enabled (why would anyone want to turn
	 * it off)? With IPv4 support enabled we have no need for the IPv6
	 * form in logging, hostname verification and access checks.
	 */
#ifdef HAS_IPV6
	if (sa->sa_family == AF_INET6) {
	    if (strchr((char *) proto_info->sa_family_list, AF_INET) != 0
		&& IN6_IS_ADDR_V4MAPPED(&SOCK_ADDR_IN6_ADDR(sa))
		&& (colonp = strrchr(client_addr.buf, ':')) != 0) {
		struct addrinfo *res0;

		if (msg_verbose > 1)
		    msg_info("%s: rewriting V4-mapped address \"%s\" to \"%s\"",
			     myname, client_addr.buf, colonp + 1);

		state->addr = mystrdup(colonp + 1);
		state->rfc_addr = mystrdup(colonp + 1);
		state->addr_family = AF_INET;
		aierr = hostaddr_to_sockaddr(state->addr, (char *) 0, 0, &res0);
		if (aierr)
		    msg_fatal("%s: cannot convert %s from string to binary: %s",
			      myname, state->addr, MAI_STRERROR(aierr));
		sa_length = res0->ai_addrlen;
		if (sa_length > sizeof(state->sockaddr))
		    sa_length = sizeof(state->sockaddr);
		memcpy((char *) sa, res0->ai_addr, sa_length);
		freeaddrinfo(res0);		/* 200412 */
	    }

	    /*
	     * Following RFC 2821 section 4.1.3, an IPv6 address literal gets
	     * a prefix of 'IPv6:'. We do this consistently for all IPv6
	     * addresses that that appear in headers or envelopes. The fact
	     * that valid_mailhost_addr() enforces the form helps of course.
	     * We use the form without IPV6: prefix when doing access
	     * control, or when accessing the connection cache.
	     */
	    else {
		state->addr = mystrdup(client_addr.buf);
		state->rfc_addr =
		    concatenate(IPV6_COL, client_addr.buf, (char *) 0);
		state->addr_family = sa->sa_family;
	    }
	}

	/*
	 * An IPv4 address is in dotted quad decimal form.
	 */
	else
#endif
	{
	    state->addr = mystrdup(client_addr.buf);
	    state->rfc_addr = mystrdup(client_addr.buf);
	    state->addr_family = sa->sa_family;
	}

	/*
	 * Look up and sanity check the client hostname.
	 * 
	 * It is unsafe to allow numeric hostnames, especially because there
	 * exists pressure to turn off the name->addr double check. In that
	 * case an attacker could trivally bypass access restrictions.
	 * 
	 * sockaddr_to_hostname() already rejects malformed or numeric names.
	 */
#define TEMP_AI_ERROR(e) \
	((e) == EAI_AGAIN || (e) == EAI_MEMORY || (e) == EAI_SYSTEM)

#define REJECT_PEER_NAME(state, code) { \
	myfree(state->name); \
	state->name = mystrdup(CLIENT_NAME_UNKNOWN); \
	state->name_status = code; \
    }

	if (var_smtpd_peername_lookup == 0) {
	    state->name = mystrdup(CLIENT_NAME_UNKNOWN);
	    state->reverse_name = mystrdup(CLIENT_NAME_UNKNOWN);
	    state->name_status = SMTPD_PEER_CODE_PERM;
	    state->reverse_name_status = SMTPD_PEER_CODE_PERM;
	} else if ((aierr = sockaddr_to_hostname(sa, sa_length, &client_name,
					 (MAI_SERVNAME_STR *) 0, 0)) != 0) {
	    state->name = mystrdup(CLIENT_NAME_UNKNOWN);
	    state->reverse_name = mystrdup(CLIENT_NAME_UNKNOWN);
	    state->name_status = (TEMP_AI_ERROR(aierr) ?
			       SMTPD_PEER_CODE_TEMP : SMTPD_PEER_CODE_PERM);
	    state->reverse_name_status = (TEMP_AI_ERROR(aierr) ?
			       SMTPD_PEER_CODE_TEMP : SMTPD_PEER_CODE_PERM);
	} else {
	    struct addrinfo *res0;
	    struct addrinfo *res;

	    state->name = mystrdup(client_name.buf);
	    state->reverse_name = mystrdup(client_name.buf);
	    state->name_status = SMTPD_PEER_CODE_OK;
	    state->reverse_name_status = SMTPD_PEER_CODE_OK;

	    /*
	     * Reject the hostname if it does not list the peer address.
	     * Without further validation or qualification, such information
	     * must not be allowed to enter the audit trail, as people would
	     * draw false conclusions.
	     */
	    aierr = hostname_to_sockaddr_pf(state->name, state->addr_family,
					    (char *) 0, 0, &res0);
	    if (aierr) {
		msg_warn("hostname %s does not resolve to address %s: %s",
			 state->name, state->addr, MAI_STRERROR(aierr));
		REJECT_PEER_NAME(state, (TEMP_AI_ERROR(aierr) ?
			    SMTPD_PEER_CODE_TEMP : SMTPD_PEER_CODE_FORGED));
	    } else {
		for (res = res0; /* void */ ; res = res->ai_next) {
		    if (res == 0) {
			msg_warn("hostname %s does not resolve to address %s",
				 state->name, state->addr);
			REJECT_PEER_NAME(state, SMTPD_PEER_CODE_FORGED);
			break;
		    }
		    if (strchr((char *) proto_info->sa_family_list, res->ai_family) == 0) {
			msg_info("skipping address family %d for host %s",
				 res->ai_family, state->name);
			continue;
		    }
		    if (sock_addr_cmp_addr(res->ai_addr, sa) == 0)
			break;			/* keep peer name */
		}
		freeaddrinfo(res0);
	    }
	}
    }

    /*
     * If it's not Internet, assume the client is local, and avoid using the
     * naming service because that can hang when the machine is disconnected.
     */
    else {
	state->name = mystrdup("localhost");
	state->reverse_name = mystrdup("localhost");
	if (proto_info->sa_family_list[0] == PF_INET6) {
	    state->addr = mystrdup("::1");	/* XXX bogus. */
	    state->rfc_addr = mystrdup(IPV6_COL "::1");	/* XXX bogus. */
	} else {
	    state->addr = mystrdup("127.0.0.1");/* XXX bogus. */
	    state->rfc_addr = mystrdup("127.0.0.1");	/* XXX bogus. */
	}
	state->addr_family = AF_UNSPEC;
	state->name_status = SMTPD_PEER_CODE_OK;
	state->reverse_name_status = SMTPD_PEER_CODE_OK;
	state->port = mystrdup("0");		/* XXX bogus. */
    }

    /*
     * Do the name[addr]:port formatting for pretty reports.
     */
    state->namaddr = SMTPD_BUILD_NAMADDRPORT(state->name, state->addr,
					     state->port);
}
Exemple #14
0
static int smtpd_peer_sockaddr_to_hostaddr(SMTPD_STATE *state)
{
    const char *myname = "smtpd_peer_sockaddr_to_hostaddr";
    struct sockaddr *sa = (struct sockaddr *) &(state->sockaddr);
    SOCKADDR_SIZE sa_length = state->sockaddr_len;

    /*
     * XXX If we're given an IPv6 (or IPv4) connection from, e.g., inetd,
     * while Postfix IPv6 (or IPv4) support is turned off, don't (skip to the
     * final else clause, pretend the origin is localhost[127.0.0.1], and
     * become an open relay).
     */
    if (sa->sa_family == AF_INET
#ifdef AF_INET6
	|| sa->sa_family == AF_INET6
#endif
	) {
	MAI_HOSTADDR_STR client_addr;
	MAI_SERVPORT_STR client_port;
	int     aierr;
	char   *colonp;

	/*
	 * Sanity check: we can't use sockets that we're not configured for.
	 */
	if (strchr((char *) proto_info->sa_family_list, sa->sa_family) == 0)
	    msg_fatal("cannot handle socket type %s with \"%s = %s\"",
#ifdef AF_INET6
		      sa->sa_family == AF_INET6 ? "AF_INET6" :
#endif
		      sa->sa_family == AF_INET ? "AF_INET" :
		      "other", VAR_INET_PROTOCOLS, var_inet_protocols);

	/*
	 * Sorry, but there are some things that we just cannot do while
	 * connected to the network.
	 */
	if (geteuid() != var_owner_uid || getuid() != var_owner_uid) {
	    msg_error("incorrect SMTP server privileges: uid=%lu euid=%lu",
		      (unsigned long) getuid(), (unsigned long) geteuid());
	    msg_fatal("the Postfix SMTP server must run with $%s privileges",
		      VAR_MAIL_OWNER);
	}

	/*
	 * Convert the client address to printable form.
	 */
	if ((aierr = sockaddr_to_hostaddr(sa, sa_length, &client_addr,
					  &client_port, 0)) != 0)
	    msg_fatal("%s: cannot convert client address/port to string: %s",
		      myname, MAI_STRERROR(aierr));
	state->port = mystrdup(client_port.buf);

	/*
	 * XXX Require that the infrastructure strips off the IPv6 datalink
	 * suffix to avoid false alarms with strict address syntax checks.
	 */
#ifdef HAS_IPV6
	if (strchr(client_addr.buf, '%') != 0)
	    msg_panic("%s: address %s has datalink suffix",
		      myname, client_addr.buf);
#endif

	/*
	 * We convert IPv4-in-IPv6 address to 'true' IPv4 address early on,
	 * but only if IPv4 support is enabled (why would anyone want to turn
	 * it off)? With IPv4 support enabled we have no need for the IPv6
	 * form in logging, hostname verification and access checks.
	 */
#ifdef HAS_IPV6
	if (sa->sa_family == AF_INET6) {
	    if (strchr((char *) proto_info->sa_family_list, AF_INET) != 0
		&& IN6_IS_ADDR_V4MAPPED(&SOCK_ADDR_IN6_ADDR(sa))
		&& (colonp = strrchr(client_addr.buf, ':')) != 0) {
		struct addrinfo *res0;

		if (msg_verbose > 1)
		    msg_info("%s: rewriting V4-mapped address \"%s\" to \"%s\"",
			     myname, client_addr.buf, colonp + 1);

		state->addr = mystrdup(colonp + 1);
		state->rfc_addr = mystrdup(colonp + 1);
		state->addr_family = AF_INET;
		aierr = hostaddr_to_sockaddr(state->addr, (char *) 0, 0, &res0);
		if (aierr)
		    msg_fatal("%s: cannot convert %s from string to binary: %s",
			      myname, state->addr, MAI_STRERROR(aierr));
		sa_length = res0->ai_addrlen;
		if (sa_length > sizeof(state->sockaddr))
		    sa_length = sizeof(state->sockaddr);
		memcpy((void *) sa, res0->ai_addr, sa_length);
		freeaddrinfo(res0);		/* 200412 */
	    }

	    /*
	     * Following RFC 2821 section 4.1.3, an IPv6 address literal gets
	     * a prefix of 'IPv6:'. We do this consistently for all IPv6
	     * addresses that that appear in headers or envelopes. The fact
	     * that valid_mailhost_addr() enforces the form helps of course.
	     * We use the form without IPV6: prefix when doing access
	     * control, or when accessing the connection cache.
	     */
	    else {
		state->addr = mystrdup(client_addr.buf);
		state->rfc_addr =
		    concatenate(IPV6_COL, client_addr.buf, (char *) 0);
		state->addr_family = sa->sa_family;
	    }
	}

	/*
	 * An IPv4 address is in dotted quad decimal form.
	 */
	else
#endif
	{
	    state->addr = mystrdup(client_addr.buf);
	    state->rfc_addr = mystrdup(client_addr.buf);
	    state->addr_family = sa->sa_family;
	}
	return (0);
    }

    /*
     * It's not Internet.
     */
    else {
	return (-1);
    }
}
Exemple #15
0
int     deliver_file(LOCAL_STATE state, USER_ATTR usr_attr, char *path)
{
    const char *myname = "deliver_file";
    struct stat st;
    MBOX   *mp;
    DSN_BUF *why = state.msg_attr.why;
    int     mail_copy_status = MAIL_COPY_STAT_WRITE;
    int     deliver_status;
    int     copy_flags;

    /*
     * Make verbose logging easier to understand.
     */
    state.level++;
    if (msg_verbose)
	MSG_LOG_STATE(myname, state);

    /*
     * DUPLICATE ELIMINATION
     * 
     * Skip this file if it was already delivered to as this user.
     */
    if (been_here(state.dup_filter, "file %ld %s", (long) usr_attr.uid, path))
	return (0);

    /*
     * DELIVERY POLICY
     * 
     * Do we allow delivery to files?
     */
    if ((local_file_deliver_mask & state.msg_attr.exp_type) == 0) {
	dsb_simple(why, "5.7.1", "mail to file is restricted");
	/* Account for possible owner- sender address override. */
	return (bounce_workaround(state));
    }

    /*
     * Don't deliver trace-only requests.
     */
    if (DEL_REQ_TRACE_ONLY(state.request->flags)) {
	dsb_simple(why, "2.0.0", "delivers to file: %s", path);
	return (sent(BOUNCE_FLAGS(state.request),
		     SENT_ATTR(state.msg_attr)));
    }

    /*
     * DELIVERY RIGHTS
     * 
     * Use a default uid/gid when none are given.
     */
    if (usr_attr.uid == 0 && (usr_attr.uid = var_default_uid) == 0)
	msg_panic("privileged default user id");
    if (usr_attr.gid == 0 && (usr_attr.gid = var_default_gid) == 0)
	msg_panic("privileged default group id");

    /*
     * If the name ends in /, use maildir-style delivery instead.
     */
    if (path[strlen(path) - 1] == '/')
	return (deliver_maildir(state, usr_attr, path));

    /*
     * Deliver. From here on, no early returns or we have a memory leak.
     */
    if (msg_verbose)
	msg_info("deliver_file (%ld,%ld): %s",
		 (long) usr_attr.uid, (long) usr_attr.gid, path);
    if (vstream_fseek(state.msg_attr.fp, state.msg_attr.offset, SEEK_SET) < 0)
	msg_fatal("seek queue file %s: %m", state.msg_attr.queue_id);

    /*
     * As the specified user, open or create the file, lock it, and append
     * the message.
     */
    copy_flags = MAIL_COPY_MBOX;
    if ((local_deliver_hdr_mask & DELIVER_HDR_FILE) == 0)
	copy_flags &= ~MAIL_COPY_DELIVERED;

    set_eugid(usr_attr.uid, usr_attr.gid);
    mp = mbox_open(path, O_APPEND | O_CREAT | O_WRONLY,
		   S_IRUSR | S_IWUSR, &st, -1, -1,
		   local_mbox_lock_mask | MBOX_DOT_LOCK_MAY_FAIL,
		   "5.2.0", why);
    if (mp != 0) {
	if (S_ISREG(st.st_mode) && st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) {
	    vstream_fclose(mp->fp);
	    dsb_simple(why, "5.7.1", "file is executable");
	} else {
	    mail_copy_status = mail_copy(COPY_ATTR(state.msg_attr), mp->fp,
					 S_ISREG(st.st_mode) ? copy_flags :
					 (copy_flags & ~MAIL_COPY_TOFILE),
					 "\n", why);
	}
	mbox_release(mp);
    }
    set_eugid(var_owner_uid, var_owner_gid);

    /*
     * As the mail system, bounce, defer delivery, or report success.
     */
    if (mail_copy_status & MAIL_COPY_STAT_CORRUPT) {
	deliver_status = DEL_STAT_DEFER;
    } else if (mail_copy_status != 0) {
	vstring_sprintf_prepend(why->reason,
				"cannot append message to file %s: ", path);
	if (STR(why->status)[0] == '4')
	    deliver_status =
		defer_append(BOUNCE_FLAGS(state.request),
			     BOUNCE_ATTR(state.msg_attr));
	else
	    /* Account for possible owner- sender address override. */
	    deliver_status = bounce_workaround(state);
    } else {
	dsb_simple(why, "2.0.0", "delivered to file: %s", path);
	deliver_status = sent(BOUNCE_FLAGS(state.request),
			      SENT_ATTR(state.msg_attr));
    }
    return (deliver_status);
}
Exemple #16
0
/**********************************************************************
 * public interface dict_mysql_lookup
 * find database entry return 0 if no alias found, set dict_errno
 * on errors to DICT_ERRBO_RETRY and set dict_errno to 0 on success
 *********************************************************************/
static const char *dict_mysql_lookup(DICT *dict, const char *name)
{
    MYSQL_RES *query_res;
    MYSQL_ROW row;
    DICT_MYSQL *dict_mysql;
    PLMYSQL *pldb;
    static VSTRING *result;
    static VSTRING *query = 0;
    int     i,
            j,
            numrows;
    char   *name_escaped = 0;

    dict_mysql = (DICT_MYSQL *) dict;
    pldb = dict_mysql->pldb;
    /* initialization  for query */
    query = vstring_alloc(24);
    vstring_strcpy(query, "");
    if ((name_escaped = (char *) mymalloc((sizeof(char) * (strlen(name) * 2) +1))) == NULL) {
        msg_fatal("dict_mysql_lookup: out of memory.");
    }
    /* prepare the query */
    mysql_escape_string(name_escaped, name, (unsigned int) strlen(name));
    vstring_sprintf(query, "select %s from %s where %s = '%s' %s", dict_mysql->name->select_field,
                    dict_mysql->name->table, dict_mysql->name->where_field, name_escaped,
                    dict_mysql->name->additional_conditions);
    if (msg_verbose)
        msg_info("dict_mysql_lookup using sql query: %s", vstring_str(query));
    /* free mem associated with preparing the query */
    myfree(name_escaped);
    /* do the query - set dict_errno & cleanup if there's an error */
    if ((query_res = plmysql_query(pldb,
                                   vstring_str(query),
                                   dict_mysql->name->dbname,
                                   dict_mysql->name->username,
                                   dict_mysql->name->password)) == 0) {
        dict_errno = DICT_ERR_RETRY;
        vstring_free(query);
        return 0;
    }
    dict_errno = 0;
    /* free the vstring query */
    vstring_free(query);
    numrows = mysql_num_rows(query_res);
    if (msg_verbose)
        msg_info("dict_mysql_lookup: retrieved %d rows", numrows);
    if (numrows == 0) {
        mysql_free_result(query_res);
        return 0;
    }
    if (result == 0)
        result = vstring_alloc(10);
    vstring_strcpy(result, "");
    for (i = 0; i < numrows; i++) {
        row = mysql_fetch_row(query_res);
        if (i > 0)
            vstring_strcat(result, ",");
        for (j = 0; j < mysql_num_fields(query_res); j++) {
            if (row[j] == 0) {
                if (msg_verbose > 1)
                    msg_info("dict_mysql_lookup: null field #%d row #%d", j, i);
                mysql_free_result(query_res);
                return (0);
            }
            if (j > 0)
                vstring_strcat(result, ",");
            vstring_strcat(result, row[j]);
            if (msg_verbose > 1)
                msg_info("dict_mysql_lookup: retrieved field: %d: %s", j, row[j]);
        }
    }
    mysql_free_result(query_res);
    return vstring_str(result);
}
Exemple #17
0
char   *tls_serverid_digest(const TLS_CLIENT_START_PROPS *props, long protomask,
			            const char *ciphers)
{
    EVP_MD_CTX *mdctx;
    const EVP_MD *md;
    const char *mdalg;
    unsigned char md_buf[EVP_MAX_MD_SIZE];
    unsigned int md_len;
    int     ok = 1;
    int     i;
    long    sslversion;
    VSTRING *result;

    /*
     * Try to use sha256: our serverid choice should be strong enough to
     * resist 2nd-preimage attacks with a difficulty comparable to that of
     * DANE TLSA digests.  Failing that, we compute serverid digests with the
     * default digest, but DANE requires sha256 and sha512, so if we must
     * fall back to our default digest, DANE support won't be available.  We
     * panic if the fallback algorithm is not available, as it was verified
     * available in tls_client_init() and must not simply vanish.
     */
    if ((md = EVP_get_digestbyname(mdalg = "sha256")) == 0
	&& (md = EVP_get_digestbyname(mdalg = props->mdalg)) == 0)
	msg_panic("digest algorithm \"%s\" not found", mdalg);

    /* Salt the session lookup key with the OpenSSL runtime version. */
    sslversion = SSLeay();

    mdctx = EVP_MD_CTX_create();
    checkok(EVP_DigestInit_ex(mdctx, md, NULL));
    digest_string(props->helo ? props->helo : "");
    digest_object(&sslversion);
    digest_object(&protomask);
    digest_string(ciphers);

    /*
     * All we get from the session cache is a single bit telling us whether
     * the certificate is trusted or not, but we need to know whether the
     * trust is CA-based (in that case we must do name checks) or whether it
     * is a direct end-point match.  We mustn't confuse the two, so it is
     * best to process only TA trust in the verify callback and check the EE
     * trust after. This works since re-used sessions always have access to
     * the leaf certificate, while only the original session has the leaf and
     * the full trust chain.
     * 
     * Only the trust anchor matchlist is hashed into the session key. The end
     * entity certs are not used to determine whether a certificate is
     * trusted or not, rather these are rechecked against the leaf cert
     * outside the verification callback, each time a session is created or
     * reused.
     * 
     * Therefore, the security context of the session does not depend on the EE
     * matching data, which is checked separately each time.  So we exclude
     * the EE part of the DANE structure from the serverid digest.
     * 
     * If the security level is "dane", we send SNI information to the peer.
     * This may cause it to respond with a non-default certificate.  Since
     * certificates for sessions with no or different SNI data may not match,
     * we must include the SNI name in the session id.
     */
    if (props->dane) {
	int     mixed = (props->dane->flags & TLS_DANE_FLAG_MIXED);

	digest_object(&mixed);
	digest_dane(props->dane, ta);
#if 0
	digest_dane(props->dane, ee);		/* See above */
#endif
	digest_string(props->tls_level == TLS_LEV_DANE ? props->host : "");
    }
    checkok(EVP_DigestFinal_ex(mdctx, md_buf, &md_len));
    EVP_MD_CTX_destroy(mdctx);
    if (!ok)
	msg_fatal("error computing %s message digest", mdalg);

    /* Check for OpenSSL contract violation */
    if (md_len > EVP_MAX_MD_SIZE)
	msg_panic("unexpectedly large %s digest size: %u", mdalg, md_len);

    /*
     * Append the digest to the serverid.  We don't compare this digest to
     * any user-specified fingerprints.  Therefore, we don't need to use a
     * colon-separated format, which saves space in the TLS session cache and
     * makes logging of session cache lookup keys more readable.
     * 
     * This does however duplicate a few lines of code from the digest encoder
     * for colon-separated cert and pkey fingerprints. If that is a
     * compelling reason to consolidate, we could use that and append the
     * result.
     */
    result = vstring_alloc(strlen(props->serverid) + 1 + 2 * md_len);
    vstring_strcpy(result, props->serverid);
    VSTRING_ADDCH(result, '&');
    for (i = 0; i < md_len; i++) {
	VSTRING_ADDCH(result, hexcodes[(md_buf[i] & 0xf0) >> 4U]);
	VSTRING_ADDCH(result, hexcodes[(md_buf[i] & 0x0f)]);
    }
    VSTRING_TERMINATE(result);
    return (vstring_export(result));
}
Exemple #18
0
static int ial_getifaddrs(INET_ADDR_LIST *addr_list,
			          INET_ADDR_LIST *mask_list,
			          int af)
{
    const char *myname = "inet_addr_local[getifaddrs]";
    struct ifaddrs *ifap, *ifa;
    struct sockaddr *sa, *sam;

    if (getifaddrs(&ifap) < 0)
	msg_fatal("%s: getifaddrs: %m", myname);

    /*
     * Get the address of each IP network interface. According to BIND we
     * must include interfaces that are down because the machine may still
     * receive packets for that address (yes, via some other interface).
     * Having no way to verify this claim on every machine, I will give them
     * the benefit of the doubt.
     * 
     * FIX 200501: The IPv6 patch did not report NetBSD loopback interfaces;
     * fixed by replacing IFF_RUNNING by IFF_UP.
     * 
     * FIX 200501: The IPV6 patch did not skip wild-card interface addresses
     * (tested on FreeBSD).
     */
    for (ifa = ifap; ifa; ifa = ifa->ifa_next) {
	if (!(ifa->ifa_flags & IFF_UP) || ifa->ifa_addr == 0)
	    continue;
	sa = ifa->ifa_addr;
	if (af != AF_UNSPEC && sa->sa_family != af)
	    continue;
	sam = ifa->ifa_netmask;
	if (sam == 0) {
	    /* XXX In mynetworks, a null netmask would match everyone. */
	    msg_warn("ignoring interface with null netmask, address family %d",
		     sa->sa_family);
	    continue;
	}
	switch (sa->sa_family) {
	case AF_INET:
	    if (SOCK_ADDR_IN_ADDR(sa).s_addr == INADDR_ANY)
		continue;
	    break;
#ifdef HAS_IPV6
	case AF_INET6:
	    if (IN6_IS_ADDR_UNSPECIFIED(&SOCK_ADDR_IN6_ADDR(sa)))
		continue;
	    break;
#endif
	default:
	    continue;
	}

	inet_addr_list_append(addr_list, sa);
	if (mask_list != 0) {

	    /*
	     * Unfortunately, sa_len/sa_family may be broken in the netmask
	     * sockaddr structure. We must fix this manually to have correct
	     * addresses.   --dcs
	     */
#ifdef HAS_SA_LEN
	    sam->sa_len = sa->sa_family == AF_INET6 ?
		sizeof(struct sockaddr_in6) :
		sizeof(struct sockaddr_in);
#endif
	    sam->sa_family = sa->sa_family;
	    inet_addr_list_append(mask_list, sam);
	}
    }
    freeifaddrs(ifap);
    return (0);
}
Exemple #19
0
NORETURN single_server_main(int argc, char **argv, SINGLE_SERVER_FN service,...)
{
    const char *myname = "single_server_main";
    VSTREAM *stream = 0;
    char   *root_dir = 0;
    char   *user_name = 0;
    int     debug_me = 0;
    int     daemon_mode = 1;
    char   *service_name = basename(argv[0]);
    int     delay;
    int     c;
    int     socket_count = 1;
    int     fd;
    va_list ap;
    MAIL_SERVER_INIT_FN pre_init = 0;
    MAIL_SERVER_INIT_FN post_init = 0;
    MAIL_SERVER_LOOP_FN loop = 0;
    int     key;
    char   *transport = 0;
    char   *lock_path;
    VSTRING *why;
    int     alone = 0;
    int     zerolimit = 0;
    WATCHDOG *watchdog;
    char   *oname_val;
    char   *oname;
    char   *oval;
    const char *err;
    char   *generation;
    int     msg_vstream_needed = 0;
    int     redo_syslog_init = 0;
    const char *dsn_filter_title;
    const char **dsn_filter_maps;

    /*
     * Process environment options as early as we can.
     */
    if (getenv(CONF_ENV_VERB))
	msg_verbose = 1;
    if (getenv(CONF_ENV_DEBUG))
	debug_me = 1;

    /*
     * Don't die when a process goes away unexpectedly.
     */
    signal(SIGPIPE, SIG_IGN);

    /*
     * Don't die for frivolous reasons.
     */
#ifdef SIGXFSZ
    signal(SIGXFSZ, SIG_IGN);
#endif

    /*
     * May need this every now and then.
     */
    var_procname = mystrdup(basename(argv[0]));
    set_mail_conf_str(VAR_PROCNAME, var_procname);

    /*
     * Initialize logging and exit handler. Do the syslog first, so that its
     * initialization completes before we enter the optional chroot jail.
     */
    msg_syslog_init(mail_task(var_procname), LOG_PID, LOG_FACILITY);
    if (msg_verbose)
	msg_info("daemon started");

    /*
     * Check the Postfix library version as soon as we enable logging.
     */
    MAIL_VERSION_CHECK;

    /*
     * Initialize from the configuration file. Allow command-line options to
     * override compiled-in defaults or configured parameter values.
     */
    mail_conf_suck();

    /*
     * After database open error, continue execution with reduced
     * functionality.
     */
    dict_allow_surrogate = 1;

    /*
     * Pick up policy settings from master process. Shut up error messages to
     * stderr, because no-one is going to see them.
     */
    opterr = 0;
    while ((c = GETOPT(argc, argv, "cdDi:lm:n:o:s:St:uvVz")) > 0) {
	switch (c) {
	case 'c':
	    root_dir = "setme";
	    break;
	case 'd':
	    daemon_mode = 0;
	    break;
	case 'D':
	    debug_me = 1;
	    break;
	case 'i':
	    mail_conf_update(VAR_MAX_IDLE, optarg);
	    break;
	case 'l':
	    alone = 1;
	    break;
	case 'm':
	    mail_conf_update(VAR_MAX_USE, optarg);
	    break;
	case 'n':
	    service_name = optarg;
	    break;
	case 'o':
	    oname_val = mystrdup(optarg);
	    if ((err = split_nameval(oname_val, &oname, &oval)) != 0)
		msg_fatal("invalid \"-o %s\" option value: %s", optarg, err);
	    mail_conf_update(oname, oval);
	    if (strcmp(oname, VAR_SYSLOG_NAME) == 0)
		redo_syslog_init = 1;
	    myfree(oname_val);
	    break;
	case 's':
	    if ((socket_count = atoi(optarg)) <= 0)
		msg_fatal("invalid socket_count: %s", optarg);
	    break;
	case 'S':
	    stream = VSTREAM_IN;
	    break;
	case 'u':
	    user_name = "setme";
	    break;
	case 't':
	    transport = optarg;
	    break;
	case 'v':
	    msg_verbose++;
	    break;
	case 'V':
	    if (++msg_vstream_needed == 1)
		msg_vstream_init(mail_task(var_procname), VSTREAM_ERR);
	    break;
	case 'z':
	    zerolimit = 1;
	    break;
	default:
	    msg_fatal("invalid option: %c", c);
	    break;
	}
    }

    /*
     * Initialize generic parameters.
     */
    mail_params_init();
    if (redo_syslog_init)
	msg_syslog_init(mail_task(var_procname), LOG_PID, LOG_FACILITY);

    /*
     * Register higher-level dictionaries and initialize the support for
     * dynamically-loaded dictionarles.
     */
    mail_dict_init();

    /*
     * If not connected to stdin, stdin must not be a terminal.
     */
    
    if (daemon_mode && stream == 0 && isatty(STDIN_FILENO)) {
	msg_vstream_init(var_procname, VSTREAM_ERR);
	msg_fatal("do not run this command by hand-3");
    }
    
    /*
     * Application-specific initialization.
     */
    va_start(ap, service);
    while ((key = va_arg(ap, int)) != 0) {
	switch (key) {
	case MAIL_SERVER_INT_TABLE:
	    get_mail_conf_int_table(va_arg(ap, CONFIG_INT_TABLE *));
	    break;
	case MAIL_SERVER_LONG_TABLE:
	    get_mail_conf_long_table(va_arg(ap, CONFIG_LONG_TABLE *));
	    break;
	case MAIL_SERVER_STR_TABLE:
	    get_mail_conf_str_table(va_arg(ap, CONFIG_STR_TABLE *));
	    break;
	case MAIL_SERVER_BOOL_TABLE:
	    get_mail_conf_bool_table(va_arg(ap, CONFIG_BOOL_TABLE *));
	    break;
	case MAIL_SERVER_TIME_TABLE:
	    get_mail_conf_time_table(va_arg(ap, CONFIG_TIME_TABLE *));
	    break;
	case MAIL_SERVER_RAW_TABLE:
	    get_mail_conf_raw_table(va_arg(ap, CONFIG_RAW_TABLE *));
	    break;
	case MAIL_SERVER_NINT_TABLE:
	    get_mail_conf_nint_table(va_arg(ap, CONFIG_NINT_TABLE *));
	    break;
	case MAIL_SERVER_NBOOL_TABLE:
	    get_mail_conf_nbool_table(va_arg(ap, CONFIG_NBOOL_TABLE *));
	    break;
	case MAIL_SERVER_PRE_INIT:
	    pre_init = va_arg(ap, MAIL_SERVER_INIT_FN);
	    break;
	case MAIL_SERVER_POST_INIT:
	    post_init = va_arg(ap, MAIL_SERVER_INIT_FN);
	    break;
	case MAIL_SERVER_LOOP:
	    loop = va_arg(ap, MAIL_SERVER_LOOP_FN);
	    break;
	case MAIL_SERVER_EXIT:
	    single_server_onexit = va_arg(ap, MAIL_SERVER_EXIT_FN);
	    break;
	case MAIL_SERVER_PRE_ACCEPT:
	    single_server_pre_accept = va_arg(ap, MAIL_SERVER_ACCEPT_FN);
	    break;
	case MAIL_SERVER_IN_FLOW_DELAY:
	    single_server_in_flow_delay = 1;
	    break;
	case MAIL_SERVER_SOLITARY:
	    if (stream == 0 && !alone)
		msg_fatal("service %s requires a process limit of 1",
			  service_name);
	    break;
	case MAIL_SERVER_UNLIMITED:
	    if (stream == 0 && !zerolimit)
		msg_fatal("service %s requires a process limit of 0",
			  service_name);
	    break;
	case MAIL_SERVER_PRIVILEGED:
	    if (user_name)
		msg_fatal("service %s requires privileged operation",
			  service_name);
	    break;
	case MAIL_SERVER_BOUNCE_INIT:
	    dsn_filter_title = va_arg(ap, const char *);
	    dsn_filter_maps = va_arg(ap, const char **);
	    bounce_client_init(dsn_filter_title, *dsn_filter_maps);
	    break;
	default:
	    msg_panic("%s: unknown argument type: %d", myname, key);
	}
    }
    va_end(ap);

    if (root_dir)
	root_dir = var_queue_dir;
    if (user_name)
	user_name = var_mail_owner;

    /*
     * Can options be required?
     */
    if (stream == 0) {
	if (transport == 0)
	    msg_fatal("no transport type specified");
	if (strcasecmp(transport, MASTER_XPORT_NAME_INET) == 0)
	    single_server_accept = single_server_accept_inet;
	else if (strcasecmp(transport, MASTER_XPORT_NAME_UNIX) == 0)
	    single_server_accept = single_server_accept_local;
#ifdef MASTER_XPORT_NAME_PASS
	else if (strcasecmp(transport, MASTER_XPORT_NAME_PASS) == 0)
	    single_server_accept = single_server_accept_pass;
#endif
	else
	    msg_fatal("unsupported transport type: %s", transport);
    }

    /*
     * Retrieve process generation from environment.
     */
    if ((generation = getenv(MASTER_GEN_NAME)) != 0) {
	if (!alldig(generation))
	    msg_fatal("bad generation: %s", generation);
	OCTAL_TO_UNSIGNED(single_server_generation, generation);
	if (msg_verbose)
	    msg_info("process generation: %s (%o)",
		     generation, single_server_generation);
    }

    /*
     * Optionally start the debugger on ourself.
     */
    if (debug_me)
	debug_process();

    /*
     * Traditionally, BSD select() can't handle multiple processes selecting
     * on the same socket, and wakes up every process in select(). See TCP/IP
     * Illustrated volume 2 page 532. We avoid select() collisions with an
     * external lock file.
     */
    if (stream == 0 && !alone) {
	lock_path = concatenate(DEF_PID_DIR, "/", transport,
				".", service_name, (void *) 0);
	why = vstring_alloc(1);
	if ((single_server_lock = safe_open(lock_path, O_CREAT | O_RDWR, 0600,
				      (struct stat *) 0, -1, -1, why)) == 0)
	    msg_fatal("open lock file %s: %s", lock_path, vstring_str(why));
	close_on_exec(vstream_fileno(single_server_lock), CLOSE_ON_EXEC);
	myfree(lock_path);
	vstring_free(why);
    }

    /*
     * Set up call-back info.
     */
    single_server_service = service;
    single_server_name = service_name;
    single_server_argv = argv + optind;

    /*
     * Run pre-jail initialization.
     */
    if (chdir(var_queue_dir) < 0)
	msg_fatal("chdir(\"%s\"): %m", var_queue_dir);
    if (pre_init)
	pre_init(single_server_name, single_server_argv);

    /*
     * Optionally, restrict the damage that this process can do.
     */
    resolve_local_init();
    tzset();
    chroot_uid(root_dir, user_name);

    /*
     * Run post-jail initialization.
     */
    if (post_init)
	post_init(single_server_name, single_server_argv);

    /*
     * Are we running as a one-shot server with the client connection on
     * standard input? If so, make sure the output is written to stdout so as
     * to satisfy common expectation.
     */
    if (stream != 0) {
	vstream_control(stream,
			CA_VSTREAM_CTL_DOUBLE,
			CA_VSTREAM_CTL_WRITE_FD(STDOUT_FILENO),
			CA_VSTREAM_CTL_END);
	service(stream, single_server_name, single_server_argv);
	vstream_fflush(stream);
	single_server_exit();
    }

    /*
     * Running as a semi-resident server. Service connection requests.
     * Terminate when we have serviced a sufficient number of clients, when
     * no-one has been talking to us for a configurable amount of time, or
     * when the master process terminated abnormally.
     */
    if (var_idle_limit > 0)
	event_request_timer(single_server_timeout, (void *) 0, var_idle_limit);
    for (fd = MASTER_LISTEN_FD; fd < MASTER_LISTEN_FD + socket_count; fd++) {
	event_enable_read(fd, single_server_accept, CAST_INT_TO_VOID_PTR(fd));
	close_on_exec(fd, CLOSE_ON_EXEC);
    }
    event_enable_read(MASTER_STATUS_FD, single_server_abort, (void *) 0);
    close_on_exec(MASTER_STATUS_FD, CLOSE_ON_EXEC);
    close_on_exec(MASTER_FLOW_READ, CLOSE_ON_EXEC);
    close_on_exec(MASTER_FLOW_WRITE, CLOSE_ON_EXEC);
    watchdog = watchdog_create(var_daemon_timeout, (WATCHDOG_FN) 0, (void *) 0);

    /*
     * The event loop, at last.
     */
    while (var_use_limit == 0 || use_count < var_use_limit) {
	if (single_server_lock != 0) {
	    watchdog_stop(watchdog);
	    if (myflock(vstream_fileno(single_server_lock), INTERNAL_LOCK,
			MYFLOCK_OP_EXCLUSIVE) < 0)
		msg_fatal("select lock: %m");
	}
	watchdog_start(watchdog);
	delay = loop ? loop(single_server_name, single_server_argv) : -1;
	event_loop(delay);
    }
    single_server_exit();
}
Exemple #20
0
static int ial_siocglif(INET_ADDR_LIST *addr_list,
			        INET_ADDR_LIST *mask_list,
			        int af)
{
    const char *myname = "inet_addr_local[siocglif]";
    struct lifconf lifc;
    struct lifreq *lifr;
    struct lifreq *lifr_mask;
    struct lifreq *the_end;
    struct sockaddr *sa;
    int     sock;
    VSTRING *buf;

    /*
     * See also comments in ial_siocgif()
     */
    if (af != AF_INET && af != AF_INET6)
	msg_fatal("%s: address family was %d, must be AF_INET (%d) or "
		  "AF_INET6 (%d)", myname, af, AF_INET, AF_INET6);
    sock = ial_socket(af);
    if (sock < 0)
	return (0);
    buf = vstring_alloc(1024);
    for (;;) {
	memset(&lifc, 0, sizeof(lifc));
	lifc.lifc_family = AF_UNSPEC;		/* XXX Why??? */
	lifc.lifc_len = vstring_avail(buf);
	lifc.lifc_buf = vstring_str(buf);
	if (ioctl(sock, SIOCGLIFCONF, (char *) &lifc) < 0) {
	    if (errno != EINVAL)
		msg_fatal("%s: ioctl SIOCGLIFCONF: %m", myname);
	} else if (lifc.lifc_len < vstring_avail(buf) / 2)
	    break;
	VSTRING_SPACE(buf, vstring_avail(buf) * 2);
    }

    the_end = (struct lifreq *) (lifc.lifc_buf + lifc.lifc_len);
    for (lifr = lifc.lifc_req; lifr < the_end;) {
	sa = (struct sockaddr *) &lifr->lifr_addr;
	if (sa->sa_family != af) {
	    lifr = NEXT_INTERFACE(lifr);
	    continue;
	}
	if (af == AF_INET) {
	    if (SOCK_ADDR_IN_ADDR(sa).s_addr == INADDR_ANY) {
		lifr = NEXT_INTERFACE(lifr);
		continue;
	    }
#ifdef HAS_IPV6
	} else if (af == AF_INET6) {
	    if (IN6_IS_ADDR_UNSPECIFIED(&SOCK_ADDR_IN6_ADDR(sa))) {
		lifr = NEXT_INTERFACE(lifr);
		continue;
	    }
	}
#endif
	inet_addr_list_append(addr_list, sa);
	if (mask_list) {
	    lifr_mask = (struct lifreq *) mymalloc(sizeof(struct lifreq));
	    memcpy((void *) lifr_mask, (void *) lifr, sizeof(struct lifreq));
	    if (ioctl(sock, SIOCGLIFNETMASK, lifr_mask) < 0)
		msg_fatal("%s: ioctl(SIOCGLIFNETMASK): %m", myname);
	    /* XXX: Check whether sa_len/family are honoured --dcs */
	    inet_addr_list_append(mask_list,
				  (struct sockaddr *) &lifr_mask->lifr_addr);
	    myfree((void *) lifr_mask);
	}
	lifr = NEXT_INTERFACE(lifr);
    }
    vstring_free(buf);
    (void) close(sock);
    return (0);
}
Exemple #21
0
const char *smtpd_expand_lookup(const char *name, int unused_mode,
				        char *context)
{
    SMTPD_STATE *state = (SMTPD_STATE *) context;
    time_t  now;
    struct tm *lt;

    if (state->expand_buf == 0)
	state->expand_buf = vstring_alloc(10);

    if (msg_verbose > 1)
	msg_info("smtpd_expand_lookup: ${%s}", name);

#define STREQN(x,y,n) (*(x) == *(y) && strncmp((x), (y), (n)) == 0)
#define CONST_LEN(x)  (sizeof(x) - 1)

    /*
     * Don't query main.cf parameters, as the result of expansion could
     * reveal system-internal information in server replies.
     * 
     * XXX: This said, multiple servers may be behind a single client-visible
     * name or IP address, and each may generate its own logs. Therefore, it
     * may be useful to expose the replying MTA id (myhostname) in the
     * contact footer, to identify the right logs. So while we don't expose
     * the raw configuration dictionary, we do expose "$myhostname" as
     * expanded in var_myhostname.
     * 
     * Return NULL only for non-existent names.
     */
    if (STREQ(name, MAIL_ATTR_SERVER_NAME)) {
	return (var_myhostname);
    } else if (STREQ(name, MAIL_ATTR_ACT_CLIENT)) {
	return (state->namaddr);
    } else if (STREQ(name, MAIL_ATTR_ACT_CLIENT_PORT)) {
	return (state->port);
    } else if (STREQ(name, MAIL_ATTR_ACT_CLIENT_ADDR)) {
	return (state->addr);
    } else if (STREQ(name, MAIL_ATTR_ACT_CLIENT_NAME)) {
	return (state->name);
    } else if (STREQ(name, MAIL_ATTR_ACT_REVERSE_CLIENT_NAME)) {
	return (state->reverse_name);
    } else if (STREQ(name, MAIL_ATTR_ACT_HELO_NAME)) {
	return (state->helo_name ? state->helo_name : "");
    } else if (STREQN(name, MAIL_ATTR_SENDER, CONST_LEN(MAIL_ATTR_SENDER))) {
	return (smtpd_expand_addr(state->expand_buf, state->sender,
				  name, CONST_LEN(MAIL_ATTR_SENDER)));
    } else if (STREQN(name, MAIL_ATTR_RECIP, CONST_LEN(MAIL_ATTR_RECIP))) {
	return (smtpd_expand_addr(state->expand_buf, state->recipient,
				  name, CONST_LEN(MAIL_ATTR_RECIP)));
    } if (STREQ(name, MAIL_ATTR_LOCALTIME)) {
	if (time(&now) == (time_t) - 1)
	    msg_fatal("time lookup failed: %m");
	lt = localtime(&now);
	VSTRING_RESET(state->expand_buf);
	do {
	    VSTRING_SPACE(state->expand_buf, 100);
	} while (strftime(STR(state->expand_buf),
			  vstring_avail(state->expand_buf),
			  "%b %d %H:%M:%S", lt) == 0);
	return (STR(state->expand_buf));
    } else {
	smtpd_expand_unknown(name);
	return (0);
    }
}
Exemple #22
0
static int ial_siocgif(INET_ADDR_LIST *addr_list,
		               INET_ADDR_LIST *mask_list,
		               int af)
{
    const char *myname = "inet_addr_local[siocgif]";
    struct in_addr addr;
    struct ifconf ifc;
    struct ifreq *ifr;
    struct ifreq *ifr_mask;
    struct ifreq *the_end;
    int     sock;
    VSTRING *buf;

    /*
     * Get the network interface list. XXX The socket API appears to have no
     * function that returns the number of network interfaces, so we have to
     * guess how much space is needed to store the result.
     * 
     * On BSD-derived systems, ioctl SIOCGIFCONF returns as much information as
     * possible, leaving it up to the application to repeat the request with
     * a larger buffer if the result caused a tight fit.
     * 
     * Other systems, such as Solaris 2.5, generate an EINVAL error when the
     * buffer is too small for the entire result. Workaround: ignore EINVAL
     * errors and repeat the request with a larger buffer. The downside is
     * that the program can run out of memory due to a non-memory problem,
     * making it more difficult than necessary to diagnose the real problem.
     */
    sock = ial_socket(af);
    if (sock < 0)
	return (0);
    buf = vstring_alloc(1024);
    for (;;) {
	ifc.ifc_len = vstring_avail(buf);
	ifc.ifc_buf = vstring_str(buf);
	if (ioctl(sock, SIOCGIFCONF, (char *) &ifc) < 0) {
	    if (errno != EINVAL)
		msg_fatal("%s: ioctl SIOCGIFCONF: %m", myname);
	} else if (ifc.ifc_len < vstring_avail(buf) / 2)
	    break;
	VSTRING_SPACE(buf, vstring_avail(buf) * 2);
    }

    the_end = (struct ifreq *) (ifc.ifc_buf + ifc.ifc_len);
    for (ifr = ifc.ifc_req; ifr < the_end;) {
	if (ifr->ifr_addr.sa_family != af) {
	    ifr = NEXT_INTERFACE(ifr);
	    continue;
	}
	if (af == AF_INET) {
	    addr = ((struct sockaddr_in *) & ifr->ifr_addr)->sin_addr;
	    if (addr.s_addr != INADDR_ANY) {
		inet_addr_list_append(addr_list, &ifr->ifr_addr);
		if (mask_list) {
		    ifr_mask = (struct ifreq *) mymalloc(IFREQ_SIZE(ifr));
		    memcpy((void *) ifr_mask, (void *) ifr, IFREQ_SIZE(ifr));
		    if (ioctl(sock, SIOCGIFNETMASK, ifr_mask) < 0)
			msg_fatal("%s: ioctl SIOCGIFNETMASK: %m", myname);

		    /*
		     * Note that this SIOCGIFNETMASK has truly screwed up the
		     * contents of sa_len/sa_family. We must fix this
		     * manually to have correct addresses.   --dcs
		     */
		    ifr_mask->ifr_addr.sa_family = af;
#ifdef HAS_SA_LEN
		    ifr_mask->ifr_addr.sa_len = sizeof(struct sockaddr_in);
#endif
		    inet_addr_list_append(mask_list, &ifr_mask->ifr_addr);
		    myfree((void *) ifr_mask);
		}
	    }
	}
#ifdef HAS_IPV6
	else if (af == AF_INET6) {
	    struct sockaddr *sa;

	    sa = SOCK_ADDR_PTR(&ifr->ifr_addr);
	    if (!(IN6_IS_ADDR_UNSPECIFIED(&SOCK_ADDR_IN6_ADDR(sa)))) {
		inet_addr_list_append(addr_list, sa);
		if (mask_list) {
		    /* XXX Assume /128 for everything */
		    struct sockaddr_in6 mask6;

		    mask6 = *SOCK_ADDR_IN6_PTR(sa);
		    memset((void *) &mask6.sin6_addr, ~0,
			   sizeof(mask6.sin6_addr));
		    inet_addr_list_append(mask_list, SOCK_ADDR_PTR(&mask6));
		}
	    }
	}
#endif
	ifr = NEXT_INTERFACE(ifr);
    }
    vstring_free(buf);
    (void) close(sock);
    return (0);
}
Exemple #23
0
int     main(int argc, char **argv)
{
    static VSTREAM *lock_fp;
    static VSTREAM *data_lock_fp;
    VSTRING *lock_path;
    VSTRING *data_lock_path;
    off_t   inherited_limit;
    int     debug_me = 0;
    int     ch;
    int     fd;
    int     n;
    int     test_lock = 0;
    VSTRING *why;
    WATCHDOG *watchdog;
    ARGV   *import_env;
    int     wait_flag = 0;
    int     monitor_fd = -1;

    /*
     * Fingerprint executables and core dumps.
     */
    MAIL_VERSION_STAMP_ALLOCATE;

    /*
     * Initialize.
     */
    umask(077);					/* never fails! */

    /*
     * Process environment options as early as we can.
     */
    if (getenv(CONF_ENV_VERB))
        msg_verbose = 1;
    if (getenv(CONF_ENV_DEBUG))
        debug_me = 1;

    /*
     * Don't die when a process goes away unexpectedly.
     */
    signal(SIGPIPE, SIG_IGN);

    /*
     * Strip and save the process name for diagnostics etc.
     */
    var_procname = mystrdup(basename(argv[0]));

    /*
     * When running a child process, don't leak any open files that were
     * leaked to us by our own (privileged) parent process. Descriptors 0-2
     * are taken care of after we have initialized error logging.
     *
     * Some systems such as AIX have a huge per-process open file limit. In
     * those cases, limit the search for potential file descriptor leaks to
     * just the first couple hundred.
     *
     * The Debian post-installation script passes an open file descriptor into
     * the master process and waits forever for someone to close it. Because
     * of this we have to close descriptors > 2, and pray that doing so does
     * not break things.
     */
    closefrom(3);

    /*
     * Initialize logging and exit handler.
     */
    msg_syslog_init(mail_task(var_procname), LOG_PID, LOG_FACILITY);

    /*
     * Check the Postfix library version as soon as we enable logging.
     */
    MAIL_VERSION_CHECK;

    /*
     * The mail system must be run by the superuser so it can revoke
     * privileges for selected operations. That's right - it takes privileges
     * to toss privileges.
     */
    if (getuid() != 0)
        msg_fatal("the master command is reserved for the superuser");
    if (unsafe() != 0)
        msg_fatal("the master command must not run as a set-uid process");

    /*
     * Process JCL.
     */
    while ((ch = GETOPT(argc, argv, "c:Dde:tvw")) > 0) {
        switch (ch) {
        case 'c':
            if (setenv(CONF_ENV_PATH, optarg, 1) < 0)
                msg_fatal("out of memory");
            break;
        case 'd':
            master_detach = 0;
            break;
        case 'e':
            event_request_timer(master_exit_event, (char *) 0, atoi(optarg));
            break;
        case 'D':
            debug_me = 1;
            break;
        case 't':
            test_lock = 1;
            break;
        case 'v':
            msg_verbose++;
            break;
        case 'w':
            wait_flag = 1;
            break;
        default:
            usage(argv[0]);
            /* NOTREACHED */
        }
    }

    /*
     * This program takes no other arguments.
     */
    if (argc > optind)
        usage(argv[0]);

    /*
     * Sanity check.
     */
    if (test_lock && wait_flag)
        msg_fatal("the -t and -w options cannot be used together");

    /*
     * Run a foreground monitor process that returns an exit status of 0 when
     * the child background process reports successful initialization as a
     * daemon process. We use a generous limit in case main/master.cf specify
     * symbolic hosts/ports and the naming service is slow.
     */
#define MASTER_INIT_TIMEOUT	100		/* keep this limit generous */

    if (wait_flag)
        monitor_fd = master_monitor(MASTER_INIT_TIMEOUT);

    /*
     * If started from a terminal, get rid of any tty association. This also
     * means that all errors and warnings must go to the syslog daemon.
     */
    if (master_detach)
        for (fd = 0; fd < 3; fd++) {
            (void) close(fd);
            if (open("/dev/null", O_RDWR, 0) != fd)
                msg_fatal("open /dev/null: %m");
        }

    /*
     * Run in a separate process group, so that "postfix stop" can terminate
     * all MTA processes cleanly. Give up if we can't separate from our
     * parent process. We're not supposed to blow away the parent.
     */
    if (debug_me == 0 && master_detach != 0 && setsid() == -1 && getsid(0) != getpid())
        msg_fatal("unable to set session and process group ID: %m");

    /*
     * Make some room for plumbing with file descriptors. XXX This breaks
     * when a service listens on many ports. In order to do this right we
     * must change the master-child interface so that descriptors do not need
     * to have fixed numbers.
     *
     * In a child we need two descriptors for the flow control pipe, one for
     * child->master status updates and at least one for listening.
     */
    for (n = 0; n < 5; n++) {
        if (close_on_exec(dup(0), CLOSE_ON_EXEC) < 0)
            msg_fatal("dup(0): %m");
    }

    /*
     * Final initializations. Unfortunately, we must read the global Postfix
     * configuration file after doing command-line processing, so that we get
     * consistent results when we SIGHUP the server to reload configuration
     * files.
     */
    master_vars_init();

    /*
     * In case of multi-protocol support. This needs to be done because
     * master does not invoke mail_params_init() (it was written before that
     * code existed).
     */
    (void) inet_proto_init(VAR_INET_PROTOCOLS, var_inet_protocols);

    /*
     * Environment import filter, to enforce consistent behavior whether
     * Postfix is started by hand, or at system boot time.
     */
    import_env = argv_split(var_import_environ, ", \t\r\n");
    clean_env(import_env->argv);
    argv_free(import_env);

    if ((inherited_limit = get_file_limit()) < 0)
        set_file_limit(OFF_T_MAX);

    if (chdir(var_queue_dir))
        msg_fatal("chdir %s: %m", var_queue_dir);

    /*
     * Lock down the master.pid file. In test mode, no file means that it
     * isn't locked.
     */
    lock_path = vstring_alloc(10);
    data_lock_path = vstring_alloc(10);
    why = vstring_alloc(10);

    vstring_sprintf(lock_path, "%s/%s.pid", DEF_PID_DIR, var_procname);
    if (test_lock && access(vstring_str(lock_path), F_OK) < 0)
        exit(0);
    lock_fp = open_lock(vstring_str(lock_path), O_RDWR | O_CREAT, 0644, why);
    if (test_lock)
        exit(lock_fp ? 0 : 1);
    if (lock_fp == 0)
        msg_fatal("open lock file %s: %s",
                  vstring_str(lock_path), vstring_str(why));
    vstream_fprintf(lock_fp, "%*lu\n", (int) sizeof(unsigned long) * 4,
                    (unsigned long) var_pid);
    if (vstream_fflush(lock_fp))
        msg_fatal("cannot update lock file %s: %m", vstring_str(lock_path));
    close_on_exec(vstream_fileno(lock_fp), CLOSE_ON_EXEC);

    /*
     * Lock down the Postfix-writable data directory.
     */
    vstring_sprintf(data_lock_path, "%s/%s.lock", var_data_dir, var_procname);
    set_eugid(var_owner_uid, var_owner_gid);
    data_lock_fp =
        open_lock(vstring_str(data_lock_path), O_RDWR | O_CREAT, 0644, why);
    set_ugid(getuid(), getgid());
    if (data_lock_fp == 0)
        msg_fatal("open lock file %s: %s",
                  vstring_str(data_lock_path), vstring_str(why));
    vstream_fprintf(data_lock_fp, "%*lu\n", (int) sizeof(unsigned long) * 4,
                    (unsigned long) var_pid);
    if (vstream_fflush(data_lock_fp))
        msg_fatal("cannot update lock file %s: %m", vstring_str(data_lock_path));
    close_on_exec(vstream_fileno(data_lock_fp), CLOSE_ON_EXEC);

    /*
     * Clean up.
     */
    vstring_free(why);
    vstring_free(lock_path);
    vstring_free(data_lock_path);

    /*
     * Optionally start the debugger on ourself.
     */
    if (debug_me)
        debug_process();

    /*
     * Finish initialization, last part. We must process configuration files
     * after processing command-line parameters, so that we get consistent
     * results when we SIGHUP the server to reload configuration files.
     */
    master_config();
    master_sigsetup();
    master_flow_init();
    msg_info("daemon started -- version %s, configuration %s",
             var_mail_version, var_config_dir);

    /*
     * Report successful initialization to the foreground monitor process.
     */
    if (monitor_fd >= 0) {
        write(monitor_fd, "", 1);
        (void) close(monitor_fd);
    }

    /*
     * Process events. The event handler will execute the read/write/timer
     * action routines. Whenever something has happened, see if we received
     * any signal in the mean time. Although the master process appears to do
     * multiple things at the same time, it really is all a single thread, so
     * that there are no concurrency conflicts within the master process.
     */
#define MASTER_WATCHDOG_TIME	1000

    watchdog = watchdog_create(MASTER_WATCHDOG_TIME, (WATCHDOG_FN) 0, (char *) 0);
    for (;;) {
#ifdef HAS_VOLATILE_LOCKS
        if (myflock(vstream_fileno(lock_fp), INTERNAL_LOCK,
                    MYFLOCK_OP_EXCLUSIVE) < 0)
            msg_fatal("refresh exclusive lock: %m");
        if (myflock(vstream_fileno(data_lock_fp), INTERNAL_LOCK,
                    MYFLOCK_OP_EXCLUSIVE) < 0)
            msg_fatal("refresh exclusive lock: %m");
#endif
        watchdog_start(watchdog);		/* same as trigger servers */
        event_loop(MASTER_WATCHDOG_TIME / 2);
        if (master_gotsighup) {
            msg_info("reload -- version %s, configuration %s",
                     var_mail_version, var_config_dir);
            master_gotsighup = 0;		/* this first */
            master_vars_init();			/* then this */
            master_refresh();			/* then this */
        }
        if (master_gotsigchld) {
            if (msg_verbose)
                msg_info("got sigchld");
            master_gotsigchld = 0;		/* this first */
            master_reap_child();		/* then this */
        }
    }
}
Exemple #24
0
static void pre_jail_init(char *unused_name, char **unused_argv)
{
    TLS_SERVER_INIT_PROPS props;
    const char *cert_file;
    int     have_server_cert;
    int     no_server_cert_ok;
    int     require_server_cert;

    /*
     * The code in this routine is pasted literally from smtpd(8). I am not
     * going to sanitize this because doing so surely will break things in
     * unexpected ways.
     */
    if (*var_tlsp_tls_level) {
	switch (tls_level_lookup(var_tlsp_tls_level)) {
	default:
	    msg_fatal("Invalid TLS level \"%s\"", var_tlsp_tls_level);
	    /* NOTREACHED */
	    break;
	case TLS_LEV_SECURE:
	case TLS_LEV_VERIFY:
	case TLS_LEV_FPRINT:
	    msg_warn("%s: unsupported TLS level \"%s\", using \"encrypt\"",
		     VAR_TLSP_TLS_LEVEL, var_tlsp_tls_level);
	    /* FALLTHROUGH */
	case TLS_LEV_ENCRYPT:
	    var_tlsp_enforce_tls = var_tlsp_use_tls = 1;
	    break;
	case TLS_LEV_MAY:
	    var_tlsp_enforce_tls = 0;
	    var_tlsp_use_tls = 1;
	    break;
	case TLS_LEV_NONE:
	    var_tlsp_enforce_tls = var_tlsp_use_tls = 0;
	    break;
	}
    }
    var_tlsp_use_tls = var_tlsp_use_tls || var_tlsp_enforce_tls;
    if (!var_tlsp_use_tls) {
	msg_warn("TLS service is requested, but disabled with %s or %s",
		 VAR_TLSP_TLS_LEVEL, VAR_TLSP_USE_TLS);
	return;
    }

    /*
     * Load TLS keys before dropping privileges.
     * 
     * Can't use anonymous ciphers if we want client certificates. Must use
     * anonymous ciphers if we have no certificates.
     */
    ask_client_cert = require_server_cert =
	(var_tlsp_tls_ask_ccert
	 || (var_tlsp_enforce_tls && var_tlsp_tls_req_ccert));
    if (strcasecmp(var_tlsp_tls_cert_file, "none") == 0) {
	no_server_cert_ok = 1;
	cert_file = "";
    } else {
	no_server_cert_ok = 0;
	cert_file = var_tlsp_tls_cert_file;
    }
    have_server_cert =
	(*cert_file || *var_tlsp_tls_dcert_file || *var_tlsp_tls_eccert_file);

    /* Some TLS configuration errors are not show stoppers. */
    if (!have_server_cert && require_server_cert)
	msg_warn("Need a server cert to request client certs");
    if (!var_tlsp_enforce_tls && var_tlsp_tls_req_ccert)
	msg_warn("Can't require client certs unless TLS is required");
    /* After a show-stopper error, log a warning. */
    if (have_server_cert || (no_server_cert_ok && !require_server_cert))

	/*
	 * Large parameter lists are error-prone, so we emulate a language
	 * feature that C does not have natively: named parameter lists.
	 */
	tlsp_server_ctx =
	    TLS_SERVER_INIT(&props,
			    log_param = VAR_TLSP_TLS_LOGLEVEL,
			    log_level = var_tlsp_tls_loglevel,
			    verifydepth = var_tlsp_tls_ccert_vd,
			    cache_type = TLS_MGR_SCACHE_SMTPD,
			    set_sessid = var_tlsp_tls_set_sessid,
			    cert_file = cert_file,
			    key_file = var_tlsp_tls_key_file,
			    dcert_file = var_tlsp_tls_dcert_file,
			    dkey_file = var_tlsp_tls_dkey_file,
			    eccert_file = var_tlsp_tls_eccert_file,
			    eckey_file = var_tlsp_tls_eckey_file,
			    CAfile = var_tlsp_tls_CAfile,
			    CApath = var_tlsp_tls_CApath,
			    dh1024_param_file
			    = var_tlsp_tls_dh1024_param_file,
			    dh512_param_file
			    = var_tlsp_tls_dh512_param_file,
			    eecdh_grade = var_tlsp_tls_eecdh,
			    protocols = var_tlsp_enforce_tls ?
			    var_tlsp_tls_mand_proto :
			    var_tlsp_tls_proto,
			    ask_ccert = ask_client_cert,
			    mdalg = var_tlsp_tls_fpt_dgst);
    else
	msg_warn("No server certs available. TLS can't be enabled");

    /*
     * To maintain sanity, allow partial SSL_write() operations, and allow
     * SSL_write() buffer pointers to change after a WANT_READ or WANT_WRITE
     * result. This is based on OpenSSL developers talking on a mailing list,
     * but is not supported by documentation. If this code stops working then
     * no-one can be held responsible.
     */
    if (tlsp_server_ctx)
	SSL_CTX_set_mode(tlsp_server_ctx->ssl_ctx,
			 SSL_MODE_ENABLE_PARTIAL_WRITE
			 | SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
}
Exemple #25
0
static void post_jail_init(char *unused_name, char **unused_argv)
{
    const NAME_CODE actions[] = {
	PSC_NAME_ACT_DROP, PSC_ACT_DROP,
	PSC_NAME_ACT_ENFORCE, PSC_ACT_ENFORCE,
	PSC_NAME_ACT_IGNORE, PSC_ACT_IGNORE,
	PSC_NAME_ACT_CONT, PSC_ACT_IGNORE,	/* compatibility */
	0, -1,
    };
    int     cache_flags;
    const char *tmp;

    /*
     * This routine runs after the skeleton code has entered the chroot jail.
     * Prevent automatic process suicide after a limited number of client
     * requests. It is OK to terminate after a limited amount of idle time.
     */
    var_use_limit = 0;

    /*
     * Workaround for parameters whose values may contain "$", and that have
     * a default of "$parametername". Not sure if it would be a good idea to
     * always to this in the mail_conf_raw(3) module.
     */
    if (*var_psc_rej_footer == '$'
	&& mail_conf_lookup(var_psc_rej_footer + 1)) {
	tmp = mail_conf_eval_once(var_psc_rej_footer);
	myfree(var_psc_rej_footer);
	var_psc_rej_footer = mystrdup(tmp);
    }
    if (*var_psc_exp_filter == '$'
	&& mail_conf_lookup(var_psc_exp_filter + 1)) {
	tmp = mail_conf_eval_once(var_psc_exp_filter);
	myfree(var_psc_exp_filter);
	var_psc_exp_filter = mystrdup(tmp);
    }

    /*
     * Other one-time initialization.
     */
    psc_temp = vstring_alloc(10);
    vstring_sprintf(psc_temp, "%s/%s", MAIL_CLASS_PRIVATE, var_smtpd_service);
    psc_smtpd_service_name = mystrdup(STR(psc_temp));
    psc_dnsbl_init();
    psc_early_init();
    psc_smtpd_init();

    if ((psc_blist_action = name_code(actions, NAME_CODE_FLAG_NONE,
				      var_psc_blist_action)) < 0)
	msg_fatal("bad %s value: %s", VAR_PSC_BLIST_ACTION,
		  var_psc_blist_action);
    if ((psc_dnsbl_action = name_code(actions, NAME_CODE_FLAG_NONE,
				      var_psc_dnsbl_action)) < 0)
	msg_fatal("bad %s value: %s", VAR_PSC_DNSBL_ACTION,
		  var_psc_dnsbl_action);
    if ((psc_pregr_action = name_code(actions, NAME_CODE_FLAG_NONE,
				      var_psc_pregr_action)) < 0)
	msg_fatal("bad %s value: %s", VAR_PSC_PREGR_ACTION,
		  var_psc_pregr_action);
    if ((psc_pipel_action = name_code(actions, NAME_CODE_FLAG_NONE,
				      var_psc_pipel_action)) < 0)
	msg_fatal("bad %s value: %s", VAR_PSC_PIPEL_ACTION,
		  var_psc_pipel_action);
    if ((psc_nsmtp_action = name_code(actions, NAME_CODE_FLAG_NONE,
				      var_psc_nsmtp_action)) < 0)
	msg_fatal("bad %s value: %s", VAR_PSC_NSMTP_ACTION,
		  var_psc_nsmtp_action);
    if ((psc_barlf_action = name_code(actions, NAME_CODE_FLAG_NONE,
				      var_psc_barlf_action)) < 0)
	msg_fatal("bad %s value: %s", VAR_PSC_BARLF_ACTION,
		  var_psc_barlf_action);
    /* Fail "closed" on error. */
    psc_wlist_if = addr_match_list_init(MATCH_FLAG_RETURN, var_psc_wlist_if);

    /*
     * Start the cache maintenance pseudo thread last. Early cleanup makes
     * verbose logging more informative (we get positive confirmation that
     * the cleanup thread runs).
     */
    cache_flags = DICT_CACHE_FLAG_STATISTICS;
    if (msg_verbose > 1)
	cache_flags |= DICT_CACHE_FLAG_VERBOSE;
    if (psc_cache_map != 0 && var_psc_cache_scan > 0)
	dict_cache_control(psc_cache_map,
			   DICT_CACHE_CTL_FLAGS, cache_flags,
			   DICT_CACHE_CTL_INTERVAL, var_psc_cache_scan,
			   DICT_CACHE_CTL_VALIDATOR, psc_cache_validator,
			   DICT_CACHE_CTL_CONTEXT, (char *) 0,
			   DICT_CACHE_CTL_END);

    /*
     * Pre-compute the minimal and maximal TTL.
     */
    psc_min_ttl =
	PSC_MIN(PSC_MIN(var_psc_pregr_ttl, var_psc_dnsbl_ttl),
		PSC_MIN(PSC_MIN(var_psc_pipel_ttl, var_psc_nsmtp_ttl),
			var_psc_barlf_ttl));
    psc_max_ttl =
	PSC_MAX(PSC_MAX(var_psc_pregr_ttl, var_psc_dnsbl_ttl),
		PSC_MAX(PSC_MAX(var_psc_pipel_ttl, var_psc_nsmtp_ttl),
			var_psc_barlf_ttl));

    /*
     * Pre-compute the stress and normal command time limits.
     */
    mail_conf_update(VAR_STRESS, "yes");
    psc_stress_cmd_time_limit =
	get_mail_conf_time(VAR_PSC_CMD_TIME, DEF_PSC_CMD_TIME, 1, 0);
    psc_stress_greet_wait =
	get_mail_conf_time(VAR_PSC_GREET_WAIT, DEF_PSC_GREET_WAIT, 1, 0);

    mail_conf_update(VAR_STRESS, "");
    psc_normal_cmd_time_limit =
	get_mail_conf_time(VAR_PSC_CMD_TIME, DEF_PSC_CMD_TIME, 1, 0);
    psc_normal_greet_wait =
	get_mail_conf_time(VAR_PSC_GREET_WAIT, DEF_PSC_GREET_WAIT, 1, 0);

    psc_lowat_check_queue_length = .7 * var_psc_pre_queue_limit;
    psc_hiwat_check_queue_length = .9 * var_psc_pre_queue_limit;
    if (msg_verbose)
	msg_info(VAR_PSC_CMD_TIME ": stress=%d normal=%d lowat=%d hiwat=%d",
		 psc_stress_cmd_time_limit, psc_normal_cmd_time_limit,
		 psc_lowat_check_queue_length, psc_hiwat_check_queue_length);

    if (psc_lowat_check_queue_length == 0)
	msg_panic("compiler error: 0.7 * %d = %d", var_psc_pre_queue_limit,
		  psc_lowat_check_queue_length);
    if (psc_hiwat_check_queue_length == 0)
	msg_panic("compiler error: 0.9 * %d = %d", var_psc_pre_queue_limit,
		  psc_hiwat_check_queue_length);

    /*
     * Per-client concurrency.
     */
    psc_client_concurrency = htable_create(var_psc_pre_queue_limit);
}
Exemple #26
0
int     main(int unused_argc, char **argv)
{
    VSTRING *inbuf = vstring_alloc(1);
    char   *bufp;
    char   *cmd;
    ssize_t cmd_len;
    char   *service;
    char   *addr;
    int     count;
    int     rate;
    int     msgs;
    int     rcpts;
    int     newtls;
    ANVIL_CLNT *anvil;

    msg_vstream_init(argv[0], VSTREAM_ERR);

    mail_conf_read();
    msg_info("using config files in %s", var_config_dir);
    if (chdir(var_queue_dir) < 0)
	msg_fatal("chdir %s: %m", var_queue_dir);

    msg_verbose++;

    anvil = anvil_clnt_create();

    while (vstring_fgets_nonl(inbuf, VSTREAM_IN)) {
	bufp = vstring_str(inbuf);
	if ((cmd = mystrtok(&bufp, " ")) == 0 || *bufp == 0
	    || (service = mystrtok(&bufp, " ")) == 0 || *service == 0
	    || (addr = mystrtok(&bufp, " ")) == 0 || *addr == 0
	    || mystrtok(&bufp, " ") != 0) {
	    vstream_printf("bad command syntax\n");
	    usage();
	    vstream_fflush(VSTREAM_OUT);
	    continue;
	}
	cmd_len = strlen(cmd);
	if (strncmp(cmd, ANVIL_REQ_CONN, cmd_len) == 0) {
	    if (anvil_clnt_connect(anvil, service, addr, &count, &rate) != ANVIL_STAT_OK)
		msg_warn("error!");
	    else
		vstream_printf("count=%d, rate=%d\n", count, rate);
	} else if (strncmp(cmd, ANVIL_REQ_MAIL, cmd_len) == 0) {
	    if (anvil_clnt_mail(anvil, service, addr, &msgs) != ANVIL_STAT_OK)
		msg_warn("error!");
	    else
		vstream_printf("rate=%d\n", msgs);
	} else if (strncmp(cmd, ANVIL_REQ_RCPT, cmd_len) == 0) {
	    if (anvil_clnt_rcpt(anvil, service, addr, &rcpts) != ANVIL_STAT_OK)
		msg_warn("error!");
	    else
		vstream_printf("rate=%d\n", rcpts);
	} else if (strncmp(cmd, ANVIL_REQ_NTLS, cmd_len) == 0) {
	    if (anvil_clnt_newtls(anvil, service, addr, &newtls) != ANVIL_STAT_OK)
		msg_warn("error!");
	    else
		vstream_printf("rate=%d\n", newtls);
	} else if (strncmp(cmd, ANVIL_REQ_NTLS_STAT, cmd_len) == 0) {
	    if (anvil_clnt_newtls_stat(anvil, service, addr, &newtls) != ANVIL_STAT_OK)
		msg_warn("error!");
	    else
		vstream_printf("rate=%d\n", newtls);
	} else if (strncmp(cmd, ANVIL_REQ_DISC, cmd_len) == 0) {
	    if (anvil_clnt_disconnect(anvil, service, addr) != ANVIL_STAT_OK)
		msg_warn("error!");
	    else
		vstream_printf("OK\n");
	} else if (strncmp(cmd, ANVIL_REQ_LOOKUP, cmd_len) == 0) {
	    if (anvil_clnt_lookup(anvil, service, addr, &count, &rate,
				  &msgs, &rcpts, &newtls) != ANVIL_STAT_OK)
		msg_warn("error!");
	    else
		vstream_printf("count=%d, rate=%d msgs=%d rcpts=%d newtls=%d\n",
			       count, rate, msgs, rcpts, newtls);
	} else {
	    vstream_printf("bad command: \"%s\"\n", cmd);
	    usage();
	}
	vstream_fflush(VSTREAM_OUT);
    }
    vstring_free(inbuf);
    anvil_clnt_free(anvil);
    return (0);
}
Exemple #27
0
static void qmgr_transport_abort(int unused_event, void *context)
{
    QMGR_TRANSPORT_ALLOC *alloc = (QMGR_TRANSPORT_ALLOC *) context;

    msg_fatal("timeout connecting to transport: %s", alloc->transport->name);
}
Exemple #28
0
static int deliver_message(DELIVER_REQUEST *request, const char *def_dsn,
                           int (*append) (int, const char *, MSG_STATS *, RECIPIENT *,
                                   const char *, DSN *))
{
    const char *myname = "deliver_message";
    VSTREAM *src;
    int     result = 0;
    int     status;
    RECIPIENT *rcpt;
    int     nrcpt;
    DSN_SPLIT dp;
    DSN     dsn;

    if (msg_verbose)
        msg_info("deliver_message: from 2-%s", request->sender);

    /*
     * Sanity checks.
     */
    if (request->nexthop[0] == 0)
        msg_fatal("empty nexthop hostname");
    if (request->rcpt_list.len <= 0)
        msg_fatal("recipient count: %d", request->rcpt_list.len);

    /*
     * Open the queue file. Opening the file can fail for a variety of
     * reasons, such as the system running out of resources. Instead of
     * throwing away mail, we're raising a fatal error which forces the mail
     * system to back off, and retry later.
     */
    src = mail_queue_open(request->queue_name, request->queue_id,
                          O_RDWR, 0);
    if (src == 0)
        msg_fatal("%s: open %s %s: %m", myname,
                  request->queue_name, request->queue_id);
    if (msg_verbose)
        msg_info("%s: file %s", myname, VSTREAM_PATH(src));

    /*
     * Bounce/defer/whatever all recipients.
     */
#define BOUNCE_FLAGS(request) DEL_REQ_TRACE_FLAGS(request->flags)

    dsn_split(&dp, def_dsn, request->nexthop);
    (void) DSN_SIMPLE(&dsn, DSN_STATUS(dp.dsn), dp.text);
    for (nrcpt = 0; nrcpt < request->rcpt_list.len; nrcpt++) {
        rcpt = request->rcpt_list.info + nrcpt;
        status = append(BOUNCE_FLAGS(request), request->queue_id,
                        &request->msg_stats, rcpt, "none", &dsn);
        if (status == 0)
            deliver_completed(src, rcpt->offset);
        result |= status;
    }

    /*
     * Clean up.
     */
    if (vstream_fclose(src))
        msg_warn("close %s %s: %m", request->queue_name, request->queue_id);

    return (result);
}
Exemple #29
0
static void postcat(VSTREAM *fp, VSTRING *buffer, int flags)
{
    int     prev_type = 0;
    int     rec_type;
    struct timeval tv;
    time_t  time;
    int     ch;
    off_t   offset;
    const char *error_text;
    char   *attr_name;
    char   *attr_value;
    int     rec_flags = (msg_verbose ? REC_FLAG_NONE : REC_FLAG_DEFAULT);
    int     state;			/* state machine, input type */
    int     do_print;			/* state machine, output control */
    long    data_offset;		/* state machine, read optimization */
    long    data_size;			/* state machine, read optimization */

#define TEXT_RECORD(rec_type) \
	    (rec_type == REC_TYPE_CONT || rec_type == REC_TYPE_NORM)

    /*
     * See if this is a plausible file.
     */
    if ((ch = VSTREAM_GETC(fp)) != VSTREAM_EOF) {
	if (!strchr(REC_TYPE_ENVELOPE, ch)) {
	    msg_warn("%s: input is not a valid queue file", VSTREAM_PATH(fp));
	    return;
	}
	vstream_ungetc(fp, ch);
    }

    /*
     * Other preliminaries.
     */
    if (flags & PC_FLAG_PRINT_ENV)
	vstream_printf("*** ENVELOPE RECORDS %s ***\n",
		       VSTREAM_PATH(fp));
    state = PC_STATE_ENV;
    do_print = (flags & PC_FLAG_PRINT_ENV);
    data_offset = data_size = -1;

    /*
     * Now look at the rest.
     */
    for (;;) {
	if (flags & PC_FLAG_PRINT_OFFSET)
	    offset = vstream_ftell(fp);
	rec_type = rec_get_raw(fp, buffer, 0, rec_flags);
	if (rec_type == REC_TYPE_ERROR)
	    msg_fatal("record read error");
	if (rec_type == REC_TYPE_EOF)
	    break;

	/*
	 * First inspect records that have side effects on the (envelope,
	 * header, body) state machine or on the record reading order.
	 * 
	 * XXX Comments marked "Optimization:" identify subtle code that will
	 * likely need to be revised when the queue file organization is
	 * changed.
	 */
#define PRINT_MARKER(flags, fp, offset, type, text) do { \
    if ((flags) & PC_FLAG_PRINT_OFFSET) \
	vstream_printf("%9lu ", (unsigned long) (offset)); \
    if (flags & PC_FLAG_PRINT_RTYPE_DEC) \
	vstream_printf("%3d ", (type)); \
    vstream_printf("*** %s %s ***\n", (text), VSTREAM_PATH(fp)); \
    vstream_fflush(VSTREAM_OUT); \
} while (0)

#define PRINT_RECORD(flags, offset, type, value) do { \
    if ((flags) & PC_FLAG_PRINT_OFFSET) \
	vstream_printf("%9lu ", (unsigned long) (offset)); \
    if (flags & PC_FLAG_PRINT_RTYPE_DEC) \
	vstream_printf("%3d ", (type)); \
    vstream_printf("%s: %s\n", rec_type_name(rec_type), (value)); \
    vstream_fflush(VSTREAM_OUT); \
} while (0)

	if (TEXT_RECORD(rec_type)) {
	    /* This is wrong when the message starts with whitespace. */
	    if (state == PC_STATE_HEADER && (flags & (PC_MASK_PRINT_TEXT))
		&& prev_type != REC_TYPE_CONT && TEXT_RECORD(rec_type)
	     && !(is_header(STR(buffer)) || IS_SPACE_TAB(STR(buffer)[0]))) {
		/* Update the state machine. */
		state = PC_STATE_BODY;
		do_print = (flags & PC_FLAG_PRINT_BODY);
		/* Optimization: terminate if nothing left to print. */
		if (do_print == 0 && (flags & PC_FLAG_PRINT_ENV) == 0)
		    break;
		/* Optimization: skip to extracted segment marker. */
		if (do_print == 0 && (flags & PC_FLAG_PRINT_ENV)
		    && data_offset >= 0 && data_size >= 0
		    && vstream_fseek(fp, data_offset + data_size, SEEK_SET) < 0)
		    msg_fatal("seek error: %m");
	    }
	    /* Optional output happens further down below. */
	} else if (rec_type == REC_TYPE_MESG) {
	    /* Sanity check. */
	    if (state != PC_STATE_ENV)
		msg_warn("%s: out-of-order message content marker",
			 VSTREAM_PATH(fp));
	    /* Optional output. */
	    if (flags & PC_FLAG_PRINT_ENV)
		PRINT_MARKER(flags, fp, offset, rec_type, "MESSAGE CONTENTS");
	    /* Optimization: skip to extracted segment marker. */
	    if ((flags & PC_MASK_PRINT_TEXT) == 0
		&& data_offset >= 0 && data_size >= 0
		&& vstream_fseek(fp, data_offset + data_size, SEEK_SET) < 0)
		msg_fatal("seek error: %m");
	    /* Update the state machine, even when skipping. */
	    state = PC_STATE_HEADER;
	    do_print = (flags & PC_FLAG_PRINT_HEADER);
	    continue;
	} else if (rec_type == REC_TYPE_XTRA) {
	    /* Sanity check. */
	    if (state != PC_STATE_HEADER && state != PC_STATE_BODY)
		msg_warn("%s: out-of-order extracted segment marker",
			 VSTREAM_PATH(fp));
	    /* Optional output (terminate preceding header/body line). */
	    if (do_print && prev_type == REC_TYPE_CONT)
		VSTREAM_PUTCHAR('\n');
	    if (flags & PC_FLAG_PRINT_ENV)
		PRINT_MARKER(flags, fp, offset, rec_type, "HEADER EXTRACTED");
	    /* Update the state machine. */
	    state = PC_STATE_ENV;
	    do_print = (flags & PC_FLAG_PRINT_ENV);
	    /* Optimization: terminate if nothing left to print. */
	    if (do_print == 0)
		break;
	    continue;
	} else if (rec_type == REC_TYPE_END) {
	    /* Sanity check. */
	    if (state != PC_STATE_ENV)
		msg_warn("%s: out-of-order message end marker",
			 VSTREAM_PATH(fp));
	    /* Optional output. */
	    if (flags & PC_FLAG_PRINT_ENV)
		PRINT_MARKER(flags, fp, offset, rec_type, "MESSAGE FILE END");
	    /* Terminate the state machine. */
	    break;
	} else if (rec_type == REC_TYPE_PTR) {
	    /* Optional output. */
	    /* This record type is exposed only with '-v'. */
	    if (do_print)
		PRINT_RECORD(flags, offset, rec_type, STR(buffer));
	    /* Skip to the pointer's target record. */
	    if (rec_goto(fp, STR(buffer)) == REC_TYPE_ERROR)
		msg_fatal("bad pointer record, or input is not seekable");
	    continue;
	} else if (rec_type == REC_TYPE_SIZE) {
	    /* Optional output (here before we update the state machine). */
	    if (do_print)
		PRINT_RECORD(flags, offset, rec_type, STR(buffer));
	    /* Read the message size/offset for the state machine optimizer. */
	    if (data_size >= 0 || data_offset >= 0) {
		msg_warn("file contains multiple size records");
	    } else {
		if (sscanf(STR(buffer), "%ld %ld", &data_size, &data_offset) != 2
		    || data_offset <= 0 || data_size <= 0)
		    msg_fatal("invalid size record: %.100s", STR(buffer));
		/* Optimization: skip to the message header. */
		if ((flags & PC_FLAG_PRINT_ENV) == 0) {
		    if (vstream_fseek(fp, data_offset, SEEK_SET) < 0)
			msg_fatal("seek error: %m");
		    /* Update the state machine. */
		    state = PC_STATE_HEADER;
		    do_print = (flags & PC_FLAG_PRINT_HEADER);
		}
	    }
	    continue;
	}

	/*
	 * Don't inspect side-effect-free records that aren't printed.
	 */
	if (do_print == 0)
	    continue;
	if (flags & PC_FLAG_PRINT_OFFSET)
	    vstream_printf("%9lu ", (unsigned long) offset);
	if (flags & PC_FLAG_PRINT_RTYPE_DEC)
	    vstream_printf("%3d ", rec_type);
	switch (rec_type) {
	case REC_TYPE_TIME:
	    REC_TYPE_TIME_SCAN(STR(buffer), tv);
	    time = tv.tv_sec;
	    vstream_printf("%s: %s", rec_type_name(rec_type),
			   asctime(localtime(&time)));
	    break;
	case REC_TYPE_WARN:
	    REC_TYPE_WARN_SCAN(STR(buffer), time);
	    vstream_printf("%s: %s", rec_type_name(rec_type),
			   asctime(localtime(&time)));
	    break;
	case REC_TYPE_CONT:			/* REC_TYPE_FILT collision */
	    if (state == PC_STATE_ENV)
		vstream_printf("%s: ", rec_type_name(rec_type));
	    else if (msg_verbose)
		vstream_printf("unterminated_text: ");
	    vstream_fwrite(VSTREAM_OUT, STR(buffer), LEN(buffer));
	    if (state == PC_STATE_ENV || msg_verbose
		|| (flags & PC_FLAG_PRINT_OFFSET) != 0) {
		rec_type = 0;
		VSTREAM_PUTCHAR('\n');
	    }
	    break;
	case REC_TYPE_NORM:
	    if (msg_verbose)
		vstream_printf("%s: ", rec_type_name(rec_type));
	    vstream_fwrite(VSTREAM_OUT, STR(buffer), LEN(buffer));
	    VSTREAM_PUTCHAR('\n');
	    break;
	case REC_TYPE_DTXT:
	    /* This record type is exposed only with '-v'. */
	    vstream_printf("%s: ", rec_type_name(rec_type));
	    vstream_fwrite(VSTREAM_OUT, STR(buffer), LEN(buffer));
	    VSTREAM_PUTCHAR('\n');
	    break;
	case REC_TYPE_ATTR:
	    error_text = split_nameval(STR(buffer), &attr_name, &attr_value);
	    if (error_text != 0) {
		msg_warn("%s: malformed attribute: %s: %.100s",
			 VSTREAM_PATH(fp), error_text, STR(buffer));
		break;
	    }
	    if (strcmp(attr_name, MAIL_ATTR_CREATE_TIME) == 0) {
		time = atol(attr_value);
		vstream_printf("%s: %s", MAIL_ATTR_CREATE_TIME,
			       asctime(localtime(&time)));
	    } else {
		vstream_printf("%s: %s=%s\n", rec_type_name(rec_type),
			       attr_name, attr_value);
	    }
	    break;
	default:
	    vstream_printf("%s: %s\n", rec_type_name(rec_type), STR(buffer));
	    break;
	}
	prev_type = rec_type;

	/*
	 * In case the next record is broken.
	 */
	vstream_fflush(VSTREAM_OUT);
    }
}
Exemple #30
0
int
main(int argc, char **argv)
{
	int		c, ndx;
	int		readonly;
	int		fd;
	const char	*file;
	const char	*runpath;
	Elf		*elf;
	GElf_Ehdr	ehdr;
	size_t		shstrndx, shnum;
	Elf_Scn		*scn;
	Elf_Data	*data;
	char		*shnames = NULL;
	Cache		*cache, *_cache;
	Cache		*dynsec, *strsec;
	GElf_Word	numdyn;
	dyn_elt_t	rpath_elt;
	dyn_elt_t	runpath_elt;
	dyn_elt_t	strpad_elt;
	dyn_elt_t	flags_1_elt;
	dyn_elt_t	null_elt;
	int		changed = 0;


	opterr = 0;
	while ((c = getopt(argc, argv, "dr")) != EOF) {
		switch (c) {
		case 'd':
			d_flg = 1;
			break;

		case 'r':
			r_flg = 1;
			break;

		case '?':
			msg_usage();
		}
	}

	/*
	 * The first plain argument is the file name, and is required.
	 * The second plain argument is the runpath, and is optional.
	 * If no runpath is given, we print the current runpath to stdout
	 * and exit. If it is present, we modify the ELF file to use it.
	 */
	argc = argc - optind;
	argv += optind;
	if ((argc < 1) || (argc > 2))
		msg_usage();
	if ((argc == 2) && r_flg)
		msg_usage();

	readonly = (argc == 1) && !r_flg;
	file = argv[0];
	if (!readonly)
		runpath = argv[1];

	if ((fd = open(file, readonly ? O_RDONLY : O_RDWR)) == -1)
		msg_fatal("unable to open file: %s: %s\n",
		    file, strerror(errno));

	(void) elf_version(EV_CURRENT);
	elf = elf_begin(fd, readonly ? ELF_C_READ : ELF_C_RDWR, NULL);
	if (elf == NULL)
		msg_elf("elf_begin");

	/* We only handle standalone ELF files */
	switch (elf_kind(elf)) {
	case ELF_K_AR:
		msg_fatal("unable to edit ELF archive: %s\n", file);
		break;
	case ELF_K_ELF:
		break;
	default:
		msg_fatal("unable to edit non-ELF file: %s\n", file);
		break;
	}

	if (gelf_getehdr(elf, &ehdr) == NULL)
		msg_elf("gelf_getehdr");

	if (elf_getshnum(elf, &shnum) == 0)
		msg_elf("elf_getshnum");

	if (elf_getshstrndx(elf, &shstrndx) == 0)
		msg_elf("elf_getshstrndx");

	/*
	 * Obtain the .shstrtab data buffer to provide the required section
	 * name strings.
	 */
	if ((scn = elf_getscn(elf, shstrndx)) == NULL)
		msg_elf("elf_getscn");
	if ((data = elf_getdata(scn, NULL)) == NULL)
		msg_elf("elf_getdata");
	shnames = data->d_buf;

	/*
	 * Allocate a cache to maintain a descriptor for each section.
	 */
	if ((cache = malloc(shnum * sizeof (Cache))) == NULL)
		msg_fatal("unable to allocate section cache: %s\n",
		    strerror(errno));
	bzero(cache, sizeof (cache[0]));
	cache->c_name = "";
	_cache = cache + 1;

	/*
	 * Fill in cache with information for each section, and
	 * locate the dynamic section.
	 */
	dynsec = strsec = NULL;
	for (ndx = 1, scn = NULL; scn = elf_nextscn(elf, scn);
	    ndx++, _cache++) {
		_cache->c_ndx = ndx;
		if (gelf_getshdr(scn, &_cache->c_shdr) == NULL)
			msg_elf("gelf_getshdr");
		_cache->c_data = elf_getdata(scn, NULL);
		_cache->c_name = shnames + _cache->c_shdr.sh_name;
		if (_cache->c_shdr.sh_type == SHT_DYNAMIC) {
			dynsec = _cache;
			numdyn = dynsec->c_shdr.sh_size /
			    dynsec->c_shdr.sh_entsize;
			msg_debug("[%d]%s: dynamic section\n",
			    ndx, _cache->c_name);
		}
	}

	/*
	 * If we got a dynamic section, locate the string table.
	 * If not, we can't continue.
	 */
	if (dynsec == NULL)
		msg_fatal("file lacks a dynamic section: %s\n", file);
	strsec = &cache[dynsec->c_shdr.sh_link];
	msg_debug("[%d]%s: dynamic string table section\n",
	    strsec->c_ndx, strsec->c_name);

	/*
	 * History Lesson And Strategy:
	 *
	 * This routine handles both DT_RPATH and DT_RUNPATH entries, altering
	 * either or both if they are present.
	 *
	 * The original SYSV ABI only had DT_RPATH, and the runtime loader used
	 * it to search for things in the following order:
	 *
	 *	DT_RPATH, LD_LIBRARY_PATH, defaults
	 *
	 * Solaris did not follow this rule. Environment variables should
	 * supersede everything else, so we have always deviated from the
	 * ABI and and instead search in the order
	 *
	 *	LD_LIBRARY_PATH, DT_RPATH, defaults
	 *
	 * Other Unix variants initially followed the ABI, but in recent years
	 * realized that it was a mistake. Hence, DT_RUNPATH was invented,
	 * with the search order:
	 *
	 *	LD_LIBRARY_PATH, DT_RUNPATH, defaults
	 *
	 * So for Solaris, DT_RPATH and DT_RUNPATH mean the same thing. If both
	 * are present (which does happen), we set them both to the new
	 * value. If either one is present, we set that one. If neither is
	 * present, and we have a spare DT_NULL slot, we create a DT_RUNPATH.
	 */

	/*
	 * Examine the dynamic section to determine the index for
	 *	- DT_RPATH
	 *	- DT_RUNPATH
	 *	- DT_SUNW_STRPAD
	 *	- DT_NULL, and whether there are any extra DT_NULL slots
	 */
	dyn_elt_init(&rpath_elt);
	dyn_elt_init(&runpath_elt);
	dyn_elt_init(&strpad_elt);
	dyn_elt_init(&flags_1_elt);
	dyn_elt_init(&null_elt);
	for (ndx = 0; ndx < numdyn; ndx++) {
		GElf_Dyn	dyn;

		if (gelf_getdyn(dynsec->c_data, ndx, &dyn) == NULL)
			msg_elf("gelf_getdyn");

		switch (dyn.d_tag) {
		case DT_NULL:
			/*
			 * Remember the state of the first DT_NULL. If there
			 * are more than one (i.e. the first one is not
			 * in the final spot), and there is no runpath, then
			 * we will turn the first one into a DT_RUNPATH.
			 */
			if (!null_elt.seen) {
				dyn_elt_save(&null_elt, ndx, &dyn);
				msg_debug("[%d]%s[%d]: DT_NULL\n",
				    dynsec->c_ndx, dynsec->c_name, ndx);
			}
			break;

		case DT_RPATH:
			dyn_elt_save(&rpath_elt, ndx, &dyn);
			msg_debug("[%d]%s[%d]: DT_RPATH: %s\n",
			    dynsec->c_ndx, dynsec->c_name, ndx,
			    DYN_ELT_STRING(&rpath_elt, strsec));
			break;

		case DT_RUNPATH:
			dyn_elt_save(&runpath_elt, ndx, &dyn);
			msg_debug("[%d]%s[%d]: DT_RUNPATH: %s\n",
			    dynsec->c_ndx, dynsec->c_name, ndx,
			    DYN_ELT_STRING(&runpath_elt, strsec));
			break;

		case DT_SUNW_STRPAD:
			dyn_elt_save(&strpad_elt, ndx, &dyn);
			msg_debug("[%d]%s[%d]: DT_STRPAD: %d\n",
			    dynsec->c_ndx, dynsec->c_name, ndx,
			    (int)strpad_elt.dyn.d_un.d_val);
			break;

		case DT_FLAGS_1:
			dyn_elt_save(&flags_1_elt, ndx, &dyn);
			break;
		}
	}

	/*
	 * If this is a readonly session, then print the existing
	 * runpath and exit. DT_RPATH and DT_RUNPATH should have
	 * the same value, so we arbitrarily favor DT_RUNPATH.
	 */
	if (readonly) {
		if (runpath_elt.seen)
			(void) printf("%s\n",
			    DYN_ELT_STRING(&runpath_elt, strsec));
		else if (rpath_elt.seen)
			(void) printf("%s\n",
			    DYN_ELT_STRING(&rpath_elt, strsec));
		else
			msg_debug("ELF file does not have a runpath: %s\n",
			    file);
		return (0);
	}


	/* Edit the file, either to remove the runpath or to add/modify it */
	if (r_flg) {
		if (!(runpath_elt.seen || rpath_elt.seen))
			msg_debug("[%d]%s: no runpath found\n",
			    dynsec->c_ndx, dynsec->c_name);
		else
			changed = remove_runpath(dynsec, numdyn);
	} else {
		changed = new_runpath(runpath, dynsec, numdyn, strsec,
		    &rpath_elt, &runpath_elt, &strpad_elt, &null_elt);
	}

	if (changed) {
		/*
		 * If possible, set the DF_1_EDITED flag, indicating that
		 * this file has been edited after the fact.
		 */
		if (flags_1_elt.seen) {
			flags_1_elt.dyn.d_un.d_val |= DF_1_EDITED;
		} else if (null_elt.seen && (null_elt.ndx < (numdyn - 1))) {
			msg_debug("[%d]%s: No existing flags_1 entry to "
			    "modify. Will use extra DT_NULL in slot [%d] \n",
			    dynsec->c_ndx, dynsec->c_name, null_elt.ndx);
			flags_1_elt.seen = 1;
			flags_1_elt.ndx = null_elt.ndx;
			flags_1_elt.dyn.d_tag = DT_FLAGS_1;
			flags_1_elt.dyn.d_un.d_val = DF_1_EDITED;
		}
		if (flags_1_elt.seen) {
			msg_debug("[%d]%s[%d]: Set DF_1_EDITED flag\n",
			    dynsec->c_ndx, dynsec->c_name, flags_1_elt.ndx);
			if (gelf_update_dyn(dynsec->c_data, flags_1_elt.ndx,
			    &flags_1_elt.dyn) == 0)
				msg_elf("gelf_update_dyn");
		}

		/*
		 * Mark the data area as dirty so libelf will flush our
		 * changes to the dynamic section data.
		 */
		(void) elf_flagdata(dynsec->c_data, ELF_C_SET, ELF_F_DIRTY);

		/* Flush the file to disk */
		if (elf_update(elf, ELF_C_WRITE) == -1)
			msg_elf("elf_update");
		(void) close(fd);
		(void) elf_end(elf);
	}
	return (0);
}