예제 #1
0
void    cleanup_final(CLEANUP_STATE *state)
{
    const char *myname = "cleanup_final";

    /*
     * vstream_fseek() would flush the buffer anyway, but the code just reads
     * better if we flush first, because it makes seek error handling more
     * straightforward.
     */
    if (vstream_fflush(state->dst)) {
	if (errno == EFBIG) {
	    msg_warn("%s: queue file size limit exceeded", state->queue_id);
	    state->errs |= CLEANUP_STAT_SIZE;
	} else {
	    msg_warn("%s: write queue file: %m", state->queue_id);
	    state->errs |= CLEANUP_STAT_WRITE;
	}
	return;
    }

    /*
     * Update the preliminary message size and count fields with the actual
     * values.
     */
    if (vstream_fseek(state->dst, 0L, SEEK_SET) < 0)
	msg_fatal("%s: vstream_fseek %s: %m", myname, cleanup_path);
    cleanup_out_format(state, REC_TYPE_SIZE, REC_TYPE_SIZE_FORMAT,
	    (REC_TYPE_SIZE_CAST1) (state->xtra_offset - state->data_offset),
		       (REC_TYPE_SIZE_CAST2) state->data_offset,
		       (REC_TYPE_SIZE_CAST3) state->rcpt_count,
		       (REC_TYPE_SIZE_CAST4) state->qmgr_opts,
		       (REC_TYPE_SIZE_CAST5) state->cont_length);
}
예제 #2
0
void    qmgr_message_update_warn(QMGR_MESSAGE *message)
{

    /*
     * After the "mail delayed" warning, optionally send a "delay cleared"
     * notification.
     */
    if (qmgr_message_open(message)
	|| vstream_fseek(message->fp, message->warn_offset, SEEK_SET) < 0
	|| rec_fprintf(message->fp, REC_TYPE_WARN, REC_TYPE_WARN_FORMAT,
		       REC_TYPE_WARN_ARG(-1)) < 0
	|| vstream_fflush(message->fp))
	msg_fatal("update queue file %s: %m", VSTREAM_PATH(message->fp));
    qmgr_message_close(message);
}
예제 #3
0
void    qmgr_message_update_warn(QMGR_MESSAGE *message)
{

    /*
     * XXX eventually this should let us schedule multiple warnings, right
     * now it just allows for one.
     */
    if (qmgr_message_open(message)
	|| vstream_fseek(message->fp, message->warn_offset, SEEK_SET) < 0
	|| rec_fprintf(message->fp, REC_TYPE_WARN, REC_TYPE_WARN_FORMAT,
		       REC_TYPE_WARN_ARG(0)) < 0
	|| vstream_fflush(message->fp))
	msg_fatal("update queue file %s: %m", VSTREAM_PATH(message->fp));
    qmgr_message_close(message);
}
예제 #4
0
DELIVERED_HDR_INFO *delivered_hdr_init(VSTREAM *fp, off_t offset, int flags)
{
    char   *cp;
    DELIVERED_HDR_INFO *info;
    const HEADER_OPTS *hdr;

    /*
     * Sanity check.
     */
    info = (DELIVERED_HDR_INFO *) mymalloc(sizeof(*info));
    info->flags = flags;
    info->buf = vstring_alloc(10);
    info->table = htable_create(0);

    if (vstream_fseek(fp, offset, SEEK_SET) < 0)
	msg_fatal("seek queue file %s: %m", VSTREAM_PATH(fp));

    /*
     * XXX Assume that mail_copy() produces delivered-to headers that fit in
     * a REC_TYPE_NORM record. Lowercase the delivered-to addresses for
     * consistency.
     * 
     * XXX Don't get bogged down by gazillions of delivered-to headers.
     */
#define DELIVERED_HDR_LIMIT	1000

    while (rec_get(fp, info->buf, 0) == REC_TYPE_NORM
	   && info->table->used < DELIVERED_HDR_LIMIT) {
	if (is_header(STR(info->buf))) {
	    if ((hdr = header_opts_find(STR(info->buf))) != 0
		&& hdr->type == HDR_DELIVERED_TO) {
		cp = STR(info->buf) + strlen(hdr->name) + 1;
		while (ISSPACE(*cp))
		    cp++;
		if (info->flags & FOLD_ADDR_ALL)
		    fold_addr(cp, info->flags);
		if (msg_verbose)
		    msg_info("delivered_hdr_init: %s", cp);
		htable_enter(info->table, cp, (char *) 0);
	    }
	} else if (ISSPACE(STR(info->buf)[0])) {
	    continue;
	} else {
	    break;
	}
    }
    return (info);
}
예제 #5
0
void    smtpd_proxy_free(SMTPD_STATE *state)
{
    SMTPD_PROXY *proxy = state->proxy;

    /*
     * Clean up.
     */
    if (proxy->service_stream != 0)
	(void) smtpd_proxy_close(state);
    if (proxy->request != 0)
	vstring_free(proxy->request);
    if (proxy->reply != 0)
	vstring_free(proxy->reply);
    myfree((char *) proxy);
    state->proxy = 0;

    /*
     * Reuse the replay logfile if possible. For security reasons we must
     * truncate the replay logfile before reuse. For performance reasons we
     * should truncate the replay logfile immediately after the end of a mail
     * transaction. We truncate the file here, and enforce the security
     * guarantee by requiring that no I/O happens before the file is reused.
     */
    if (smtpd_proxy_replay_stream == 0)
	return;
    if (vstream_ferror(smtpd_proxy_replay_stream)) {
	/* Errors are already reported. */
	(void) vstream_fclose(smtpd_proxy_replay_stream);
	smtpd_proxy_replay_stream = 0;
	return;
    }
    /* Flush output from aborted transaction before truncating the file!! */
    if (vstream_fseek(smtpd_proxy_replay_stream, (off_t) 0, SEEK_SET) < 0) {
	msg_warn("seek before-queue filter speed-adjust log: %m");
	(void) vstream_fclose(smtpd_proxy_replay_stream);
	smtpd_proxy_replay_stream = 0;
	return;
    }
    if (ftruncate(vstream_fileno(smtpd_proxy_replay_stream), (off_t) 0) < 0) {
	msg_warn("truncate before-queue filter speed-adjust log: %m");
	(void) vstream_fclose(smtpd_proxy_replay_stream);
	smtpd_proxy_replay_stream = 0;
	return;
    }
}
예제 #6
0
void    cleanup_extracted_finish(CLEANUP_STATE *state)
{
    const char myname[] = "cleanup_extracted_finish";

    /*
     * On the way out, add the optional automatic BCC recipient.
     */
    if ((state->flags & CLEANUP_FLAG_BCC_OK)
	&& state->recip != 0 && *var_always_bcc)
	cleanup_addr_bcc(state, var_always_bcc);

    /*
     * Terminate the extracted segment.
     */
    cleanup_out_string(state, REC_TYPE_END, "");

    /*
     * vstream_fseek() would flush the buffer anyway, but the code just reads
     * better if we flush first, because it makes seek error handling more
     * straightforward.
     */
    if (vstream_fflush(state->dst)) {
	if (errno == EFBIG) {
	    msg_warn("%s: queue file size limit exceeded", state->queue_id);
	    state->errs |= CLEANUP_STAT_SIZE;
	} else {
	    msg_warn("%s: write queue file: %m", state->queue_id);
	    state->errs |= CLEANUP_STAT_WRITE;
	}
	return;
    }

    /*
     * Update the preliminary message size and count fields with the actual
     * values.
     */
    if (vstream_fseek(state->dst, 0L, SEEK_SET) < 0)
	msg_fatal("%s: vstream_fseek %s: %m", myname, cleanup_path);
    cleanup_out_format(state, REC_TYPE_SIZE, REC_TYPE_SIZE_FORMAT,
	    (REC_TYPE_SIZE_CAST1) (state->xtra_offset - state->data_offset),
		       (REC_TYPE_SIZE_CAST2) state->data_offset,
		       (REC_TYPE_SIZE_CAST3) state->rcpt_count,
		       (REC_TYPE_SIZE_CAST4) state->qmgr_opts);
}
예제 #7
0
static int deliver_mailbox_file(LOCAL_STATE state, USER_ATTR usr_attr)
{
    const char *myname = "deliver_mailbox_file";
    DSN_BUF *why = state.msg_attr.why;
    MBOX   *mp;
    int     mail_copy_status;
    int     deliver_status;
    int     copy_flags;
    long    end;
    struct stat st;

    /*
     * 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));
    state.msg_attr.delivered = state.msg_attr.rcpt.address;
    mail_copy_status = MAIL_COPY_STAT_WRITE;

    /*
     * Lock the mailbox and open/create the mailbox file.
     * 
     * Write the file as the recipient, so that file quota work.
     */
    copy_flags = MAIL_COPY_MBOX;

    set_eugid(usr_attr.uid, usr_attr.gid);
    mp = mbox_open(usr_attr.mailbox, O_APPEND | O_WRONLY | O_CREAT,
		   S_IRUSR | S_IWUSR, &st, -1, -1,
		   virtual_mbox_lock_mask, "4.2.0", why);
    if (mp != 0) {
	if (S_ISREG(st.st_mode) == 0) {
	    vstream_fclose(mp->fp);
	    msg_warn("recipient %s: destination %s is not a regular file",
		     state.msg_attr.rcpt.address, usr_attr.mailbox);
	    dsb_simple(why, "5.3.5", "mail system configuration error");
	} 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", usr_attr.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);
	}
	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, "delivery failed to mailbox %s: ",
				usr_attr.mailbox);
	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));
    }
    return (deliver_status);
}
예제 #8
0
파일: command.c 프로젝트: hiroya/postfix
int     deliver_command(LOCAL_STATE state, USER_ATTR usr_attr, const char *command)
{
    const char *myname = "deliver_command";
    DSN_BUF *why = state.msg_attr.why;
    int     cmd_status;
    int     deliver_status;
    ARGV   *env;
    int     copy_flags;
    char  **cpp;
    char   *cp;
    ARGV   *export_env;
    VSTRING *exec_dir;
    int     expand_status;

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

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

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

    /*
     * DELIVERY RIGHTS
     * 
     * Choose a default uid and gid when none have been selected (i.e. values
     * are still zero).
     */
    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");

    /*
     * Deliver.
     */
    copy_flags = MAIL_COPY_FROM | MAIL_COPY_RETURN_PATH
	| MAIL_COPY_ORIG_RCPT;
    if (local_deliver_hdr_mask & DELIVER_HDR_CMD)
	copy_flags |= MAIL_COPY_DELIVERED;

    if (vstream_fseek(state.msg_attr.fp, state.msg_attr.offset, SEEK_SET) < 0)
	msg_fatal("%s: seek queue file %s: %m",
		  myname, VSTREAM_PATH(state.msg_attr.fp));

    /*
     * Pass additional environment information. XXX This should be
     * configurable. However, passing untrusted information via environment
     * parameters opens up a whole can of worms. Lesson from web servers:
     * don't let any network data even near a shell. It causes trouble.
     */
    env = argv_alloc(1);
    if (usr_attr.home)
	argv_add(env, "HOME", usr_attr.home, ARGV_END);
    argv_add(env,
	     "LOGNAME", state.msg_attr.user,
	     "USER", state.msg_attr.user,
	     "SENDER", state.msg_attr.sender,
	     "RECIPIENT", state.msg_attr.rcpt.address,
	     "LOCAL", state.msg_attr.local,
	     ARGV_END);
    if (usr_attr.shell)
	argv_add(env, "SHELL", usr_attr.shell, ARGV_END);
    if (state.msg_attr.domain)
	argv_add(env, "DOMAIN", state.msg_attr.domain, ARGV_END);
    if (state.msg_attr.extension)
	argv_add(env, "EXTENSION", state.msg_attr.extension, ARGV_END);
    if (state.msg_attr.rcpt.orig_addr && state.msg_attr.rcpt.orig_addr[0])
	argv_add(env, "ORIGINAL_RECIPIENT", state.msg_attr.rcpt.orig_addr,
		 ARGV_END);

#define EXPORT_REQUEST(name, value) \
	if ((value)[0]) argv_add(env, (name), (value), ARGV_END);

    EXPORT_REQUEST("CLIENT_HOSTNAME", state.msg_attr.request->client_name);
    EXPORT_REQUEST("CLIENT_ADDRESS", state.msg_attr.request->client_addr);
    EXPORT_REQUEST("CLIENT_HELO", state.msg_attr.request->client_helo);
    EXPORT_REQUEST("CLIENT_PROTOCOL", state.msg_attr.request->client_proto);
    EXPORT_REQUEST("SASL_METHOD", state.msg_attr.request->sasl_method);
    EXPORT_REQUEST("SASL_SENDER", state.msg_attr.request->sasl_sender);
    EXPORT_REQUEST("SASL_USERNAME", state.msg_attr.request->sasl_username);

    argv_terminate(env);

    /*
     * Censor out undesirable characters from exported data.
     */
    for (cpp = env->argv; *cpp; cpp += 2)
	for (cp = cpp[1]; *(cp += strspn(cp, var_cmd_exp_filter)) != 0;)
	    *cp++ = '_';

    /*
     * Evaluate the command execution directory. Defer delivery if expansion
     * fails.
     */
    export_env = mail_parm_split(VAR_EXPORT_ENVIRON, var_export_environ);
    exec_dir = vstring_alloc(10);
    expand_status = local_expand(exec_dir, var_exec_directory,
				 &state, &usr_attr, var_exec_exp_filter);

    if (expand_status & MAC_PARSE_ERROR) {
	cmd_status = PIPE_STAT_DEFER;
	dsb_simple(why, "4.3.5", "mail system configuration error");
	msg_warn("bad parameter value syntax for %s: %s",
		 VAR_EXEC_DIRECTORY, var_exec_directory);
    } else {
	cmd_status = pipe_command(state.msg_attr.fp, why,
				  PIPE_CMD_UID, usr_attr.uid,
				  PIPE_CMD_GID, usr_attr.gid,
				  PIPE_CMD_COMMAND, command,
				  PIPE_CMD_COPY_FLAGS, copy_flags,
				  PIPE_CMD_SENDER, state.msg_attr.sender,
			  PIPE_CMD_ORIG_RCPT, state.msg_attr.rcpt.orig_addr,
			       PIPE_CMD_DELIVERED, state.msg_attr.delivered,
				  PIPE_CMD_TIME_LIMIT, var_command_maxtime,
				  PIPE_CMD_ENV, env->argv,
				  PIPE_CMD_EXPORT, export_env->argv,
				  PIPE_CMD_SHELL, var_local_cmd_shell,
				  PIPE_CMD_CWD, *STR(exec_dir) ?
				  STR(exec_dir) : (char *) 0,
				  PIPE_CMD_END);
    }
    vstring_free(exec_dir);
    argv_free(export_env);
    argv_free(env);

    /*
     * Depending on the result, bounce or defer the message.
     */
    switch (cmd_status) {
    case PIPE_STAT_OK:
	dsb_simple(why, "2.0.0", "delivered to command: %s", command);
	deliver_status = sent(BOUNCE_FLAGS(state.request),
			      SENT_ATTR(state.msg_attr));
	break;
    case PIPE_STAT_BOUNCE:
    case PIPE_STAT_DEFER:
	/* Account for possible owner- sender address override. */
	deliver_status = bounce_workaround(state);
	break;
    case PIPE_STAT_CORRUPT:
	deliver_status = DEL_STAT_DEFER;
	break;
    default:
	msg_panic("%s: bad status %d", myname, cmd_status);
	/* NOTREACHED */
    }

    return (deliver_status);
}
예제 #9
0
static int qmgr_message_read(QMGR_MESSAGE *message)
{
    VSTRING *buf;
    int     rec_type;
    long    curr_offset;
    long    save_offset = message->rcpt_offset;	/* save a flag */
    int     save_unread = message->rcpt_unread;	/* save a count */
    char   *start;
    int     recipient_limit;
    const char *error_text;
    char   *name;
    char   *value;
    char   *orig_rcpt = 0;
    int     count;
    int     dsn_notify = 0;
    char   *dsn_orcpt = 0;
    int     n;
    int     have_log_client_attr = 0;

    /*
     * Initialize. No early returns or we have a memory leak.
     */
    buf = vstring_alloc(100);

    /*
     * If we re-open this file, skip over on-file recipient records that we
     * already looked at, and refill the in-core recipient address list.
     * 
     * For the first time, the message recipient limit is calculated from the
     * global recipient limit. This is to avoid reading little recipients
     * when the active queue is near empty. When the queue becomes full, only
     * the necessary amount is read in core. Such priming is necessary
     * because there are no message jobs yet.
     * 
     * For the next time, the recipient limit is based solely on the message
     * jobs' positions in the job lists and/or job stacks.
     */
    if (message->rcpt_offset) {
	if (message->rcpt_list.len)
	    msg_panic("%s: recipient list not empty on recipient reload",
		      message->queue_id);
	if (vstream_fseek(message->fp, message->rcpt_offset, SEEK_SET) < 0)
	    msg_fatal("seek file %s: %m", VSTREAM_PATH(message->fp));
	message->rcpt_offset = 0;
	recipient_limit = message->rcpt_limit - message->rcpt_count;
    } else {
	recipient_limit = var_qmgr_rcpt_limit - qmgr_recipient_count;
	if (recipient_limit < message->rcpt_limit)
	    recipient_limit = message->rcpt_limit;
    }
    /* Keep interrupt latency in check. */
    if (recipient_limit > 5000)
	recipient_limit = 5000;
    if (recipient_limit <= 0)
	msg_panic("%s: no recipient slots available", message->queue_id);
    if (msg_verbose)
	msg_info("%s: recipient limit %d", message->queue_id, recipient_limit);

    /*
     * Read envelope records. XXX Rely on the front-end programs to enforce
     * record size limits. Read up to recipient_limit recipients from the
     * queue file, to protect against memory exhaustion. Recipient records
     * may appear before or after the message content, so we keep reading
     * from the queue file until we have enough recipients (rcpt_offset != 0)
     * and until we know all the non-recipient information.
     * 
     * Note that the total recipient count record is accurate only for fresh
     * queue files. After some of the recipients are marked as done and the
     * queue file is deferred, it can be used as upper bound estimate only.
     * Fortunately, this poses no major problem on the scheduling algorithm,
     * as the only impact is that the already deferred messages are not
     * chosen by qmgr_job_candidate() as often as they could.
     * 
     * On the first open, we must examine all non-recipient records.
     * 
     * Optimization: when we know that recipient records are not mixed with
     * non-recipient records, as is typical with mailing list mail, then we
     * can avoid having to examine all the queue file records before we can
     * start deliveries. This avoids some file system thrashing with huge
     * mailing lists.
     */
    for (;;) {
	if ((curr_offset = vstream_ftell(message->fp)) < 0)
	    msg_fatal("vstream_ftell %s: %m", VSTREAM_PATH(message->fp));
	if (curr_offset == message->data_offset && curr_offset > 0) {
	    if (vstream_fseek(message->fp, message->data_size, SEEK_CUR) < 0)
		msg_fatal("seek file %s: %m", VSTREAM_PATH(message->fp));
	    curr_offset += message->data_size;
	}
	rec_type = rec_get_raw(message->fp, buf, 0, REC_FLAG_NONE);
	start = vstring_str(buf);
	if (msg_verbose > 1)
	    msg_info("record %c %s", rec_type, start);
	if (rec_type == REC_TYPE_PTR) {
	    if ((rec_type = rec_goto(message->fp, start)) == REC_TYPE_ERROR)
		break;
	    /* Need to update curr_offset after pointer jump. */
	    continue;
	}
	if (rec_type <= 0) {
	    msg_warn("%s: message rejected: missing end record",
		     message->queue_id);
	    break;
	}
	if (rec_type == REC_TYPE_END) {
	    message->rflags |= QMGR_READ_FLAG_SEEN_ALL_NON_RCPT;
	    break;
	}

	/*
	 * Map named attributes to pseudo record types, so that we don't have
	 * to pollute the queue file with records that are incompatible with
	 * past Postfix versions. Preferably, people should be able to back
	 * out from an upgrade without losing mail.
	 */
	if (rec_type == REC_TYPE_ATTR) {
	    if ((error_text = split_nameval(start, &name, &value)) != 0) {
		msg_warn("%s: ignoring bad attribute: %s: %.200s",
			 message->queue_id, error_text, start);
		rec_type = REC_TYPE_ERROR;
		break;
	    }
	    if ((n = rec_attr_map(name)) != 0) {
		start = value;
		rec_type = n;
	    }
	}

	/*
	 * Process recipient records.
	 */
	if (rec_type == REC_TYPE_RCPT) {
	    /* See also below for code setting orig_rcpt etc. */
	    if (message->rcpt_offset == 0) {
		message->rcpt_unread--;
		recipient_list_add(&message->rcpt_list, curr_offset,
				   dsn_orcpt ? dsn_orcpt : "",
				   dsn_notify ? dsn_notify : 0,
				   orig_rcpt ? orig_rcpt : "", start);
		if (dsn_orcpt) {
		    myfree(dsn_orcpt);
		    dsn_orcpt = 0;
		}
		if (orig_rcpt) {
		    myfree(orig_rcpt);
		    orig_rcpt = 0;
		}
		if (dsn_notify)
		    dsn_notify = 0;
		if (message->rcpt_list.len >= recipient_limit) {
		    if ((message->rcpt_offset = vstream_ftell(message->fp)) < 0)
			msg_fatal("vstream_ftell %s: %m",
				  VSTREAM_PATH(message->fp));
		    if (message->rflags & QMGR_READ_FLAG_SEEN_ALL_NON_RCPT)
			/* We already examined all non-recipient records. */
			break;
		    if (message->rflags & QMGR_READ_FLAG_MIXED_RCPT_OTHER)
			/* Examine all remaining non-recipient records. */
			continue;
		    /* Optimizations for "pure recipient" record sections. */
		    if (curr_offset > message->data_offset) {
			/* We already examined all non-recipient records. */
			message->rflags |= QMGR_READ_FLAG_SEEN_ALL_NON_RCPT;
			break;
		    }
		    /* Examine non-recipient records in extracted segment. */
		    if (vstream_fseek(message->fp, message->data_offset
				      + message->data_size, SEEK_SET) < 0)
			msg_fatal("seek file %s: %m", VSTREAM_PATH(message->fp));
		    continue;
		}
	    }
	    continue;
	}
	if (rec_type == REC_TYPE_DONE || rec_type == REC_TYPE_DRCP) {
	    if (message->rcpt_offset == 0) {
		message->rcpt_unread--;
		if (dsn_orcpt) {
		    myfree(dsn_orcpt);
		    dsn_orcpt = 0;
		}
		if (orig_rcpt) {
		    myfree(orig_rcpt);
		    orig_rcpt = 0;
		}
		if (dsn_notify)
		    dsn_notify = 0;
	    }
	    continue;
	}
	if (rec_type == REC_TYPE_DSN_ORCPT) {
	    /* See also above for code clearing dsn_orcpt. */
	    if (dsn_orcpt != 0) {
		msg_warn("%s: ignoring out-of-order DSN original recipient address <%.200s>",
			 message->queue_id, dsn_orcpt);
		myfree(dsn_orcpt);
		dsn_orcpt = 0;
	    }
	    if (message->rcpt_offset == 0)
		dsn_orcpt = mystrdup(start);
	    continue;
	}
	if (rec_type == REC_TYPE_DSN_NOTIFY) {
	    /* See also above for code clearing dsn_notify. */
	    if (dsn_notify != 0) {
		msg_warn("%s: ignoring out-of-order DSN notify flags <%d>",
			 message->queue_id, dsn_notify);
		dsn_notify = 0;
	    }
	    if (message->rcpt_offset == 0) {
		if (!alldig(start) || (n = atoi(start)) == 0 || !DSN_NOTIFY_OK(n))
		    msg_warn("%s: ignoring malformed DSN notify flags <%.200s>",
			     message->queue_id, start);
		else
		    dsn_notify = n;
		continue;
	    }
	}
	if (rec_type == REC_TYPE_ORCP) {
	    /* See also above for code clearing orig_rcpt. */
	    if (orig_rcpt != 0) {
		msg_warn("%s: ignoring out-of-order original recipient <%.200s>",
			 message->queue_id, orig_rcpt);
		myfree(orig_rcpt);
		orig_rcpt = 0;
	    }
	    if (message->rcpt_offset == 0)
		orig_rcpt = mystrdup(start);
	    continue;
	}

	/*
	 * Process non-recipient records.
	 */
	if (message->rflags & QMGR_READ_FLAG_SEEN_ALL_NON_RCPT)
	    /* We already examined all non-recipient records. */
	    continue;
	if (rec_type == REC_TYPE_SIZE) {
	    if (message->data_offset == 0) {
		if ((count = sscanf(start, "%ld %ld %d %d %ld %d",
				 &message->data_size, &message->data_offset,
				    &message->rcpt_unread, &message->rflags,
				    &message->cont_length,
				    &message->smtputf8)) >= 3) {
		    /* Postfix >= 1.0 (a.k.a. 20010228). */
		    if (message->data_offset <= 0 || message->data_size <= 0) {
			msg_warn("%s: invalid size record: %.100s",
				 message->queue_id, start);
			rec_type = REC_TYPE_ERROR;
			break;
		    }
		    if (message->rflags & ~QMGR_READ_FLAG_USER) {
			msg_warn("%s: invalid flags in size record: %.100s",
				 message->queue_id, start);
			rec_type = REC_TYPE_ERROR;
			break;
		    }
		} else if (count == 1) {
		    /* Postfix < 1.0 (a.k.a. 20010228). */
		    qmgr_message_oldstyle_scan(message);
		} else {
		    /* Can't happen. */
		    msg_warn("%s: message rejected: weird size record",
			     message->queue_id);
		    rec_type = REC_TYPE_ERROR;
		    break;
		}
	    }
	    /* Postfix < 2.4 compatibility. */
	    if (message->cont_length == 0) {
		message->cont_length = message->data_size;
	    } else if (message->cont_length < 0) {
		msg_warn("%s: invalid size record: %.100s",
			 message->queue_id, start);
		rec_type = REC_TYPE_ERROR;
		break;
	    }
	    continue;
	}
	if (rec_type == REC_TYPE_TIME) {
	    if (message->arrival_time.tv_sec == 0)
		REC_TYPE_TIME_SCAN(start, message->arrival_time);
	    continue;
	}
	if (rec_type == REC_TYPE_CTIME) {
	    if (message->create_time == 0)
		message->create_time = atol(start);
	    continue;
	}
	if (rec_type == REC_TYPE_FILT) {
	    if (message->filter_xport != 0)
		myfree(message->filter_xport);
	    message->filter_xport = mystrdup(start);
	    continue;
	}
	if (rec_type == REC_TYPE_INSP) {
	    if (message->inspect_xport != 0)
		myfree(message->inspect_xport);
	    message->inspect_xport = mystrdup(start);
	    continue;
	}
	if (rec_type == REC_TYPE_RDR) {
	    if (message->redirect_addr != 0)
		myfree(message->redirect_addr);
	    message->redirect_addr = mystrdup(start);
	    continue;
	}
	if (rec_type == REC_TYPE_FROM) {
	    if (message->sender == 0) {
		message->sender = mystrdup(start);
		opened(message->queue_id, message->sender,
		       message->cont_length, message->rcpt_unread,
		       "queue %s", message->queue_name);
	    }
	    continue;
	}
	if (rec_type == REC_TYPE_DSN_ENVID) {
	    if (message->dsn_envid == 0)
		message->dsn_envid = mystrdup(start);
	}
	if (rec_type == REC_TYPE_DSN_RET) {
	    if (message->dsn_ret == 0) {
		if (!alldig(start) || (n = atoi(start)) == 0 || !DSN_RET_OK(n))
		    msg_warn("%s: ignoring malformed DSN RET flags in queue file record:%.100s",
			     message->queue_id, start);
		else
		    message->dsn_ret = n;
	    }
	}
	if (rec_type == REC_TYPE_ATTR) {
	    /* Allow extra segment to override envelope segment info. */
	    if (strcmp(name, MAIL_ATTR_ENCODING) == 0) {
		if (message->encoding != 0)
		    myfree(message->encoding);
		message->encoding = mystrdup(value);
	    }

	    /*
	     * Backwards compatibility. Before Postfix 2.3, the logging
	     * attributes were called client_name, etc. Now they are called
	     * log_client_name. etc., and client_name is used for the actual
	     * client information. To support old queue files we accept both
	     * names for the purpose of logging; the new name overrides the
	     * old one.
	     * 
	     * XXX Do not use the "legacy" client_name etc. attribute values for
	     * initializing the logging attributes, when this file already
	     * contains the "modern" log_client_name etc. logging attributes.
	     * Otherwise, logging attributes that are not present in the
	     * queue file would be set with information from the real client.
	     */
	    else if (strcmp(name, MAIL_ATTR_ACT_CLIENT_NAME) == 0) {
		if (have_log_client_attr == 0 && message->client_name == 0)
		    message->client_name = mystrdup(value);
	    } else if (strcmp(name, MAIL_ATTR_ACT_CLIENT_ADDR) == 0) {
		if (have_log_client_attr == 0 && message->client_addr == 0)
		    message->client_addr = mystrdup(value);
	    } else if (strcmp(name, MAIL_ATTR_ACT_CLIENT_PORT) == 0) {
		if (have_log_client_attr == 0 && message->client_port == 0)
		    message->client_port = mystrdup(value);
	    } else if (strcmp(name, MAIL_ATTR_ACT_PROTO_NAME) == 0) {
		if (have_log_client_attr == 0 && message->client_proto == 0)
		    message->client_proto = mystrdup(value);
	    } else if (strcmp(name, MAIL_ATTR_ACT_HELO_NAME) == 0) {
		if (have_log_client_attr == 0 && message->client_helo == 0)
		    message->client_helo = mystrdup(value);
	    }
	    /* Original client attributes. */
	    else if (strcmp(name, MAIL_ATTR_LOG_CLIENT_NAME) == 0) {
		if (message->client_name != 0)
		    myfree(message->client_name);
		message->client_name = mystrdup(value);
		have_log_client_attr = 1;
	    } else if (strcmp(name, MAIL_ATTR_LOG_CLIENT_ADDR) == 0) {
		if (message->client_addr != 0)
		    myfree(message->client_addr);
		message->client_addr = mystrdup(value);
		have_log_client_attr = 1;
	    } else if (strcmp(name, MAIL_ATTR_LOG_CLIENT_PORT) == 0) {
		if (message->client_port != 0)
		    myfree(message->client_port);
		message->client_port = mystrdup(value);
		have_log_client_attr = 1;
	    } else if (strcmp(name, MAIL_ATTR_LOG_PROTO_NAME) == 0) {
		if (message->client_proto != 0)
		    myfree(message->client_proto);
		message->client_proto = mystrdup(value);
		have_log_client_attr = 1;
	    } else if (strcmp(name, MAIL_ATTR_LOG_HELO_NAME) == 0) {
		if (message->client_helo != 0)
		    myfree(message->client_helo);
		message->client_helo = mystrdup(value);
		have_log_client_attr = 1;
	    } else if (strcmp(name, MAIL_ATTR_SASL_METHOD) == 0) {
		if (message->sasl_method == 0)
		    message->sasl_method = mystrdup(value);
		else
		    msg_warn("%s: ignoring multiple %s attribute: %s",
			   message->queue_id, MAIL_ATTR_SASL_METHOD, value);
	    } else if (strcmp(name, MAIL_ATTR_SASL_USERNAME) == 0) {
		if (message->sasl_username == 0)
		    message->sasl_username = mystrdup(value);
		else
		    msg_warn("%s: ignoring multiple %s attribute: %s",
			 message->queue_id, MAIL_ATTR_SASL_USERNAME, value);
	    } else if (strcmp(name, MAIL_ATTR_SASL_SENDER) == 0) {
		if (message->sasl_sender == 0)
		    message->sasl_sender = mystrdup(value);
		else
		    msg_warn("%s: ignoring multiple %s attribute: %s",
			   message->queue_id, MAIL_ATTR_SASL_SENDER, value);
	    } else if (strcmp(name, MAIL_ATTR_LOG_IDENT) == 0) {
		if (message->log_ident == 0)
		    message->log_ident = mystrdup(value);
		else
		    msg_warn("%s: ignoring multiple %s attribute: %s",
			     message->queue_id, MAIL_ATTR_LOG_IDENT, value);
	    } else if (strcmp(name, MAIL_ATTR_RWR_CONTEXT) == 0) {
		if (message->rewrite_context == 0)
		    message->rewrite_context = mystrdup(value);
		else
		    msg_warn("%s: ignoring multiple %s attribute: %s",
			   message->queue_id, MAIL_ATTR_RWR_CONTEXT, value);
	    }

	    /*
	     * Optional tracing flags (verify, sendmail -v, sendmail -bv).
	     * This record is killed after a trace logfile report is sent and
	     * after the logfile is deleted.
	     */
	    else if (strcmp(name, MAIL_ATTR_TRACE_FLAGS) == 0) {
		message->tflags = DEL_REQ_TRACE_FLAGS(atoi(value));
		if (message->tflags == DEL_REQ_FLAG_RECORD)
		    message->tflags_offset = curr_offset;
		else
		    message->tflags_offset = 0;
	    }
	    continue;
	}
	if (rec_type == REC_TYPE_WARN) {
	    if (message->warn_offset == 0) {
		message->warn_offset = curr_offset;
		REC_TYPE_WARN_SCAN(start, message->warn_time);
	    }
	    continue;
	}
	if (rec_type == REC_TYPE_VERP) {
	    if (message->verp_delims == 0) {
		if (message->sender == 0 || message->sender[0] == 0) {
		    msg_warn("%s: ignoring VERP request for null sender",
			     message->queue_id);
		} else if (verp_delims_verify(start) != 0) {
		    msg_warn("%s: ignoring bad VERP request: \"%.100s\"",
			     message->queue_id, start);
		} else {
		    if (msg_verbose)
			msg_info("%s: enabling VERP for sender \"%.100s\"",
				 message->queue_id, message->sender);
		    message->single_rcpt = 1;
		    message->verp_delims = mystrdup(start);
		}
	    }
	    continue;
	}
    }

    /*
     * Grr.
     */
    if (dsn_orcpt != 0) {
	if (rec_type > 0)
	    msg_warn("%s: ignoring out-of-order DSN original recipient <%.200s>",
		     message->queue_id, dsn_orcpt);
	myfree(dsn_orcpt);
    }
    if (orig_rcpt != 0) {
	if (rec_type > 0)
	    msg_warn("%s: ignoring out-of-order original recipient <%.200s>",
		     message->queue_id, orig_rcpt);
	myfree(orig_rcpt);
    }

    /*
     * After sending a "delayed" warning, request sender notification when
     * message delivery is completed. While "mail delayed" notifications are
     * bad enough because they multiply the amount of email traffic, "delay
     * cleared" notifications are even worse because they come in a sudden
     * burst when the queue drains after a network outage.
     */
    if (var_dsn_delay_cleared && message->warn_time < 0)
	message->tflags |= DEL_REQ_FLAG_REC_DLY_SENT;

    /*
     * Remember when we have read the last recipient batch. Note that we do
     * it here after reading as reading might have used considerable amount
     * of time.
     */
    message->refill_time = sane_time();

    /*
     * Avoid clumsiness elsewhere in the program. When sending data across an
     * IPC channel, sending an empty string is more convenient than sending a
     * null pointer.
     */
    if (message->dsn_envid == 0)
	message->dsn_envid = mystrdup("");
    if (message->encoding == 0)
	message->encoding = mystrdup(MAIL_ATTR_ENC_NONE);
    if (message->client_name == 0)
	message->client_name = mystrdup("");
    if (message->client_addr == 0)
	message->client_addr = mystrdup("");
    if (message->client_port == 0)
	message->client_port = mystrdup("");
    if (message->client_proto == 0)
	message->client_proto = mystrdup("");
    if (message->client_helo == 0)
	message->client_helo = mystrdup("");
    if (message->sasl_method == 0)
	message->sasl_method = mystrdup("");
    if (message->sasl_username == 0)
	message->sasl_username = mystrdup("");
    if (message->sasl_sender == 0)
	message->sasl_sender = mystrdup("");
    if (message->log_ident == 0)
	message->log_ident = mystrdup("");
    if (message->rewrite_context == 0)
	message->rewrite_context = mystrdup(MAIL_ATTR_RWR_LOCAL);
    /* Postfix < 2.3 compatibility. */
    if (message->create_time == 0)
	message->create_time = message->arrival_time.tv_sec;

    /*
     * Clean up.
     */
    vstring_free(buf);

    /*
     * Sanity checks. Verify that all required information was found,
     * including the queue file end marker.
     */
    if (message->rcpt_unread < 0
	|| (message->rcpt_offset == 0 && message->rcpt_unread != 0)) {
	msg_warn("%s: rcpt count mismatch (%d)",
		 message->queue_id, message->rcpt_unread);
	message->rcpt_unread = 0;
    }
    if (rec_type <= 0) {
	/* Already logged warning. */
    } else if (message->arrival_time.tv_sec == 0) {
	msg_warn("%s: message rejected: missing arrival time record",
		 message->queue_id);
    } else if (message->sender == 0) {
	msg_warn("%s: message rejected: missing sender record",
		 message->queue_id);
    } else if (message->data_offset == 0) {
	msg_warn("%s: message rejected: missing size record",
		 message->queue_id);
    } else {
	return (0);
    }
    message->rcpt_offset = save_offset;		/* restore flag */
    message->rcpt_unread = save_unread;		/* restore count */
    recipient_list_free(&message->rcpt_list);
    recipient_list_init(&message->rcpt_list, RCPT_LIST_INIT_QUEUE);
    return (-1);
}
예제 #10
0
static int forward_send(FORWARD_INFO *info, DELIVER_REQUEST *request,
			        DELIVER_ATTR attr, char *delivered)
{
    const char *myname = "forward_send";
    VSTRING *buffer = vstring_alloc(100);
    VSTRING *folded;
    int     status;
    int     rec_type = 0;

    /*
     * Start the message content segment. Prepend our Delivered-To: header to
     * the message data. Stop at the first error. XXX Rely on the front-end
     * services to enforce record size limits.
     */
    rec_fputs(info->cleanup, REC_TYPE_MESG, "");
    vstring_strcpy(buffer, delivered);
    rec_fprintf(info->cleanup, REC_TYPE_NORM, "Received: by %s (%s)",
		var_myhostname, var_mail_name);
    rec_fprintf(info->cleanup, REC_TYPE_NORM, "\tid %s; %s",
		info->queue_id, mail_date(info->posting_time.tv_sec));
    if (local_deliver_hdr_mask & DELIVER_HDR_FWD) {
	folded = vstring_alloc(100);
	rec_fprintf(info->cleanup, REC_TYPE_NORM, "Delivered-To: %s",
		    casefold(folded, (STR(buffer))));
	vstring_free(folded);
    }
    if ((status = vstream_ferror(info->cleanup)) == 0)
	if (vstream_fseek(attr.fp, attr.offset, SEEK_SET) < 0)
	    msg_fatal("%s: seek queue file %s: %m:",
		      myname, VSTREAM_PATH(attr.fp));
    while (status == 0 && (rec_type = rec_get(attr.fp, buffer, 0)) > 0) {
	if (rec_type != REC_TYPE_CONT && rec_type != REC_TYPE_NORM)
	    break;
	status = (REC_PUT_BUF(info->cleanup, rec_type, buffer) != rec_type);
    }
    if (status == 0 && rec_type != REC_TYPE_XTRA) {
	msg_warn("%s: bad record type: %d in message content",
		 info->queue_id, rec_type);
	status |= mark_corrupt(attr.fp);
    }

    /*
     * Send the end-of-data marker only when there were no errors.
     */
    if (status == 0) {
	rec_fputs(info->cleanup, REC_TYPE_XTRA, "");
	rec_fputs(info->cleanup, REC_TYPE_END, "");
    }

    /*
     * Retrieve the cleanup service completion status only if there are no
     * problems.
     */
    if (status == 0)
	if (vstream_fflush(info->cleanup)
	    || attr_scan(info->cleanup, ATTR_FLAG_MISSING,
			 RECV_ATTR_INT(MAIL_ATTR_STATUS, &status),
			 ATTR_TYPE_END) != 1)
	    status = 1;

    /*
     * Log successful forwarding.
     * 
     * XXX DSN alias and .forward expansion already report SUCCESS, so don't do
     * it again here.
     */
    if (status == 0) {
	attr.rcpt.dsn_notify =
	    (attr.rcpt.dsn_notify == DSN_NOTIFY_SUCCESS ?
	     DSN_NOTIFY_NEVER : attr.rcpt.dsn_notify & ~DSN_NOTIFY_SUCCESS);
	dsb_update(attr.why, "2.0.0", "relayed", DSB_SKIP_RMTA, DSB_SKIP_REPLY,
		   "forwarded as %s", info->queue_id);
	status = sent(BOUNCE_FLAGS(request), SENT_ATTR(attr));
    }

    /*
     * Cleanup.
     */
    vstring_free(buffer);
    return (status);
}
예제 #11
0
int     bounce_append_service(int unused_flags, char *service, char *queue_id,
			              RECIPIENT *rcpt, DSN *dsn)
{
    VSTRING *in_buf = vstring_alloc(100);
    VSTREAM *log;
    long    orig_length;

    /*
     * This code is paranoid for a good reason. Once the bounce service takes
     * responsibility, the mail system will make no further attempts to
     * deliver this recipient. Whenever file access fails, assume that the
     * system is under stress or that something has been mis-configured, and
     * force a backoff by raising a fatal run-time error.
     */
    log = mail_queue_open(service, queue_id,
			  O_WRONLY | O_APPEND | O_CREAT, 0600);
    if (log == 0)
	msg_fatal("open file %s %s: %m", service, queue_id);

    /*
     * Lock out other processes to avoid truncating someone else's data in
     * case of trouble.
     */
    if (deliver_flock(vstream_fileno(log), INTERNAL_LOCK, (VSTRING *) 0) < 0)
	msg_fatal("lock file %s %s: %m", service, queue_id);

    /*
     * Now, go for it. Append a record. Truncate the log to the original
     * length when the append operation fails. We use the plain stream-lf
     * file format because we do not need anything more complicated. As a
     * benefit, we can still recover some data when the file is a little
     * garbled.
     * 
     * XXX addresses in defer logfiles are in printable quoted form, while
     * addresses in message envelope records are in raw unquoted form. This
     * may change once we replace the present ad-hoc bounce/defer logfile
     * format by one that is transparent for control etc. characters. See
     * also: showq/showq.c.
     * 
     * While migrating from old format to new format, allow backwards
     * compatibility by writing an old-style record before the new-style
     * records.
     */
    if ((orig_length = vstream_fseek(log, 0L, SEEK_END)) < 0)
	msg_fatal("seek file %s %s: %m", service, queue_id);

#define NOT_NULL_EMPTY(s) ((s) != 0 && *(s) != 0)
#define STR(x) vstring_str(x)

    vstream_fputs("\n", log);
    if (var_oldlog_compat) {
	vstream_fprintf(log, "<%s>: %s\n", *rcpt->address == 0 ? "" :
			STR(quote_822_local(in_buf, rcpt->address)),
			dsn->reason);
    }
    vstream_fprintf(log, "%s=%s\n", MAIL_ATTR_RECIP, *rcpt->address ?
		  STR(quote_822_local(in_buf, rcpt->address)) : "<>");
    if (NOT_NULL_EMPTY(rcpt->orig_addr)
	&& strcasecmp(rcpt->address, rcpt->orig_addr) != 0)
	vstream_fprintf(log, "%s=%s\n", MAIL_ATTR_ORCPT,
			STR(quote_822_local(in_buf, rcpt->orig_addr)));
    if (rcpt->offset > 0)
	vstream_fprintf(log, "%s=%ld\n", MAIL_ATTR_OFFSET, rcpt->offset);
    if (NOT_NULL_EMPTY(rcpt->dsn_orcpt))
	vstream_fprintf(log, "%s=%s\n", MAIL_ATTR_DSN_ORCPT, rcpt->dsn_orcpt);
    if (rcpt->dsn_notify != 0)
	vstream_fprintf(log, "%s=%d\n", MAIL_ATTR_DSN_NOTIFY, rcpt->dsn_notify);

    if (NOT_NULL_EMPTY(dsn->status))
	vstream_fprintf(log, "%s=%s\n", MAIL_ATTR_DSN_STATUS, dsn->status);
    if (NOT_NULL_EMPTY(dsn->action))
	vstream_fprintf(log, "%s=%s\n", MAIL_ATTR_DSN_ACTION, dsn->action);
    if (NOT_NULL_EMPTY(dsn->dtype) && NOT_NULL_EMPTY(dsn->dtext)) {
	vstream_fprintf(log, "%s=%s\n", MAIL_ATTR_DSN_DTYPE, dsn->dtype);
	vstream_fprintf(log, "%s=%s\n", MAIL_ATTR_DSN_DTEXT, dsn->dtext);
    }
    if (NOT_NULL_EMPTY(dsn->mtype) && NOT_NULL_EMPTY(dsn->mname)) {
	vstream_fprintf(log, "%s=%s\n", MAIL_ATTR_DSN_MTYPE, dsn->mtype);
	vstream_fprintf(log, "%s=%s\n", MAIL_ATTR_DSN_MNAME, dsn->mname);
    }
    if (NOT_NULL_EMPTY(dsn->reason))
	vstream_fprintf(log, "%s=%s\n", MAIL_ATTR_WHY, dsn->reason);
    vstream_fputs("\n", log);

    if (vstream_fflush(log) != 0 || fsync(vstream_fileno(log)) < 0) {
#ifndef NO_TRUNCATE
	if (ftruncate(vstream_fileno(log), (off_t) orig_length) < 0)
	    msg_fatal("truncate file %s %s: %m", service, queue_id);
#endif
	msg_fatal("append file %s %s: %m", service, queue_id);
    }

    /*
     * Darn. If closing the log detects a problem, the only way to undo the
     * damage is to open the log once more, and to truncate the log to the
     * original length. But, this could happen only when the log is kept on a
     * remote file system, and that is not recommended practice anyway.
     */
    if (vstream_fclose(log) != 0)
	msg_warn("append file %s %s: %m", service, queue_id);

    vstring_free(in_buf);
    return (0);
}
예제 #12
0
static void qmgr_message_oldstyle_scan(QMGR_MESSAGE *message)
{
    VSTRING *buf;
    long    orig_offset, extra_offset;
    int     rec_type;
    char   *start;

    /*
     * Initialize. No early returns or we have a memory leak.
     */
    buf = vstring_alloc(100);
    if ((orig_offset = vstream_ftell(message->fp)) < 0)
	msg_fatal("vstream_ftell %s: %m", VSTREAM_PATH(message->fp));

    /*
     * Rewind to the very beginning to make sure we see all records.
     */
    if (vstream_fseek(message->fp, 0, SEEK_SET) < 0)
	msg_fatal("seek file %s: %m", VSTREAM_PATH(message->fp));

    /*
     * Scan through the old style queue file. Count the total number of
     * recipients and find the data/extra sections offsets. Note that the new
     * queue files require that data_size equals extra_offset - data_offset,
     * so we set data_size to this as well and ignore the size record itself
     * completely.
     */
    message->rcpt_unread = 0;
    for (;;) {
	rec_type = rec_get(message->fp, buf, 0);
	if (rec_type <= 0)
	    /* Report missing end record later. */
	    break;
	start = vstring_str(buf);
	if (msg_verbose > 1)
	    msg_info("old-style scan record %c %s", rec_type, start);
	if (rec_type == REC_TYPE_END)
	    break;
	if (rec_type == REC_TYPE_DONE
	    || rec_type == REC_TYPE_RCPT
	    || rec_type == REC_TYPE_DRCP) {
	    message->rcpt_unread++;
	    continue;
	}
	if (rec_type == REC_TYPE_MESG) {
	    if (message->data_offset == 0) {
		if ((message->data_offset = vstream_ftell(message->fp)) < 0)
		    msg_fatal("vstream_ftell %s: %m", VSTREAM_PATH(message->fp));
		if ((extra_offset = atol(start)) <= message->data_offset)
		    msg_fatal("bad extra offset %s file %s",
			      start, VSTREAM_PATH(message->fp));
		if (vstream_fseek(message->fp, extra_offset, SEEK_SET) < 0)
		    msg_fatal("seek file %s: %m", VSTREAM_PATH(message->fp));
		message->data_size = extra_offset - message->data_offset;
	    }
	    continue;
	}
    }

    /*
     * Clean up.
     */
    if (vstream_fseek(message->fp, orig_offset, SEEK_SET) < 0)
	msg_fatal("seek file %s: %m", VSTREAM_PATH(message->fp));
    vstring_free(buf);

    /*
     * Sanity checks. Verify that all required information was found,
     * including the queue file end marker.
     */
    if (message->data_offset == 0 || rec_type != REC_TYPE_END)
	msg_fatal("%s: envelope records out of order", message->queue_id);
}
예제 #13
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);
}
예제 #14
0
static void postmap(char *map_type, char *path_name, int postmap_flags,
		            int open_flags, int dict_flags)
{
    VSTREAM *NOCLOBBER source_fp;
    VSTRING *line_buffer;
    MKMAP  *mkmap;
    int     lineno;
    int     last_line;
    char   *key;
    char   *value;
    struct stat st;
    mode_t  saved_mask;

    /*
     * Initialize.
     */
    line_buffer = vstring_alloc(100);
    if ((open_flags & O_TRUNC) == 0) {
	/* Incremental mode. */
	source_fp = VSTREAM_IN;
	vstream_control(source_fp, CA_VSTREAM_CTL_PATH("stdin"), CA_VSTREAM_CTL_END);
    } else {
	/* Create database. */
	if (strcmp(map_type, DICT_TYPE_PROXY) == 0)
	    msg_fatal("can't create maps via the proxy service");
	dict_flags |= DICT_FLAG_BULK_UPDATE;
	if ((source_fp = vstream_fopen(path_name, O_RDONLY, 0)) == 0)
	    msg_fatal("open %s: %m", path_name);
    }
    if (fstat(vstream_fileno(source_fp), &st) < 0)
	msg_fatal("fstat %s: %m", path_name);

    /*
     * Turn off group/other read permissions as indicated in the source file.
     */
    if ((postmap_flags & POSTMAP_FLAG_SAVE_PERM) && S_ISREG(st.st_mode))
	saved_mask = umask(022 | (~st.st_mode & 077));

    /*
     * If running as root, run as the owner of the source file, so that the
     * result shows proper ownership, and so that a bug in postmap does not
     * allow privilege escalation.
     */
    if ((postmap_flags & POSTMAP_FLAG_AS_OWNER) && getuid() == 0
	&& (st.st_uid != geteuid() || st.st_gid != getegid()))
	set_eugid(st.st_uid, st.st_gid);

    /*
     * Open the database, optionally create it when it does not exist,
     * optionally truncate it when it does exist, and lock out any
     * spectators.
     */
    mkmap = mkmap_open(map_type, path_name, open_flags, dict_flags);

    /*
     * And restore the umask, in case it matters.
     */
    if ((postmap_flags & POSTMAP_FLAG_SAVE_PERM) && S_ISREG(st.st_mode))
	umask(saved_mask);

    /*
     * Trap "exceptions" so that we can restart a bulk-mode update after a
     * recoverable error.
     */
    for (;;) {
	if (dict_isjmp(mkmap->dict) != 0
	    && dict_setjmp(mkmap->dict) != 0
	    && vstream_fseek(source_fp, SEEK_SET, 0) < 0)
	    msg_fatal("seek %s: %m", VSTREAM_PATH(source_fp));

	/*
	 * Add records to the database.
	 */
	last_line = 0;
	while (readllines(line_buffer, source_fp, &last_line, &lineno)) {

	    /*
	     * First some UTF-8 checks sans casefolding.
	     */
	    if ((mkmap->dict->flags & DICT_FLAG_UTF8_ACTIVE)
		&& !allascii(STR(line_buffer))
		&& !valid_utf8_string(STR(line_buffer), LEN(line_buffer))) {
		msg_warn("%s, line %d: non-UTF-8 input \"%s\""
			 " -- ignoring this line",
			 VSTREAM_PATH(source_fp), lineno, STR(line_buffer));
		continue;
	    }

	    /*
	     * Split on the first whitespace character, then trim leading and
	     * trailing whitespace from key and value.
	     */
	    key = STR(line_buffer);
	    value = key + strcspn(key, CHARS_SPACE);
	    if (*value)
		*value++ = 0;
	    while (ISSPACE(*value))
		value++;
	    trimblanks(key, 0)[0] = 0;
	    trimblanks(value, 0)[0] = 0;

	    /*
	     * Enforce the "key whitespace value" format. Disallow missing
	     * keys or missing values.
	     */
	    if (*key == 0 || *value == 0) {
		msg_warn("%s, line %d: expected format: key whitespace value",
			 VSTREAM_PATH(source_fp), lineno);
		continue;
	    }
	    if (key[strlen(key) - 1] == ':')
		msg_warn("%s, line %d: record is in \"key: value\" format; is this an alias file?",
			 VSTREAM_PATH(source_fp), lineno);

	    /*
	     * Store the value under a case-insensitive key.
	     */
	    mkmap_append(mkmap, key, value);
	    if (mkmap->dict->error)
		msg_fatal("table %s:%s: write error: %m",
			  mkmap->dict->type, mkmap->dict->name);
	}
	break;
    }

    /*
     * Close the mapping database, and release the lock.
     */
    mkmap_close(mkmap);

    /*
     * Cleanup. We're about to terminate, but it is a good sanity check.
     */
    vstring_free(line_buffer);
    if (source_fp != VSTREAM_IN)
	vstream_fclose(source_fp);
}
예제 #15
0
static int smtpd_proxy_replay_send(SMTPD_STATE *state)
{
    const char *myname = "smtpd_proxy_replay_send";
    static VSTRING *replay_buf = 0;
    SMTPD_PROXY *proxy = state->proxy;
    int     rec_type;
    int     expect = SMTPD_PROX_WANT_BAD;

    /*
     * Sanity check.
     */
    if (smtpd_proxy_replay_stream == 0)
	msg_panic("%s: no before-queue filter speed-adjust log", myname);

    /*
     * Errors first.
     */
    if (vstream_ferror(smtpd_proxy_replay_stream)
	|| vstream_feof(smtpd_proxy_replay_stream)
	|| rec_put(smtpd_proxy_replay_stream, REC_TYPE_END, "", 0) != REC_TYPE_END
	|| vstream_fflush(smtpd_proxy_replay_stream))
	/* NOT: fsync(vstream_fileno(smtpd_proxy_replay_stream)) */
	return (smtpd_proxy_replay_rdwr_error(state));

    /*
     * Delayed connection to the before-queue filter.
     */
    if (smtpd_proxy_connect(state) < 0)
	return (-1);

    /*
     * Replay the speed-match log. We do sanity check record content, but we
     * don't implement a protocol state engine here, since we are reading
     * from a file that we just wrote ourselves.
     */
    if (replay_buf == 0)
	replay_buf = vstring_alloc(100);
    if (vstream_fseek(smtpd_proxy_replay_stream, (off_t) 0, SEEK_SET) < 0)
	return (smtpd_proxy_replay_rdwr_error(state));

    for (;;) {
	switch (rec_type = rec_get(smtpd_proxy_replay_stream, replay_buf,
				   REC_FLAG_NONE)) {

	    /*
	     * Message content.
	     */
	case REC_TYPE_NORM:
	case REC_TYPE_CONT:
	    if (smtpd_proxy_rec_put(proxy->service_stream, rec_type,
				    STR(replay_buf), LEN(replay_buf)) < 0)
		return (-1);
	    break;

	    /*
	     * Expected server reply type.
	     */
	case REC_TYPE_RCPT:
	    if (!alldig(STR(replay_buf))
		|| (expect = atoi(STR(replay_buf))) == SMTPD_PROX_WANT_BAD)
		msg_panic("%s: malformed server reply type: %s",
			  myname, STR(replay_buf));
	    break;

	    /*
	     * Client command, or void. Bail out on the first negative proxy
	     * response. This is OK, because the filter must use the same
	     * reply code for all recipients of a multi-recipient message.
	     */
	case REC_TYPE_FROM:
	    if (expect == SMTPD_PROX_WANT_BAD)
		msg_panic("%s: missing server reply type", myname);
	    if (smtpd_proxy_cmd(state, expect, *STR(replay_buf) ? "%s" :
				SMTPD_PROXY_CONN_FMT, STR(replay_buf)) < 0)
		return (-1);
	    expect = SMTPD_PROX_WANT_BAD;
	    break;

	    /*
	     * Explicit end marker, instead of implicit EOF.
	     */
	case REC_TYPE_END:
	    return (0);

	    /*
	     * Errors.
	     */
	case REC_TYPE_ERROR:
	    return (smtpd_proxy_replay_rdwr_error(state));
	default:
	    msg_panic("%s: unexpected record type; %d", myname, rec_type);
	}
    }
}
예제 #16
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);
}
예제 #17
0
int     deliver_maildir(LOCAL_STATE state, USER_ATTR usr_attr)
{
    const char *myname = "deliver_maildir";
    char   *newdir;
    char   *tmpdir;
    char   *curdir;
    char   *tmpfile;
    char   *newfile;
    DSN_BUF *why = state.msg_attr.why;
    VSTRING *buf;
    VSTREAM *dst;
    int     mail_copy_status;
    int     deliver_status;
    int     copy_flags;
    struct stat st;
    struct timeval starttime;

    GETTIMEOFDAY(&starttime);

    /*
     * 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 maildir");
	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));
    state.msg_attr.delivered = state.msg_attr.rcpt.address;
    mail_copy_status = MAIL_COPY_STAT_WRITE;
    buf = vstring_alloc(100);

    copy_flags = MAIL_COPY_TOFILE | MAIL_COPY_RETURN_PATH
	| MAIL_COPY_DELIVERED | MAIL_COPY_ORIG_RCPT;

    newdir = concatenate(usr_attr.mailbox, "new/", (char *) 0);
    tmpdir = concatenate(usr_attr.mailbox, "tmp/", (char *) 0);
    curdir = concatenate(usr_attr.mailbox, "cur/", (char *) 0);

    /*
     * Create and write the file as the recipient, so that file quota work.
     * Create any missing directories on the fly. The file name is chosen
     * according to ftp://koobera.math.uic.edu/www/proto/maildir.html:
     * 
     * "A unique name has three pieces, separated by dots. On the left is the
     * result of time(). On the right is the result of gethostname(). In the
     * middle is something that doesn't repeat within one second on a single
     * host. I fork a new process for each delivery, so I just use the
     * process ID. If you're delivering several messages from one process,
     * use starttime.pid_count.host, where starttime is the time that your
     * process started, and count is the number of messages you've
     * delivered."
     * 
     * Well, that stopped working on fast machines, and on operating systems
     * that randomize process ID values. When creating a file in tmp/ we use
     * the process ID because it still is an exclusive resource. When moving
     * the file to new/ we use the device number and inode number. I do not
     * care if this breaks on a remote AFS file system, because people should
     * know better.
     * 
     * On January 26, 2003, http://cr.yp.to/proto/maildir.html said:
     * 
     * A unique name has three pieces, separated by dots. On the left is the
     * result of time() or the second counter from gettimeofday(). On the
     * right is the result of gethostname(). (To deal with invalid host
     * names, replace / with \057 and : with \072.) In the middle is a
     * delivery identifier, discussed below.
     * 
     * [...]
     * 
     * Modern delivery identifiers are created by concatenating enough of the
     * following strings to guarantee uniqueness:
     * 
     * [...]
     * 
     * In, where n is (in hexadecimal) the UNIX inode number of this file.
     * Unfortunately, inode numbers aren't always available through NFS.
     * 
     * Vn, where n is (in hexadecimal) the UNIX device number of this file.
     * Unfortunately, device numbers aren't always available through NFS.
     * (Device numbers are also not helpful with the standard UNIX
     * filesystem: a maildir has to be within a single UNIX device for link()
     * and rename() to work.)
     * 
     * Mn, where n is (in decimal) the microsecond counter from the same
     * gettimeofday() used for the left part of the unique name.
     * 
     * Pn, where n is (in decimal) the process ID.
     * 
     * [...]
     */
    set_eugid(usr_attr.uid, usr_attr.gid);
    vstring_sprintf(buf, "%lu.P%d.%s",
		 (unsigned long) starttime.tv_sec, var_pid, get_hostname());
    tmpfile = concatenate(tmpdir, STR(buf), (char *) 0);
    newfile = 0;
    if ((dst = vstream_fopen(tmpfile, O_WRONLY | O_CREAT | O_EXCL, 0600)) == 0
	&& (errno != ENOENT
	    || make_dirs(tmpdir, 0700) < 0
	    || (dst = vstream_fopen(tmpfile, O_WRONLY | O_CREAT | O_EXCL, 0600)) == 0)) {
	dsb_simple(why, mbox_dsn(errno, "4.2.0"),
		   "create maildir file %s: %m", tmpfile);
    } else if (fstat(vstream_fileno(dst), &st) < 0) {

	/*
	 * Coverity 200604: file descriptor leak in code that never executes.
	 * Code replaced by msg_fatal(), as it is not worthwhile to continue
	 * after an impossible error condition.
	 */
	msg_fatal("fstat %s: %m", tmpfile);
    } else {
	vstring_sprintf(buf, "%lu.V%lxI%lxM%lu.%s",
			(unsigned long) starttime.tv_sec,
			(unsigned long) st.st_dev,
			(unsigned long) st.st_ino,
			(unsigned long) starttime.tv_usec,
			get_hostname());
	newfile = concatenate(newdir, STR(buf), (char *) 0);
	if ((mail_copy_status = mail_copy(COPY_ATTR(state.msg_attr),
					  dst, copy_flags, "\n",
					  why)) == 0) {
	    if (sane_link(tmpfile, newfile) < 0
		&& (errno != ENOENT
		    || (make_dirs(curdir, 0700), make_dirs(newdir, 0700)) < 0
		    || sane_link(tmpfile, newfile) < 0)) {
		dsb_simple(why, mbox_dsn(errno, "4.2.0"),
			   "create maildir file %s: %m", newfile);
		mail_copy_status = MAIL_COPY_STAT_WRITE;
	    }
	}
	if (unlink(tmpfile) < 0)
	    msg_warn("remove %s: %m", tmpfile);
    }
    set_eugid(var_owner_uid, var_owner_gid);

    /*
     * The maildir location is controlled by the mail administrator. If
     * delivery fails, try again later. We would just bounce when the maildir
     * location possibly under user control.
     */
    if (mail_copy_status & MAIL_COPY_STAT_CORRUPT) {
	deliver_status = DEL_STAT_DEFER;
    } else if (mail_copy_status != 0) {
	if (errno == EACCES) {
	    msg_warn("maildir access problem for UID/GID=%lu/%lu: %s",
		     (long) usr_attr.uid, (long) usr_attr.gid,
		     STR(why->reason));
	    msg_warn("perhaps you need to create the maildirs in advance");
	}
	vstring_sprintf_prepend(why->reason, "maildir delivery failed: ");
	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 maildir");
	deliver_status = sent(BOUNCE_FLAGS(state.request),
			      SENT_ATTR(state.msg_attr));
    }
    vstring_free(buf);
    myfree(newdir);
    myfree(tmpdir);
    myfree(curdir);
    myfree(tmpfile);
    if (newfile)
	myfree(newfile);
    return (deliver_status);
}
예제 #18
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));
}
예제 #19
0
static int deliver_message(DELIVER_REQUEST *request, char *service, char **argv)
{
    const char *myname = "deliver_message";
    static PIPE_PARAMS conf;
    static PIPE_ATTR attr;
    RECIPIENT_LIST *rcpt_list = &request->rcpt_list;
    DSN_BUF *why = dsb_create();
    VSTRING *buf;
    ARGV   *expanded_argv = 0;
    int     deliver_status;
    int     command_status;
    ARGV   *export_env;
    const char *sender;

#define DELIVER_MSG_CLEANUP() { \
	dsb_free(why); \
	if (expanded_argv) argv_free(expanded_argv); \
    }

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

    /*
     * Sanity checks. The get_service_params() and get_service_attr()
     * routines also do some sanity checks. Look up service attributes and
     * config information only once. This is safe since the information comes
     * from a trusted source, not from the delivery request.
     */
    if (request->nexthop[0] == 0)
	msg_fatal("empty nexthop hostname");
    if (rcpt_list->len <= 0)
	msg_fatal("recipient count: %d", rcpt_list->len);
    if (attr.command == 0) {
	get_service_params(&conf, service);
	get_service_attr(&attr, argv);
    }

    /*
     * The D flag cannot be specified for multi-recipient deliveries.
     */
    if ((attr.flags & MAIL_COPY_DELIVERED) && (rcpt_list->len > 1)) {
	dsb_simple(why, "4.3.5", "mail system configuration error");
	deliver_status = eval_command_status(PIPE_STAT_DEFER, service,
					     request, &attr, why);
	msg_warn("pipe flag `D' requires %s_destination_recipient_limit = 1",
		 service);
	DELIVER_MSG_CLEANUP();
	return (deliver_status);
    }

    /*
     * The O flag cannot be specified for multi-recipient deliveries.
     */
    if ((attr.flags & MAIL_COPY_ORIG_RCPT) && (rcpt_list->len > 1)) {
	dsb_simple(why, "4.3.5", "mail system configuration error");
	deliver_status = eval_command_status(PIPE_STAT_DEFER, service,
					     request, &attr, why);
	msg_warn("pipe flag `O' requires %s_destination_recipient_limit = 1",
		 service);
	DELIVER_MSG_CLEANUP();
	return (deliver_status);
    }

    /*
     * Check that this agent accepts messages this large.
     */
    if (attr.size_limit != 0 && request->data_size > attr.size_limit) {
	if (msg_verbose)
	    msg_info("%s: too big: size_limit = %ld, request->data_size = %ld",
		     myname, (long) attr.size_limit, request->data_size);
	dsb_simple(why, "5.2.3", "message too large");
	deliver_status = eval_command_status(PIPE_STAT_BOUNCE, service,
					     request, &attr, why);
	DELIVER_MSG_CLEANUP();
	return (deliver_status);
    }

    /*
     * Don't deliver a trace-only request.
     */
    if (DEL_REQ_TRACE_ONLY(request->flags)) {
	RECIPIENT *rcpt;
	int     status;
	int     n;

	deliver_status = 0;
	dsb_simple(why, "2.0.0", "delivers to command: %s", attr.command[0]);
	(void) DSN_FROM_DSN_BUF(why);
	for (n = 0; n < request->rcpt_list.len; n++) {
	    rcpt = request->rcpt_list.info + n;
	    status = sent(DEL_REQ_TRACE_FLAGS(request->flags),
			  request->queue_id, &request->msg_stats,
			  rcpt, service, &why->dsn);
	    if (status == 0 && (request->flags & DEL_REQ_FLAG_SUCCESS))
		deliver_completed(request->fp, rcpt->offset);
	    deliver_status |= status;
	}
	DELIVER_MSG_CLEANUP();
	return (deliver_status);
    }

    /*
     * Report mail delivery loops. By definition, this requires
     * single-recipient delivery. Don't silently lose recipients.
     */
    if (attr.flags & MAIL_COPY_DELIVERED) {
	DELIVERED_HDR_INFO *info;
	RECIPIENT *rcpt;
	int     loop_found;

	if (request->rcpt_list.len > 1)
	    msg_panic("%s: delivered-to enabled with multi-recipient request",
		      myname);
	info = delivered_hdr_init(request->fp, request->data_offset,
				  FOLD_ADDR_ALL);
	rcpt = request->rcpt_list.info;
	loop_found = delivered_hdr_find(info, rcpt->address);
	delivered_hdr_free(info);
	if (loop_found) {
	    dsb_simple(why, "5.4.6", "mail forwarding loop for %s",
		       rcpt->address);
	    deliver_status = eval_command_status(PIPE_STAT_BOUNCE, service,
						 request, &attr, why);
	    DELIVER_MSG_CLEANUP();
	    return (deliver_status);
	}
    }

    /*
     * Deliver. Set the nexthop and sender variables, and expand the command
     * argument vector. Recipients will be expanded on the fly. XXX Rewrite
     * envelope and header addresses according to transport-specific
     * rewriting rules.
     */
    if (vstream_fseek(request->fp, request->data_offset, SEEK_SET) < 0)
	msg_fatal("seek queue file %s: %m", VSTREAM_PATH(request->fp));

    /*
     * A non-empty null sender replacement is subject to the 'q' flag.
     */
    buf = vstring_alloc(10);
    sender = *request->sender ? request->sender : STR(attr.null_sender);
    if (*sender && (attr.flags & PIPE_OPT_QUOTE_LOCAL)) {
	quote_822_local(buf, sender);
	dict_update(PIPE_DICT_TABLE, PIPE_DICT_SENDER, STR(buf));
    } else
	dict_update(PIPE_DICT_TABLE, PIPE_DICT_SENDER, sender);
    if (attr.flags & PIPE_OPT_FOLD_HOST) {
	vstring_strcpy(buf, request->nexthop);
	lowercase(STR(buf));
	dict_update(PIPE_DICT_TABLE, PIPE_DICT_NEXTHOP, STR(buf));
    } else
	dict_update(PIPE_DICT_TABLE, PIPE_DICT_NEXTHOP, request->nexthop);
    vstring_sprintf(buf, "%ld", (long) request->data_size);
    dict_update(PIPE_DICT_TABLE, PIPE_DICT_SIZE, STR(buf));
    dict_update(PIPE_DICT_TABLE, PIPE_DICT_CLIENT_ADDR,
		request->client_addr);
    dict_update(PIPE_DICT_TABLE, PIPE_DICT_CLIENT_HELO,
		request->client_helo);
    dict_update(PIPE_DICT_TABLE, PIPE_DICT_CLIENT_NAME,
		request->client_name);
    dict_update(PIPE_DICT_TABLE, PIPE_DICT_CLIENT_PORT,
		request->client_port);
    dict_update(PIPE_DICT_TABLE, PIPE_DICT_CLIENT_PROTO,
		request->client_proto);
    dict_update(PIPE_DICT_TABLE, PIPE_DICT_SASL_METHOD,
		request->sasl_method);
    dict_update(PIPE_DICT_TABLE, PIPE_DICT_SASL_USERNAME,
		request->sasl_username);
    dict_update(PIPE_DICT_TABLE, PIPE_DICT_SASL_SENDER,
		request->sasl_sender);
    dict_update(PIPE_DICT_TABLE, PIPE_DICT_QUEUE_ID,
		request->queue_id);
    vstring_free(buf);

    if ((expanded_argv = expand_argv(service, attr.command,
				     rcpt_list, attr.flags)) == 0) {
	dsb_simple(why, "4.3.5", "mail system configuration error");
	deliver_status = eval_command_status(PIPE_STAT_DEFER, service,
					     request, &attr, why);
	DELIVER_MSG_CLEANUP();
	return (deliver_status);
    }
    export_env = argv_split(var_export_environ, ", \t\r\n");

    command_status = pipe_command(request->fp, why,
				  PIPE_CMD_UID, attr.uid,
				  PIPE_CMD_GID, attr.gid,
				  PIPE_CMD_SENDER, sender,
				  PIPE_CMD_COPY_FLAGS, attr.flags,
				  PIPE_CMD_ARGV, expanded_argv->argv,
				  PIPE_CMD_TIME_LIMIT, conf.time_limit,
				  PIPE_CMD_EOL, STR(attr.eol),
				  PIPE_CMD_EXPORT, export_env->argv,
				  PIPE_CMD_CWD, attr.exec_dir,
				  PIPE_CMD_CHROOT, attr.chroot_dir,
			   PIPE_CMD_ORIG_RCPT, rcpt_list->info[0].orig_addr,
			     PIPE_CMD_DELIVERED, rcpt_list->info[0].address,
				  PIPE_CMD_END);
    argv_free(export_env);

    deliver_status = eval_command_status(command_status, service, request,
					 &attr, why);

    /*
     * Clean up.
     */
    DELIVER_MSG_CLEANUP();

    return (deliver_status);
}
예제 #20
0
int     cleanup_bounce(CLEANUP_STATE *state)
{
    const char *myname = "cleanup_bounce";
    VSTRING *buf = vstring_alloc(100);
    const CLEANUP_STAT_DETAIL *detail;
    DSN_SPLIT dp;
    const char *dsn_status;
    const char *dsn_text;
    char   *rcpt = 0;
    RECIPIENT recipient;
    DSN     dsn;
    char   *attr_name;
    char   *attr_value;
    char   *dsn_orcpt = 0;
    int     dsn_notify = 0;
    char   *orig_rcpt = 0;
    char   *start;
    int     rec_type;
    int     junk;
    long    curr_offset;
    const char *encoding;
    const char *dsn_envid;
    int     dsn_ret;
    int     bounce_err;

    /*
     * Parse the failure reason if one was given, otherwise use a generic
     * mapping from cleanup-internal error code to (DSN + text).
     */
    if (state->reason) {
	dsn_split(&dp, "5.0.0", state->reason);
	dsn_status = DSN_STATUS(dp.dsn);
	dsn_text = dp.text;
    } else {
	detail = cleanup_stat_detail(state->errs);
	dsn_status = detail->dsn;
	dsn_text = detail->text;
    }

    /*
     * Create a bounce logfile with one entry for each final recipient.
     * Degrade gracefully in case of no recipients or no queue file.
     * 
     * Victor Duchovni observes that the number of recipients in the queue file
     * can potentially be very large due to virtual alias expansion. This can
     * expand the recipient count by virtual_alias_expansion_limit (default:
     * 1000) times.
     * 
     * After a queue file write error, purge any unwritten data (so that
     * vstream_fseek() won't fail while trying to flush it) and reset the
     * stream error flags to avoid false alarms.
     */
    if (vstream_ferror(state->dst) || vstream_fflush(state->dst)) {
	(void) vstream_fpurge(state->dst, VSTREAM_PURGE_BOTH);
	vstream_clearerr(state->dst);
    }
    if (vstream_fseek(state->dst, 0L, SEEK_SET) < 0)
	msg_fatal("%s: seek %s: %m", myname, cleanup_path);

    while ((state->errs & CLEANUP_STAT_WRITE) == 0) {
	if ((curr_offset = vstream_ftell(state->dst)) < 0)
	    msg_fatal("%s: vstream_ftell %s: %m", myname, cleanup_path);
	if ((rec_type = rec_get(state->dst, buf, 0)) <= 0
	    || rec_type == REC_TYPE_END)
	    break;
	start = STR(buf);
	if (rec_type == REC_TYPE_ATTR) {
	    if (split_nameval(STR(buf), &attr_name, &attr_value) != 0
		|| *attr_value == 0)
		continue;
	    /* Map attribute names to pseudo record type. */
	    if ((junk = rec_attr_map(attr_name)) != 0) {
		start = attr_value;
		rec_type = junk;
	    }
	}
	switch (rec_type) {
	case REC_TYPE_DSN_ORCPT:		/* RCPT TO ORCPT parameter */
	    if (dsn_orcpt != 0)			/* can't happen */
		myfree(dsn_orcpt);
	    dsn_orcpt = mystrdup(start);
	    break;
	case REC_TYPE_DSN_NOTIFY:		/* RCPT TO NOTIFY parameter */
	    if (alldig(start) && (junk = atoi(start)) > 0
		&& DSN_NOTIFY_OK(junk))
		dsn_notify = junk;
	    else
		dsn_notify = 0;
	    break;
	case REC_TYPE_ORCP:			/* unmodified RCPT TO address */
	    if (orig_rcpt != 0)			/* can't happen */
		myfree(orig_rcpt);
	    orig_rcpt = mystrdup(start);
	    break;
	case REC_TYPE_RCPT:			/* rewritten RCPT TO address */
	    rcpt = start;
	    RECIPIENT_ASSIGN(&recipient, curr_offset,
			     dsn_orcpt ? dsn_orcpt : "", dsn_notify,
			     orig_rcpt ? orig_rcpt : rcpt, rcpt);
	    (void) DSN_SIMPLE(&dsn, dsn_status, dsn_text);
	    cleanup_bounce_append(state, &recipient, &dsn);
	    /* FALLTHROUGH */
	case REC_TYPE_DRCP:			/* canceled recipient */
	case REC_TYPE_DONE:			/* can't happen */
	    if (orig_rcpt != 0) {
		myfree(orig_rcpt);
		orig_rcpt = 0;
	    }
	    if (dsn_orcpt != 0) {
		myfree(dsn_orcpt);
		dsn_orcpt = 0;
	    }
	    dsn_notify = 0;
	    break;
	}
    }
    if (orig_rcpt != 0)				/* can't happen */
	myfree(orig_rcpt);
    if (dsn_orcpt != 0)				/* can't happen */
	myfree(dsn_orcpt);

    /*
     * No recipients. Yes, this can happen.
     */
    if ((state->errs & CLEANUP_STAT_WRITE) == 0 && rcpt == 0) {
	RECIPIENT_ASSIGN(&recipient, 0, "", 0, "", "unknown");
	(void) DSN_SIMPLE(&dsn, dsn_status, dsn_text);
	cleanup_bounce_append(state, &recipient, &dsn);
    }
    vstring_free(buf);

    /*
     * Flush the bounce logfile to the sender. See also qmgr_active.c.
     */
    if ((state->errs & CLEANUP_STAT_WRITE) == 0) {
	if ((encoding = nvtable_find(state->attr, MAIL_ATTR_ENCODING)) == 0)
	    encoding = MAIL_ATTR_ENC_NONE;
	dsn_envid = state->dsn_envid ?
	    state->dsn_envid : "";
	/* Do not send unfiltered (body) content. */
	dsn_ret = (state->errs & (CLEANUP_STAT_CONT | CLEANUP_STAT_SIZE)) ?
	    DSN_RET_HDRS : state->dsn_ret;

	if (state->verp_delims == 0 || var_verp_bounce_off) {
	    bounce_err =
		bounce_flush(BOUNCE_FLAG_CLEAN,
			     state->queue_name, state->queue_id,
			     encoding, state->sender, dsn_envid,
			     dsn_ret);
	} else {
	    bounce_err =
		bounce_flush_verp(BOUNCE_FLAG_CLEAN,
				  state->queue_name, state->queue_id,
				  encoding, state->sender, dsn_envid,
				  dsn_ret, state->verp_delims);
	}
	if (bounce_err != 0) {
	    msg_warn("%s: bounce message failure", state->queue_id);
	    state->errs |= CLEANUP_STAT_WRITE;
	}
    }

    /*
     * Schedule this message (and trace logfile) for deletion when all is
     * well. When all is not well these files would be deleted too, but the
     * client would get a different completion status so we have to carefully
     * maintain the bits anyway.
     */
    if ((state->errs &= CLEANUP_STAT_WRITE) == 0)
	state->flags |= CLEANUP_FLAG_DISCARD;

    return (state->errs);
}
예제 #21
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);
    }
}
예제 #22
0
static int smtpd_proxy_replay_send(SMTPD_STATE *state)
{
    const char *myname = "smtpd_proxy_replay_send";
    static VSTRING *replay_buf = 0;
    SMTPD_PROXY *proxy = state->proxy;
    int     rec_type;
    int     expect = SMTPD_PROX_WANT_BAD;

    /*
     * Sanity check.
     */
    if (smtpd_proxy_replay_stream == 0)
	msg_panic("%s: no before-queue filter speed-adjust log", myname);

    /*
     * Errors first.
     */
    if (vstream_ferror(smtpd_proxy_replay_stream)
	|| vstream_feof(smtpd_proxy_replay_stream)
	|| rec_put(smtpd_proxy_replay_stream, REC_TYPE_END, "", 0) != REC_TYPE_END
	|| vstream_fflush(smtpd_proxy_replay_stream))
	/* NOT: fsync(vstream_fileno(smtpd_proxy_replay_stream)) */
	return (smtpd_proxy_replay_rdwr_error(state));

    /*
     * Delayed connection to the before-queue filter.
     */
    if (smtpd_proxy_connect(state) < 0)
	return (-1);

    /*
     * Replay the speed-match log. We do sanity check record content, but we
     * don't implement a protocol state engine here, since we are reading
     * from a file that we just wrote ourselves.
     * 
     * This is different than the MailChannels patented solution that
     * multiplexes a large number of slowed-down inbound connections over a
     * small number of fast connections to a local MTA.
     * 
     * - MailChannels receives mail directly from the Internet. It uses one
     * connection to the local MTA to reject invalid recipients before
     * receiving the entire email message at reduced bit rates, and then uses
     * a different connection to quickly deliver the message to the local
     * MTA.
     * 
     * - Postfix receives mail directly from the Internet. The Postfix SMTP
     * server rejects invalid recipients before receiving the entire message
     * over the Internet, and then delivers the message quickly to a local
     * SMTP-based content filter.
     */
    if (replay_buf == 0)
	replay_buf = vstring_alloc(100);
    if (vstream_fseek(smtpd_proxy_replay_stream, (off_t) 0, SEEK_SET) < 0)
	return (smtpd_proxy_replay_rdwr_error(state));

    for (;;) {
	switch (rec_type = rec_get(smtpd_proxy_replay_stream, replay_buf,
				   REC_FLAG_NONE)) {

	    /*
	     * Message content.
	     */
	case REC_TYPE_NORM:
	case REC_TYPE_CONT:
	    if (smtpd_proxy_rec_put(proxy->service_stream, rec_type,
				    STR(replay_buf), LEN(replay_buf)) < 0)
		return (-1);
	    break;

	    /*
	     * Expected server reply type.
	     */
	case REC_TYPE_RCPT:
	    if (!alldig(STR(replay_buf))
		|| (expect = atoi(STR(replay_buf))) == SMTPD_PROX_WANT_BAD)
		msg_panic("%s: malformed server reply type: %s",
			  myname, STR(replay_buf));
	    break;

	    /*
	     * Client command, or void. Bail out on the first negative proxy
	     * response. This is OK, because the filter must use the same
	     * reply code for all recipients of a multi-recipient message.
	     */
	case REC_TYPE_FROM:
	    if (expect == SMTPD_PROX_WANT_BAD)
		msg_panic("%s: missing server reply type", myname);
	    if (smtpd_proxy_cmd(state, expect, *STR(replay_buf) ? "%s" :
				SMTPD_PROXY_CONN_FMT, STR(replay_buf)) < 0)
		return (-1);
	    expect = SMTPD_PROX_WANT_BAD;
	    break;

	    /*
	     * Explicit end marker, instead of implicit EOF.
	     */
	case REC_TYPE_END:
	    return (0);

	    /*
	     * Errors.
	     */
	case REC_TYPE_ERROR:
	    return (smtpd_proxy_replay_rdwr_error(state));
	default:
	    msg_panic("%s: unexpected record type; %d", myname, rec_type);
	}
    }
}
예제 #23
0
static void postalias(char *map_type, char *path_name, int postalias_flags,
		              int open_flags, int dict_flags)
{
    VSTREAM *NOCLOBBER source_fp;
    VSTRING *line_buffer;
    MKMAP  *mkmap;
    int     lineno;
    int     last_line;
    VSTRING *key_buffer;
    VSTRING *value_buffer;
    TOK822 *tok_list;
    TOK822 *key_list;
    TOK822 *colon;
    TOK822 *value_list;
    struct stat st;
    mode_t  saved_mask;

    /*
     * Initialize.
     */
    line_buffer = vstring_alloc(100);
    key_buffer = vstring_alloc(100);
    value_buffer = vstring_alloc(100);
    if ((open_flags & O_TRUNC) == 0) {
	/* Incremental mode. */
	source_fp = VSTREAM_IN;
	vstream_control(source_fp, CA_VSTREAM_CTL_PATH("stdin"), CA_VSTREAM_CTL_END);
    } else {
	/* Create database. */
	if (strcmp(map_type, DICT_TYPE_PROXY) == 0)
	    msg_fatal("can't create maps via the proxy service");
	dict_flags |= DICT_FLAG_BULK_UPDATE;
	if ((source_fp = vstream_fopen(path_name, O_RDONLY, 0)) == 0)
	    msg_fatal("open %s: %m", path_name);
    }
    if (fstat(vstream_fileno(source_fp), &st) < 0)
	msg_fatal("fstat %s: %m", path_name);

    /*
     * Turn off group/other read permissions as indicated in the source file.
     */
    if ((postalias_flags & POSTALIAS_FLAG_SAVE_PERM) && S_ISREG(st.st_mode))
	saved_mask = umask(022 | (~st.st_mode & 077));

    /*
     * If running as root, run as the owner of the source file, so that the
     * result shows proper ownership, and so that a bug in postalias does not
     * allow privilege escalation.
     */
    if ((postalias_flags & POSTALIAS_FLAG_AS_OWNER) && getuid() == 0
	&& (st.st_uid != geteuid() || st.st_gid != getegid()))
	set_eugid(st.st_uid, st.st_gid);

    /*
     * Open the database, create it when it does not exist, truncate it when
     * it does exist, and lock out any spectators.
     */
    mkmap = mkmap_open(map_type, path_name, open_flags, dict_flags);

    /*
     * And restore the umask, in case it matters.
     */
    if ((postalias_flags & POSTALIAS_FLAG_SAVE_PERM) && S_ISREG(st.st_mode))
	umask(saved_mask);

    /*
     * Trap "exceptions" so that we can restart a bulk-mode update after a
     * recoverable error.
     */
    for (;;) {
	if (dict_isjmp(mkmap->dict) != 0
	    && dict_setjmp(mkmap->dict) != 0
	    && vstream_fseek(source_fp, SEEK_SET, 0) < 0)
	    msg_fatal("seek %s: %m", VSTREAM_PATH(source_fp));

	/*
	 * Add records to the database.
	 */
	last_line = 0;
	while (readllines(line_buffer, source_fp, &last_line, &lineno)) {

	    /*
	     * First some UTF-8 checks sans casefolding.
	     */
	    if ((mkmap->dict->flags & DICT_FLAG_UTF8_ACTIVE)
		&& !allascii(STR(line_buffer))
		&& !valid_utf8_string(STR(line_buffer), LEN(line_buffer))) {
		msg_warn("%s, line %d: non-UTF-8 input \"%s\""
			 " -- ignoring this line",
			 VSTREAM_PATH(source_fp), lineno, STR(line_buffer));
		continue;
	    }

	    /*
	     * Tokenize the input, so that we do the right thing when a
	     * quoted localpart contains special characters such as "@", ":"
	     * and so on.
	     */
	    if ((tok_list = tok822_scan(STR(line_buffer), (TOK822 **) 0)) == 0)
		continue;

	    /*
	     * Enforce the key:value format. Disallow missing keys,
	     * multi-address keys, or missing values. In order to specify an
	     * empty string or value, enclose it in double quotes.
	     */
	    if ((colon = tok822_find_type(tok_list, ':')) == 0
		|| colon->prev == 0 || colon->next == 0
		|| tok822_rfind_type(colon, ',')) {
		msg_warn("%s, line %d: need name:value pair",
			 VSTREAM_PATH(source_fp), lineno);
		tok822_free_tree(tok_list);
		continue;
	    }

	    /*
	     * Key must be local. XXX We should use the Postfix rewriting and
	     * resolving services to handle all address forms correctly.
	     * However, we can't count on the mail system being up when the
	     * alias database is being built, so we're guessing a bit.
	     */
	    if (tok822_rfind_type(colon, '@') || tok822_rfind_type(colon, '%')) {
		msg_warn("%s, line %d: name must be local",
			 VSTREAM_PATH(source_fp), lineno);
		tok822_free_tree(tok_list);
		continue;
	    }

	    /*
	     * Split the input into key and value parts, and convert from
	     * token representation back to string representation. Convert
	     * the key to internal (unquoted) form, because the resolver
	     * produces addresses in internal form. Convert the value to
	     * external (quoted) form, because it will have to be re-parsed
	     * upon lookup. Discard the token representation when done.
	     */
	    key_list = tok_list;
	    tok_list = 0;
	    value_list = tok822_cut_after(colon);
	    tok822_unlink(colon);
	    tok822_free(colon);

	    tok822_internalize(key_buffer, key_list, TOK822_STR_DEFL);
	    tok822_free_tree(key_list);

	    tok822_externalize(value_buffer, value_list, TOK822_STR_DEFL);
	    tok822_free_tree(value_list);

	    /*
	     * Store the value under a case-insensitive key.
	     */
	    mkmap_append(mkmap, STR(key_buffer), STR(value_buffer));
	    if (mkmap->dict->error)
		msg_fatal("table %s:%s: write error: %m",
			  mkmap->dict->type, mkmap->dict->name);
	}
	break;
    }

    /*
     * Update or append sendmail and NIS signatures.
     */
    if ((open_flags & O_TRUNC) == 0)
	mkmap->dict->flags |= DICT_FLAG_DUP_REPLACE;

    /*
     * Sendmail compatibility: add the @:@ signature to indicate that the
     * database is complete. This might be needed by NIS clients running
     * sendmail.
     */
    mkmap_append(mkmap, "@", "@");
    if (mkmap->dict->error)
	msg_fatal("table %s:%s: write error: %m",
		  mkmap->dict->type, mkmap->dict->name);

    /*
     * NIS compatibility: add time and master info. Unlike other information,
     * this information MUST be written without a trailing null appended to
     * key or value.
     */
    mkmap->dict->flags &= ~DICT_FLAG_TRY1NULL;
    mkmap->dict->flags |= DICT_FLAG_TRY0NULL;
    vstring_sprintf(value_buffer, "%010ld", (long) time((time_t *) 0));
#if (defined(HAS_NIS) || defined(HAS_NISPLUS))
    mkmap->dict->flags &= ~DICT_FLAG_FOLD_FIX;
    mkmap_append(mkmap, "YP_LAST_MODIFIED", STR(value_buffer));
    mkmap_append(mkmap, "YP_MASTER_NAME", var_myhostname);
#endif

    /*
     * Close the alias database, and release the lock.
     */
    mkmap_close(mkmap);

    /*
     * Cleanup. We're about to terminate, but it is a good sanity check.
     */
    vstring_free(value_buffer);
    vstring_free(key_buffer);
    vstring_free(line_buffer);
    if (source_fp != VSTREAM_IN)
	vstream_fclose(source_fp);
}