Commit message: lib-auth: Fix memory leak in auth_client_request_abort()

This caused memory leaks when authentication was aborted. For example
with IMAP:

a AUTHENTICATE PLAIN
*

Broken by 9137c55411aa39d41c1e705ddc34d5bd26c65021
CVE: CVE-2017-15132
CWE: CWE-284
Vulnerability description: A flaw was found in dovecot 2.0 up to 2.2.33 and 2.3.0. An abort of SASL authentication results in a memory leak in dovecot's auth client used by login processes. The leak has impact in high performance configuration where same login processes are reused and can cause the process to crash due to memory exhaustion.

Project: core

static void auth_server_send_new_request(struct auth_server_connection *conn,
					 struct auth_client_request *request,
					 const struct auth_request_info *info)
{
	string_t *str;

	str = t_str_new(512);
	str_printfa(str, "AUTH\t%u\t", request->id);
	str_append_tabescaped(str, info->mech);
	str_append(str, "\tservice=");
	str_append_tabescaped(str, info->service);

	if ((info->flags & AUTH_REQUEST_FLAG_SUPPORT_FINAL_RESP) != 0)
		str_append(str, "\tfinal-resp-ok");
	if ((info->flags & AUTH_REQUEST_FLAG_SECURED) != 0) {
		str_append(str, "\tsecured");
		if ((info->flags & AUTH_REQUEST_FLAG_TRANSPORT_SECURITY_TLS) != 0)
			str_append(str, "=tls");
	} else {
		i_assert((info->flags & AUTH_REQUEST_FLAG_TRANSPORT_SECURITY_TLS) == 0);
	}
	if ((info->flags & AUTH_REQUEST_FLAG_NO_PENALTY) != 0)
		str_append(str, "\tno-penalty");
	if ((info->flags & AUTH_REQUEST_FLAG_VALID_CLIENT_CERT) != 0)
		str_append(str, "\tvalid-client-cert");
	if ((info->flags & AUTH_REQUEST_FLAG_DEBUG) != 0)
		str_append(str, "\tdebug");

	if (info->session_id != NULL) {
		str_append(str, "\tsession=");
		str_append_tabescaped(str, info->session_id);
	}
	if (info->cert_username != NULL) {
		str_append(str, "\tcert_username=");
		str_append_tabescaped(str, info->cert_username);
	}
	if (info->local_ip.family != 0)
		str_printfa(str, "\tlip=%s", net_ip2addr(&info->local_ip));
	if (info->remote_ip.family != 0)
		str_printfa(str, "\trip=%s", net_ip2addr(&info->remote_ip));
	if (info->local_port != 0)
		str_printfa(str, "\tlport=%u", info->local_port);
	if (info->remote_port != 0)
		str_printfa(str, "\trport=%u", info->remote_port);

	/* send the real_* variants only when they differ from the unreal
	   ones */
	if (info->real_local_ip.family != 0 &&
	    !net_ip_compare(&info->real_local_ip, &info->local_ip)) {
		str_printfa(str, "\treal_lip=%s",
			    net_ip2addr(&info->real_local_ip));
	}
	if (info->real_remote_ip.family != 0 &&
	    !net_ip_compare(&info->real_remote_ip, &info->remote_ip)) {
		str_printfa(str, "\treal_rip=%s",
			    net_ip2addr(&info->real_remote_ip));
	}
	if (info->real_local_port != 0 &&
	    info->real_local_port != info->local_port)
		str_printfa(str, "\treal_lport=%u", info->real_local_port);
	if (info->real_remote_port != 0 &&
	    info->real_remote_port != info->remote_port)
		str_printfa(str, "\treal_rport=%u", info->real_remote_port);
	if (info->local_name != NULL &&
	    *info->local_name != '\0') {
		str_append(str, "\tlocal_name=");
		str_append_tabescaped(str, info->local_name);
	}
	if (info->ssl_cipher_bits != 0 && info->ssl_cipher != NULL) {
		str_append(str, "\tssl_cipher=");
		str_append_tabescaped(str, info->ssl_cipher);
		str_printfa(str, "\tssl_cipher_bits=%u", info->ssl_cipher_bits);
		if (info->ssl_pfs != NULL) {
			str_append(str, "\tssl_pfs=");
			str_append_tabescaped(str, info->ssl_pfs);
		}
	}
	if (info->ssl_protocol != NULL) {
		str_append(str, "\tssl_protocol=");
		str_append_tabescaped(str, info->ssl_protocol);
	}
	if (info->client_id != NULL &&
	    *info->client_id != '\0') {
		str_append(str, "\tclient_id=");
		str_append_tabescaped(str, info->client_id);
	}
	if (info->forward_fields != NULL &&
	    *info->forward_fields != '\0') {
		str_append(str, "\tforward_fields=");
		str_append_tabescaped(str, info->forward_fields);
	}
	if (info->initial_resp_base64 != NULL) {
		str_append(str, "\tresp=");
		str_append_tabescaped(str, info->initial_resp_base64);
	}
	str_append_c(str, '\n');

	if (o_stream_send(conn->output, str_data(str), str_len(str)) < 0)
		i_error("Error sending request to auth server: %m");
}

Project: core

void auth_client_send_cancel(struct auth_client *client, unsigned int id)
{
	const char *str = t_strdup_printf("CANCEL\t%u\n", id);

	if (o_stream_send_str(client->conn->output, str) < 0)
		i_error("Error sending request to auth server: %m");
}

Project: core

unsigned int auth_client_request_get_id(struct auth_client_request *request)
{
	return request->id;
}

Project: core

call_callback(struct auth_client_request *request,
	      enum auth_request_status status,
	      const char *data_base64,
	      const char *const *args)
{
	auth_request_callback_t *callback = request->callback;

	if (status != AUTH_REQUEST_STATUS_CONTINUE)
		request->callback = NULL;
	callback(request, status, data_base64, args, request->context);
}

Project: core

auth_client_request_get_server_pid(struct auth_client_request *request)
{
	return request->conn->server_pid;
}

Project: core

void auth_client_request_continue(struct auth_client_request *request,
                                  const char *data_base64)
{
	struct const_iovec iov[3];
	const char *prefix;

	prefix = t_strdup_printf("CONT\t%u\t", request->id);

	iov[0].iov_base = prefix;
	iov[0].iov_len = strlen(prefix);
	iov[1].iov_base = data_base64;
	iov[1].iov_len = strlen(data_base64);
	iov[2].iov_base = "\n";
	iov[2].iov_len = 1;

	if (o_stream_sendv(request->conn->output, iov, 3) < 0)
		i_error("Error sending continue request to auth server: %m");
}

Project: core

bool auth_client_request_is_aborted(struct auth_client_request *request)
{
	return request->callback == NULL;
}

Project: core

auth_client_request_new(struct auth_client *client,
			const struct auth_request_info *request_info,
			auth_request_callback_t *callback, void *context)
{
	struct auth_client_request *request;
	pool_t pool;

	pool = pool_alloconly_create("auth client request", 512);
	request = p_new(pool, struct auth_client_request, 1);
	request->pool = pool;
	request->conn = client->conn;

	request->callback = callback;
	request->context = context;

	request->id =
		auth_server_connection_add_request(request->conn, request);
	request->created = ioloop_time;
	T_BEGIN {
		auth_server_send_new_request(request->conn, request, request_info);
	} T_END;
	return request;
}

Project: core

void auth_client_request_server_input(struct auth_client_request *request,
				      enum auth_request_status status,
				      const char *const *args)
{
	const char *const *tmp, *base64_data = NULL;

	if (request->callback == NULL) {
		/* aborted already */
		return;
	}

	switch (status) {
	case AUTH_REQUEST_STATUS_OK:
		for (tmp = args; *tmp != NULL; tmp++) {
			if (strncmp(*tmp, "resp=", 5) == 0) {
				base64_data = *tmp + 5;
				break;
			}
		}
		break;
	case AUTH_REQUEST_STATUS_CONTINUE:
		base64_data = args[0];
		args = NULL;
		break;
	case AUTH_REQUEST_STATUS_FAIL:
	case AUTH_REQUEST_STATUS_INTERNAL_FAIL:
	case AUTH_REQUEST_STATUS_ABORT:
		break;
	}

	call_callback(request, status, base64_data, args);
	if (status != AUTH_REQUEST_STATUS_CONTINUE)
		pool_unref(&request->pool);
}

Project: core

const char *auth_client_request_get_cookie(struct auth_client_request *request)
{
	return request->conn->cookie;
}

Project: core

time_t auth_client_request_get_create_time(struct auth_client_request *request)
{
	return request->created;
}