Beispiel #1
0
static void proxymap_open_service(VSTREAM *client_stream)
{
    int     request_flags;
    DICT   *dict;
    int     reply_status;
    int     reply_flags;

    /*
     * Process the request.
     */
    if (attr_scan(client_stream, ATTR_FLAG_STRICT,
                  ATTR_TYPE_STR, MAIL_ATTR_TABLE, request_map,
                  ATTR_TYPE_INT, MAIL_ATTR_FLAGS, &request_flags,
                  ATTR_TYPE_END) != 2) {
        reply_status = PROXY_STAT_BAD;
        reply_flags = 0;
    } else if ((dict = proxy_map_find(STR(request_map), request_flags,
                                      &reply_status)) == 0) {
        reply_flags = 0;
    } else {
        reply_status = PROXY_STAT_OK;
        reply_flags = dict->flags;
    }

    /*
     * Respond to the client.
     */
    attr_print(client_stream, ATTR_FLAG_NONE,
               ATTR_TYPE_INT, MAIL_ATTR_STATUS, reply_status,
               ATTR_TYPE_INT, MAIL_ATTR_FLAGS, reply_flags,
               ATTR_TYPE_END);
}
Beispiel #2
0
static void verify_service(VSTREAM *client_stream, char *unused_service,
			           char **argv)
{
    VSTRING *request = vstring_alloc(10);

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

    /*
     * This routine runs whenever a client connects to the socket dedicated
     * to the address verification service. All connection-management stuff
     * is handled by the common code in multi_server.c.
     */
    if (attr_scan(client_stream,
		  ATTR_FLAG_MORE | ATTR_FLAG_STRICT,
		  RECV_ATTR_STR(MAIL_ATTR_REQ, request),
		  ATTR_TYPE_END) == 1) {
	if (STREQ(STR(request), VRFY_REQ_UPDATE)) {
	    verify_update_service(client_stream);
	} else if (STREQ(STR(request), VRFY_REQ_QUERY)) {
	    verify_query_service(client_stream);
	} else {
	    msg_warn("unrecognized request: \"%s\", ignored", STR(request));
	    attr_print(client_stream, ATTR_FLAG_NONE,
		       SEND_ATTR_INT(MAIL_ATTR_STATUS, VRFY_STAT_BAD),
		       ATTR_TYPE_END);
	}
    }
    vstream_fflush(client_stream);
    vstring_free(request);
}
Beispiel #3
0
int     resolve_proto(RES_CONTEXT *context, VSTREAM *stream)
{
    int     flags;

    if (attr_scan(stream, ATTR_FLAG_STRICT,
		  ATTR_TYPE_STR, MAIL_ATTR_ADDR, query,
		  ATTR_TYPE_END) != 1)
	return (-1);

    resolve_addr(context, STR(query),
		 channel, nexthop, nextrcpt, &flags);

    if (msg_verbose)
	msg_info("%s -> (`%s' `%s' `%s' `%d')", STR(query), STR(channel),
		 STR(nexthop), STR(nextrcpt), flags);

    attr_print(stream, ATTR_FLAG_NONE,
	       ATTR_TYPE_STR, MAIL_ATTR_TRANSPORT, STR(channel),
	       ATTR_TYPE_STR, MAIL_ATTR_NEXTHOP, STR(nexthop),
	       ATTR_TYPE_STR, MAIL_ATTR_RECIP, STR(nextrcpt),
	       ATTR_TYPE_NUM, MAIL_ATTR_FLAGS, flags,
	       ATTR_TYPE_END);

    if (vstream_fflush(stream) != 0) {
	msg_warn("write resolver reply: %m");
	return (-1);
    }
    return (0);
}
Beispiel #4
0
int     rewrite_proto(VSTREAM *stream)
{
    RWR_CONTEXT *context;
    TOK822 *tree;

    if (attr_scan(stream, ATTR_FLAG_STRICT,
		  ATTR_TYPE_STR, MAIL_ATTR_RULE, ruleset,
		  ATTR_TYPE_STR, MAIL_ATTR_ADDR, address,
		  ATTR_TYPE_END) != 2)
	return (-1);

    if (strcmp(vstring_str(ruleset), MAIL_ATTR_RWR_LOCAL) == 0)
	context = &local_context;
    else if (strcmp(vstring_str(ruleset), MAIL_ATTR_RWR_REMOTE) == 0)
	context = &remote_context;
    else {
	msg_warn("unknown context: %s", vstring_str(ruleset));
	return (-1);
    }

    /*
     * Sanity check. An address is supposed to be in externalized form.
     */
    if (*vstring_str(address) == 0) {
	msg_warn("rewrite_addr: null address");
	vstring_strcpy(result, vstring_str(address));
    }

    /*
     * Convert the address from externalized (quoted) form to token list,
     * rewrite it, and convert back.
     */
    else {
	tree = tok822_scan_addr(vstring_str(address));
	rewrite_tree(context, tree);
	tok822_externalize(result, tree, TOK822_STR_DEFL);
	tok822_free_tree(tree);
    }
    if (msg_verbose)
	msg_info("`%s' `%s' -> `%s'", vstring_str(ruleset),
		 vstring_str(address), vstring_str(result));

    attr_print(stream, ATTR_FLAG_NONE,
	       ATTR_TYPE_INT, MAIL_ATTR_FLAGS, server_flags,
	       ATTR_TYPE_STR, MAIL_ATTR_ADDR, vstring_str(result),
	       ATTR_TYPE_END);

    if (vstream_fflush(stream) != 0) {
	msg_warn("write rewrite reply: %m");
	return (-1);
    }
    return (0);
}
Beispiel #5
0
static void verify_update_service(VSTREAM *client_stream)
{
    VSTRING *buf = vstring_alloc(10);
    VSTRING *addr = vstring_alloc(10);
    int     addr_status;
    VSTRING *text = vstring_alloc(10);
    const char *status_name;
    const char *raw_data;
    long    probed;
    long    updated;

    if (attr_scan(client_stream, ATTR_FLAG_STRICT,
		  RECV_ATTR_STR(MAIL_ATTR_ADDR, addr),
		  RECV_ATTR_INT(MAIL_ATTR_ADDR_STATUS, &addr_status),
		  RECV_ATTR_STR(MAIL_ATTR_WHY, text),
		  ATTR_TYPE_END) == 3) {
	/* FIX 200501 IPv6 patch did not neuter ":" in address literals. */
	translit(STR(addr), ":", "_");
	if ((status_name = verify_stat2name(addr_status)) == 0) {
	    msg_warn("bad recipient status %d for recipient %s",
		     addr_status, STR(addr));
	    attr_print(client_stream, ATTR_FLAG_NONE,
		       SEND_ATTR_INT(MAIL_ATTR_STATUS, VRFY_STAT_BAD),
		       ATTR_TYPE_END);
	} else {

	    /*
	     * Robustness: don't allow a failed probe to clobber an OK
	     * address before it expires. The failed probe is ignored so that
	     * the address will be re-probed upon the next query. As long as
	     * some probes succeed the address will remain cached as OK.
	     */
	    if (addr_status == DEL_RCPT_STAT_OK
		|| (raw_data = dict_cache_lookup(verify_map, STR(addr))) == 0
		|| STATUS_FROM_RAW_ENTRY(raw_data) != DEL_RCPT_STAT_OK) {
		probed = 0;
		updated = (long) time((time_t *) 0);
		verify_make_entry(buf, addr_status, probed, updated, STR(text));
		if (msg_verbose)
		    msg_info("PUT %s status=%d probed=%ld updated=%ld text=%s",
			STR(addr), addr_status, probed, updated, STR(text));
		dict_cache_update(verify_map, STR(addr), STR(buf));
	    }
	    attr_print(client_stream, ATTR_FLAG_NONE,
		       SEND_ATTR_INT(MAIL_ATTR_STATUS, VRFY_STAT_OK),
		       ATTR_TYPE_END);
	}
    }
    vstring_free(buf);
    vstring_free(addr);
    vstring_free(text);
}
Beispiel #6
0
static void proxymap_service(VSTREAM *client_stream, char *unused_service,
                             char **argv)
{

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

    /*
     * Deadline enforcement.
     */
    if (vstream_fstat(client_stream, VSTREAM_FLAG_DEADLINE) == 0)
        vstream_control(client_stream,
                        VSTREAM_CTL_TIMEOUT, 1,
                        VSTREAM_CTL_END);

    /*
     * This routine runs whenever a client connects to the socket dedicated
     * to the proxymap service. All connection-management stuff is handled by
     * the common code in multi_server.c.
     */
    vstream_control(client_stream,
                    VSTREAM_CTL_START_DEADLINE,
                    VSTREAM_CTL_END);
    if (attr_scan(client_stream,
                  ATTR_FLAG_MORE | ATTR_FLAG_STRICT,
                  ATTR_TYPE_STR, MAIL_ATTR_REQ, request,
                  ATTR_TYPE_END) == 1) {
        if (VSTREQ(request, PROXY_REQ_LOOKUP)) {
            proxymap_lookup_service(client_stream);
        } else if (VSTREQ(request, PROXY_REQ_UPDATE)) {
            proxymap_update_service(client_stream);
        } else if (VSTREQ(request, PROXY_REQ_DELETE)) {
            proxymap_delete_service(client_stream);
        } else if (VSTREQ(request, PROXY_REQ_SEQUENCE)) {
            proxymap_sequence_service(client_stream);
        } else if (VSTREQ(request, PROXY_REQ_OPEN)) {
            proxymap_open_service(client_stream);
        } else {
            msg_warn("unrecognized request: \"%s\", ignored", STR(request));
            attr_print(client_stream, ATTR_FLAG_NONE,
                       ATTR_TYPE_INT, MAIL_ATTR_STATUS, PROXY_STAT_BAD,
                       ATTR_TYPE_END);
        }
    }
    vstream_control(client_stream,
                    VSTREAM_CTL_START_DEADLINE,
                    VSTREAM_CTL_END);
    vstream_fflush(client_stream);
}
Beispiel #7
0
static void rewrite_service(VSTREAM *stream, char *unused_service, char **argv)
{
    int     status = -1;

#ifdef DETACH_AND_ASK_CLIENTS_TO_RECONNECT
    static time_t last;
    time_t  now;
    const char *table;

#endif

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

    /*
     * Client connections are long-lived. Be sure to refesh timely.
     */
#ifdef DETACH_AND_ASK_CLIENTS_TO_RECONNECT
    if (server_flags == 0 && (now = event_time()) - last > 10) {
	if ((table = dict_changed_name()) != 0) {
	    msg_info("table %s has changed -- restarting", table);
	    if (multi_server_drain() == 0)
		server_flags = 1;
	}
	last = now;
    }
#endif

    /*
     * This routine runs whenever a client connects to the UNIX-domain socket
     * dedicated to address rewriting. All connection-management stuff is
     * handled by the common code in multi_server.c.
     */
    if (attr_scan(stream, ATTR_FLAG_STRICT | ATTR_FLAG_MORE,
		  ATTR_TYPE_STR, MAIL_ATTR_REQ, command,
		  ATTR_TYPE_END) == 1) {
	if (strcmp(vstring_str(command), REWRITE_ADDR) == 0) {
	    status = rewrite_proto(stream);
	} else if (strcmp(vstring_str(command), RESOLVE_REGULAR) == 0) {
	    status = resolve_proto(&resolve_regular, stream);
	} else if (strcmp(vstring_str(command), RESOLVE_VERIFY) == 0) {
	    status = resolve_proto(&resolve_verify, stream);
	} else {
	    msg_warn("bad command %.30s", printable(vstring_str(command), '?'));
	}
    }
    if (status < 0)
	multi_server_disconnect(stream);
}
Beispiel #8
0
static void proxymap_update_service(VSTREAM *client_stream)
{
    int     request_flags;
    DICT   *dict;
    int     dict_status;
    int     reply_status;

    /*
     * Process the request.
     *
     * XXX We don't close maps, so we must turn on synchronous update to ensure
     * that the on-disk data is in a consistent state between updates.
     *
     * XXX We ignore duplicates, because the proxymap server would abort
     * otherwise.
     */
    if (attr_scan(client_stream, ATTR_FLAG_STRICT,
                  ATTR_TYPE_STR, MAIL_ATTR_TABLE, request_map,
                  ATTR_TYPE_INT, MAIL_ATTR_FLAGS, &request_flags,
                  ATTR_TYPE_STR, MAIL_ATTR_KEY, request_key,
                  ATTR_TYPE_STR, MAIL_ATTR_VALUE, request_value,
                  ATTR_TYPE_END) != 4) {
        reply_status = PROXY_STAT_BAD;
    } else if (proxy_writer == 0) {
        msg_warn("refusing %s update request on non-%s service",
                 STR(request_map), MAIL_SERVICE_PROXYWRITE);
        reply_status = PROXY_STAT_DENY;
    } else if ((dict = proxy_map_find(STR(request_map), request_flags,
                                      &reply_status)) == 0) {
        /* void */ ;
    } else {
        dict->flags = ((dict->flags & ~DICT_FLAG_RQST_MASK)
                       | (request_flags & DICT_FLAG_RQST_MASK)
                       | DICT_FLAG_SYNC_UPDATE | DICT_FLAG_DUP_REPLACE);
        dict_status = dict_put(dict, STR(request_key), STR(request_value));
        if (dict_status == 0) {
            reply_status = PROXY_STAT_OK;
        } else if (dict->error == 0) {
            reply_status = PROXY_STAT_NOKEY;
        } else {
            reply_status = PROXY_STAT_RETRY;
        }
    }

    /*
     * Respond to the client.
     */
    attr_print(client_stream, ATTR_FLAG_NONE,
               ATTR_TYPE_INT, MAIL_ATTR_STATUS, reply_status,
               ATTR_TYPE_END);
}
Beispiel #9
0
static void post_mail_init(VSTREAM *stream, const char *sender,
			           const char *recipient,
			           int filter_class, int trace_flags,
			           VSTRING *queue_id)
{
    VSTRING *id = queue_id ? queue_id : vstring_alloc(100);
    struct timeval now;
    const char *date;
    int cleanup_flags =
	int_filt_flags(filter_class) | CLEANUP_FLAG_MASK_INTERNAL;

    GETTIMEOFDAY(&now);
    date = mail_date(now.tv_sec);

    /*
     * Negotiate with the cleanup service. Give up if we can't agree.
     */
    if (attr_scan(stream, ATTR_FLAG_STRICT,
		  ATTR_TYPE_STR, MAIL_ATTR_QUEUEID, id,
		  ATTR_TYPE_END) != 1
	|| attr_print(stream, ATTR_FLAG_NONE,
		      ATTR_TYPE_INT, MAIL_ATTR_FLAGS, cleanup_flags,
		      ATTR_TYPE_END) != 0)
	msg_fatal("unable to contact the %s service", var_cleanup_service);

    /*
     * Generate a minimal envelope section. The cleanup service will add a
     * size record.
     */
    rec_fprintf(stream, REC_TYPE_TIME, REC_TYPE_TIME_FORMAT,
		REC_TYPE_TIME_ARG(now));
    rec_fprintf(stream, REC_TYPE_ATTR, "%s=%s",
		MAIL_ATTR_LOG_ORIGIN, MAIL_ATTR_ORG_LOCAL);
    rec_fprintf(stream, REC_TYPE_ATTR, "%s=%d",
		MAIL_ATTR_TRACE_FLAGS, trace_flags);
    rec_fputs(stream, REC_TYPE_FROM, sender);
    rec_fputs(stream, REC_TYPE_RCPT, recipient);
    rec_fputs(stream, REC_TYPE_MESG, "");

    /*
     * Do the Received: and Date: header lines. This allows us to shave a few
     * cycles by using the expensive date conversion result for both.
     */
    post_mail_fprintf(stream, "Received: by %s (%s)",
		      var_myhostname, var_mail_name);
    post_mail_fprintf(stream, "\tid %s; %s", vstring_str(id), date);
    post_mail_fprintf(stream, "Date: %s", date);
    if (queue_id == 0)
	vstring_free(id);
}
Beispiel #10
0
static void proxymap_sequence_service(VSTREAM *client_stream)
{
    int     request_flags;
    DICT   *dict;
    int     request_func;
    const char *reply_key;
    const char *reply_value;
    int     dict_status;
    int     reply_status;

    /*
     * Process the request.
     */
    if (attr_scan(client_stream, ATTR_FLAG_STRICT,
                  ATTR_TYPE_STR, MAIL_ATTR_TABLE, request_map,
                  ATTR_TYPE_INT, MAIL_ATTR_FLAGS, &request_flags,
                  ATTR_TYPE_INT, MAIL_ATTR_FUNC, &request_func,
                  ATTR_TYPE_END) != 3
            || (request_func != DICT_SEQ_FUN_FIRST
                && request_func != DICT_SEQ_FUN_NEXT)) {
        reply_status = PROXY_STAT_BAD;
        reply_key = reply_value = "";
    } else if ((dict = proxy_map_find(STR(request_map), request_flags,
                                      &reply_status)) == 0) {
        reply_key = reply_value = "";
    } else {
        dict->flags = ((dict->flags & ~DICT_FLAG_RQST_MASK)
                       | (request_flags & DICT_FLAG_RQST_MASK));
        dict_status = dict_seq(dict, request_func, &reply_key, &reply_value);
        if (dict_status == 0) {
            reply_status = PROXY_STAT_OK;
        } else if (dict->error == 0) {
            reply_status = PROXY_STAT_NOKEY;
            reply_key = reply_value = "";
        } else {
            reply_status = PROXY_STAT_RETRY;
            reply_key = reply_value = "";
        }
    }

    /*
     * Respond to the client.
     */
    attr_print(client_stream, ATTR_FLAG_NONE,
               ATTR_TYPE_INT, MAIL_ATTR_STATUS, reply_status,
               ATTR_TYPE_STR, MAIL_ATTR_KEY, reply_key,
               ATTR_TYPE_STR, MAIL_ATTR_VALUE, reply_value,
               ATTR_TYPE_END);
}
Beispiel #11
0
TLS_SESS_STATE *tls_proxy_context_receive(VSTREAM *proxy_stream)
{
    TLS_SESS_STATE *tls_context;

    tls_context = (TLS_SESS_STATE *) mymalloc(sizeof(*tls_context));

    if (attr_scan(proxy_stream, ATTR_FLAG_STRICT,
	       ATTR_TYPE_FUNC, tls_proxy_context_scan, (char *) tls_context,
		  ATTR_TYPE_END) != 1) {
	tls_proxy_context_free(tls_context);
	return (0);
    } else {
	return (tls_context);
    }
}
Beispiel #12
0
static int qmgr_deliver_initial_reply(VSTREAM *stream)
{
    int     stat;

    if (peekfd(vstream_fileno(stream)) < 0) {
	msg_warn("%s: premature disconnect", VSTREAM_PATH(stream));
	return (DELIVER_STAT_CRASH);
    } else if (attr_scan(stream, ATTR_FLAG_STRICT,
			 ATTR_TYPE_INT, MAIL_ATTR_STATUS, &stat,
			 ATTR_TYPE_END) != 1) {
	msg_warn("%s: malformed response", VSTREAM_PATH(stream));
	return (DELIVER_STAT_CRASH);
    } else {
	return (stat ? DELIVER_STAT_DEFER : 0);
    }
}
Beispiel #13
0
static int flush_request_receive(VSTREAM *client_stream, VSTRING *request)
{
    int     count;

    /*
     * Kluge: choose the protocol depending on the request size.
     */
    if (read_wait(vstream_fileno(client_stream), var_ipc_timeout) < 0) {
	msg_warn("timeout while waiting for data from %s",
		 VSTREAM_PATH(client_stream));
	return (-1);
    }
    if ((count = peekfd(vstream_fileno(client_stream))) < 0) {
	msg_warn("cannot examine read buffer of %s: %m",
		 VSTREAM_PATH(client_stream));
	return (-1);
    }

    /*
     * Short request: master trigger. Use the string+null protocol.
     */
    if (count <= 2) {
	if (vstring_get_null(request, client_stream) == VSTREAM_EOF) {
	    msg_warn("end-of-input while reading request from %s: %m",
		     VSTREAM_PATH(client_stream));
	    return (-1);
	}
    }

    /*
     * Long request: real flush client. Use the attribute list protocol.
     */
    else {
	if (attr_scan(client_stream,
		      ATTR_FLAG_MORE | ATTR_FLAG_STRICT,
		      RECV_ATTR_STR(MAIL_ATTR_REQ, request),
		      ATTR_TYPE_END) != 1) {
	    return (-1);
	}
    }
    return (0);
}
Beispiel #14
0
int     post_mail_fclose(VSTREAM *cleanup)
{
    int     status = 0;

    /*
     * Send the message end marker only when there were no errors.
     */
    if (vstream_ferror(cleanup) != 0) {
	status = CLEANUP_STAT_WRITE;
    } else {
	rec_fputs(cleanup, REC_TYPE_XTRA, "");
	rec_fputs(cleanup, REC_TYPE_END, "");
	if (vstream_fflush(cleanup)
	    || attr_scan(cleanup, ATTR_FLAG_MISSING,
			 ATTR_TYPE_INT, MAIL_ATTR_STATUS, &status,
			 ATTR_TYPE_END) != 1)
	    status = CLEANUP_STAT_WRITE;
    }
    (void) vstream_fclose(cleanup);
    return (status);
}
Beispiel #15
0
int     verify_clnt_query(const char *addr, int *addr_status, VSTRING *why)
{
    VSTREAM *stream;
    int     request_status;
    int     count = 0;

    /*
     * Do client-server plumbing.
     */
    if (vrfy_clnt == 0)
	verify_clnt_init();

    /*
     * Request status for this address.
     */
    for (;;) {
	stream = clnt_stream_access(vrfy_clnt);
	errno = 0;
	count += 1;
	if (attr_print(stream, ATTR_FLAG_NONE,
		       ATTR_TYPE_STR, MAIL_ATTR_REQ, VRFY_REQ_QUERY,
		       ATTR_TYPE_STR, MAIL_ATTR_ADDR, addr,
		       ATTR_TYPE_END) != 0
	    || vstream_fflush(stream)
	    || attr_scan(stream, ATTR_FLAG_MISSING,
			 ATTR_TYPE_INT, MAIL_ATTR_STATUS, &request_status,
			 ATTR_TYPE_INT, MAIL_ATTR_ADDR_STATUS, addr_status,
			 ATTR_TYPE_STR, MAIL_ATTR_WHY, why,
			 ATTR_TYPE_END) != 3) {
	    if (msg_verbose || count > 1 || (errno && errno != EPIPE && errno != ENOENT))
		msg_warn("problem talking to service %s: %m",
			 var_verify_service);
	} else {
	    break;
	}
	sleep(1);
	clnt_stream_recover(vrfy_clnt);
    }
    return (request_status);
}
Beispiel #16
0
static void proxymap_lookup_service(VSTREAM *client_stream)
{
    int     request_flags;
    DICT   *dict;
    const char *reply_value;
    int     reply_status;

    /*
     * Process the request.
     */
    if (attr_scan(client_stream, ATTR_FLAG_STRICT,
                  ATTR_TYPE_STR, MAIL_ATTR_TABLE, request_map,
                  ATTR_TYPE_INT, MAIL_ATTR_FLAGS, &request_flags,
                  ATTR_TYPE_STR, MAIL_ATTR_KEY, request_key,
                  ATTR_TYPE_END) != 3) {
        reply_status = PROXY_STAT_BAD;
        reply_value = "";
    } else if ((dict = proxy_map_find(STR(request_map), request_flags,
                                      &reply_status)) == 0) {
        reply_value = "";
    } else if (dict->flags = ((dict->flags & ~DICT_FLAG_RQST_MASK)
                              | (request_flags & DICT_FLAG_RQST_MASK)),
               (reply_value = dict_get(dict, STR(request_key))) != 0) {
        reply_status = PROXY_STAT_OK;
    } else if (dict->error == 0) {
        reply_status = PROXY_STAT_NOKEY;
        reply_value = "";
    } else {
        reply_status = PROXY_STAT_RETRY;
        reply_value = "";
    }

    /*
     * Respond to the client.
     */
    attr_print(client_stream, ATTR_FLAG_NONE,
               ATTR_TYPE_INT, MAIL_ATTR_STATUS, reply_status,
               ATTR_TYPE_STR, MAIL_ATTR_VALUE, reply_value,
               ATTR_TYPE_END);
}
Beispiel #17
0
int     verify_clnt_update(const char *addr, int addr_status, const char *why)
{
    VSTREAM *stream;
    int     request_status;

    /*
     * Do client-server plumbing.
     */
    if (vrfy_clnt == 0)
	verify_clnt_init();

    /*
     * Send status for this address. Supply a default status if the address
     * verification service is unavailable.
     */
    for (;;) {
	stream = clnt_stream_access(vrfy_clnt);
	errno = 0;
	if (attr_print(stream, ATTR_FLAG_NONE,
		       ATTR_TYPE_STR, MAIL_ATTR_REQ, VRFY_REQ_UPDATE,
		       ATTR_TYPE_STR, MAIL_ATTR_ADDR, addr,
		       ATTR_TYPE_INT, MAIL_ATTR_ADDR_STATUS, addr_status,
		       ATTR_TYPE_STR, MAIL_ATTR_WHY, why,
		       ATTR_TYPE_END) != 0
	    || attr_scan(stream, ATTR_FLAG_MISSING,
			 ATTR_TYPE_INT, MAIL_ATTR_STATUS, &request_status,
			 ATTR_TYPE_END) != 1) {
	    if (msg_verbose || (errno != EPIPE && errno != ENOENT))
		msg_warn("problem talking to service %s: %m",
			 var_verify_service);
	} else {
	    break;
	}
	sleep(1);
	clnt_stream_recover(vrfy_clnt);
    }
    return (request_status);
}
Beispiel #18
0
int     recv_pass_attr(int fd, HTABLE **attr, int timeout, ssize_t bufsize)
{
    VSTREAM *fp;
    int     stream_err;

    /*
     * Set up a temporary VSTREAM to receive the attributes.
     * 
     * XXX We use one-character reads to simplify the implementation.
     */
    fp = vstream_fdopen(fd, O_RDWR);
    vstream_control(fp,
		    VSTREAM_CTL_BUFSIZE, bufsize,
		    VSTREAM_CTL_TIMEOUT, timeout,
		    VSTREAM_CTL_START_DEADLINE,
		    VSTREAM_CTL_END);
    (void) attr_scan(fp, ATTR_FLAG_NONE,
		     ATTR_TYPE_HASH, *attr = htable_create(1),
		     ATTR_TYPE_END);
    stream_err = (vstream_feof(fp) || vstream_ferror(fp));
    vstream_fdclose(fp);

    /*
     * Error reporting and recovery.
     */
    if (stream_err) {
	htable_free(*attr, myfree);
	*attr = 0;
	return (-1);
    } else {
	if ((*attr)->used == 0) {
	    htable_free(*attr, myfree);
	    *attr = 0;
	}
	return (0);
    }
}
Beispiel #19
0
static void tlsp_get_request_event(int event, void *context)
{
    const char *myname = "tlsp_get_request_event";
    TLSP_STATE *state = (TLSP_STATE *) context;
    VSTREAM *plaintext_stream = state->plaintext_stream;
    int     plaintext_fd = vstream_fileno(plaintext_stream);
    static VSTRING *remote_endpt;
    static VSTRING *server_id;
    int     req_flags;
    int     timeout;
    int     ready;

    /*
     * One-time initialization.
     */
    if (remote_endpt == 0) {
        remote_endpt = vstring_alloc(10);
        server_id = vstring_alloc(10);
    }

    /*
     * At this point we still manually manage plaintext read/write/timeout
     * events. Turn off timer events. Below we disable read events on error,
     * and redefine read events on success.
     */
    if (event != EVENT_TIME)
        event_cancel_timer(tlsp_get_request_event, (void *) state);
    else
        errno = ETIMEDOUT;

    /*
     * We must send some data, after receiving the request attributes and
     * before receiving the remote file descriptor. We can't assume
     * UNIX-domain socket semantics here.
     */
    if (event != EVENT_READ
            || attr_scan(plaintext_stream, ATTR_FLAG_STRICT,
                         RECV_ATTR_STR(MAIL_ATTR_REMOTE_ENDPT, remote_endpt),
                         RECV_ATTR_INT(MAIL_ATTR_FLAGS, &req_flags),
                         RECV_ATTR_INT(MAIL_ATTR_TIMEOUT, &timeout),
                         RECV_ATTR_STR(MAIL_ATTR_SERVER_ID, server_id),
                         ATTR_TYPE_END) != 4) {
        msg_warn("%s: receive request attributes: %m", myname);
        event_disable_readwrite(plaintext_fd);
        tlsp_state_free(state);
        return;
    }

    /*
     * If the requested TLS engine is unavailable, hang up after making sure
     * that the plaintext peer has received our "sorry" indication.
     */
    ready = ((req_flags & TLS_PROXY_FLAG_ROLE_SERVER) != 0
             && tlsp_server_ctx != 0);
    if (attr_print(plaintext_stream, ATTR_FLAG_NONE,
                   SEND_ATTR_INT(MAIL_ATTR_STATUS, ready),
                   ATTR_TYPE_END) != 0
            || vstream_fflush(plaintext_stream) != 0
            || ready == 0) {
        read_wait(plaintext_fd, TLSP_INIT_TIMEOUT);	/* XXX */
        event_disable_readwrite(plaintext_fd);
        tlsp_state_free(state);
        return;
    }

    /*
     * XXX We use the same fixed timeout throughout the entire session for
     * both plaintext and ciphertext communication. This timeout is just a
     * safety feature; the real timeout will be enforced by our plaintext
     * peer.
     */
    else {
        state->remote_endpt = mystrdup(STR(remote_endpt));
        state->server_id = mystrdup(STR(server_id));
        msg_info("CONNECT %s %s",
                 (req_flags & TLS_PROXY_FLAG_ROLE_SERVER) ? "from" :
                 (req_flags & TLS_PROXY_FLAG_ROLE_CLIENT) ? "to" :
                 "(bogus_direction)", state->remote_endpt);
        state->req_flags = req_flags;
        state->timeout = timeout + 10;		/* XXX */
        event_enable_read(plaintext_fd, tlsp_get_fd_event, (void *) state);
        event_request_timer(tlsp_get_fd_event, (void *) state,
                            TLSP_INIT_TIMEOUT);
        return;
    }
}
Beispiel #20
0
static int pickup_copy(VSTREAM *qfile, VSTREAM *cleanup,
		               PICKUP_INFO *info, VSTRING *buf)
{
    time_t  now = time((time_t *) 0);
    int     status;
    char   *name;

    /*
     * Protect against time-warped time stamps. Warn about mail that has been
     * queued for an excessive amount of time. Allow for some time drift with
     * network clients that mount the maildrop remotely - especially clients
     * that can't get their daylight savings offsets right.
     */
#define DAY_SECONDS 86400
#define HOUR_SECONDS 3600

    if (info->st.st_mtime > now + 2 * HOUR_SECONDS) {
	msg_warn("%s: message dated %ld seconds into the future",
		 info->id, (long) (info->st.st_mtime - now));
	info->st.st_mtime = now;
    } else if (info->st.st_mtime < now - DAY_SECONDS) {
	msg_warn("%s: message has been queued for %d days",
		 info->id, (int) ((now - info->st.st_mtime) / DAY_SECONDS));
    }

    /*
     * Add content inspection transport. See also postsuper(1).
     */
    if (*var_filter_xport)
	rec_fprintf(cleanup, REC_TYPE_FILT, "%s", var_filter_xport);

    /*
     * Copy the message envelope segment. Allow only those records that we
     * expect to see in the envelope section. The envelope segment must
     * contain an envelope sender address.
     */
    if ((status = copy_segment(qfile, cleanup, info, buf, REC_TYPE_ENVELOPE)) != 0)
	return (status);
    if (info->sender == 0) {
	msg_warn("%s: uid=%ld: no envelope sender",
		 info->id, (long) info->st.st_uid);
	return (REMOVE_MESSAGE_FILE);
    }

    /*
     * For messages belonging to $mail_owner also log the maildrop queue id.
     * This supports message tracking for mail requeued via "postsuper -r".
     */
#define MAIL_IS_REQUEUED(info) \
    ((info)->st.st_uid == var_owner_uid && ((info)->st.st_mode & S_IROTH) == 0)

    if (MAIL_IS_REQUEUED(info)) {
	msg_info("%s: uid=%d from=<%s> orig_id=%s", info->id,
		 (int) info->st.st_uid, info->sender,
		 ((name = strrchr(info->path, '/')) != 0 ?
		  name + 1 : info->path));
    } else {
	msg_info("%s: uid=%d from=<%s>", info->id,
		 (int) info->st.st_uid, info->sender);
    }

    /*
     * Message content segment. Send a dummy message length. Prepend a
     * Received: header to the message contents. For tracing purposes,
     * include the message file ownership, without revealing the login name.
     */
    rec_fputs(cleanup, REC_TYPE_MESG, "");
    rec_fprintf(cleanup, REC_TYPE_NORM, "Received: by %s (%s, from userid %ld)",
		var_myhostname, var_mail_name, (long) info->st.st_uid);
    rec_fprintf(cleanup, REC_TYPE_NORM, "\tid %s; %s", info->id,
		mail_date(info->st.st_mtime));

    /*
     * Copy the message content segment. Allow only those records that we
     * expect to see in the message content section.
     */
    if ((status = copy_segment(qfile, cleanup, info, buf, REC_TYPE_CONTENT)) != 0)
	return (status);

    /*
     * Send the segment with information extracted from message headers.
     * Permit a non-empty extracted segment, so that list manager software
     * can to output recipients after the message, and so that sysadmins can
     * re-inject messages after a change of configuration.
     */
    rec_fputs(cleanup, REC_TYPE_XTRA, "");
    if ((status = copy_segment(qfile, cleanup, info, buf, REC_TYPE_EXTRACT)) != 0)
	return (status);

    /*
     * There are no errors. Send the end-of-data marker, and get the cleanup
     * service completion status. XXX Since the pickup service is unable to
     * bounce, the cleanup service can report only soft errors here.
     */
    rec_fputs(cleanup, REC_TYPE_END, "");
    if (attr_scan(cleanup, ATTR_FLAG_MISSING,
		  RECV_ATTR_INT(MAIL_ATTR_STATUS, &status),
		  RECV_ATTR_STR(MAIL_ATTR_WHY, buf),
		  ATTR_TYPE_END) != 2)
	return (cleanup_service_error(info, CLEANUP_STAT_WRITE));

    /*
     * Depending on the cleanup service completion status, delete the message
     * file, or try again later. Bounces are dealt with by the cleanup
     * service itself. The master process wakes up the cleanup service every
     * now and then.
     */
    if (status) {
	return (cleanup_service_error_reason(info, status, vstring_str(buf)));
    } else {
	return (REMOVE_MESSAGE_FILE);
    }
}
Beispiel #21
0
static int pickup_file(PICKUP_INFO *info)
{
    VSTRING *buf = vstring_alloc(100);
    int     status;
    VSTREAM *qfile;
    VSTREAM *cleanup;
    int     cleanup_flags;

    /*
     * Open the submitted file. If we cannot open it, and we're not having a
     * file descriptor leak problem, delete the submitted file, so that we
     * won't keep complaining about the same file again and again. XXX
     * Perhaps we should save "bad" files elsewhere for further inspection.
     * XXX How can we delete a file when open() fails with ENOENT?
     */
    qfile = safe_open(info->path, O_RDONLY | O_NONBLOCK, 0,
		      (struct stat *) 0, -1, -1, buf);
    if (qfile == 0) {
	if (errno != ENOENT)
	    msg_warn("open input file %s: %s", info->path, vstring_str(buf));
	vstring_free(buf);
	if (errno == EACCES)
	    msg_warn("if this file was created by Postfix < 1.1, then you may have to chmod a+r %s/%s",
		     var_queue_dir, info->path);
	return (errno == EACCES ? KEEP_MESSAGE_FILE : REMOVE_MESSAGE_FILE);
    }

    /*
     * Contact the cleanup service and read the queue ID that it has
     * allocated. In case of trouble, request that the cleanup service
     * bounces its copy of the message. because the original input file is
     * not readable by the bounce service.
     * 
     * If mail is re-injected with "postsuper -r", disable Milter applications.
     * If they were run before the mail was queued then there is no need to
     * run them again. Moreover, the queue file does not contain enough
     * information to reproduce the exact same SMTP events and Sendmail
     * macros that Milters received when the mail originally arrived in
     * Postfix.
     * 
     * The actual message copying code is in a separate routine, so that it is
     * easier to implement the many possible error exits without forgetting
     * to close files, or to release memory.
     */
    cleanup_flags =
	input_transp_cleanup(CLEANUP_FLAG_BOUNCE | CLEANUP_FLAG_MASK_EXTERNAL,
			     pickup_input_transp_mask);
    /* As documented in postsuper(1). */
    if (MAIL_IS_REQUEUED(info))
	cleanup_flags &= ~CLEANUP_FLAG_MILTER;
    else
	cleanup_flags |= smtputf8_autodetect(MAIL_SRC_MASK_SENDMAIL);

    cleanup = mail_connect_wait(MAIL_CLASS_PUBLIC, var_cleanup_service);
    if (attr_scan(cleanup, ATTR_FLAG_STRICT,
		  RECV_ATTR_STR(MAIL_ATTR_QUEUEID, buf),
		  ATTR_TYPE_END) != 1
	|| attr_print(cleanup, ATTR_FLAG_NONE,
		      SEND_ATTR_INT(MAIL_ATTR_FLAGS, cleanup_flags),
		      ATTR_TYPE_END) != 0) {
	status = KEEP_MESSAGE_FILE;
    } else {
	info->id = mystrdup(vstring_str(buf));
	status = pickup_copy(qfile, cleanup, info, buf);
    }
    vstream_fclose(qfile);
    vstream_fclose(cleanup);
    vstring_free(buf);
    return (status);
}
Beispiel #22
0
static void tlsmgr_service(VSTREAM *client_stream, char *unused_service,
			           char **argv)
{
    static VSTRING *request = 0;
    static VSTRING *cache_type = 0;
    static VSTRING *cache_id = 0;
    static VSTRING *buffer = 0;
    int     len;
    static char wakeup[] = {		/* master wakeup request */
	TRIGGER_REQ_WAKEUP,
	0,
    };
    TLSMGR_SCACHE *ent;
    int     status = TLS_MGR_STAT_FAIL;

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

    /*
     * Initialize. We're select threaded, so we can use static buffers.
     */
    if (request == 0) {
	request = vstring_alloc(10);
	cache_type = vstring_alloc(10);
	cache_id = vstring_alloc(10);
	buffer = vstring_alloc(10);
    }

    /*
     * This routine runs whenever a client connects to the socket dedicated
     * to the tlsmgr service (including wake up events sent by the master).
     * All connection-management stuff is handled by the common code in
     * multi_server.c.
     */
    if (tlsmgr_request_receive(client_stream, request) == 0) {

	/*
	 * Load session from cache.
	 */
	if (STREQ(STR(request), TLS_MGR_REQ_LOOKUP)) {
	    if (attr_scan(client_stream, ATTR_FLAG_STRICT,
			  ATTR_TYPE_STR, TLS_MGR_ATTR_CACHE_TYPE, cache_type,
			  ATTR_TYPE_STR, TLS_MGR_ATTR_CACHE_ID, cache_id,
			  ATTR_TYPE_END) == 2) {
		for (ent = cache_table; ent->cache_label; ++ent)
		    if (strcmp(ent->cache_label, STR(cache_type)) == 0)
			break;
		if (ent->cache_label == 0) {
		    msg_warn("bogus cache type \"%s\" in \"%s\" request",
			     STR(cache_type), TLS_MGR_REQ_LOOKUP);
		    VSTRING_RESET(buffer);
		} else if (ent->cache_info == 0) {

		    /*
		     * Cache type valid, but not enabled
		     */
		    VSTRING_RESET(buffer);
		} else {
		    status = tls_scache_lookup(ent->cache_info,
					       STR(cache_id), buffer) ?
			TLS_MGR_STAT_OK : TLS_MGR_STAT_ERR;
		}
	    }
	    attr_print(client_stream, ATTR_FLAG_NONE,
		       ATTR_TYPE_INT, MAIL_ATTR_STATUS, status,
		       ATTR_TYPE_DATA, TLS_MGR_ATTR_SESSION,
		       LEN(buffer), STR(buffer),
		       ATTR_TYPE_END);
	}

	/*
	 * Save session to cache.
	 */
	else if (STREQ(STR(request), TLS_MGR_REQ_UPDATE)) {
	    if (attr_scan(client_stream, ATTR_FLAG_STRICT,
			  ATTR_TYPE_STR, TLS_MGR_ATTR_CACHE_TYPE, cache_type,
			  ATTR_TYPE_STR, TLS_MGR_ATTR_CACHE_ID, cache_id,
			  ATTR_TYPE_DATA, TLS_MGR_ATTR_SESSION, buffer,
			  ATTR_TYPE_END) == 3) {
		for (ent = cache_table; ent->cache_label; ++ent)
		    if (strcmp(ent->cache_label, STR(cache_type)) == 0)
			break;
		if (ent->cache_label == 0) {
		    msg_warn("bogus cache type \"%s\" in \"%s\" request",
			     STR(cache_type), TLS_MGR_REQ_UPDATE);
		} else if (ent->cache_info != 0) {
		    status =
			tls_scache_update(ent->cache_info, STR(cache_id),
					  STR(buffer), LEN(buffer)) ?
			TLS_MGR_STAT_OK : TLS_MGR_STAT_ERR;
		}
	    }
	    attr_print(client_stream, ATTR_FLAG_NONE,
		       ATTR_TYPE_INT, MAIL_ATTR_STATUS, status,
		       ATTR_TYPE_END);
	}

	/*
	 * Delete session from cache.
	 */
	else if (STREQ(STR(request), TLS_MGR_REQ_DELETE)) {
	    if (attr_scan(client_stream, ATTR_FLAG_STRICT,
			  ATTR_TYPE_STR, TLS_MGR_ATTR_CACHE_TYPE, cache_type,
			  ATTR_TYPE_STR, TLS_MGR_ATTR_CACHE_ID, cache_id,
			  ATTR_TYPE_END) == 2) {
		for (ent = cache_table; ent->cache_label; ++ent)
		    if (strcmp(ent->cache_label, STR(cache_type)) == 0)
			break;
		if (ent->cache_label == 0) {
		    msg_warn("bogus cache type \"%s\" in \"%s\" request",
			     STR(cache_type), TLS_MGR_REQ_DELETE);
		} else if (ent->cache_info != 0) {
		    status = tls_scache_delete(ent->cache_info,
					       STR(cache_id)) ?
			TLS_MGR_STAT_OK : TLS_MGR_STAT_ERR;
		}
	    }
	    attr_print(client_stream, ATTR_FLAG_NONE,
		       ATTR_TYPE_INT, MAIL_ATTR_STATUS, status,
		       ATTR_TYPE_END);
	}

	/*
	 * RFC 5077 TLS session ticket keys
	 */
	else if (STREQ(STR(request), TLS_MGR_REQ_TKTKEY)) {
	    if (attr_scan(client_stream, ATTR_FLAG_STRICT,
			  ATTR_TYPE_DATA, TLS_MGR_ATTR_KEYNAME, buffer,
			  ATTR_TYPE_END) == 1) {
		if (LEN(buffer) != 0 && LEN(buffer) != TLS_TICKET_NAMELEN) {
		    msg_warn("invalid session ticket key name length: %ld",
			     (long) LEN(buffer));
		    VSTRING_RESET(buffer);
		} else if (*smtpd_cache.cache_timeout <= 0) {
		    status = TLS_MGR_STAT_ERR;
		    VSTRING_RESET(buffer);
		} else {
		    status = tlsmgr_key(buffer, *smtpd_cache.cache_timeout);
		}
	    }
	    attr_print(client_stream, ATTR_FLAG_NONE,
		       ATTR_TYPE_INT, MAIL_ATTR_STATUS, status,
		       ATTR_TYPE_DATA, TLS_MGR_ATTR_KEYBUF,
		       LEN(buffer), STR(buffer),
		       ATTR_TYPE_END);
	}

	/*
	 * Entropy request.
	 */
	else if (STREQ(STR(request), TLS_MGR_REQ_SEED)) {
	    if (attr_scan(client_stream, ATTR_FLAG_STRICT,
			  ATTR_TYPE_INT, TLS_MGR_ATTR_SIZE, &len,
			  ATTR_TYPE_END) == 1) {
		VSTRING_RESET(buffer);
		if (len <= 0 || len > 255) {
		    msg_warn("bogus seed length \"%d\" in \"%s\" request",
			     len, TLS_MGR_REQ_SEED);
		} else {
		    VSTRING_SPACE(buffer, len);
		    RAND_bytes((unsigned char *) STR(buffer), len);
		    VSTRING_AT_OFFSET(buffer, len);	/* XXX not part of the
							 * official interface */
		    status = TLS_MGR_STAT_OK;
		}
	    }
	    attr_print(client_stream, ATTR_FLAG_NONE,
		       ATTR_TYPE_INT, MAIL_ATTR_STATUS, status,
		       ATTR_TYPE_DATA, TLS_MGR_ATTR_SEED,
		       LEN(buffer), STR(buffer),
		       ATTR_TYPE_END);
	}

	/*
	 * Caching policy request.
	 */
	else if (STREQ(STR(request), TLS_MGR_REQ_POLICY)) {
	    int     cachable = 0;
	    int     timeout = 0;

	    if (attr_scan(client_stream, ATTR_FLAG_STRICT,
			  ATTR_TYPE_STR, TLS_MGR_ATTR_CACHE_TYPE, cache_type,
			  ATTR_TYPE_END) == 1) {
		for (ent = cache_table; ent->cache_label; ++ent)
		    if (strcmp(ent->cache_label, STR(cache_type)) == 0)
			break;
		if (ent->cache_label == 0) {
		    msg_warn("bogus cache type \"%s\" in \"%s\" request",
			     STR(cache_type), TLS_MGR_REQ_POLICY);
		} else {
		    cachable = (ent->cache_info != 0) ? 1 : 0;
		    timeout = *ent->cache_timeout;
		    status = TLS_MGR_STAT_OK;
		}
	    }
	    attr_print(client_stream, ATTR_FLAG_NONE,
		       ATTR_TYPE_INT, MAIL_ATTR_STATUS, status,
		       ATTR_TYPE_INT, TLS_MGR_ATTR_CACHABLE, cachable,
		       ATTR_TYPE_INT, TLS_MGR_ATTR_SESSTOUT, timeout,
		       ATTR_TYPE_END);
	}

	/*
	 * Master trigger. Normally, these triggers arrive only after some
	 * other process requested the tlsmgr's service. The purpose is to
	 * restart the tlsmgr after it aborted due to a fatal run-time error,
	 * so that it can continue its housekeeping even while nothing is
	 * using TLS.
	 * 
	 * XXX Which begs the question, if TLS isn't used often, do we need a
	 * tlsmgr background process? It could terminate when the session
	 * caches are empty.
	 */
	else if (STREQ(STR(request), wakeup)) {
	    if (msg_verbose)
		msg_info("received master trigger");
	    multi_server_disconnect(client_stream);
	    return;				/* NOT: vstream_fflush */
	}
    }

    /*
     * Protocol error.
     */
    else {
	attr_print(client_stream, ATTR_FLAG_NONE,
		   ATTR_TYPE_INT, MAIL_ATTR_STATUS, TLS_MGR_STAT_FAIL,
		   ATTR_TYPE_END);
    }
    vstream_fflush(client_stream);
}
Beispiel #23
0
static void flush_service(VSTREAM *client_stream, char *unused_service,
			          char **argv)
{
    VSTRING *request = vstring_alloc(10);
    VSTRING *site = 0;
    VSTRING *queue_id = 0;
    static char wakeup[] = {		/* master wakeup request */
	TRIGGER_REQ_WAKEUP,
	0,
    };
    int     status = FLUSH_STAT_BAD;

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

    /*
     * This routine runs whenever a client connects to the UNIX-domain socket
     * dedicated to the fast flush service. What we see below is a little
     * protocol to (1) read a request from the client (the name of the site)
     * and (2) acknowledge that we have received the request.
     * 
     * All connection-management stuff is handled by the common code in
     * single_server.c.
     */
    if (flush_request_receive(client_stream, request) == 0) {
	if (STREQ(STR(request), FLUSH_REQ_ADD)) {
	    site = vstring_alloc(10);
	    queue_id = vstring_alloc(10);
	    if (attr_scan(client_stream, ATTR_FLAG_STRICT,
			  RECV_ATTR_STR(MAIL_ATTR_SITE, site),
			  RECV_ATTR_STR(MAIL_ATTR_QUEUEID, queue_id),
			  ATTR_TYPE_END) == 2
		&& mail_queue_id_ok(STR(queue_id)))
		status = flush_add_service(STR(site), STR(queue_id));
	    attr_print(client_stream, ATTR_FLAG_NONE,
		       SEND_ATTR_INT(MAIL_ATTR_STATUS, status),
		       ATTR_TYPE_END);
	} else if (STREQ(STR(request), FLUSH_REQ_SEND_SITE)) {
	    site = vstring_alloc(10);
	    if (attr_scan(client_stream, ATTR_FLAG_STRICT,
			  RECV_ATTR_STR(MAIL_ATTR_SITE, site),
			  ATTR_TYPE_END) == 1)
		status = flush_send_service(STR(site), UNTHROTTLE_BEFORE);
	    attr_print(client_stream, ATTR_FLAG_NONE,
		       SEND_ATTR_INT(MAIL_ATTR_STATUS, status),
		       ATTR_TYPE_END);
	} else if (STREQ(STR(request), FLUSH_REQ_SEND_FILE)) {
	    queue_id = vstring_alloc(10);
	    if (attr_scan(client_stream, ATTR_FLAG_STRICT,
			  RECV_ATTR_STR(MAIL_ATTR_QUEUEID, queue_id),
			  ATTR_TYPE_END) == 1)
		status = flush_send_file_service(STR(queue_id));
	    attr_print(client_stream, ATTR_FLAG_NONE,
		       SEND_ATTR_INT(MAIL_ATTR_STATUS, status),
		       ATTR_TYPE_END);
	} else if (STREQ(STR(request), FLUSH_REQ_REFRESH)
		   || STREQ(STR(request), wakeup)) {
	    attr_print(client_stream, ATTR_FLAG_NONE,
		       SEND_ATTR_INT(MAIL_ATTR_STATUS, FLUSH_STAT_OK),
		       ATTR_TYPE_END);
	    vstream_fflush(client_stream);
	    (void) flush_refresh_service(var_fflush_refresh);
	} else if (STREQ(STR(request), FLUSH_REQ_PURGE)) {
	    attr_print(client_stream, ATTR_FLAG_NONE,
		       SEND_ATTR_INT(MAIL_ATTR_STATUS, FLUSH_STAT_OK),
		       ATTR_TYPE_END);
	    vstream_fflush(client_stream);
	    (void) flush_refresh_service(0);
	}
    } else
	attr_print(client_stream, ATTR_FLAG_NONE,
		   SEND_ATTR_INT(MAIL_ATTR_STATUS, status),
		   ATTR_TYPE_END);
    vstring_free(request);
    if (site)
	vstring_free(site);
    if (queue_id)
	vstring_free(queue_id);
}
Beispiel #24
0
MILTERS *milter_receive(VSTREAM *stream, int count)
{
    MILTERS *milters;
    MILTER *head = 0;
    MILTER *tail = 0;
    MILTER *milter = 0;

    if (msg_verbose)
	msg_info("receive %d milters", count);

    /*
     * XXX We must instantiate a MILTERS structure even when the sender has
     * no active filters, otherwise the cleanup server would try to use its
     * own non_smtpd_milters settings.
     */
#define NO_MILTERS	((char *) 0)
#define NO_TIMEOUTS	0, 0, 0
#define NO_PROTOCOL	((char *) 0)
#define NO_ACTION	((char *) 0)
#define NO_MACROS	((MILTER_MACROS *) 0)

    milters = milter_new(NO_MILTERS, NO_TIMEOUTS, NO_PROTOCOL, NO_ACTION,
			 NO_MACROS);

    /*
     * XXX Optimization: don't send or receive further information when there
     * aren't any active filters.
     */
    if (count <= 0)
	return (milters);

    /*
     * Receive the global macro name lists.
     */
    milters->macros = milter_macros_alloc(MILTER_MACROS_ALLOC_ZERO);
    if (attr_scan(stream, ATTR_FLAG_STRICT | ATTR_FLAG_MORE,
		  ATTR_TYPE_FUNC, milter_macros_scan,
		  (void *) milters->macros,
		  ATTR_TYPE_END) != 1) {
	milter_free(milters);
	return (0);
    }

    /*
     * Receive the filters.
     */
    for (; count > 0; count--) {
	if ((milter = milter8_receive(stream, milters)) == 0) {
	    msg_warn("cannot receive milters via service %s socket",
		     VSTREAM_PATH(stream));
	    milter_free(milters);
	    return (0);
	}
	if (head == 0) {
	    /* Coverity: milter_free() depends on milters->milter_list. */
	    milters->milter_list = head = milter;
	} else {
	    tail->next = milter;
	}
	tail = milter;
    }

    /*
     * Over to you.
     */
    (void) attr_print(stream, ATTR_FLAG_NONE,
		      ATTR_TYPE_INT, MAIL_ATTR_STATUS, 0,
		      ATTR_TYPE_END);
    return (milters);
}
Beispiel #25
0
int     milter_send(MILTERS *milters, VSTREAM *stream)
{
    MILTER *m;
    int     status = 0;
    int     count = 0;

    /*
     * XXX Optimization: send only the filters that are actually used in the
     * remote process. No point sending a filter that looks at HELO commands
     * to a cleanup server. For now we skip only the filters that are known
     * to be disabled (either in global error state or in global accept
     * state).
     * 
     * XXX We must send *some* information, even when there are no active
     * filters, otherwise the cleanup server would try to apply its own
     * non_smtpd_milters settings.
     */
    if (milters != 0)
	for (m = milters->milter_list; m != 0; m = m->next)
	    if (m->active(m))
		count++;
    (void) rec_fprintf(stream, REC_TYPE_MILT_COUNT, "%d", count);

    if (msg_verbose)
	msg_info("send %d milters", count);

    /*
     * XXX Optimization: don't send or receive further information when there
     * aren't any active filters.
     */
    if (count <= 0)
	return (0);

    /*
     * Send the filter macro name lists.
     */
    (void) attr_print(stream, ATTR_FLAG_MORE,
		      ATTR_TYPE_FUNC, milter_macros_print,
		      (void *) milters->macros,
		      ATTR_TYPE_END);

    /*
     * Send the filter instances.
     */
    for (m = milters->milter_list; m != 0; m = m->next)
	if (m->active(m) && (status = m->send(m, stream)) != 0)
	    break;

    /*
     * Over to you.
     */
    if (status != 0
	|| attr_scan(stream, ATTR_FLAG_STRICT,
		     ATTR_TYPE_INT, MAIL_ATTR_STATUS, &status,
		     ATTR_TYPE_END) != 1
	|| status != 0) {
	msg_warn("cannot send milters to service %s", VSTREAM_PATH(stream));
	return (-1);
    }
    return (0);
}
Beispiel #26
0
static void psc_dnsbl_receive(int event, void *context)
{
    const char *myname = "psc_dnsbl_receive";
    VSTREAM *stream = (VSTREAM *) context;
    PSC_DNSBL_SCORE *score;
    PSC_DNSBL_HEAD *head;
    PSC_DNSBL_SITE *site;
    ARGV   *reply_argv;
    int     request_id;
    int     dnsbl_ttl;

    PSC_CLEAR_EVENT_REQUEST(vstream_fileno(stream), psc_dnsbl_receive, context);

    /*
     * Receive the DNSBL lookup result.
     * 
     * This is preliminary code to explore the field. Later, DNSBL lookup will
     * be handled by an UDP-based DNS client that is built directly into some
     * Postfix daemon.
     * 
     * Don't bother looking up the blocklist score when the client IP address is
     * not listed at the DNSBL.
     * 
     * Don't panic when the blocklist score no longer exists. It may be deleted
     * when the client triggers a "drop" action after pregreet, when the
     * client does not pregreet and the DNSBL reply arrives late, or when the
     * client triggers a "drop" action after hanging up.
     */
    if (event == EVENT_READ
	&& attr_scan(stream,
		     ATTR_FLAG_STRICT,
		     RECV_ATTR_STR(MAIL_ATTR_RBL_DOMAIN, reply_dnsbl),
		     RECV_ATTR_STR(MAIL_ATTR_ACT_CLIENT_ADDR, reply_client),
		     RECV_ATTR_INT(MAIL_ATTR_LABEL, &request_id),
		     RECV_ATTR_STR(MAIL_ATTR_RBL_ADDR, reply_addr),
		     RECV_ATTR_INT(MAIL_ATTR_TTL, &dnsbl_ttl),
		     ATTR_TYPE_END) == 5
	&& (score = (PSC_DNSBL_SCORE *)
	    htable_find(dnsbl_score_cache, STR(reply_client))) != 0
	&& score->request_id == request_id) {

	/*
	 * Run this response past all applicable DNSBL filters and update the
	 * blocklist score for this client IP address.
	 * 
	 * Don't panic when the DNSBL domain name is not found. The DNSBLOG
	 * server may be messed up.
	 */
	if (msg_verbose > 1)
	    msg_info("%s: client=\"%s\" score=%d domain=\"%s\" reply=\"%d %s\"",
		     myname, STR(reply_client), score->total,
		     STR(reply_dnsbl), dnsbl_ttl, STR(reply_addr));
	head = (PSC_DNSBL_HEAD *)
	    htable_find(dnsbl_site_cache, STR(reply_dnsbl));
	if (head == 0) {
	    /* Bogus domain. Do nothing. */
	} else if (*STR(reply_addr) != 0) {
	    /* DNS reputation record(s) found. */
	    reply_argv = 0;
	    for (site = head->first; site != 0; site = site->next) {
		if (site->byte_codes == 0
		    || psc_dnsbl_match(site->byte_codes, reply_argv ? reply_argv :
			 (reply_argv = argv_split(STR(reply_addr), " ")))) {
		    if (score->dnsbl_name == 0
			|| score->dnsbl_weight < site->weight) {
			score->dnsbl_name = head->safe_dnsbl;
			score->dnsbl_weight = site->weight;
		    }
		    score->total += site->weight;
		    if (msg_verbose > 1)
			msg_info("%s: filter=\"%s\" weight=%d score=%d",
			       myname, site->filter ? site->filter : "null",
				 site->weight, score->total);
		}
		/* As with dnsblog(8), a value < 0 means no reply TTL. */
		if (site->weight > 0) {
		    if (score->fail_ttl < 0 || score->fail_ttl > dnsbl_ttl)
			score->fail_ttl = dnsbl_ttl;
		} else {
		    if (score->pass_ttl < 0 || score->pass_ttl > dnsbl_ttl)
			score->pass_ttl = dnsbl_ttl;
		}
	    }
	    if (reply_argv != 0)
		argv_free(reply_argv);
	} else {
	    /* No DNS reputation record found. */
	    for (site = head->first; site != 0; site = site->next) {
		/* As with dnsblog(8), a value < 0 means no reply TTL. */
		if (site->weight > 0) {
		    if (score->pass_ttl < 0 || score->pass_ttl > dnsbl_ttl)
			score->pass_ttl = dnsbl_ttl;
		} else {
		    if (score->fail_ttl < 0 || score->fail_ttl > dnsbl_ttl)
			score->fail_ttl = dnsbl_ttl;
		}
	    }
	}

	/*
	 * Notify the requestor(s) that the result is ready to be picked up.
	 * If this call isn't made, clients have to sit out the entire
	 * pre-handshake delay.
	 */
	score->pending_lookups -= 1;
	if (score->pending_lookups == 0)
	    PSC_CALL_BACK_NOTIFY(score, PSC_NULL_EVENT);
    } else if (event == EVENT_TIME) {
	msg_warn("dnsblog reply timeout %ds for %s",
		 var_psc_dnsbl_tmout, (char *) vstream_context(stream));
    }
    /* Here, score may be a null pointer. */
    vstream_fclose(stream);
}
Beispiel #27
0
static void verify_query_service(VSTREAM *client_stream)
{
    VSTRING *addr = vstring_alloc(10);
    VSTRING *get_buf = 0;
    VSTRING *put_buf = 0;
    const char *raw_data;
    int     addr_status;
    long    probed;
    long    updated;
    char   *text;

    if (attr_scan(client_stream, ATTR_FLAG_STRICT,
		  RECV_ATTR_STR(MAIL_ATTR_ADDR, addr),
		  ATTR_TYPE_END) == 1) {
	long    now = (long) time((time_t *) 0);

	/*
	 * Produce a default record when no usable record exists.
	 * 
	 * If negative caching is disabled, purge an expired record from the
	 * database.
	 * 
	 * XXX Assume that a probe is lost if no response is received in 1000
	 * seconds. If this number is too small the queue will slowly fill up
	 * with delayed probes.
	 * 
	 * XXX Maintain a moving average for the probe turnaround time, and
	 * allow probe "retransmission" when a probe is outstanding for, say
	 * some minimal amount of time (1000 sec) plus several times the
	 * observed probe turnaround time. This causes probing to back off
	 * when the mail system becomes congested.
	 */
#define POSITIVE_ENTRY_EXPIRED(addr_status, updated) \
    (addr_status == DEL_RCPT_STAT_OK && updated + var_verify_pos_exp < now)
#define NEGATIVE_ENTRY_EXPIRED(addr_status, updated) \
    (addr_status != DEL_RCPT_STAT_OK && updated + var_verify_neg_exp < now)
#define PROBE_TTL	1000

	/* FIX 200501 IPv6 patch did not neuter ":" in address literals. */
	translit(STR(addr), ":", "_");
	if ((raw_data = dict_cache_lookup(verify_map, STR(addr))) == 0	/* not found */
	    || ((get_buf = vstring_alloc(10)),
		vstring_strcpy(get_buf, raw_data),	/* malformed */
		verify_parse_entry(STR(get_buf), &addr_status, &probed,
				   &updated, &text) < 0)
	    || (now - probed > PROBE_TTL	/* safe to probe */
		&& (POSITIVE_ENTRY_EXPIRED(addr_status, updated)
		    || NEGATIVE_ENTRY_EXPIRED(addr_status, updated)))) {
	    addr_status = DEL_RCPT_STAT_TODO;
	    probed = 0;
	    updated = 0;
	    text = "Address verification in progress";
	    if (raw_data != 0 && var_verify_neg_cache == 0)
		dict_cache_delete(verify_map, STR(addr));
	}
	if (msg_verbose)
	    msg_info("GOT %s status=%d probed=%ld updated=%ld text=%s",
		     STR(addr), addr_status, probed, updated, text);

	/*
	 * Respond to the client.
	 */
	attr_print(client_stream, ATTR_FLAG_NONE,
		   SEND_ATTR_INT(MAIL_ATTR_STATUS, VRFY_STAT_OK),
		   SEND_ATTR_INT(MAIL_ATTR_ADDR_STATUS, addr_status),
		   SEND_ATTR_STR(MAIL_ATTR_WHY, text),
		   ATTR_TYPE_END);

	/*
	 * Send a new probe when the information needs to be refreshed.
	 * 
	 * XXX For an initial proof of concept implementation, use synchronous
	 * mail submission. This needs to be made async for high-volume
	 * sites, which makes it even more interesting to eliminate duplicate
	 * queries while a probe is being built.
	 * 
	 * If negative caching is turned off, update the database only when
	 * refreshing an existing entry.
	 */
#define POSITIVE_REFRESH_NEEDED(addr_status, updated) \
    (addr_status == DEL_RCPT_STAT_OK && updated + var_verify_pos_try < now)
#define NEGATIVE_REFRESH_NEEDED(addr_status, updated) \
    (addr_status != DEL_RCPT_STAT_OK && updated + var_verify_neg_try < now)

	if (now - probed > PROBE_TTL
	    && (POSITIVE_REFRESH_NEEDED(addr_status, updated)
		|| NEGATIVE_REFRESH_NEEDED(addr_status, updated))) {
	    if (msg_verbose)
		msg_info("PROBE %s status=%d probed=%ld updated=%ld",
			 STR(addr), addr_status, now, updated);
	    post_mail_fopen_async(make_verify_sender_addr(), STR(addr),
				  MAIL_SRC_MASK_VERIFY,
				  DEL_REQ_FLAG_MTA_VRFY,
				  SMTPUTF8_FLAG_NONE,
				  (VSTRING *) 0,
				  verify_post_mail_action,
				  (void *) 0);
	    if (updated != 0 || var_verify_neg_cache != 0) {
		put_buf = vstring_alloc(10);
		verify_make_entry(put_buf, addr_status, now, updated, text);
		if (msg_verbose)
		    msg_info("PUT %s status=%d probed=%ld updated=%ld text=%s",
			     STR(addr), addr_status, now, updated, text);
		dict_cache_update(verify_map, STR(addr), STR(put_buf));
	    }
	}
    }
    vstring_free(addr);
    if (get_buf)
	vstring_free(get_buf);
    if (put_buf)
	vstring_free(put_buf);
}
Beispiel #28
0
static void bounce_service(VSTREAM *client, char *service_name, char **argv)
{
    int     command;
    int     status;

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

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

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

    /*
     * When a cleanup trap was set, delete the log file in case of error.
     * This includes errors while sending the completion status to the
     * client.
     */
    if (bounce_cleanup_path) {
	if (status || vstream_ferror(client))
	    bounce_cleanup_log();
	bounce_cleanup_unregister();
    }
}
Beispiel #29
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 #30
0
VSTREAM *tls_proxy_open(const char *service, int flags,
			        VSTREAM *peer_stream,
			        const char *peer_addr,
			        const char *peer_port,
			        int timeout)
{
    VSTREAM *tlsproxy_stream;
    int     status;
    int     fd;
    static VSTRING *tlsproxy_service = 0;
    static VSTRING *remote_endpt = 0;

    /*
     * Initialize.
     */
    if (tlsproxy_service == 0) {
	tlsproxy_service = vstring_alloc(20);
	remote_endpt = vstring_alloc(20);
    }

    /*
     * Connect to the tlsproxy(8) daemon.
     */
    vstring_sprintf(tlsproxy_service, "%s/%s", MAIL_CLASS_PRIVATE, service);
    if ((fd = LOCAL_CONNECT(STR(tlsproxy_service), BLOCKING,
			    TLSPROXY_INIT_TIMEOUT)) < 0) {
	msg_warn("connect to %s service: %m", STR(tlsproxy_service));
	return (0);
    }

    /*
     * Initial handshake. Send the data attributes now, and send the client
     * file descriptor in a later transaction.
     * 
     * XXX The formatted endpoint should be a state member. Then, we can
     * simplify all the format strings throughout the program.
     */
    tlsproxy_stream = vstream_fdopen(fd, O_RDWR);
    vstring_sprintf(remote_endpt, "[%s]:%s", peer_addr, peer_port);
    attr_print(tlsproxy_stream, ATTR_FLAG_NONE,
	       ATTR_TYPE_STR, MAIL_ATTR_REMOTE_ENDPT, STR(remote_endpt),
	       ATTR_TYPE_INT, MAIL_ATTR_FLAGS, flags,
	       ATTR_TYPE_INT, MAIL_ATTR_TIMEOUT, timeout,
	       ATTR_TYPE_END);
    if (vstream_fflush(tlsproxy_stream) != 0) {
	msg_warn("error sending request to %s service: %m",
		 STR(tlsproxy_service));
	vstream_fclose(tlsproxy_stream);
	return (0);
    }

    /*
     * Receive the "TLS is available" indication.
     * 
     * This may seem out of order, but we must have a read transaction between
     * sending the request attributes and sending the SMTP client file
     * descriptor. We can't assume UNIX-domain socket semantics here.
     */
    if (attr_scan(tlsproxy_stream, ATTR_FLAG_STRICT,
		  ATTR_TYPE_INT, MAIL_ATTR_STATUS, &status,
		  ATTR_TYPE_END) != 1 || status == 0) {

	/*
	 * The TLS proxy reports that the TLS engine is not available (due to
	 * configuration error, or other causes).
	 */
	msg_warn("%s service role \"%s\" is not available",
		 STR(tlsproxy_service),
		 (flags & TLS_PROXY_FLAG_ROLE_SERVER) ? "server" :
		 (flags & TLS_PROXY_FLAG_ROLE_CLIENT) ? "client" :
		 "bogus role");
	vstream_fclose(tlsproxy_stream);
	return (0);
    }

    /*
     * Send the remote SMTP client file descriptor.
     */
    if (LOCAL_SEND_FD(vstream_fileno(tlsproxy_stream),
		      vstream_fileno(peer_stream)) < 0) {

	/*
	 * Some error: drop the TLS proxy stream.
	 */
	msg_warn("sending file handle to %s service: %m",
		 STR(tlsproxy_service));
	vstream_fclose(tlsproxy_stream);
	return (0);
    }
    return (tlsproxy_stream);
}