Exemplo n.º 1
0
char   *fold_addr(VSTRING *result, const char *addr, int flags)
{
    char   *cp;

    /*
     * Fold the address as appropriate.
     */
    switch (flags & FOLD_ADDR_ALL) {
    case FOLD_ADDR_HOST:
	if ((cp = strrchr(addr, '@')) != 0) {
	    cp += 1;
	    vstring_strncpy(result, addr, cp - addr);
	    casefold_append(result, cp);
	    break;
	}
	/* FALLTHROUGH */
    case 0:
	vstring_strcpy(result, addr);
	break;
    case FOLD_ADDR_USER:
	if ((cp = strrchr(addr, '@')) != 0) {
	    casefold_len(result, addr, cp - addr);
	    vstring_strcat(result, cp);
	    break;
	}
	/* FALLTHROUGH */
    case FOLD_ADDR_USER | FOLD_ADDR_HOST:
	casefold(result, addr);
	break;
    }
    return (STR(result));
}
Exemplo n.º 2
0
VSTRING *unquote_822_local(VSTRING *dst, const char *mbox)
{
    const char *start;			/* first byte of localpart */
    const char *end;			/* first byte after localpart */
    const char *colon;
    const char *cp;

    if (mbox[0] == '@' && (colon = strchr(mbox, ':')) != 0) {
	start = colon + 1;
	vstring_strncpy(dst, mbox, start - mbox);
    } else {
	start = mbox;
	VSTRING_RESET(dst);
    }
    if ((end = strrchr(start, '@')) == 0)
	end = start + strlen(start);
    for (cp = start; cp < end; cp++) {
	if (*cp == '"')
	    continue;
	if (*cp == '\\') {
	    if (cp[1] == 0)
		continue;
	    cp++;
	}
	VSTRING_ADDCH(dst, *cp);
    }
    if (*end)
	vstring_strcat(dst, end);
    else
	VSTRING_TERMINATE(dst);
    return (dst);
}
Exemplo n.º 3
0
static const char *psc_get_footer(const char *text, ssize_t text_len)
{
    static VSTRING *footer_buf = 0;

    if (footer_buf == 0)
	footer_buf = vstring_alloc(100);
    /* Strip the \r\n for consistency with smtpd. */
    vstring_strncpy(footer_buf, text, text_len);
    return (psc_maps_find(psc_rej_ftr_maps, STR(footer_buf), 0));
}
Exemplo n.º 4
0
char   *sane_dirname(VSTRING *bp, const char *path)
{
    static VSTRING *buf;
    const char *last;

    /*
     * Your buffer or mine?
     */
    if (bp == 0) {
	bp = buf;
	if (bp == 0)
	    bp = buf = vstring_alloc(10);
    }

    /*
     * Special case: return "." for null or zero-length input.
     */
    if (path == 0 || *path == 0)
	return (STR(vstring_strcpy(bp, ".")));

    /*
     * Remove trailing '/' characters from input. Return "/" if input is all
     * '/' characters.
     */
    last = path + strlen(path) - 1;
    while (*last == '/') {
	if (last == path)
	    return (STR(vstring_strcpy(bp, "/")));
	last--;
    }

    /*
     * This pathname does not end in '/'. Skip to last '/' character if any.
     */
    while (last >= path && *last != '/')
	last--;
    if (last < path)				/* no '/' */
	return (STR(vstring_strcpy(bp, ".")));

    /*
     * Strip trailing '/' characters from dirname (not strictly needed).
     */
    while (last > path && *last == '/')
	last--;

    return (STR(vstring_strncpy(bp, path, last - path + 1)));
}
Exemplo n.º 5
0
char   *data_redirect_map(VSTRING *result, const char *map)
{
    const char *path;
    const char *map_type;
    size_t  map_type_len;

#define MAP_DELIMITER ":"

    /*
     * Sanity check.
     */
    if (map == STR(result))
	msg_panic("data_redirect_map: result clobbers input");

    /*
     * Parse the input into map type and map name.
     */
    path = strchr(map, MAP_DELIMITER[0]);
    if (path != 0) {
	map_type = map;
	map_type_len = path - map;
	path += 1;
    } else {
	map_type = var_db_type;
	map_type_len = strlen(map_type);
	path = map;
    }

    /*
     * Redirect the pathname.
     */
    vstring_strncpy(result, map_type, map_type_len);
    if (name_code(data_redirect_map_types, NAME_CODE_FLAG_NONE, STR(result))) {
	data_redirect_path(result, path, "table", map);
    } else {
	vstring_strcpy(result, path);
    }

    /*
     * (Re)combine the map type with the map name.
     */
    vstring_prepend(result, MAP_DELIMITER, sizeof(MAP_DELIMITER) - 1);
    vstring_prepend(result, map_type, map_type_len);
    return (STR(result));
}
Exemplo n.º 6
0
char   *sane_basename(VSTRING *bp, const char *path)
{
    static VSTRING *buf;
    const char *first;
    const char *last;

    /*
     * Your buffer or mine?
     */
    if (bp == 0) {
	bp = buf;
	if (bp == 0)
	    bp = buf = vstring_alloc(10);
    }

    /*
     * Special case: return "." for null or zero-length input.
     */
    if (path == 0 || *path == 0)
	return (STR(vstring_strcpy(bp, ".")));

    /*
     * Remove trailing '/' characters from input. Return "/" if input is all
     * '/' characters.
     */
    last = path + strlen(path) - 1;
    while (*last == '/') {
	if (last == path)
	    return (STR(vstring_strcpy(bp, "/")));
	last--;
    }

    /*
     * The pathname does not end in '/'. Skip to last '/' character if any.
     */
    first = last - 1;
    while (first >= path && *first != '/')
	first--;

    return (STR(vstring_strncpy(bp, first + 1, last - first)));
}
Exemplo n.º 7
0
static void qmgr_message_resolve(QMGR_MESSAGE *message)
{
    static ARGV *defer_xport_argv;
    RECIPIENT_LIST list = message->rcpt_list;
    RECIPIENT *recipient;
    QMGR_TRANSPORT *transport = 0;
    QMGR_QUEUE *queue = 0;
    RESOLVE_REPLY reply;
    VSTRING *queue_name;
    char   *at;
    char  **cpp;
    char   *nexthop;
    ssize_t len;
    int     status;
    DSN     dsn;
    MSG_STATS stats;
    DSN    *saved_dsn;

#define STREQ(x,y)	(strcmp(x,y) == 0)
#define STR		vstring_str
#define LEN		VSTRING_LEN

    resolve_clnt_init(&reply);
    queue_name = vstring_alloc(1);
    for (recipient = list.info; recipient < list.info + list.len; recipient++) {

	/*
	 * Redirect overrides all else. But only once (per entire message).
	 * For consistency with the remainder of Postfix, rewrite the address
	 * to canonical form before resolving it.
	 */
	if (message->redirect_addr) {
	    if (recipient > list.info) {
		recipient->u.queue = 0;
		continue;
	    }
	    message->rcpt_offset = 0;
	    message->rcpt_unread = 0;

	    rewrite_clnt_internal(REWRITE_CANON, message->redirect_addr,
				  reply.recipient);
	    RECIPIENT_UPDATE(recipient->address, STR(reply.recipient));
	    if (qmgr_resolve_one(message, recipient,
				 recipient->address, &reply) < 0)
		continue;
	    if (!STREQ(recipient->address, STR(reply.recipient)))
		RECIPIENT_UPDATE(recipient->address, STR(reply.recipient));
	}

	/*
	 * Content filtering overrides the address resolver.
	 * 
	 * XXX Bypass content_filter inspection for user-generated probes
	 * (sendmail -bv). MTA-generated probes never have the "please filter
	 * me" bits turned on, but we handle them here anyway for the sake of
	 * future proofing.
	 */
#define FILTER_WITHOUT_NEXTHOP(filter, next) \
	(((next) = split_at((filter), ':')) == 0 || *(next) == 0)

#define RCPT_WITHOUT_DOMAIN(rcpt, next) \
	((next = strrchr(rcpt, '@')) == 0 || *++(next) == 0)

	else if (message->filter_xport
		 && (message->tflags & DEL_REQ_TRACE_ONLY_MASK) == 0) {
	    reply.flags = 0;
	    vstring_strcpy(reply.transport, message->filter_xport);
	    if (FILTER_WITHOUT_NEXTHOP(STR(reply.transport), nexthop)
		&& *(nexthop = var_def_filter_nexthop) == 0
		&& RCPT_WITHOUT_DOMAIN(recipient->address, nexthop))
		nexthop = var_myhostname;
	    vstring_strcpy(reply.nexthop, nexthop);
	    vstring_strcpy(reply.recipient, recipient->address);
	}

	/*
	 * Resolve the destination to (transport, nexthop, address). The
	 * result address may differ from the one specified by the sender.
	 */
	else {
	    if (qmgr_resolve_one(message, recipient,
				 recipient->address, &reply) < 0)
		continue;
	    if (!STREQ(recipient->address, STR(reply.recipient)))
		RECIPIENT_UPDATE(recipient->address, STR(reply.recipient));
	}

	/*
	 * Bounce null recipients. This should never happen, but is most
	 * likely the result of a fault in a different program, so aborting
	 * the queue manager process does not help.
	 */
	if (recipient->address[0] == 0) {
	    QMGR_REDIRECT(&reply, MAIL_SERVICE_ERROR,
			  "5.1.3 null recipient address");
	}

	/*
	 * Discard mail to the local double bounce address here, so this
	 * system can run without a local delivery agent. They'd still have
	 * to configure something for mail directed to the local postmaster,
	 * though, but that is an RFC requirement anyway.
	 * 
	 * XXX This lookup should be done in the resolver, and the mail should
	 * be directed to a general-purpose null delivery agent.
	 */
	if (reply.flags & RESOLVE_CLASS_LOCAL) {
	    at = strrchr(STR(reply.recipient), '@');
	    len = (at ? (at - STR(reply.recipient))
		   : strlen(STR(reply.recipient)));
	    if (strncasecmp(STR(reply.recipient), var_double_bounce_sender,
			    len) == 0
		&& !var_double_bounce_sender[len]) {
		status = sent(message->tflags, message->queue_id,
			      QMGR_MSG_STATS(&stats, message), recipient,
			      "none", DSN_SIMPLE(&dsn, "2.0.0",
			"undeliverable postmaster notification discarded"));
		if (status == 0) {
		    deliver_completed(message->fp, recipient->offset);
#if 0
		    /* It's the default verification probe sender address. */
		    msg_warn("%s: undeliverable postmaster notification discarded",
			     message->queue_id);
#endif
		} else
		    message->flags |= status;
		continue;
	    }
	}

	/*
	 * Optionally defer deliveries over specific transports, unless the
	 * restriction is lifted temporarily.
	 */
	if (*var_defer_xports && (message->qflags & QMGR_FLUSH_DFXP) == 0) {
	    if (defer_xport_argv == 0)
		defer_xport_argv = argv_split(var_defer_xports, CHARS_COMMA_SP);
	    for (cpp = defer_xport_argv->argv; *cpp; cpp++)
		if (strcmp(*cpp, STR(reply.transport)) == 0)
		    break;
	    if (*cpp) {
		QMGR_REDIRECT(&reply, MAIL_SERVICE_RETRY,
			      "4.3.2 deferred transport");
	    }
	}

	/*
	 * Look up or instantiate the proper transport.
	 */
	if (transport == 0 || !STREQ(transport->name, STR(reply.transport))) {
	    if ((transport = qmgr_transport_find(STR(reply.transport))) == 0)
		transport = qmgr_transport_create(STR(reply.transport));
	    queue = 0;
	}

	/*
	 * This message is being flushed. If need-be unthrottle the
	 * transport.
	 */
	if ((message->qflags & QMGR_FLUSH_EACH) != 0
	    && QMGR_TRANSPORT_THROTTLED(transport))
	    qmgr_transport_unthrottle(transport);

	/*
	 * This transport is dead. Defer delivery to this recipient.
	 */
	if (QMGR_TRANSPORT_THROTTLED(transport)) {
	    saved_dsn = transport->dsn;
	    if ((transport = qmgr_error_transport(MAIL_SERVICE_RETRY)) != 0) {
		nexthop = qmgr_error_nexthop(saved_dsn);
		vstring_strcpy(reply.nexthop, nexthop);
		myfree(nexthop);
		queue = 0;
	    } else {
		qmgr_defer_recipient(message, recipient, saved_dsn);
		continue;
	    }
	}

	/*
	 * The nexthop destination provides the default name for the
	 * per-destination queue. When the delivery agent accepts only one
	 * recipient per delivery, give each recipient its own queue, so that
	 * deliveries to different recipients of the same message can happen
	 * in parallel, and so that we can enforce per-recipient concurrency
	 * limits and prevent one recipient from tying up all the delivery
	 * agent resources. We use recipient@nexthop as queue name rather
	 * than the actual recipient domain name, so that one recipient in
	 * multiple equivalent domains cannot evade the per-recipient
	 * concurrency limit. Split the address on the recipient delimiter if
	 * one is defined, so that extended addresses don't get extra
	 * delivery slots.
	 * 
	 * Fold the result to lower case so that we don't have multiple queues
	 * for the same name.
	 * 
	 * Important! All recipients in a queue must have the same nexthop
	 * value. It is OK to have multiple queues with the same nexthop
	 * value, but only when those queues are named after recipients.
	 * 
	 * The single-recipient code below was written for local(8) like
	 * delivery agents, and assumes that all domains that deliver to the
	 * same (transport + nexthop) are aliases for $nexthop. Delivery
	 * concurrency is changed from per-domain into per-recipient, by
	 * changing the queue name from nexthop into localpart@nexthop.
	 * 
	 * XXX This assumption is incorrect when different destinations share
	 * the same (transport + nexthop). In reality, such transports are
	 * rarely configured to use single-recipient deliveries. The fix is
	 * to decouple the per-destination recipient limit from the
	 * per-destination concurrency.
	 */
	vstring_strcpy(queue_name, STR(reply.nexthop));
	if (strcmp(transport->name, MAIL_SERVICE_ERROR) != 0
	    && strcmp(transport->name, MAIL_SERVICE_RETRY) != 0
	    && transport->recipient_limit == 1) {
	    /* Copy the recipient localpart. */
	    at = strrchr(STR(reply.recipient), '@');
	    len = (at ? (at - STR(reply.recipient))
		   : strlen(STR(reply.recipient)));
	    vstring_strncpy(queue_name, STR(reply.recipient), len);
	    /* Remove the address extension from the recipient localpart. */
	    if (*var_rcpt_delim && split_addr(STR(queue_name), var_rcpt_delim))
		vstring_truncate(queue_name, strlen(STR(queue_name)));
	    /* Assume the recipient domain is equivalent to nexthop. */
	    vstring_sprintf_append(queue_name, "@%s", STR(reply.nexthop));
	}
	lowercase(STR(queue_name));

	/*
	 * This transport is alive. Find or instantiate a queue for this
	 * recipient.
	 */
	if (queue == 0 || !STREQ(queue->name, STR(queue_name))) {
	    if ((queue = qmgr_queue_find(transport, STR(queue_name))) == 0)
		queue = qmgr_queue_create(transport, STR(queue_name),
					  STR(reply.nexthop));
	}

	/*
	 * This message is being flushed. If need-be unthrottle the queue.
	 */
	if ((message->qflags & QMGR_FLUSH_EACH) != 0
	    && QMGR_QUEUE_THROTTLED(queue))
	    qmgr_queue_unthrottle(queue);

	/*
	 * This queue is dead. Defer delivery to this recipient.
	 */
	if (QMGR_QUEUE_THROTTLED(queue)) {
	    saved_dsn = queue->dsn;
	    if ((queue = qmgr_error_queue(MAIL_SERVICE_RETRY, saved_dsn)) == 0) {
		qmgr_defer_recipient(message, recipient, saved_dsn);
		continue;
	    }
	}

	/*
	 * This queue is alive. Bind this recipient to this queue instance.
	 */
	recipient->u.queue = queue;
    }
    resolve_clnt_free(&reply);
    vstring_free(queue_name);
}
Exemplo n.º 8
0
static const char *smtpd_expand_addr(VSTRING *buf, const char *addr,
				           const char *name, int prefix_len)
{
    const char *p;
    const char *suffix;

    /*
     * Return NULL only for unknown names in expansion requests.
     */
    if (addr == 0)
	return ("");

    suffix = name + prefix_len;

    /*
     * MAIL_ATTR_SENDER or MAIL_ATTR_RECIP.
     */
    if (*suffix == 0) {
	if (*addr)
	    return (addr);
	else
	    return ("<>");
    }

    /*
     * "sender_name" or "recipient_name".
     */
#define STREQ(x,y) (*(x) == *(y) && strcmp((x), (y)) == 0)

    else if (STREQ(suffix, MAIL_ATTR_S_NAME)) {
	if (*addr) {
	    if ((p = strrchr(addr, '@')) != 0) {
		vstring_strncpy(buf, addr, p - addr);
		return (STR(buf));
	    } else {
		return (addr);
	    }
	} else
	    return ("<>");
    }

    /*
     * "sender_domain" or "recipient_domain".
     */
    else if (STREQ(suffix, MAIL_ATTR_S_DOMAIN)) {
	if (*addr) {
	    if ((p = strrchr(addr, '@')) != 0) {
		return (p + 1);
	    } else {
		return ("");
	    }
	} else
	    return ("");
    }

    /*
     * Unknown. Return NULL to indicate an "unknown name" error.
     */
    else {
	smtpd_expand_unknown(name);
	return (0);
    }
}
Exemplo n.º 9
0
ARGV   *mail_addr_map(MAPS *path, const char *address, int propagate)
{
    VSTRING *buffer = 0;
    const char *myname = "mail_addr_map";
    const char *string;
    char   *ratsign;
    char   *extension = 0;
    ARGV   *argv = 0;
    int     i;

    /*
     * Look up the full address; if no match is found, look up the address
     * with the extension stripped off, and remember the unmatched extension.
     */
    if ((string = mail_addr_find(path, address, &extension)) != 0) {

	/*
	 * Prepend the original user to @otherdomain, but do not propagate
	 * the unmatched address extension.
	 */
	if (*string == '@') {
	    buffer = vstring_alloc(100);
	    if ((ratsign = strrchr(address, '@')) != 0)
		vstring_strncpy(buffer, address, ratsign - address);
	    else
		vstring_strcpy(buffer, address);
	    if (extension)
		vstring_truncate(buffer, LEN(buffer) - strlen(extension));
	    vstring_strcat(buffer, string);
	    string = STR(buffer);
	}

	/*
	 * Canonicalize and externalize the result, and propagate the
	 * unmatched extension to each address found.
	 */
	argv = mail_addr_crunch(string, propagate ? extension : 0);
	if (buffer)
	    vstring_free(buffer);
	if (msg_verbose)
	    for (i = 0; i < argv->argc; i++)
		msg_info("%s: %s -> %d: %s", myname, address, i, argv->argv[i]);
	if (argv->argc == 0) {
	    msg_warn("%s lookup of %s returns non-address result \"%s\"",
		     path->title, address, string);
	    argv = argv_free(argv);
	    path->error = DICT_ERR_RETRY;
	}
    }

    /*
     * No match found.
     */
    else {
	if (msg_verbose)
	    msg_info("%s: %s -> %s", myname, address,
		     path->error ? "(try again)" : "(not found)");
    }

    /*
     * Cleanup.
     */
    if (extension)
	myfree(extension);

    return (argv);
}
Exemplo n.º 10
0
SMTP_RESP *smtp_chat_resp(SMTP_SESSION *session)
{
    static SMTP_RESP rdata;
    char   *cp;
    int     last_char;
    int     three_digs = 0;
    size_t  len;
    const char *new_reply;
    int     chat_append_flag;
    int     chat_append_skipped = 0;

    /*
     * Initialize the response data buffer.
     */
    if (rdata.str_buf == 0) {
	rdata.dsn_buf = vstring_alloc(10);
	rdata.str_buf = vstring_alloc(100);
    }

    /*
     * Censor out non-printable characters in server responses. Concatenate
     * multi-line server responses. Separate the status code from the text.
     * Leave further parsing up to the application.
     * 
     * We can't parse or store input that exceeds var_line_limit, so we just
     * skip over it to simplify the remainder of the code below.
     */
    VSTRING_RESET(rdata.str_buf);
    for (;;) {
	last_char = smtp_get(session->buffer, session->stream, var_line_limit,
			     SMTP_GET_FLAG_SKIP);
	/* XXX Update the per-line time limit. */
	printable(STR(session->buffer), '?');
	if (last_char != '\n')
	    msg_warn("%s: response longer than %d: %.30s...",
		session->namaddrport, var_line_limit, STR(session->buffer));
	if (msg_verbose)
	    msg_info("< %s: %.100s", session->namaddrport, STR(session->buffer));

	/*
	 * Defend against a denial of service attack by limiting the amount
	 * of multi-line text that we are willing to store.
	 */
	chat_append_flag = (LEN(rdata.str_buf) < var_line_limit);
	if (chat_append_flag)
	    smtp_chat_append(session, "In:  ", STR(session->buffer));
	else {
	    if (chat_append_skipped == 0)
		msg_warn("%s: multi-line response longer than %d %.30s...",
		  session->namaddrport, var_line_limit, STR(rdata.str_buf));
	    if (chat_append_skipped < INT_MAX)
		chat_append_skipped++;
	}

	/*
	 * Server reply substitution, for fault-injection testing, or for
	 * working around broken systems. Use with care.
	 */
	if (smtp_chat_resp_filter != 0) {
	    new_reply = dict_get(smtp_chat_resp_filter, STR(session->buffer));
	    if (new_reply != 0) {
		msg_info("%s: replacing server reply \"%s\" with \"%s\"",
		     session->namaddrport, STR(session->buffer), new_reply);
		vstring_strcpy(session->buffer, new_reply);
		if (chat_append_flag) {
		    smtp_chat_append(session, "Replaced-by: ", "");
		    smtp_chat_append(session, "     ", new_reply);
		}
	    } else if (smtp_chat_resp_filter->error != 0) {
		msg_warn("%s: table %s:%s lookup error for %s",
			 session->state->request->queue_id,
			 smtp_chat_resp_filter->type,
			 smtp_chat_resp_filter->name,
			 printable(STR(session->buffer), '?'));
		vstream_longjmp(session->stream, SMTP_ERR_DATA);
	    }
	}
	if (chat_append_flag) {
	    if (LEN(rdata.str_buf))
		VSTRING_ADDCH(rdata.str_buf, '\n');
	    vstring_strcat(rdata.str_buf, STR(session->buffer));
	}

	/*
	 * Parse into code and text. Do not ignore garbage (see below).
	 */
	for (cp = STR(session->buffer); *cp && ISDIGIT(*cp); cp++)
	     /* void */ ;
	if ((three_digs = (cp - STR(session->buffer) == 3)) != 0) {
	    if (*cp == '-')
		continue;
	    if (*cp == ' ' || *cp == 0)
		break;
	}

	/*
	 * XXX Do not simply ignore garbage in the server reply when ESMTP
	 * command pipelining is turned on.  For example, after sending
	 * ".<CR><LF>QUIT<CR><LF>" and receiving garbage followed by a
	 * legitimate 2XX reply, Postfix recognizes the server's QUIT reply
	 * as the END-OF-DATA reply after garbage, causing mail to be lost.
	 * 
	 * Without the ability to store per-domain status information in queue
	 * files, automatic workarounds are problematic:
	 * 
	 * - Automatically deferring delivery creates a "repeated delivery"
	 * problem when garbage arrives after the DATA stage. Without the
	 * workaround, Postfix delivers only once.
	 * 
	 * - Automatically deferring delivery creates a "no delivery" problem
	 * when the garbage arrives before the DATA stage. Without the
	 * workaround, mail might still get through.
	 * 
	 * - Automatically turning off pipelining for delayed mail affects
	 * deliveries to correctly implemented servers, and may also affect
	 * delivery of large mailing lists.
	 * 
	 * So we leave the decision with the administrator, but we don't force
	 * them to take action, like we would with automatic deferral.  If
	 * loss of mail is not acceptable then they can turn off pipelining
	 * for specific sites, or they can turn off pipelining globally when
	 * they find that there are just too many broken sites.
	 */
	session->error_mask |= MAIL_ERROR_PROTOCOL;
	if (session->features & SMTP_FEATURE_PIPELINING) {
	    msg_warn("%s: non-%s response from %s: %.100s",
		     session->state->request->queue_id,
		     smtp_mode ? "ESMTP" : "LMTP",
		     session->namaddrport, STR(session->buffer));
	    if (var_helpful_warnings)
		msg_warn("to prevent loss of mail, turn off command pipelining "
			 "for %s with the %s parameter",
			 STR(session->iterator->addr),
			 VAR_LMTP_SMTP(EHLO_DIS_MAPS));
	}
    }

    /*
     * Extract RFC 821 reply code and RFC 2034 detail. Use a default detail
     * code if none was given.
     * 
     * Ignore out-of-protocol enhanced status codes: codes that accompany 3XX
     * replies, or codes whose initial digit is out of sync with the reply
     * code.
     * 
     * XXX Potential stability problem. In order to save memory, the queue
     * manager stores DSNs in a compact manner:
     * 
     * - empty strings are represented by null pointers,
     * 
     * - the status and reason are required to be non-empty.
     * 
     * Other Postfix daemons inherit this behavior, because they use the same
     * DSN support code. This means that everything that receives DSNs must
     * cope with null pointers for the optional DSN attributes, and that
     * everything that provides DSN information must provide a non-empty
     * status and reason, otherwise the DSN support code wil panic().
     * 
     * Thus, when the remote server sends a malformed reply (or 3XX out of
     * context) we should not panic() in DSN_COPY() just because we don't
     * have a status. Robustness suggests that we supply a status here, and
     * that we leave it up to the down-stream code to override the
     * server-supplied status in case of an error we can't detect here, such
     * as an out-of-order server reply.
     */
    VSTRING_TERMINATE(rdata.str_buf);
    vstring_strcpy(rdata.dsn_buf, "5.5.0");	/* SAFETY! protocol error */
    if (three_digs != 0) {
	rdata.code = atoi(STR(session->buffer));
	if (strchr("245", STR(session->buffer)[0]) != 0) {
	    for (cp = STR(session->buffer) + 4; *cp == ' '; cp++)
		 /* void */ ;
	    if ((len = dsn_valid(cp)) > 0 && *cp == *STR(session->buffer)) {
		vstring_strncpy(rdata.dsn_buf, cp, len);
	    } else {
		vstring_strcpy(rdata.dsn_buf, "0.0.0");
		STR(rdata.dsn_buf)[0] = STR(session->buffer)[0];
	    }
	}
    } else {
	rdata.code = 0;
    }
    rdata.dsn = STR(rdata.dsn_buf);
    rdata.str = STR(rdata.str_buf);
    return (&rdata);
}
Exemplo n.º 11
0
static const char *dict_nis_lookup(DICT *dict, const char *key)
{
    DICT_NIS *dict_nis = (DICT_NIS *) dict;
    static char *result;
    int     result_len;
    int     err;
    static VSTRING *buf;

    /*
     * Sanity check.
     */
    if ((dict->flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0)
	msg_panic("dict_nis_lookup: no DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL flag");

    dict_errno = 0;
    if (dict_nis_domain == dict_nis_disabled)
	return (0);

    /*
     * Optionally fold the key.
     */
    if (dict->flags & DICT_FLAG_FOLD_FIX) {
	if (dict->fold_buf == 0)
	    dict->fold_buf = vstring_alloc(10);
	vstring_strcpy(dict->fold_buf, key);
	key = lowercase(vstring_str(dict->fold_buf));
    }

    /*
     * See if this NIS map was written with one null byte appended to key and
     * value.
     */
    if (dict->flags & DICT_FLAG_TRY1NULL) {
	err = yp_match(dict_nis_domain, dict_nis->dict.name,
		       (void *) key, strlen(key) + 1,
		       &result, &result_len);
	if (err == 0) {
	    dict->flags &= ~DICT_FLAG_TRY0NULL;
	    return (result);
	}
    }

    /*
     * See if this NIS map was written with no null byte appended to key and
     * value. This should never be the case, but better play safe.
     */
    if (dict->flags & DICT_FLAG_TRY0NULL) {
	err = yp_match(dict_nis_domain, dict_nis->dict.name,
		       (void *) key, strlen(key),
		       &result, &result_len);
	if (err == 0) {
	    dict->flags &= ~DICT_FLAG_TRY1NULL;
	    if (buf == 0)
		buf = vstring_alloc(10);
	    vstring_strncpy(buf, result, result_len);
	    return (vstring_str(buf));
	}
    }

    /*
     * When the NIS lookup fails for reasons other than "key not found", keep
     * logging warnings, and hope that someone will eventually notice the
     * problem and fix it.
     */
    if (err != YPERR_KEY) {
	msg_warn("lookup %s, NIS domain %s, map %s: %s",
		 key, dict_nis_domain, dict_nis->dict.name,
		 dict_nis_strerror(err));
	dict_errno = DICT_ERR_RETRY;
    }
    return (0);
}
Exemplo n.º 12
0
void    edit_parameters(int mode, int argc, char **argv)
{
    char   *path;
    EDIT_FILE *ep;
    VSTREAM *src;
    VSTREAM *dst;
    VSTRING *buf = vstring_alloc(100);
    VSTRING *key = vstring_alloc(10);
    char   *cp;
    char   *edit_key;
    char   *edit_val;
    HTABLE *table;
    struct cvalue {
	char   *value;
	int     found;
    };
    struct cvalue *cvalue;
    HTABLE_INFO **ht_info;
    HTABLE_INFO **ht;
    int     interesting;
    const char *err;

    /*
     * Store command-line parameters for quick lookup.
     */
    table = htable_create(argc);
    while ((cp = *argv++) != 0) {
	if (strchr(cp, '\n') != 0)
	    msg_fatal("-e or -# accepts no multi-line input");
	while (ISSPACE(*cp))
	    cp++;
	if (*cp == '#')
	    msg_fatal("-e or -# accepts no comment input");
	if (mode & EDIT_MAIN) {
	    if ((err = split_nameval(cp, &edit_key, &edit_val)) != 0)
		msg_fatal("%s: \"%s\"", err, cp);
	} else if (mode & COMMENT_OUT) {
	    if (*cp == 0)
		msg_fatal("-# requires non-blank parameter names");
	    if (strchr(cp, '=') != 0)
		msg_fatal("-# requires parameter names only");
	    edit_key = mystrdup(cp);
	    trimblanks(edit_key, 0);
	    edit_val = 0;
	} else {
	    msg_panic("edit_parameters: unknown mode %d", mode);
	}
	cvalue = (struct cvalue *) mymalloc(sizeof(*cvalue));
	cvalue->value = edit_val;
	cvalue->found = 0;
	htable_enter(table, edit_key, (char *) cvalue);
    }

    /*
     * Open a temp file for the result. This uses a deterministic name so we
     * don't leave behind thrash with random names.
     */
    set_config_dir();
    path = concatenate(var_config_dir, "/", MAIN_CONF_FILE, (char *) 0);
    if ((ep = edit_file_open(path, O_CREAT | O_WRONLY, 0644)) == 0)
	msg_fatal("open %s%s: %m", path, EDIT_FILE_SUFFIX);
    dst = ep->tmp_fp;

    /*
     * Open the original file for input.
     */
    if ((src = vstream_fopen(path, O_RDONLY, 0)) == 0) {
	/* OK to delete, since we control the temp file name exclusively. */
	(void) unlink(ep->tmp_path);
	msg_fatal("open %s for reading: %m", path);
    }

    /*
     * Copy original file to temp file, while replacing parameters on the
     * fly. Issue warnings for names found multiple times.
     */
#define STR(x) vstring_str(x)

    interesting = 0;
    while (vstring_get(buf, src) != VSTREAM_EOF) {
	for (cp = STR(buf); ISSPACE(*cp) /* including newline */ ; cp++)
	     /* void */ ;
	/* Copy comment, all-whitespace, or empty line. */
	if (*cp == '#' || *cp == 0) {
	    vstream_fputs(STR(buf), dst);
	}
	/* Copy, skip or replace continued text. */
	else if (cp > STR(buf)) {
	    if (interesting == 0)
		vstream_fputs(STR(buf), dst);
	    else if (mode & COMMENT_OUT)
		vstream_fprintf(dst, "#%s", STR(buf));
	}
	/* Copy or replace start of logical line. */
	else {
	    vstring_strncpy(key, cp, strcspn(cp, " \t\r\n="));
	    cvalue = (struct cvalue *) htable_find(table, STR(key));
	    if ((interesting = !!cvalue) != 0) {
		if (cvalue->found++ == 1)
		    msg_warn("%s: multiple entries for \"%s\"", path, STR(key));
		if (mode & EDIT_MAIN)
		    vstream_fprintf(dst, "%s = %s\n", STR(key), cvalue->value);
		else if (mode & COMMENT_OUT)
		    vstream_fprintf(dst, "#%s", cp);
		else
		    msg_panic("edit_parameters: unknown mode %d", mode);
	    } else {
		vstream_fputs(STR(buf), dst);
	    }
	}
    }

    /*
     * Generate new entries for parameters that were not found.
     */
    if (mode & EDIT_MAIN) {
	for (ht_info = ht = htable_list(table); *ht; ht++) {
	    cvalue = (struct cvalue *) ht[0]->value;
	    if (cvalue->found == 0)
		vstream_fprintf(dst, "%s = %s\n", ht[0]->key, cvalue->value);
	}
	myfree((char *) ht_info);
    }

    /*
     * When all is well, rename the temp file to the original one.
     */
    if (vstream_fclose(src))
	msg_fatal("read %s: %m", path);
    if (edit_file_close(ep) != 0)
	msg_fatal("close %s%s: %m", path, EDIT_FILE_SUFFIX);

    /*
     * Cleanup.
     */
    myfree(path);
    vstring_free(buf);
    vstring_free(key);
    htable_free(table, myfree);
}
Exemplo n.º 13
0
int     mime_state_update(MIME_STATE *state, int rec_type,
			          const char *text, int len)
{
    int     input_is_text = (rec_type == REC_TYPE_NORM
			     || rec_type == REC_TYPE_CONT);
    MIME_STACK *sp;
    HEADER_OPTS *header_info;
    const unsigned char *cp;

#define SAVE_PREV_REC_TYPE_AND_RETURN_ERR_FLAGS(state, rec_type) do { \
	state->prev_rec_type = rec_type; \
	return (state->err_flags); \
    } while (0)

    /*
     * Be sure to flush any partial output line that might still be buffered
     * up before taking any other "end of input" actions.
     */
    if (!input_is_text && state->prev_rec_type == REC_TYPE_CONT)
	mime_state_update(state, REC_TYPE_NORM, "", 0);

    /*
     * This message state machine is kept simple for the sake of robustness.
     * Standards evolve over time, and we want to be able to correctly
     * process messages that are not yet defined. This state machine knows
     * about headers and bodies, understands that multipart/whatever has
     * multiple body parts with a header and body, and that message/whatever
     * has message headers at the start of a body part.
     */
    switch (state->curr_state) {

	/*
	 * First, deal with header information that we have accumulated from
	 * previous input records. Discard text that does not fit in a header
	 * buffer. Our limit is quite generous; Sendmail will refuse mail
	 * with only 32kbyte in all the message headers combined.
	 */
    case MIME_STATE_PRIMARY:
    case MIME_STATE_MULTIPART:
    case MIME_STATE_NESTED:
	if (LEN(state->output_buffer) > 0) {
	    if (input_is_text) {
		if (state->prev_rec_type == REC_TYPE_CONT) {
		    if (LEN(state->output_buffer) < var_header_limit) {
			vstring_strcat(state->output_buffer, text);
		    } else {
			if (state->static_flags & MIME_OPT_REPORT_TRUNC_HEADER)
			    REPORT_ERROR(state, MIME_ERR_TRUNC_HEADER,
					 STR(state->output_buffer));
		    }
		    SAVE_PREV_REC_TYPE_AND_RETURN_ERR_FLAGS(state, rec_type);
		}
		if (IS_SPACE_TAB(*text)) {
		    if (LEN(state->output_buffer) < var_header_limit) {
			vstring_strcat(state->output_buffer, "\n");
			vstring_strcat(state->output_buffer, text);
		    } else {
			if (state->static_flags & MIME_OPT_REPORT_TRUNC_HEADER)
			    REPORT_ERROR(state, MIME_ERR_TRUNC_HEADER,
					 STR(state->output_buffer));
		    }
		    SAVE_PREV_REC_TYPE_AND_RETURN_ERR_FLAGS(state, rec_type);
		}
	    }

	    /*
	     * The input is (the beginning of) another message header, or is
	     * not a message header, or is not even a text record. With no
	     * more input to append to this saved header, do output
	     * processing and reset the saved header buffer. Hold on to the
	     * content transfer encoding header if we have to do a 8->7
	     * transformation, because the proper information depends on the
	     * content type header: message and multipart require a domain,
	     * leaf entities have either a transformation or a domain.
	     */
	    if (LEN(state->output_buffer) > 0) {
		header_info = header_opts_find(STR(state->output_buffer));
		if (!(state->static_flags & MIME_OPT_DISABLE_MIME)
		    && header_info != 0) {
		    if (header_info->type == HDR_CONTENT_TYPE)
			mime_state_content_type(state, header_info);
		    if (header_info->type == HDR_CONTENT_TRANSFER_ENCODING)
			mime_state_content_encoding(state, header_info);
		}
		if ((state->static_flags & MIME_OPT_REPORT_8BIT_IN_HEADER) != 0
		    && (state->err_flags & MIME_ERR_8BIT_IN_HEADER) == 0) {
		    for (cp = CU_CHAR_PTR(STR(state->output_buffer));
			 cp < CU_CHAR_PTR(END(state->output_buffer)); cp++)
			if (*cp & 0200) {
			    REPORT_ERROR(state, MIME_ERR_8BIT_IN_HEADER,
					 STR(state->output_buffer));
			    break;
			}
		}
		/* Output routine is explicitly allowed to change the data. */
		if (header_info == 0
		    || header_info->type != HDR_CONTENT_TRANSFER_ENCODING
		    || (state->static_flags & MIME_OPT_DOWNGRADE) == 0
		    || state->curr_domain == MIME_ENC_7BIT)
		    HEAD_OUT(state, header_info, len);
		state->prev_rec_type = 0;
		VSTRING_RESET(state->output_buffer);
	    }
	}

	/*
	 * With past header information moved out of the way, proceed with a
	 * clean slate.
	 */
	if (input_is_text) {
	    int     header_len;

	    /*
	     * See if this input is (the beginning of) a message header.
	     * Normalize obsolete "name space colon" syntax to "name colon".
	     * Things would be too confusing otherwise.
	     */
	    if ((header_len = is_header(text)) > 0) {
		vstring_strncpy(state->output_buffer, text, header_len);
		for (text += header_len; IS_SPACE_TAB(*text); text++)
		     /* void */ ;
		vstring_strcat(state->output_buffer, text);
		SAVE_PREV_REC_TYPE_AND_RETURN_ERR_FLAGS(state, rec_type);
	    }
	}

	/*
	 * This input terminates a block of message headers. When converting
	 * 8-bit to 7-bit mail, this is the right place to emit the correct
	 * content-transfer-encoding header. With message or multipart we
	 * specify 7bit, with leaf entities we specify quoted-printable.
	 * 
	 * We're not going to convert non-text data into base 64. If they send
	 * arbitrary binary data as 8-bit text, then the data is already
	 * broken beyond recovery, because the Postfix SMTP server sanitizes
	 * record boundaries, treating broken record boundaries as CRLF.
	 * 
	 * Clear the output buffer, we will need it for storage of the
	 * conversion result.
	 */
	if ((state->static_flags & MIME_OPT_DOWNGRADE)
	    && state->curr_domain != MIME_ENC_7BIT) {
	    if (state->curr_ctype == MIME_CTYPE_MESSAGE
		|| state->curr_ctype == MIME_CTYPE_MULTIPART)
		cp = CU_CHAR_PTR("7bit");
	    else
		cp = CU_CHAR_PTR("quoted-printable");
	    vstring_sprintf(state->output_buffer,
			    "Content-Transfer-Encoding: %s", cp);
	    HEAD_OUT(state, (HEADER_OPTS *) 0, len);
	    VSTRING_RESET(state->output_buffer);
	}

	/*
	 * This input terminates a block of message headers. Call the
	 * optional header end routine at the end of the first header block.
	 */
	if (state->curr_state == MIME_STATE_PRIMARY && state->head_end)
	    state->head_end(state->app_context);

	/*
	 * This is the right place to check if the sender specified an
	 * appropriate identity encoding (7bit, 8bit, binary) for multipart
	 * and for message.
	 */
	if (state->static_flags & MIME_OPT_REPORT_ENCODING_DOMAIN) {
	    if (state->curr_ctype == MIME_CTYPE_MESSAGE) {
		if (state->curr_stype == MIME_STYPE_PARTIAL
		    || state->curr_stype == MIME_STYPE_EXTERN_BODY) {
		    if (state->curr_domain != MIME_ENC_7BIT)
			REPORT_ERROR(state, MIME_ERR_ENCODING_DOMAIN,
				 mime_state_enc_name(state->curr_encoding));
		} else {
		    if (state->curr_encoding != state->curr_domain)
			REPORT_ERROR(state, MIME_ERR_ENCODING_DOMAIN,
				 mime_state_enc_name(state->curr_encoding));
		}
	    } else if (state->curr_ctype == MIME_CTYPE_MULTIPART) {
		if (state->curr_encoding != state->curr_domain)
		    REPORT_ERROR(state, MIME_ERR_ENCODING_DOMAIN,
				 mime_state_enc_name(state->curr_encoding));
	    }
	}

	/*
	 * Find out if the next body starts with its own message headers. In
	 * agressive mode, examine headers of partial and external-body
	 * messages. Otherwise, treat such headers as part of the "body". Set
	 * the proper encoding information for the multipart prolog.
	 * 
	 * XXX This changes state to MIME_STATE_NESTED and then outputs a body
	 * line, so that the body offset is not properly reset.
	 */
	if (input_is_text) {
	    if (*text == 0) {
		state->body_offset = 0;		/* XXX */
		if (state->curr_ctype == MIME_CTYPE_MESSAGE) {
		    if (state->curr_stype == MIME_STYPE_RFC822
		    || (state->static_flags & MIME_OPT_RECURSE_ALL_MESSAGE))
			SET_MIME_STATE(state, MIME_STATE_NESTED,
				       MIME_CTYPE_TEXT, MIME_STYPE_PLAIN,
				       MIME_ENC_7BIT, MIME_ENC_7BIT);
		    else
			SET_CURR_STATE(state, MIME_STATE_BODY);
		} else if (state->curr_ctype == MIME_CTYPE_MULTIPART) {
		    SET_MIME_STATE(state, MIME_STATE_BODY,
				   MIME_CTYPE_OTHER, MIME_STYPE_OTHER,
				   MIME_ENC_7BIT, MIME_ENC_7BIT);
		} else {
		    SET_CURR_STATE(state, MIME_STATE_BODY);
		}
	    }

	    /*
	     * Invalid input. Force output of one blank line and jump to the
	     * body state, leaving all other state alone.
	     */
	    else {
		SET_CURR_STATE(state, MIME_STATE_BODY);
		BODY_OUT(state, REC_TYPE_NORM, "", 0);
	    }
	}

	/*
	 * This input is not text. Go to body state, unconditionally.
	 */
	else {
	    SET_CURR_STATE(state, MIME_STATE_BODY);
	}
	/* FALLTHROUGH */

	/*
	 * Body text. Look for message boundaries, and recover from missing
	 * boundary strings. Missing boundaries can happen in agressive mode
	 * with text/rfc822-headers or with message/partial. Ignore non-space
	 * cruft after --boundary or --boundary--, because some MUAs do, and
	 * because only perverse software would take advantage of this to
	 * escape detection. We have to ignore trailing cruft anyway, because
	 * our saved copy of the boundary string may have been truncated for
	 * safety reasons.
	 * 
	 * Optionally look for 8-bit data in content that was announced as, or
	 * that defaults to, 7-bit. Unfortunately, we cannot turn this on by
	 * default. Majordomo sends requests for approval that do not
	 * propagate the MIME information from the enclosed message to the
	 * message headers of the approval request.
	 * 
	 * Set the proper state information after processing a message boundary
	 * string.
	 * 
	 * Don't look for boundary strings at the start of a continued record.
	 */
    case MIME_STATE_BODY:
	if (input_is_text) {
	    if ((state->static_flags & MIME_OPT_REPORT_8BIT_IN_7BIT_BODY) != 0
		&& state->curr_encoding == MIME_ENC_7BIT
		&& (state->err_flags & MIME_ERR_8BIT_IN_7BIT_BODY) == 0) {
		for (cp = CU_CHAR_PTR(text); cp < CU_CHAR_PTR(text + len); cp++)
		    if (*cp & 0200) {
			REPORT_ERROR(state, MIME_ERR_8BIT_IN_7BIT_BODY, text);
			break;
		    }
	    }
	    if (state->stack && state->prev_rec_type != REC_TYPE_CONT
		&& text[0] == '-' && text[1] == '-') {
		for (sp = state->stack; sp != 0; sp = sp->next) {
		    if (strncmp(text + 2, sp->boundary, sp->bound_len) == 0) {
			while (sp != state->stack)
			    mime_state_pop(state);
			if (strncmp(text + 2 + sp->bound_len, "--", 2) == 0) {
			    mime_state_pop(state);
			    SET_MIME_STATE(state, MIME_STATE_BODY,
					 MIME_CTYPE_OTHER, MIME_STYPE_OTHER,
					   MIME_ENC_7BIT, MIME_ENC_7BIT);
			} else {
			    SET_MIME_STATE(state, MIME_STATE_MULTIPART,
					   sp->def_ctype, sp->def_stype,
					   MIME_ENC_7BIT, MIME_ENC_7BIT);
			}
			break;
		    }
		}
	    }
	    /* Put last for consistency with header output routine. */
	    if ((state->static_flags & MIME_OPT_DOWNGRADE)
		&& state->curr_domain != MIME_ENC_7BIT)
		mime_state_downgrade(state, rec_type, text, len);
	    else
		BODY_OUT(state, rec_type, text, len);
	}

	/*
	 * The input is not a text record. Inform the application that this
	 * is the last opportunity to send any pending output.
	 */
	else {
	    if (state->body_end)
		state->body_end(state->app_context);
	}
	SAVE_PREV_REC_TYPE_AND_RETURN_ERR_FLAGS(state, rec_type);

	/*
	 * Oops. This can't happen.
	 */
    default:
	msg_panic("mime_state_update: unknown state: %d", state->curr_state);
    }
}