smtp_server_connection_alloc(struct smtp_server *server,
			     const struct smtp_server_settings *set,
			     int fd_in, int fd_out,
			     const struct smtp_server_callbacks *callbacks,
			     void *context)
{
	struct smtp_server_connection *conn;
	pool_t pool;

	pool = pool_alloconly_create("smtp server", 1024);
	conn = p_new(pool, struct smtp_server_connection, 1);
	conn->pool = pool;
	conn->refcount = 1;
	conn->server = server;
	conn->callbacks = callbacks;
	conn->context = context;

	/* Merge settings with global server settings */
	conn->set = server->set;
	if (set != NULL) {
		conn->set.protocol = server->set.protocol;
		if (set->rawlog_dir != NULL && *set->rawlog_dir != '\0')
			conn->set.rawlog_dir = p_strdup(pool, set->rawlog_dir);

		if (set->ssl != NULL)
			conn->set.ssl = ssl_iostream_settings_dup(pool, set->ssl);

		if (set->hostname != NULL && *set->hostname != '\0')
			conn->set.hostname = p_strdup(pool, set->hostname);
		if (set->login_greeting != NULL &&
			*set->login_greeting != '\0') {
			conn->set.login_greeting =
				p_strdup(pool, set->login_greeting);
		}
		if (set->capabilities != 0)
			conn->set.capabilities = set->capabilities;
		conn->set.workarounds |= set->workarounds;

		if (set->max_client_idle_time_msecs > 0) {
			conn->set.max_client_idle_time_msecs =
				set->max_client_idle_time_msecs;
		}
		if (set->max_pipelined_commands > 0) {
			conn->set.max_pipelined_commands =
				set->max_pipelined_commands;
		}
		if (set->max_bad_commands > 0) {
			conn->set.max_bad_commands = set->max_bad_commands;
		}
		if (set->max_recipients > 0)
			conn->set.max_recipients = set->max_recipients;
		smtp_command_limits_merge(&conn->set.command_limits,
					  &set->command_limits);

		conn->set.max_message_size = set->max_message_size;
		if (set->max_message_size == 0 ||
		    set->max_message_size == UOFF_T_MAX) {
			conn->set.command_limits.max_data_size = UOFF_T_MAX;
		} else if (conn->set.command_limits.max_data_size != 0) {
			/* Explicit limit given */
		} else if (set->max_message_size >
			(UOFF_T_MAX - SMTP_SERVER_DEFAULT_MAX_SIZE_EXCESS_LIMIT)) {
			/* Very high limit */
			conn->set.command_limits.max_data_size = UOFF_T_MAX;
		} else {
			/* Absolute maximum before connection is closed in DATA
			   command */
			conn->set.command_limits.max_data_size =
				set->max_message_size +
					SMTP_SERVER_DEFAULT_MAX_SIZE_EXCESS_LIMIT;
		}

		if (set->mail_param_extensions != NULL) {
			conn->set.mail_param_extensions =
				p_strarray_dup(pool, set->mail_param_extensions);
		}
		if (set->rcpt_param_extensions != NULL) {
			conn->set.rcpt_param_extensions =
				p_strarray_dup(pool, set->rcpt_param_extensions);
		}
		if (set->xclient_extensions != NULL) {
			conn->set.xclient_extensions =
				p_strarray_dup(pool, set->xclient_extensions);
		}

		if (set->socket_send_buffer_size > 0) {
			conn->set.socket_send_buffer_size =
				set->socket_send_buffer_size;
		}
		if (set->socket_recv_buffer_size > 0) {
			conn->set.socket_recv_buffer_size =
				set->socket_recv_buffer_size;
		}

		conn->set.tls_required =
			conn->set.tls_required || set->tls_required;
		conn->set.auth_optional =
			conn->set.auth_optional || set->auth_optional;
		conn->set.mail_path_allow_broken =
			conn->set.mail_path_allow_broken ||
				set->mail_path_allow_broken;
		conn->set.rcpt_domain_optional =
			conn->set.rcpt_domain_optional ||
				set->rcpt_domain_optional;
		conn->set.debug = conn->set.debug || set->debug;
	}

	if (set != NULL && set->mail_param_extensions != NULL) {
		const char *const *extp;

		p_array_init(&conn->mail_param_extensions, pool,
			     str_array_length(set->mail_param_extensions) + 8);
		for (extp = set->mail_param_extensions; *extp != NULL; extp++) {
			const char *ext = p_strdup(pool, *extp);
			array_push_back(&conn->mail_param_extensions, &ext);
		}
		array_append_zero(&conn->mail_param_extensions);
	}
	if (set != NULL && set->rcpt_param_extensions != NULL) {
		const char *const *extp;

		p_array_init(&conn->rcpt_param_extensions, pool,
			     str_array_length(set->rcpt_param_extensions) + 8);
		for (extp = set->rcpt_param_extensions; *extp != NULL; extp++) {
			const char *ext = p_strdup(pool, *extp);
			array_push_back(&conn->rcpt_param_extensions, &ext);
		}
		array_append_zero(&conn->rcpt_param_extensions);
	}

	net_set_nonblock(fd_in, TRUE);
	if (fd_in != fd_out)
		net_set_nonblock(fd_out, TRUE);
	(void)net_set_tcp_nodelay(fd_out, TRUE);

	set = &conn->set;
	if (set->socket_send_buffer_size > 0 &&
	    net_set_send_buffer_size(fd_out,
			             set->socket_send_buffer_size) < 0) {
		e_error(conn->event,
			"net_set_send_buffer_size(%zu) failed: %m",
			set->socket_send_buffer_size);
	}
	if (set->socket_recv_buffer_size > 0 &&
	    net_set_recv_buffer_size(fd_in,
				     set->socket_recv_buffer_size) < 0) {
		e_error(conn->event,
			"net_set_recv_buffer_size(%zu) failed: %m",
			set->socket_recv_buffer_size);
	}

	return conn;
}