Commit message: Prevent the LDAP validator from accepting an empty password.
CVE: CVE-2014-4668
CWE: CWE-287
Vulnerability description: The cherokee_validator_ldap_check function in validator_ldap.c in Cherokee 1.2.103 and earlier, when LDAP is used, does not properly consider unauthenticated-bind semantics, which allows remote attackers to bypass authentication via an empty password.

Project: webserver
File: validator_ldap.c

cherokee_validator_ldap_add_headers (cherokee_validator_ldap_t *ldap, cherokee_connection_t *conn, cherokee_buffer_t *buf)
{
	UNUSED(ldap);
	UNUSED(conn);
	UNUSED(buf);

	return ret_ok;
}

Project: webserver
File: validator_ldap.c

cherokee_validator_ldap_configure (cherokee_config_node_t *conf, cherokee_server_t *srv, cherokee_module_props_t **_props)
{
	ret_t                            ret;
	cherokee_list_t                 *i;
	cherokee_validator_ldap_props_t *props;

	UNUSED(srv);

	if (*_props == NULL) {
		CHEROKEE_NEW_STRUCT (n, validator_ldap_props);

		cherokee_validator_props_init_base (VALIDATOR_PROPS(n), MODULE_PROPS_FREE(props_free));

		n->port = LDAP_DEFAULT_PORT;
		n->tls  = false;

		cherokee_buffer_init (&n->server);
		cherokee_buffer_init (&n->binddn);
		cherokee_buffer_init (&n->bindpw);
		cherokee_buffer_init (&n->basedn);
		cherokee_buffer_init (&n->filter);
		cherokee_buffer_init (&n->ca_file);

		*_props = MODULE_PROPS(n);
	}

	props = PROP_LDAP(*_props);

	cherokee_config_node_foreach (i, conf) {
		cherokee_config_node_t *subconf = CONFIG_NODE(i);

		if (equal_buf_str (&subconf->key, "server")) {
			cherokee_buffer_add_buffer (&props->server, &subconf->val);

		} else if (equal_buf_str (&subconf->key, "port")) {
			ret = cherokee_atoi (subconf->val.buf, &props->port);
			if (ret != ret_ok) return ret_error;

		} else if (equal_buf_str (&subconf->key, "bind_dn")) {
			cherokee_buffer_add_buffer (&props->binddn, &subconf->val);

		} else if (equal_buf_str (&subconf->key, "bind_pw")) {
			cherokee_buffer_add_buffer (&props->bindpw, &subconf->val);

		} else if (equal_buf_str (&subconf->key, "base_dn")) {
			cherokee_buffer_add_buffer (&props->basedn, &subconf->val);

		} else if (equal_buf_str (&subconf->key, "filter")) {
			cherokee_buffer_add_buffer (&props->filter, &subconf->val);

		} else if (equal_buf_str (&subconf->key, "tls")) {
			ret = cherokee_atob (subconf->val.buf, &props->tls);
			if (ret != ret_ok) return ret_error;

		} else if (equal_buf_str (&subconf->key, "ca_file")) {
			cherokee_buffer_add_buffer (&props->ca_file, &subconf->val);

		} else if (equal_buf_str (&subconf->key, "methods") ||
		           equal_buf_str (&subconf->key, "realm")   ||
		           equal_buf_str (&subconf->key, "users")) {
			/* Handled in validator.c
			 */
		} else {
			LOG_WARNING (CHEROKEE_ERROR_VALIDATOR_LDAP_KEY, subconf->key.buf);
		}
	}

	/* Checks
	 */
	if (cherokee_buffer_is_empty (&props->basedn)) {
		LOG_ERROR (CHEROKEE_ERROR_VALIDATOR_LDAP_PROPERTY, "base_dn");
		return ret_error;
	}

	if (cherokee_buffer_is_empty (&props->server)) {
		LOG_ERROR (CHEROKEE_ERROR_VALIDATOR_LDAP_PROPERTY, "server");
		return ret_error;
	}

	if ((cherokee_buffer_is_empty (&props->bindpw) &&
	     (! cherokee_buffer_is_empty (&props->basedn))))
	{
		LOG_ERROR_S (CHEROKEE_ERROR_VALIDATOR_LDAP_SECURITY);
		return ret_error;
	}

	return ret_ok;
}

Project: webserver
File: validator_ldap.c

cherokee_validator_ldap_free (cherokee_validator_ldap_t *ldap)
{
	cherokee_buffer_mrproper (&ldap->filter);

	if (ldap->conn)
		ldap_unbind (ldap->conn);

	return ret_ok;
}

Project: webserver
File: validator_ldap.c

cherokee_validator_ldap_new (cherokee_validator_ldap_t **ldap, cherokee_module_props_t *props)
{
	ret_t ret;
	CHEROKEE_NEW_STRUCT(n,validator_ldap);

	/* Init
	 */
	cherokee_validator_init_base (VALIDATOR(n), VALIDATOR_PROPS(props), PLUGIN_INFO_VALIDATOR_PTR(ldap));
	VALIDATOR(n)->support = http_auth_basic;

	MODULE(n)->free           = (module_func_free_t)           cherokee_validator_ldap_free;
	VALIDATOR(n)->check       = (validator_func_check_t)       cherokee_validator_ldap_check;
	VALIDATOR(n)->add_headers = (validator_func_add_headers_t) cherokee_validator_ldap_add_headers;

	/* Init properties
	 */
	cherokee_buffer_init (&n->filter);

	/* Connect to the LDAP server
	 */
	ret = init_ldap_connection (n, PROP_LDAP(props));
	if (ret != ret_ok) {
		cherokee_validator_free (VALIDATOR(n));
		return ret;
	}

	*ldap = n;
	return ret_ok;
}

Project: webserver
File: validator_ldap.c

init_ldap_connection (cherokee_validator_ldap_t *ldap, cherokee_validator_ldap_props_t *props)
{
	int re;
	int val;

	/* Connect
	 */
	ldap->conn = ldap_init (props->server.buf, props->port);
	if (ldap->conn == NULL) {
		LOG_ERRNO (errno, cherokee_err_critical,
		           CHEROKEE_ERROR_VALIDATOR_LDAP_CONNECT,
		           props->server.buf, props->port);
		return ret_error;
	}

	TRACE (ENTRIES, "Connected to %s:%d\n", props->server.buf, props->port);

	/* Set LDAP protocol version
	 */
	val = LDAP_VERSION3;
	re = ldap_set_option (ldap->conn, LDAP_OPT_PROTOCOL_VERSION, &val);
	if (re != LDAP_OPT_SUCCESS) {
		LOG_ERROR (CHEROKEE_ERROR_VALIDATOR_LDAP_V3, ldap_err2string(re));
		return ret_error;
	}

	TRACE (ENTRIES, "LDAP protocol version %d set\n", LDAP_VERSION3);

	/* Secure connections
	 */
	if (props->tls) {
#ifdef LDAP_OPT_X_TLS
		if (! cherokee_buffer_is_empty (&props->ca_file)) {
			re = ldap_set_option (NULL, LDAP_OPT_X_TLS_CACERTFILE, props->ca_file.buf);
			if (re != LDAP_OPT_SUCCESS) {
				LOG_CRITICAL (CHEROKEE_ERROR_VALIDATOR_LDAP_CA,
				              props->ca_file.buf, ldap_err2string (re));
				return ret_error;
			}
		}
#else
		LOG_ERROR_S (CHEROKEE_ERROR_VALIDATOR_LDAP_STARTTLS);
#endif
	}

	/* Bind
	 */
	if (cherokee_buffer_is_empty (&props->binddn)) {
		TRACE (ENTRIES, "anonymous bind %s", "\n");
		re = ldap_simple_bind_s (ldap->conn, NULL, NULL);
	} else {
		TRACE (ENTRIES, "bind user=%s password=%s\n",
		       props->binddn.buf, props->bindpw.buf);
		re = ldap_simple_bind_s (ldap->conn, props->binddn.buf, props->bindpw.buf);
	}

	if (re != LDAP_SUCCESS) {
		LOG_CRITICAL (CHEROKEE_ERROR_VALIDATOR_LDAP_BIND,
		              props->server.buf, props->port, props->binddn.buf,
		              props->bindpw.buf, ldap_err2string(re));
		return ret_error;
	}

	return ret_ok;
}

Project: webserver
File: validator_ldap.c

props_free (cherokee_validator_ldap_props_t *props)
{
	cherokee_buffer_mrproper (&props->server);
	cherokee_buffer_mrproper (&props->binddn);
	cherokee_buffer_mrproper (&props->bindpw);
	cherokee_buffer_mrproper (&props->basedn);
	cherokee_buffer_mrproper (&props->filter);
	cherokee_buffer_mrproper (&props->ca_file);

	return cherokee_validator_props_free_base (VALIDATOR_PROPS(props));
}

Project: webserver
File: validator_ldap.c

validate_dn (cherokee_validator_ldap_props_t *props, char *dn, char *password)
{
	int   re;
	int   val;
	LDAP *conn;

	conn = ldap_init (props->server.buf, props->port);
	if (conn == NULL) return ret_error;

	val = LDAP_VERSION3;
	re = ldap_set_option (conn, LDAP_OPT_PROTOCOL_VERSION, &val);
	if (re != LDAP_OPT_SUCCESS)
		goto error;

	if (props->tls) {
#ifdef LDAP_HAVE_START_TLS_S
		re = ldap_start_tls_s (conn, NULL,  NULL);
		if (re != LDAP_OPT_SUCCESS) {
			TRACE (ENTRIES, "Couldn't StartTLS\n");
			goto error;
		}
#else
		LOG_ERROR_S (CHEROKEE_ERROR_VALIDATOR_LDAP_STARTTLS);
#endif
	}

	re = ldap_simple_bind_s (conn, dn, password);
	if (re != LDAP_SUCCESS)
		goto error;

	return ret_ok;

error:
	ldap_unbind_s (conn);
	return ret_error;
}