Beispiel #1
0
char   *safe_ultostr(VSTRING *buf, unsigned long ulval, int base,
		             int padlen, int padchar)
{
    const char *myname = "safe_ultostr";
    char   *start;
    char   *last;
    int     i;

    /*
     * Sanity check.
     */
    if (base < SAFE_MIN_BASE || base > SAFE_MAX_BASE)
	msg_panic("%s: bad base: %d", myname, base);

    /*
     * First accumulate the result, backwards.
     */
    VSTRING_RESET(buf);
    while (ulval != 0) {
	VSTRING_ADDCH(buf, safe_chars[ulval % base]);
	ulval /= base;
    }
    while (VSTRING_LEN(buf) < padlen)
	VSTRING_ADDCH(buf, padchar);
    VSTRING_TERMINATE(buf);

    /*
     * Then, reverse the result.
     */
    start = STR(buf);
    last = END(buf) - 1;
    for (i = 0; i < VSTRING_LEN(buf) / 2; i++)
	SWAP(int, start[i], last[-i]);
    return (STR(buf));
}
Beispiel #2
0
const char *cleanup_strflags(unsigned flags)
{
    static VSTRING *result;
    unsigned i;

    if (flags == 0)
	return ("none");

    if (result == 0)
	result = vstring_alloc(20);
    else
	VSTRING_RESET(result);

    for (i = 0; i < sizeof(cleanup_flag_map) / sizeof(cleanup_flag_map[0]); i++) {
	if (cleanup_flag_map[i].flag & flags) {
	    vstring_sprintf_append(result, "%s ", cleanup_flag_map[i].text);
	    flags &= ~cleanup_flag_map[i].flag;
	}
    }

    if (flags != 0 || VSTRING_LEN(result) == 0)
	msg_panic("cleanup_strflags: unrecognized flag value(s) 0x%x", flags);

    vstring_truncate(result, VSTRING_LEN(result) - 1);
    VSTRING_TERMINATE(result);

    return (vstring_str(result));
}
Beispiel #3
0
static const char *dict_union_lookup(DICT *dict, const char *query)
{
    static const char myname[] = "dict_union_lookup";
    DICT_UNION *dict_union = (DICT_UNION *) dict;
    DICT   *map;
    char  **cpp;
    char   *dict_type_name;
    const char *result = 0;

    /*
     * After Roel van Meer, postfix-users mailing list, Sept 2014.
     */
    VSTRING_RESET(dict_union->re_buf);
    for (cpp = dict_union->map_union->argv; (dict_type_name = *cpp) != 0; cpp++) {
	if ((map = dict_handle(dict_type_name)) == 0)
	    msg_panic("%s: dictionary \"%s\" not found", myname, dict_type_name);
	if ((result = dict_get(map, query)) == 0)
	    continue;
	if (VSTRING_LEN(dict_union->re_buf) > 0)
	    VSTRING_ADDCH(dict_union->re_buf, ',');
	vstring_strcat(dict_union->re_buf, result);
    }
    DICT_ERR_VAL_RETURN(dict, DICT_ERR_NONE,
			VSTRING_LEN(dict_union->re_buf) > 0 ?
			STR(dict_union->re_buf) : 0);
}
Beispiel #4
0
static void dict_mysql_quote(DICT *dict, const char *name, VSTRING *result)
{
    DICT_MYSQL *dict_mysql = (DICT_MYSQL *) dict;
    int     len = strlen(name);
    int     buflen;

    /*
     * We won't get integer overflows in 2*len + 1, because Postfix input
     * keys have reasonable size limits, better safe than sorry.
     */
    if (len > (INT_MAX - VSTRING_LEN(result) - 1) / 2)
	msg_panic("dict_mysql_quote: integer overflow in %lu+2*%d+1",
		  (unsigned long) VSTRING_LEN(result), len);
    buflen = 2 * len + 1;
    VSTRING_SPACE(result, buflen);

#if defined(MYSQL_VERSION_ID) && MYSQL_VERSION_ID >= 40000
    if (dict_mysql->active_host)
	mysql_real_escape_string(dict_mysql->active_host->db,
				 vstring_end(result), name, len);
    else
#endif
	mysql_escape_string(vstring_end(result), name, len);

    VSTRING_SKIP(result);
}
Beispiel #5
0
int     main(int unused_argc, char **unused_argv)
{
    VSTRING *buf = vstring_alloc(100);
    VSTRING *result = vstring_alloc(100);
    char   *cp;
    char   *name;
    char   *value;
    HTABLE *table;
    int     stat;

    while (!vstream_feof(VSTREAM_IN)) {

	table = htable_create(0);

	/*
	 * Read a block of definitions, terminated with an empty line.
	 */
	while (vstring_get_nonl(buf, VSTREAM_IN) != VSTREAM_EOF) {
	    vstream_printf("<< %s\n", vstring_str(buf));
	    vstream_fflush(VSTREAM_OUT);
	    if (VSTRING_LEN(buf) == 0)
		break;
	    cp = vstring_str(buf);
	    name = mystrtok(&cp, " \t\r\n=");
	    value = mystrtok(&cp, " \t\r\n=");
	    htable_enter(table, name, value ? mystrdup(value) : 0);
	}

	/*
	 * Read a block of patterns, terminated with an empty line or EOF.
	 */
	while (vstring_get_nonl(buf, VSTREAM_IN) != VSTREAM_EOF) {
	    vstream_printf("<< %s\n", vstring_str(buf));
	    vstream_fflush(VSTREAM_OUT);
	    if (VSTRING_LEN(buf) == 0)
		break;
	    cp = vstring_str(buf);
	    VSTRING_RESET(result);
	    stat = mac_expand(result, vstring_str(buf), MAC_EXP_FLAG_NONE,
			      (char *) 0, lookup, (char *) table);
	    vstream_printf("stat=%d result=%s\n", stat, vstring_str(result));
	    vstream_fflush(VSTREAM_OUT);
	}
	htable_free(table, myfree);
	vstream_printf("\n");
    }

    /*
     * Clean up.
     */
    vstring_free(buf);
    vstring_free(result);
    exit(0);
}
Beispiel #6
0
VSTRING *vstring_truncate(VSTRING *vp, ssize_t len)
{
    ssize_t move;

    if (len < 0) {
	len = (-len);
	if ((move = VSTRING_LEN(vp) - len) > 0)
	    memmove(vstring_str(vp), vstring_str(vp) + move, len);
    }
    if (len < VSTRING_LEN(vp))
	VSTRING_AT_OFFSET(vp, len);
    return (vp);
}
Beispiel #7
0
int     main(int unused_argc, char **unused_argv)
{
    VSTRING *extension = vstring_alloc(1);
    VSTRING *buf = vstring_alloc(1);
    ARGV   *argv;
    char  **cpp;

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

    vstream_printf("extension: (CR for none): ");
    vstream_fflush(VSTREAM_OUT);
    if (vstring_get_nonl(extension, VSTREAM_IN) == VSTREAM_EOF)
        exit(0);

    vstream_printf("print strings to be translated, one per line\n");
    vstream_fflush(VSTREAM_OUT);
    while (vstring_get_nonl(buf, VSTREAM_IN) != VSTREAM_EOF) {
        argv = mail_addr_crunch(STR(buf), VSTRING_LEN(extension) ? STR(extension) : 0);
        for (cpp = argv->argv; *cpp; cpp++)
            vstream_printf("	%s\n", *cpp);
        vstream_fflush(VSTREAM_OUT);
    }
    return (0);
}
Beispiel #8
0
VSTRING *off_cvt_number(VSTRING *buf, off_t offset)
{
    static char digs[] = "0123456789";
    char   *start;
    char   *last;
    int     i;

    /*
     * Sanity checks
     */
    if (offset < 0)
        msg_panic("off_cvt_number: negative offset -%s",
                  STR(off_cvt_number(buf, -offset)));

    /*
     * First accumulate the result, backwards.
     */
    VSTRING_RESET(buf);
    while (offset != 0) {
        VSTRING_ADDCH(buf, digs[offset % 10]);
        offset /= 10;
    }
    VSTRING_TERMINATE(buf);

    /*
     * Then, reverse the result.
     */
    start = STR(buf);
    last = END(buf) - 1;
    for (i = 0; i < VSTRING_LEN(buf) / 2; i++)
        SWAP(int, start[i], last[-i]);
    return (buf);
}
static char *xsasl_dovecot_server_mech_filter(ARGV *mechanism_argv,
				           XSASL_DCSRV_MECH *mechanism_list,
					            unsigned int conf_props)
{
    const char *myname = "xsasl_dovecot_server_mech_filter";
    unsigned int pos_conf_props = (conf_props & SEC_PROPS_POS_MASK);
    unsigned int neg_conf_props = (conf_props & SEC_PROPS_NEG_MASK);
    VSTRING *mechanisms_str = vstring_alloc(10);
    XSASL_DCSRV_MECH *mp;

    /*
     * Match Postfix properties against Dovecot server properties.
     */
    for (mp = mechanism_list; mp != 0; mp = mp->next) {
	if ((mp->sec_props & pos_conf_props) == pos_conf_props
	    && (mp->sec_props & neg_conf_props) == 0) {
	    if (VSTRING_LEN(mechanisms_str) > 0)
		VSTRING_ADDCH(mechanisms_str, ' ');
	    vstring_strcat(mechanisms_str, mp->mech_name);
	    argv_add(mechanism_argv, mp->mech_name, (char *) 0);
	    if (msg_verbose)
		msg_info("%s: keep mechanism: %s", myname, mp->mech_name);
	} else {
	    if (msg_verbose)
		msg_info("%s: skip mechanism: %s", myname, mp->mech_name);
	}
    }
    return (vstring_export(mechanisms_str));
}
Beispiel #10
0
VSTRING *vstring_memcat(VSTRING *vp, const char *src, ssize_t len)
{
    VSTRING_SPACE(vp, len);
    memcpy(vstring_end(vp), src, len);
    len += VSTRING_LEN(vp);
    VSTRING_AT_OFFSET(vp, len);
    return (vp);
}
Beispiel #11
0
int     memcache_get(VSTREAM *stream, VSTRING *vp, ssize_t bound)
{
    int     last_char;
    int     next_char;

    last_char = (bound == 0 ? vstring_get(vp, stream) :
		 vstring_get_bound(vp, stream, bound));

    switch (last_char) {

	/*
	 * Do some repair in the rare case that we stopped reading in the
	 * middle of the CRLF record terminator.
	 */
    case '\r':
	if ((next_char = VSTREAM_GETC(stream)) == '\n') {
	    VSTRING_ADDCH(vp, '\n');
	    /* FALLTRHOUGH */
	} else {
	    if (next_char != VSTREAM_EOF)
		vstream_ungetc(stream, next_char);

	    /*
	     * Input too long, or EOF
	     */
    default:
	    if (msg_verbose)
		msg_info("%s got %s", VSTREAM_PATH(stream),
			 LEN(vp) < bound ? "EOF" : "input too long");
	    return (-1);
	}

	/*
	 * Strip off the record terminator: either CRLF or just bare LF.
	 */
    case '\n':
	vstring_truncate(vp, VSTRING_LEN(vp) - 1);
	if (VSTRING_LEN(vp) > 0 && vstring_end(vp)[-1] == '\r')
	    vstring_truncate(vp, VSTRING_LEN(vp) - 1);
	VSTRING_TERMINATE(vp);
	if (msg_verbose)
	    msg_info("%s got: %s", VSTREAM_PATH(stream), STR(vp));
	return (0);
    }
}
Beispiel #12
0
int     psc_send_reply(PSC_STATE *state, const char *text)
{
    ssize_t start;
    int     ret;
    const char *footer;
    ssize_t text_len = strlen(text) - 2;

    if (msg_verbose)
	msg_info("> [%s]:%s: %.*s", state->smtp_client_addr,
		 state->smtp_client_port, (int) text_len, text);

    /*
     * Append the new text to earlier text that could not be sent because the
     * output was throttled.
     */
    start = VSTRING_LEN(state->send_buf);
    vstring_strcat(state->send_buf, text);

    /*
     * For soft_bounce support, we also fix the REJECT logging before the
     * dummy SMTP engine calls the psc_send_reply() output routine. We do
     * some double work, but it is for debugging only.
     */
    if (var_soft_bounce) {
	if (text[0] == '5')
	    STR(state->send_buf)[start + 0] = '4';
	if (text[4] == '5')
	    STR(state->send_buf)[start + 4] = '4';
    }

    /*
     * Append the optional reply footer.
     */
    if ((*text == '4' || *text == '5')
	&& ((psc_rej_ftr_maps != 0
	     && (footer = psc_get_footer(text, text_len)) != 0)
	    || *(footer = var_psc_rej_footer) != 0))
	smtp_reply_footer(state->send_buf, start, footer,
			  STR(psc_expand_filter), psc_expand_lookup,
			  (void *) state);

    /*
     * Do a best effort sending text, but don't block when the output is
     * throttled by a hostile peer.
     */
    ret = write(vstream_fileno(state->smtp_client_stream),
		STR(state->send_buf), LEN(state->send_buf));
    if (ret > 0)
	vstring_truncate(state->send_buf, ret - LEN(state->send_buf));
    if (ret < 0 && errno != EAGAIN && errno != EPIPE && errno != ECONNRESET)
	msg_warn("write [%s]:%s: %m", state->smtp_client_addr,
		 state->smtp_client_port);
    return (ret < 0 && errno != EAGAIN);
}
Beispiel #13
0
static int smtpd_proxy_xforward_flush(SMTPD_STATE *state, VSTRING *buf)
{
    int     ret;

    if (VSTRING_LEN(buf) > 0) {
	ret = smtpd_proxy_cmd(state, SMTPD_PROX_WANT_OK,
			      XFORWARD_CMD "%s", STR(buf));
	VSTRING_RESET(buf);
	return (ret);
    }
    return (0);
}
Beispiel #14
0
VSTRING *vstring_prepend(VSTRING *vp, const char *buf, ssize_t len)
{
    ssize_t new_len;

    /*
     * Sanity check.
     */
    if (len < 0)
	msg_panic("vstring_prepend: bad length %ld", (long) len);

    /*
     * Move the existing content and copy the new content.
     */
    new_len = VSTRING_LEN(vp) + len;
    VSTRING_SPACE(vp, len);
    memmove(vstring_str(vp) + len, vstring_str(vp), VSTRING_LEN(vp));
    memcpy(vstring_str(vp), buf, len);
    VSTRING_AT_OFFSET(vp, new_len);
    VSTRING_TERMINATE(vp);
    return (vp);
}
Beispiel #15
0
int     main(int unused_argc, char **unused_argv)
{
    VSTRING *in = vstring_alloc(10);
    VSTRING *out = vstring_alloc(10);

    while (vstring_fgets_nonl(in, VSTREAM_IN)) {
	unescape(out, vstring_str(in));
	vstream_fwrite(VSTREAM_OUT, vstring_str(out), VSTRING_LEN(out));
    }
    vstream_fflush(VSTREAM_OUT);
    exit(0);
}
Beispiel #16
0
/*
 * Locate or allocate connection cache entry.
 */
static void dict_ldap_conn_find(DICT_LDAP *dict_ldap)
{
    VSTRING *keybuf = vstring_alloc(10);
    char   *key;
    int     len;

#ifdef LDAP_API_FEATURE_X_OPENLDAP
    int     sslon = dict_ldap->start_tls || dict_ldap->ldap_ssl;

#endif
    LDAP_CONN *conn;

#define ADDSTR(vp, s) vstring_memcat((vp), (s), strlen((s))+1)
#define ADDINT(vp, i) vstring_sprintf_append((vp), "%lu", (unsigned long)(i))

    ADDSTR(keybuf, dict_ldap->server_host);
    ADDINT(keybuf, dict_ldap->server_port);
    ADDINT(keybuf, dict_ldap->bind);
    ADDSTR(keybuf, dict_ldap->bind ? dict_ldap->bind_dn : "");
    ADDSTR(keybuf, dict_ldap->bind ? dict_ldap->bind_pw : "");
    ADDINT(keybuf, dict_ldap->dereference);
    ADDINT(keybuf, dict_ldap->chase_referrals);
    ADDINT(keybuf, dict_ldap->debuglevel);
    ADDINT(keybuf, dict_ldap->version);
#ifdef LDAP_API_FEATURE_X_OPENLDAP
    ADDINT(keybuf, dict_ldap->ldap_ssl);
    ADDINT(keybuf, dict_ldap->start_tls);
    ADDINT(keybuf, sslon ? dict_ldap->tls_require_cert : 0);
    ADDSTR(keybuf, sslon ? dict_ldap->tls_ca_cert_file : "");
    ADDSTR(keybuf, sslon ? dict_ldap->tls_ca_cert_dir : "");
    ADDSTR(keybuf, sslon ? dict_ldap->tls_cert : "");
    ADDSTR(keybuf, sslon ? dict_ldap->tls_key : "");
    ADDSTR(keybuf, sslon ? dict_ldap->tls_random_file : "");
    ADDSTR(keybuf, sslon ? dict_ldap->tls_cipher_suite : "");
#endif

    key = vstring_str(keybuf);
    len = VSTRING_LEN(keybuf);

    if (conn_hash == 0)
	conn_hash = binhash_create(0);

    if ((dict_ldap->ht = binhash_locate(conn_hash, key, len)) == 0) {
	conn = (LDAP_CONN *) mymalloc(sizeof(LDAP_CONN));
	conn->conn_ld = 0;
	conn->conn_refcount = 0;
	dict_ldap->ht = binhash_enter(conn_hash, key, len, (char *) conn);
    }
    ++DICT_LDAP_CONN(dict_ldap)->conn_refcount;

    vstring_free(keybuf);
}
Beispiel #17
0
static int smtpd_proxy_xforward_send(SMTPD_STATE *state, VSTRING *buf,
				             const char *name,
				             int value_available,
				             const char *value)
{
    size_t  new_len;
    int     ret;

#define CONSTR_LEN(s)	(sizeof(s) - 1)
#define PAYLOAD_LIMIT	(512 - CONSTR_LEN("250 " XFORWARD_CMD "\r\n"))

    if (!value_available)
	value = XFORWARD_UNAVAILABLE;

    /*
     * Encode the attribute value.
     */
    if (state->expand_buf == 0)
	state->expand_buf = vstring_alloc(100);
    xtext_quote(state->expand_buf, value, "");

    /*
     * How much space does this attribute need? SPACE name = value.
     */
    new_len = strlen(name) + strlen(STR(state->expand_buf)) + 2;
    if (new_len > PAYLOAD_LIMIT)
	msg_warn("%s command payload %s=%.10s... exceeds SMTP protocol limit",
		 XFORWARD_CMD, name, value);

    /*
     * Flush the buffer if we need to, and store the attribute.
     */
    if (VSTRING_LEN(buf) > 0 && VSTRING_LEN(buf) + new_len > PAYLOAD_LIMIT)
	if ((ret = smtpd_proxy_xforward_flush(state, buf)) < 0)
	    return (ret);
    vstring_sprintf_append(buf, " %s=%s", name, STR(state->expand_buf));

    return (0);
}
Beispiel #18
0
VSTRING *vstring_sprintf_prepend(VSTRING *vp, const char *format,...)
{
    va_list ap;
    ssize_t old_len = VSTRING_LEN(vp);
    ssize_t result_len;

    /* Construct: old|new|free */
    va_start(ap, format);
    vp = vstring_vsprintf_append(vp, format, ap);
    va_end(ap);
    result_len = VSTRING_LEN(vp);

    /* Construct: old|new|old|free */
    VSTRING_SPACE(vp, old_len);
    vstring_memcat(vp, vstring_str(vp), old_len);

    /* Construct: new|old|free */
    memmove(vstring_str(vp), vstring_str(vp) + old_len, result_len);
    VSTRING_AT_OFFSET(vp, result_len);
    VSTRING_TERMINATE(vp);
    return (vp);
}
Beispiel #19
0
const char *str_long_name_mask_opt(VSTRING *buf, const char *context,
				           const LONG_NAME_MASK * table,
				           long mask, int flags)
{
    const char *myname = "name_mask";
    int     len;
    static VSTRING *my_buf = 0;
    int     delim = (flags & NAME_MASK_COMMA ? ',' :
		     (flags & NAME_MASK_PIPE ? '|' : ' '));
    const LONG_NAME_MASK *np;

    if ((flags & STR_NAME_MASK_REQUIRED) == 0)
	msg_panic("%s: missing NAME_MASK_NUMBER/FATAL/RETURN/WARN/IGNORE flag",
		  myname);

    if (buf == 0) {
	if (my_buf == 0)
	    my_buf = vstring_alloc(1);
	buf = my_buf;
    }
    VSTRING_RESET(buf);

    for (np = table; mask != 0; np++) {
	if (np->name == 0) {
	    if (flags & NAME_MASK_NUMBER) {
		vstring_sprintf_append(buf, "0x%lx%c", mask, delim);
	    } else if (flags & NAME_MASK_FATAL) {
		msg_fatal("%s: unknown %s bit in mask: 0x%lx",
			  myname, context, mask);
	    } else if (flags & NAME_MASK_RETURN) {
		msg_warn("%s: unknown %s bit in mask: 0x%lx",
			 myname, context, mask);
		return (0);
	    } else if (flags & NAME_MASK_WARN) {
		msg_warn("%s: unknown %s bit in mask: 0x%lx",
			 myname, context, mask);
	    }
	    break;
	}
	if (mask & np->mask) {
	    mask &= ~np->mask;
	    vstring_sprintf_append(buf, "%s%c", np->name, delim);
	}
    }
    if ((len = VSTRING_LEN(buf)) > 0)
	vstring_truncate(buf, len - 1);
    VSTRING_TERMINATE(buf);

    return (STR(buf));
}
Beispiel #20
0
int     main(int argc, char **argv)
{
	VSTRING *in = vstring_alloc(10);
	VSTRING *out = vstring_alloc(10);
	int     un_escape = 1;

	if (argc > 2 || (argc > 1 && (un_escape = strcmp(argv[1], "-e"))) != 0)
		msg_fatal("usage: %s [-e (escape)]", argv[0]);

	if (un_escape) {
		while (vstring_fgets_nonl(in, VSTREAM_IN)) {
			unescape(out, vstring_str(in));
			vstream_fwrite(VSTREAM_OUT, vstring_str(out), VSTRING_LEN(out));
		}
	} else {
		while (vstring_fgets(in, VSTREAM_IN)) {
			escape(out, vstring_str(in), VSTRING_LEN(in));
			vstream_fwrite(VSTREAM_OUT, vstring_str(out), VSTRING_LEN(out));
		}
	}
	vstream_fflush(VSTREAM_OUT);
	exit(0);
}
Beispiel #21
0
int     main(int unused_argc, char **unused_argv)
{
	VSTRING *vp = vstring_alloc(100);
	TOK822 *list;
	VSTRING *buf = vstring_alloc(100);

#define TEST_TOKEN_LIMIT 20

	while (readlline(buf, VSTREAM_IN, (int *) 0)) {
		while (VSTRING_LEN(buf) > 0 && vstring_end(buf)[-1] == '\n') {
			vstring_end(buf)[-1] = 0;
			vstring_truncate(buf, VSTRING_LEN(buf) - 1);
		}
		if (!isatty(vstream_fileno(VSTREAM_IN)))
			vstream_printf(">>>%s<<<\n\n", vstring_str(buf));
		list = tok822_parse_limit(vstring_str(buf), TEST_TOKEN_LIMIT);
		vstream_printf("Parse tree:\n");
		tok822_print(list, 0);
		vstream_printf("\n");

		vstream_printf("Internalized:\n%s\n\n",
				vstring_str(tok822_internalize(vp, list, TOK822_STR_DEFL)));
		vstream_fflush(VSTREAM_OUT);
		vstream_printf("Externalized, no newlines inserted:\n%s\n\n",
				vstring_str(tok822_externalize(vp, list,
						TOK822_STR_DEFL | TOK822_STR_TRNC)));
		vstream_fflush(VSTREAM_OUT);
		vstream_printf("Externalized, newlines inserted:\n%s\n\n",
				vstring_str(tok822_externalize(vp, list,
						TOK822_STR_DEFL | TOK822_STR_LINE | TOK822_STR_TRNC)));
		vstream_fflush(VSTREAM_OUT);
		tok822_free_tree(list);
	}
	vstring_free(vp);
	vstring_free(buf);
	return (0);
}
Beispiel #22
0
VSTRING *vstring_insert(VSTRING *vp, ssize_t start, const char *buf, ssize_t len)
{
    ssize_t new_len;

    /*
     * Sanity check.
     */
    if (start < 0 || start >= VSTRING_LEN(vp))
	msg_panic("vstring_insert: bad start %ld", (long) start);
    if (len < 0)
	msg_panic("vstring_insert: bad length %ld", (long) len);

    /*
     * Move the existing content and copy the new content.
     */
    new_len = VSTRING_LEN(vp) + len;
    VSTRING_SPACE(vp, len);
    memmove(vstring_str(vp) + start + len, vstring_str(vp) + start,
	    VSTRING_LEN(vp) - start);
    memcpy(vstring_str(vp) + start, buf, len);
    VSTRING_AT_OFFSET(vp, new_len);
    VSTRING_TERMINATE(vp);
    return (vp);
}
Beispiel #23
0
int     tls_mgr_lookup(const char *unused_type, const char *key, VSTRING *buf)
{
    VSTRING *s;

    if (tls_cache == 0)
	return TLS_MGR_STAT_ERR;

    if ((s = (VSTRING *) htable_find(tls_cache, key)) == 0)
	return TLS_MGR_STAT_ERR;

    vstring_memcpy(buf, vstring_str(s), VSTRING_LEN(s));

    ++cache_hits;
    return (TLS_MGR_STAT_OK);
}
Beispiel #24
0
static char *pop3_quoted(char *cp, POP3D_TOKEN *arg, int start, int last)
{
    static VSTRING *stack;
    int     wanted;
    int     c;

    /*
     * Parser stack. `ch' is always the most-recently entered character.
     */
#define ENTER_CHAR(buf, ch) VSTRING_ADDCH(buf, ch);
#define LEAVE_CHAR(buf, ch) { \
	vstring_truncate(buf, VSTRING_LEN(buf) - 1); \
	ch = vstring_end(buf)[-1]; \
    }

    if (stack == 0)
	stack = vstring_alloc(1);
    VSTRING_RESET(stack);
    ENTER_CHAR(stack, wanted = last);

    VSTRING_ADDCH(arg->vstrval, start);
    for (;;) {
	if ((c = *cp) == 0)
	    break;
	cp++;
	VSTRING_ADDCH(arg->vstrval, c);
	if (c == '\\') {			/* parse escape sequence */
	    if ((c = *cp) == 0)
		break;
	    cp++;
	    VSTRING_ADDCH(arg->vstrval, c);
	} else if (c == wanted) {		/* closing quote etc. */
	    if (VSTRING_LEN(stack) == 1)
		return (cp);
	    LEAVE_CHAR(stack, wanted);
	} else if (c == '"') {
	    ENTER_CHAR(stack, wanted = '"');	/* highest precedence */
	} else if (c == '<' && wanted == '>') {
	    ENTER_CHAR(stack, wanted = '>');	/* lowest precedence */
	}
    }
    arg->tokval = POP3D_TOK_ERROR;		/* missing end */
    return (cp);
}
Beispiel #25
0
int     main(int argc, char **argv)
{
    SESSION *session;
    char   *host;
    char   *port;
    char   *path;
    int     path_len;
    int     sessions = 1;
    int     ch;
    int     i;
    char   *buf;
    const char *parse_err;
    struct addrinfo *res;
    int     aierr;
    const char *protocols = INET_PROTO_NAME_ALL;
    INET_PROTO_INFO *proto_info;
    char   *message_file = 0;

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

    signal(SIGPIPE, SIG_IGN);
    msg_vstream_init(argv[0], VSTREAM_ERR);

    /*
     * Parse JCL.
     */
    while ((ch = GETOPT(argc, argv, "46AcC:df:F:l:Lm:M:Nor:R:s:S:t:T:vw:")) > 0) {
        switch (ch) {
        case '4':
            protocols = INET_PROTO_NAME_IPV4;
            break;
        case '6':
            protocols = INET_PROTO_NAME_IPV6;
            break;
        case 'A':
            allow_reject = 1;
            break;
        case 'c':
            count++;
            break;
        case 'C':
            if ((connect_count = atoi(optarg)) <= 0)
                msg_fatal("bad connection count: %s", optarg);
            break;
        case 'd':
            disconnect = 0;
            break;
        case 'f':
            sender = optarg;
            break;
        case 'F':
            if (message_file == 0 && message_length > 0)
                msg_fatal("-l option cannot be used with -F");
            message_file = optarg;
            break;
        case 'l':
            if (message_file != 0)
                msg_fatal("-l option cannot be used with -F");
            if ((message_length = atoi(optarg)) <= 0)
                msg_fatal("bad message length: %s", optarg);
            break;
        case 'L':
            talk_lmtp = 1;
            break;
        case 'm':
            if ((message_count = atoi(optarg)) <= 0)
                msg_fatal("bad message count: %s", optarg);
            break;
        case 'M':
            if (*optarg == '[') {
                if (!valid_mailhost_literal(optarg, DO_GRIPE))
                    msg_fatal("bad address literal: %s", optarg);
            } else {
                if (!valid_hostname(optarg, DO_GRIPE))
                    msg_fatal("bad hostname: %s", optarg);
            }
            var_myhostname = optarg;
            break;
        case 'N':
            number_rcpts = 1;
            break;
        case 'o':
            send_helo_first = 0;
            send_headers = 0;
            break;
        case 'r':
            if ((recipients = atoi(optarg)) <= 0)
                msg_fatal("bad recipient count: %s", optarg);
            break;
        case 'R':
            if (fixed_delay > 0)
                msg_fatal("do not use -w and -R options at the same time");
            if ((random_delay = atoi(optarg)) <= 0)
                msg_fatal("bad random delay: %s", optarg);
            break;
        case 's':
            if ((sessions = atoi(optarg)) <= 0)
                msg_fatal("bad session count: %s", optarg);
            break;
        case 'S':
            subject = optarg;
            break;
        case 't':
            recipient = optarg;
            break;
        case 'T':
            if ((inet_windowsize = atoi(optarg)) <= 0)
                msg_fatal("bad TCP window size: %s", optarg);
            break;
        case 'v':
            msg_verbose++;
            break;
        case 'w':
            if (random_delay > 0)
                msg_fatal("do not use -w and -R options at the same time");
            if ((fixed_delay = atoi(optarg)) <= 0)
                msg_fatal("bad fixed delay: %s", optarg);
            break;
        default:
            usage(argv[0]);
        }
    }
    if (argc - optind != 1)
        usage(argv[0]);

    if (random_delay > 0)
        srand(getpid());

    /*
     * Initialize the message content, SMTP encoded. smtp_fputs() will append
     * another \r\n but we don't care.
     */
    if (message_file != 0) {
        VSTREAM *fp;
        VSTRING *buf = vstring_alloc(100);
        VSTRING *msg = vstring_alloc(100);

        if ((fp = vstream_fopen(message_file, O_RDONLY, 0)) == 0)
            msg_fatal("open %s: %m", message_file);
        while (vstring_get_nonl(buf, fp) != VSTREAM_EOF) {
            if (*vstring_str(buf) == '.')
                VSTRING_ADDCH(msg, '.');
            vstring_memcat(msg, vstring_str(buf), VSTRING_LEN(buf));
            vstring_memcat(msg, "\r\n", 2);
        }
        if (vstream_ferror(fp))
            msg_fatal("read %s: %m", message_file);
        vstream_fclose(fp);
        vstring_free(buf);
        message_length = VSTRING_LEN(msg);
        message_data = vstring_export(msg);
        send_headers = 0;
    } else if (message_length > 0) {
        message_data = mymalloc(message_length);
        memset(message_data, 'X', message_length);
        for (i = 80; i < message_length; i += 80) {
            message_data[i - 80] = "0123456789"[(i / 80) % 10];
            message_data[i - 2] = '\r';
            message_data[i - 1] = '\n';
        }
    }

    /*
     * Translate endpoint address to internal form.
     */
    proto_info = inet_proto_init("protocols", protocols);
    if (strncmp(argv[optind], "unix:", 5) == 0) {
        path = argv[optind] + 5;
        path_len = strlen(path);
        if (path_len >= (int) sizeof(sun.sun_path))
            msg_fatal("unix-domain name too long: %s", path);
        memset((char *) &sun, 0, sizeof(sun));
        sun.sun_family = AF_UNIX;
#ifdef HAS_SUN_LEN
        sun.sun_len = path_len + 1;
#endif
        memcpy(sun.sun_path, path, path_len);
        sa = (struct sockaddr *) & sun;
        sa_length = sizeof(sun);
    } else {
        if (strncmp(argv[optind], "inet:", 5) == 0)
            argv[optind] += 5;
        buf = mystrdup(argv[optind]);
        if ((parse_err = host_port(buf, &host, (char *) 0, &port, "smtp")) != 0)
            msg_fatal("%s: %s", argv[optind], parse_err);
        if ((aierr = hostname_to_sockaddr(host, port, SOCK_STREAM, &res)) != 0)
            msg_fatal("%s: %s", argv[optind], MAI_STRERROR(aierr));
        myfree(buf);
        sa = (struct sockaddr *) & ss;
        if (res->ai_addrlen > sizeof(ss))
            msg_fatal("address length %d > buffer length %d",
                      (int) res->ai_addrlen, (int) sizeof(ss));
        memcpy((char *) sa, res->ai_addr, res->ai_addrlen);
        sa_length = res->ai_addrlen;
#ifdef HAS_SA_LEN
        sa->sa_len = sa_length;
#endif
        freeaddrinfo(res);
    }

    /*
     * Make sure the SMTP server cannot run us out of memory by sending
     * never-ending lines of text.
     */
    if (buffer == 0) {
        buffer = vstring_alloc(100);
        vstring_ctl(buffer, VSTRING_CTL_MAXLEN, (ssize_t) var_line_limit, 0);
    }

    /*
     * Make sure we have sender and recipient addresses.
     */
    if (var_myhostname == 0)
        var_myhostname = get_hostname();
    if (sender == 0 || recipient == 0) {
        vstring_sprintf(buffer, "foo@%s", var_myhostname);
        defaddr = mystrdup(vstring_str(buffer));
        if (sender == 0)
            sender = defaddr;
        if (recipient == 0)
            recipient = defaddr;
    }

    /*
     * Start sessions.
     */
    while (sessions-- > 0) {
        session = (SESSION *) mymalloc(sizeof(*session));
        session->stream = 0;
        session->xfer_count = 0;
        session->connect_count = connect_count;
        session->next = 0;
        session_count++;
        startup(session);
    }
    for (;;) {
        event_loop(-1);
        if (session_count <= 0 && message_count <= 0) {
            if (count) {
                VSTREAM_PUTC('\n', VSTREAM_OUT);
                vstream_fflush(VSTREAM_OUT);
            }
            exit(0);
        }
    }
}
Beispiel #26
0
static void cleanup_service(VSTREAM *src, char *unused_service, char **argv)
{
    VSTRING *buf = vstring_alloc(100);
    CLEANUP_STATE *state;
    int     flags;
    int     type = 0;
    int     status;

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

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

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

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

    /*
     * Keep reading in case of problems, until the sender is ready to receive
     * our status report.
     */
    if (CLEANUP_OUT_OK(state) == 0 && type > 0) {
	while (type != REC_TYPE_END
	       && (type = rec_get(src, buf, 0)) > 0)
	     /* void */ ;
    }

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

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

    /*
     * Cleanup.
     */
    vstring_free(buf);
}
Beispiel #27
0
static int mac_expand_callback(int type, VSTRING *buf, char *ptr)
{
    char   *myname = "mac_expand_callback";
    MAC_EXP *mc = (MAC_EXP *) ptr;
    int     lookup_mode;
    const char *text;
    char   *cp;
    int     ch;
    int     len;

    /*
     * Sanity check.
     */
    if (mc->level++ > 100) {
	msg_warn("unreasonable macro call nesting: \"%s\"", vstring_str(buf));
	mc->status |= MAC_PARSE_ERROR;
    }
    if (mc->status & MAC_PARSE_ERROR)
	return (mc->status);

    /*
     * $Name etc. reference.
     */
    if (type == MAC_PARSE_VARNAME) {

	/*
	 * Look for the ? or : delimiter. In case of a syntax error, return
	 * without doing damage, and issue a warning instead.
	 */
	for (cp = vstring_str(buf); /* void */ ; cp++) {
	    if ((ch = *cp) == 0) {
		lookup_mode = MAC_EXP_MODE_USE;
		break;
	    }
	    if (ch == '?' || ch == ':') {
		*cp++ = 0;
		lookup_mode = MAC_EXP_MODE_TEST;
		break;
	    }
	    if (!ISALNUM(ch) && ch != '_') {
		msg_warn("macro name syntax error: \"%s\"", vstring_str(buf));
		mc->status |= MAC_PARSE_ERROR;
		return (mc->status);
	    }
	}

	/*
	 * Look up the named parameter.
	 */
	text = mc->lookup(vstring_str(buf), lookup_mode, mc->context);

	/*
	 * Perform the requested substitution.
	 */
	switch (ch) {
	case '?':
	    if (text != 0 && *text != 0)
		mac_parse(cp, mac_expand_callback, (char *) mc);
	    break;
	case ':':
	    if (text == 0 || *text == 0)
		mac_parse(cp, mac_expand_callback, (char *) mc);
	    break;
	default:
	    if (text == 0) {
		mc->status |= MAC_PARSE_UNDEF;
	    } else if (*text == 0) {
		 /* void */ ;
	    } else if (mc->flags & MAC_EXP_FLAG_RECURSE) {
		mac_parse(text, mac_expand_callback, (char *) mc);
	    } else {
		len = VSTRING_LEN(mc->result);
		vstring_strcat(mc->result, text);
		if (mc->filter) {
		    cp = vstring_str(mc->result) + len;
		    while (*(cp += strspn(cp, mc->filter)))
			*cp++ = '_';
		}
	    }
	    break;
	}
    }

    /*
     * Literal text.
     */
    else {
	text = vstring_str(buf);
	vstring_strcat(mc->result, text);
    }

    /*
     * Give the poor tester a clue of what is going on.
     */
    if (msg_verbose)
	msg_info("%s: %s = %s", myname, vstring_str(buf),
		 text ? text : "(undef)");

    mc->level--;

    return (mc->status);
}
Beispiel #28
0
void    rewrite_tree(RWR_CONTEXT *context, TOK822 *tree)
{
    TOK822 *colon;
    TOK822 *domain;
    TOK822 *bang;
    TOK822 *local;
    VSTRING *vstringval;

    /*
     * XXX If you change this module, quote_822_local.c, or tok822_parse.c,
     * be sure to re-run the tests under "make rewrite_clnt_test" and "make
     * resolve_clnt_test" in the global directory.
     */

    /*
     * Sanity check.
     */
    if (tree->head == 0)
	msg_panic("rewrite_tree: empty tree");

    /*
     * An empty address is a special case.
     */
    if (tree->head == tree->tail
	&& tree->tail->type == TOK822_QSTRING
	&& VSTRING_LEN(tree->tail->vstr) == 0)
	return;

    /*
     * Treat a lone @ as if it were an empty address.
     */
    if (tree->head == tree->tail
	&& tree->tail->type == '@') {
	tok822_free_tree(tok822_sub_keep_before(tree, tree->tail));
	tok822_sub_append(tree, tok822_alloc(TOK822_QSTRING, ""));
	return;
    }

    /*
     * Strip source route.
     */
    if (tree->head->type == '@'
	&& (colon = tok822_find_type(tree->head, ':')) != 0
	&& colon != tree->tail)
	tok822_free_tree(tok822_sub_keep_after(tree, colon));

    /*
     * Optionally, transform address forms without @.
     */
    if ((domain = tok822_rfind_type(tree->tail, '@')) == 0) {

	/*
	 * Swap domain!user to user@domain.
	 */
	if (var_swap_bangpath != 0
	    && (bang = tok822_find_type(tree->head, '!')) != 0) {
	    tok822_sub_keep_before(tree, bang);
	    local = tok822_cut_after(bang);
	    tok822_free(bang);
	    tok822_sub_prepend(tree, tok822_alloc('@', (char *) 0));
	    if (local)
		tok822_sub_prepend(tree, local);
	}

	/*
	 * Promote user%domain to user@domain.
	 */
	else if (var_percent_hack != 0
		 && (domain = tok822_rfind_type(tree->tail, '%')) != 0) {
	    domain->type = '@';
	}

	/*
	 * Append missing @origin
	 */
	else if (var_append_at_myorigin != 0
		 && REW_PARAM_VALUE(context->origin) != 0
		 && REW_PARAM_VALUE(context->origin)[0] != 0) {
	    domain = tok822_sub_append(tree, tok822_alloc('@', (char *) 0));
	    tok822_sub_append(tree, tok822_scan(REW_PARAM_VALUE(context->origin),
						(TOK822 **) 0));
	}
    }

    /*
     * Append missing .domain, but leave broken forms ending in @ alone. This
     * merely makes diagnostics more accurate by leaving bogus addresses
     * alone.
     * 
     * Backwards-compatibility warning: warn for "user@localhost" when there is
     * no "localhost" in mydestination or in any other address class with an
     * explicit domain list.
     */
    if (var_append_dot_mydomain != 0
	&& REW_PARAM_VALUE(context->domain) != 0
	&& REW_PARAM_VALUE(context->domain)[0] != 0
	&& (domain = tok822_rfind_type(tree->tail, '@')) != 0
	&& domain != tree->tail
	&& tok822_find_type(domain, TOK822_DOMLIT) == 0
	&& tok822_find_type(domain, '.') == 0) {
	if (warn_compat_break_app_dot_mydomain
	    && (vstringval = domain->next->vstr) != 0) {
	    if (strcasecmp(vstring_str(vstringval), "localhost") != 0) {
		msg_info("using backwards-compatible default setting "
			 VAR_APP_DOT_MYDOMAIN "=yes to rewrite \"%s\" to "
			 "\"%s.%s\"", vstring_str(vstringval),
			 vstring_str(vstringval), var_mydomain);
	    } else if (resolve_class("localhost") == RESOLVE_CLASS_DEFAULT) {
		msg_info("using backwards-compatible default setting "
			 VAR_APP_DOT_MYDOMAIN "=yes to rewrite \"%s\" to "
			 "\"%s.%s\"; please add \"localhost\" to "
			 "mydestination or other address class",
			 vstring_str(vstringval), vstring_str(vstringval),
			 var_mydomain);
	    }
	}
	tok822_sub_append(tree, tok822_alloc('.', (char *) 0));
	tok822_sub_append(tree, tok822_scan(REW_PARAM_VALUE(context->domain),
					    (TOK822 **) 0));
    }

    /*
     * Strip trailing dot at end of domain, but not dot-dot or @-dot. This
     * merely makes diagnostics more accurate by leaving bogus addresses
     * alone.
     */
    if (tree->tail->type == '.'
	&& tree->tail->prev
	&& tree->tail->prev->type != '.'
	&& tree->tail->prev->type != '@')
	tok822_free_tree(tok822_sub_keep_before(tree, tree->tail));
}
Beispiel #29
0
char   *casefoldx(int flags, VSTRING *dest, const char *src, ssize_t len)
{
    size_t  old_len;

#ifdef NO_EAI

    /*
     * ASCII mode only.
     */
    if (len < 0)
	len = strlen(src);
    if ((flags & CASEF_FLAG_APPEND) == 0)
	VSTRING_RESET(dest);
    old_len = VSTRING_LEN(dest);
    vstring_strncat(dest, src, len);
    lowercase(STR(dest) + old_len);
    return (STR(dest));
#else

    /*
     * Unicode mode.
     */
    const char myname[] = "casefold";
    static VSTRING *fold_buf = 0;
    static UCaseMap *csm = 0;
    UErrorCode error;
    ssize_t space_needed;
    int     n;

    /*
     * Handle special cases.
     */
    if (len < 0)
	len = strlen(src);
    if (dest == 0)
	dest = (fold_buf != 0 ? fold_buf : (fold_buf = vstring_alloc(100)));
    if ((flags & CASEF_FLAG_APPEND) == 0)
	VSTRING_RESET(dest);
    old_len = VSTRING_LEN(dest);

    /*
     * All-ASCII input, or ASCII mode only.
     */
    if ((flags & CASEF_FLAG_UTF8) == 0 || allascii(src)) {
	vstring_strncat(dest, src, len);
	lowercase(STR(dest) + old_len);
	return (STR(dest));
    }

    /*
     * ICU 4.8 ucasemap_utf8FoldCase() does not complain about UTF-8 syntax
     * errors. XXX Based on source-code review we conclude that non-UTF-8
     * bytes are copied verbatim, and experiments confirm this. Given that
     * this behavior is intentional, we assume that it will stay that way.
     */
#if 0
    if (valid_utf8_string(src, len) == 0) {
	if (err)
	    *err = "malformed UTF-8 or invalid codepoint";
	return (0);
    }
#endif

    /*
     * One-time initialization. With ICU 4.8 this works while chrooted.
     */
    if (csm == 0) {
	error = U_ZERO_ERROR;
	csm = ucasemap_open("en_US", U_FOLD_CASE_DEFAULT, &error);
	if (U_SUCCESS(error) == 0)
	    msg_fatal("ucasemap_open error: %s", u_errorName(error));
    }

    /*
     * Fold the input, adjusting the buffer size if needed. Safety: don't
     * loop forever.
     * 
     * Note: the requested amount of space for casemapped output (as reported
     * with space_needed below) does not include storage for the null
     * terminator. The terminator is written only when the output buffer is
     * large enough. This is why we overallocate space when the output does
     * not fit. But if the output fits exactly, then the ouput will be
     * unterminated, and we have to terminate the output ourselves.
     */
    for (n = 0; n < 3; n++) {
	error = U_ZERO_ERROR;
	space_needed = ucasemap_utf8FoldCase(csm, STR(dest) + old_len,
				     vstring_avail(dest), src, len, &error);
	if (U_SUCCESS(error)) {
	    VSTRING_AT_OFFSET(dest, old_len + space_needed);
	    if (vstring_avail(dest) == 0)	/* exact fit, no terminator */
		VSTRING_TERMINATE(dest);	/* add terminator */
	    break;
	} else if (error == U_BUFFER_OVERFLOW_ERROR) {
	    VSTRING_SPACE(dest, space_needed + 1);	/* for terminator */
	} else {
	    msg_fatal("%s: conversion error for \"%s\": %s",
		      myname, src, u_errorName(error));
	}
    }
    return (STR(dest));
#endif						/* NO_EAI */
}
Beispiel #30
0
static void qmgr_deliver_update(int unused_event, char *context)
{
    QMGR_ENTRY *entry = (QMGR_ENTRY *) context;
    QMGR_QUEUE *queue = entry->queue;
    QMGR_TRANSPORT *transport = queue->transport;
    QMGR_MESSAGE *message = entry->message;
    static DSN_BUF *dsb;
    int     status;

    /*
     * Release the delivery agent from a "hot" queue entry.
     */
#define QMGR_DELIVER_RELEASE_AGENT(entry) do { \
	event_disable_readwrite(vstream_fileno(entry->stream)); \
	(void) vstream_fclose(entry->stream); \
	entry->stream = 0; \
	qmgr_deliver_concurrency--; \
    } while (0)

    if (dsb == 0)
	dsb = dsb_create();

    /*
     * The message transport has responded. Stop the watchdog timer.
     */
    event_cancel_timer(qmgr_deliver_abort, context);

    /*
     * Retrieve the delivery agent status report. The numerical status code
     * indicates if delivery should be tried again. The reason text is sent
     * only when a site should be avoided for a while, so that the queue
     * manager can log why it does not even try to schedule delivery to the
     * affected recipients.
     */
    status = qmgr_deliver_final_reply(entry->stream, dsb);

    /*
     * The mail delivery process failed for some reason (although delivery
     * may have been successful). Back off with this transport type for a
     * while. Dispose of queue entries for this transport that await
     * selection (the todo lists). Stay away from queue entries that have
     * been selected (the busy lists), or we would have dangling pointers.
     * The queue itself won't go away before we dispose of the current queue
     * entry.
     */
    if (status == DELIVER_STAT_CRASH) {
	message->flags |= DELIVER_STAT_DEFER;
#if 0
	whatsup = concatenate("unknown ", transport->name,
			      " mail transport error", (char *) 0);
	qmgr_transport_throttle(transport,
				DSN_SIMPLE(&dsb->dsn, "4.3.0", whatsup));
	myfree(whatsup);
#else
	qmgr_transport_throttle(transport,
				DSN_SIMPLE(&dsb->dsn, "4.3.0",
					   "unknown mail transport error"));
#endif
	msg_warn("transport %s failure -- see a previous warning/fatal/panic logfile record for the problem description",
		 transport->name);

	/*
	 * Assume the worst and write a defer logfile record for each
	 * recipient. This omission was already present in the first queue
	 * manager implementation of 199703, and was fixed 200511.
	 * 
	 * To avoid the synchronous qmgr_defer_recipient() operation for each
	 * recipient of this queue entry, release the delivery process and
	 * move the entry back to the todo queue. Let qmgr_defer_transport()
	 * log the recipient asynchronously if possible, and get out of here.
	 * Note: if asynchronous logging is not possible,
	 * qmgr_defer_transport() eventually invokes qmgr_entry_done() and
	 * the entry becomes a dangling pointer.
	 */
	QMGR_DELIVER_RELEASE_AGENT(entry);
	qmgr_entry_unselect(queue, entry);
	qmgr_defer_transport(transport, &dsb->dsn);
	return;
    }

    /*
     * This message must be tried again.
     * 
     * If we have a problem talking to this site, back off with this site for a
     * while; dispose of queue entries for this site that await selection
     * (the todo list); stay away from queue entries that have been selected
     * (the busy list), or we would have dangling pointers. The queue itself
     * won't go away before we dispose of the current queue entry.
     * 
     * XXX Caution: DSN_COPY() will panic on empty status or reason.
     */
#define SUSPENDED	"delivery temporarily suspended: "

    if (status == DELIVER_STAT_DEFER) {
	message->flags |= DELIVER_STAT_DEFER;
	if (VSTRING_LEN(dsb->status)) {
	    /* Sanitize the DSN status/reason from the delivery agent. */
	    if (!dsn_valid(vstring_str(dsb->status)))
		vstring_strcpy(dsb->status, "4.0.0");
	    if (VSTRING_LEN(dsb->reason) == 0)
		vstring_strcpy(dsb->reason, "unknown error");
	    vstring_prepend(dsb->reason, SUSPENDED, sizeof(SUSPENDED) - 1);
	    if (QMGR_QUEUE_READY(queue)) {
		qmgr_queue_throttle(queue, DSN_FROM_DSN_BUF(dsb));
		if (QMGR_QUEUE_THROTTLED(queue))
		    qmgr_defer_todo(queue, &dsb->dsn);
	    }
	}
    }

    /*
     * No problems detected. Mark the transport and queue as alive. The queue
     * itself won't go away before we dispose of the current queue entry.
     */
    if (status != DELIVER_STAT_CRASH && VSTRING_LEN(dsb->reason) == 0) {
	qmgr_transport_unthrottle(transport);
	qmgr_queue_unthrottle(queue);
    }

    /*
     * Release the delivery process, and give some other queue entry a chance
     * to be delivered. When all recipients for a message have been tried,
     * decide what to do next with this message: defer, bounce, delete.
     */
    QMGR_DELIVER_RELEASE_AGENT(entry);
    qmgr_entry_done(entry, QMGR_QUEUE_BUSY);
}