/*
SSSD
Async LDAP Helper routines
Copyright (C) Simo Sorce <ssorce@redhat.com> - 2009
Copyright (C) 2010, rhafer@suse.de, Novell Inc.
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <unistd.h>
#include <fcntl.h>
#include "util/sss_krb5.h"
#include "util/sss_ldap.h"
#include "util/strtonum.h"
#include "providers/ldap/sdap_async_private.h"
#include "providers/ldap/ldap_common.h"
/* ==Connect-to-LDAP-Server=============================================== */
struct sdap_rebind_proc_params {
bool use_start_tls;
};
struct sdap_connect_state {
const char *uri;
bool use_start_tls;
int result;
};
struct tevent_context *ev,
struct sdap_options *opts,
const char *uri,
struct sockaddr_storage *sockaddr,
bool use_start_tls)
{
int ret;
int timeout;
goto fail;
}
return NULL;
}
return NULL;
}
return NULL;
}
sizeof(struct sockaddr_storage),
timeout);
goto fail;
}
return req;
fail:
return req;
}
{
struct tevent_req);
struct sdap_connect_state);
int ver;
int lret;
int optret;
int msgid;
bool ldap_referrals;
const char *ldap_deref;
int ldap_deref_val;
int sd;
bool sasl_nocanon;
const char *sasl_mech;
int sasl_minssf;
"sdap_async_connect_call request failed: [%d]: %s.\n",
return;
}
"setup_ldap_connection_callbacks failed: [%d]: %s.\n",
goto fail;
}
/* If sss_ldap_init_recv() does not return a valid file descriptor we have
* to assume that the connection callback will be called by internally by
* the OpenLDAP client library. */
if (sd != -1) {
goto fail;
}
}
/* Force ldap version to 3 */
ver = LDAP_VERSION3;
if (lret != LDAP_OPT_SUCCESS) {
goto fail;
}
/* TODO: maybe this can be remove when we go async, currently we need it
* to handle EINTR during poll(). */
if (ret != LDAP_OPT_SUCCESS) {
}
/* Set Network Timeout */
if (lret != LDAP_OPT_SUCCESS) {
goto fail;
}
/* Set Default Timeout */
if (lret != LDAP_OPT_SUCCESS) {
goto fail;
}
/* Set Referral chasing */
if (lret != LDAP_OPT_SUCCESS) {
goto fail;
}
if (ldap_referrals) {
struct sdap_rebind_proc_params);
if (rebind_proc_params == NULL) {
goto fail;
}
if (lret != LDAP_SUCCESS) {
goto fail;
}
}
/* Set alias dereferencing */
if (ldap_deref != NULL) {
goto fail;
}
if (lret != LDAP_OPT_SUCCESS) {
"Failed to set deref option to %d\n", ldap_deref_val);
goto fail;
}
}
/* Set host name canonicalization for LDAP SASL bind */
if (lret != LDAP_OPT_SUCCESS) {
/* Do not fail, just warn into both debug logs and syslog */
"Failed to set LDAP SASL nocanon option to %s. If your system "
"is configured to use SASL, LDAP operations might fail.\n",
"Failed to set LDAP SASL nocanon option to %s. If your system "
"is configured to use SASL, LDAP operations might fail.\n",
}
if (sasl_minssf >= 0) {
if (lret != LDAP_OPT_SUCCESS) {
"to %d\n", sasl_minssf);
goto fail;
}
}
}
/* if we do not use start_tls the connection is not really connected yet
* just fake an async procedure and leave connection to the bind call */
if (!state->use_start_tls) {
return;
}
if (lret != LDAP_SUCCESS) {
&errmsg);
if (optret == LDAP_SUCCESS) {
errmsg);
}
else {
"Check for certificate issues.");
}
goto fail;
}
if (ret) {
goto fail;
}
return;
fail:
if (ret) {
} else {
if (lret == LDAP_SERVER_DOWN) {
} else {
}
}
return;
}
{
struct sdap_connect_state);
char *tlserr;
int ret;
int optret;
if (error) {
return;
}
if (ret != LDAP_SUCCESS) {
return;
}
return;
}
/* FIXME: take care that ldap_install_tls might block */
if (ret != LDAP_SUCCESS) {
&tlserr);
if (optret == LDAP_SUCCESS) {
tlserr);
}
else {
"Check for certificate issues.");
}
return;
}
}
struct sdap_handle **sh)
{
struct sdap_connect_state);
if (!*sh) {
return ENOMEM;
}
return EOK;
}
struct sdap_connect_host_state {
char *uri;
char *protocol;
char *host;
int port;
bool use_start_tls;
};
struct tevent_context *ev,
struct sdap_options *opts,
struct resolv_ctx *resolv_ctx,
enum restrict_family family_order,
enum host_database *host_db,
const char *protocol,
const char *host,
int port,
bool use_start_tls)
{
struct sdap_connect_host_state);
return NULL;
}
goto immediately;
}
goto immediately;
}
goto immediately;
}
goto immediately;
}
return req;
} else {
}
return req;
}
{
int status;
goto done;
}
goto done;
}
goto done;
}
done:
}
return;
}
{
goto done;
}
/* if TLS was used, the sdap handle is already marked as connected */
if (!state->use_start_tls) {
/* we need to mark handle as connected to allow anonymous bind */
goto done;
}
}
done:
return;
}
}
struct tevent_req *req,
struct sdap_handle **_sh)
{
return EOK;
}
/* ==Simple-Bind========================================================== */
struct simple_bind_state {
const char *user_dn;
};
struct tevent_context *ev,
struct sdap_handle *sh,
int timeout,
const char *user_dn,
{
int msgid;
int ldap_err;
return NULL;
}
"Password Policy control.\n");
goto fail;
}
if (ret != LDAP_OPT_SUCCESS) {
"ldap_bind failed (couldn't get ldap error)\n");
} else {
}
goto fail;
}
}
if (ret) {
goto fail;
}
return req;
fail:
if (ret == LDAP_SERVER_DOWN) {
} else {
}
return req;
}
{
struct simple_bind_state);
char *nval;
int lret;
int c;
if (error) {
return;
}
&response_controls, 0);
if (lret != LDAP_SUCCESS) {
ret = ERR_INTERNAL;
goto done;
}
if (result == LDAP_SUCCESS) {
} else if (result == LDAP_INVALID_CREDENTIALS
/* Value 775 is described in
* for more details please see commit message. */
} else {
}
if (response_controls == NULL) {
} else {
for (c = 0; response_controls[c] != NULL; c++) {
"Server returned control [%s].\n",
response_controls[c]->ldctl_oid);
LDAP_CONTROL_PASSWORDPOLICYRESPONSE) == 0) {
&pp_error);
if (lret != LDAP_SUCCESS) {
"ldap_parse_passwordpolicy_control failed.\n");
ret = ERR_INTERNAL;
goto done;
}
"Password Policy Response: expire [%d] grace [%d] "
struct sdap_ppolicy_data);
goto done;
}
if (result == LDAP_SUCCESS) {
if (pp_error == PP_changeAfterReset) {
"Password was reset. "
"User must set a new password.\n");
} else if (pp_grace >= 0) {
"Password expired. "
"[%d] grace logins remaining.\n",
pp_grace);
} else if (pp_expire > 0) {
"Password will expire in [%d] seconds.\n",
}
} else if (result == LDAP_INVALID_CREDENTIALS &&
pp_error == PP_passwordExpired) {
"Password expired user must set a new password.\n");
}
LDAP_CONTROL_PWEXPIRED) == 0) {
"Password expired user must set a new password.\n");
LDAP_CONTROL_PWEXPIRING) == 0) {
/* ignore controls with suspiciously long values */
continue;
}
}
goto done;
}
/* ensure that bv_val is a null-terminated string */
goto done;
}
"Couldn't convert control response "
ret = ERR_INTERNAL;
goto done;
}
"Password will expire in [%d] seconds.\n",
}
}
}
}
done:
} else {
}
}
struct sdap_ppolicy_data **ppolicy)
{
struct simple_bind_state);
}
return EOK;
}
/* ==SASL-Bind============================================================ */
struct sasl_bind_state {
const char *sasl_mech;
const char *sasl_user;
};
struct tevent_context *ev,
struct sdap_handle *sh,
const char *sasl_mech,
const char *sasl_user,
{
int optret;
/* FIXME: Warning, this is a sync call!
* No async variant exist in openldap libraries yet */
"Trying LDAP search while not connected.\n");
goto fail;
}
(*sdap_sasl_interact), state);
if (ret != LDAP_SUCCESS) {
"ldap_sasl_bind failed (%d)[%s]\n",
&diag_msg);
"Extended failure message: [%s]\n", diag_msg);
}
goto fail;
}
}
/* This is a hack, relies on the fact that tevent_req_done() will always
* set the state but will not complain if no callback has been set.
* tevent_req_post() will only set the immediate event and then just call
* the async callback set by the caller right after we return using the
* state value set previously by tevent_req_done() */
return req;
fail:
} else {
}
return req;
}
{
struct sasl_bind_state);
if (!ld) return LDAP_PARAM_ERROR;
case SASL_CB_GETREALM:
case SASL_CB_USER:
case SASL_CB_PASS:
} else {
}
break;
case SASL_CB_AUTHNAME:
} else {
}
break;
case SASL_CB_NOECHOPROMPT:
case SASL_CB_ECHOPROMPT:
goto fail;
}
in++;
}
return LDAP_SUCCESS;
fail:
return LDAP_UNAVAILABLE;
}
{
return EOK;
}
/* ==Perform-Kinit-given-keytab-and-principal============================= */
struct sdap_kinit_state {
const char *keytab;
const char *principal;
const char *realm;
int timeout;
int lifetime;
const char *krb_service_name;
};
static
struct tevent_context *ev,
struct sdap_handle *sh,
const char *krb_service_name,
int timeout,
const char *keytab,
const char *principal,
const char *realm,
bool canonicalize,
int lifetime)
{
int ret;
return NULL;
}
if (canonicalize) {
} else {
}
if (ret == -1) {
return NULL;
}
if (!subreq) {
return NULL;
}
return req;
}
{
struct sdap_kinit_state);
return NULL;
}
return next_req;
}
{
struct tevent_req);
struct sdap_kinit_state);
int ret;
/* all servers have been tried and none
* was found good, go offline */
return;
}
if (!tgtreq) {
return;
}
}
{
struct tevent_req);
struct sdap_kinit_state);
int ret;
int result;
/* The child didn't even respond. Perhaps the KDC is too busy,
* retry with another KDC */
"Communication with KDC timed out, trying the next one\n");
if (!nextreq) {
}
return;
/* A severe error while executing the child. Abort the operation. */
return;
}
if (ret == -1) {
"Unable to set env. variable KRB5CCNAME!\n");
return;
}
return;
} else {
if (kerr == KRB5_KDC_UNREACH) {
if (!nextreq) {
}
return;
}
}
}
{
struct sdap_kinit_state);
if (tstate != TEVENT_REQ_IN_PROGRESS) {
return ERR_INTERNAL;
}
return err;
}
}
return EOK;
}
/* ==Authenticaticate-User-by-DN========================================== */
struct sdap_auth_state {
bool is_sasl;
};
/* TODO: handle sasl_cred */
struct tevent_context *ev,
struct sdap_handle *sh,
const char *sasl_mech,
const char *sasl_user,
const char *user_dn,
struct sss_auth_token *authtok,
int simple_bind_timeout)
{
if (sasl_mech) {
if (!subreq) {
}
} else {
}
/* Treat a zero-length password as a failure */
if (*password == '\0') {
}
if (!subreq) {
}
}
return req;
}
struct dp_opt_blob authtok,
{
if (!authtok_type) return EOK;
} else {
"Authentication token type [%s] is not supported\n",
return EINVAL;
}
return EOK;
}
{
struct tevent_req);
struct sdap_auth_state);
int ret;
} else {
}
return;
}
}
struct sdap_ppolicy_data **ppolicy)
{
struct sdap_auth_state);
}
return EOK;
}
/* ==Client connect============================================ */
struct sdap_cli_connect_state {
bool use_rootdse;
bool do_auth;
bool use_tls;
};
static errno_t
{
bool use_tls = true;
switch (force_tls) {
case CON_TLS_DFL:
break;
case CON_TLS_ON:
use_tls = true;
break;
case CON_TLS_OFF:
use_tls = false;
break;
default:
return EINVAL;
break;
}
"[%s] is a secure channel. No need to run START_TLS\n", uri);
use_tls = false;
}
return EOK;
}
struct tevent_context *ev,
struct sdap_options *opts,
struct sdap_service *service,
bool skip_rootdse,
enum connect_tls force_tls,
bool skip_auth)
{
int ret;
if (ret) {
}
return req;
}
{
struct sdap_cli_connect_state);
/* Before stepping to next server destroy any connection from previous attempt */
/* NOTE: this call may cause service->uri to be refreshed
* with a new valid server. Do not use service->uri before */
if (!subreq) {
return ENOMEM;
}
return EOK;
}
{
struct tevent_req);
struct sdap_cli_connect_state);
int ret;
if (ret) {
/* all servers have been tried and none
* was found good, go offline */
return;
}
return;
}
if (!subreq) {
return;
}
}
{
struct tevent_req);
struct sdap_cli_connect_state);
const char *sasl_mech;
int ret;
if (ret) {
/* retry another server */
}
return;
}
if (state->use_rootdse) {
/* fetch the rootDSE this time */
return;
}
/* check if server claims to support GSSAPI */
return;
}
}
return;
}
}
}
{
struct sdap_cli_connect_state);
int ret;
if (!subreq) {
return;
}
/* this rootdse search is performed before we actually do a bind,
* so we need to set up the callbacks or we will never get notified
* of a reply */
if (ret) {
}
}
}
{
struct tevent_req);
struct sdap_cli_connect_state);
const char *sasl_mech;
int ret;
if (ret) {
}
return;
}
/* RootDSE was not available on
* the server.
* Continue, and just assume that the
* features requested by the config
* work properly.
*/
}
return;
}
/* check if server claims to support GSSAPI */
return;
}
}
return;
}
}
}
{
/* save rootdse data about supported features */
if (ret) {
"sdap_set_rootdse_supported_lists failed\n");
return ret;
}
if (ret) {
"sdap_set_config_options_with_rootdse failed.\n");
return ret;
}
}
if (ret) {
"sdap_get_server_opts_from_rootdse failed.\n");
return ret;
}
return EOK;
}
{
struct sdap_cli_connect_state);
if (!subreq) {
return;
}
}
{
struct tevent_req);
struct sdap_cli_connect_state);
/* We're not able to authenticate to the LDAP server.
* There's not much we can do except for going offline */
return;
}
}
{
struct sdap_cli_connect_state);
int expire_timeout;
const char *authtok_type;
/* It's possible that connection was terminated by server (e.g. #2435),
to overcome this try to connect again. */
"Trying to reconnect.\n");
"sdap_cli_auth_reconnect failed: %d:[%s]\n",
}
return;
}
/* Set the LDAP expiration time
* If SASL has already set it, use the sooner of the two
*/
}
"No authentication requested or SASL auth forced off\n");
return;
}
return;
}
if (authtok_type != NULL) {
return;
}
if (authtok_blob.data) {
(const char *)authtok_blob.data,
if (ret) {
return;
}
}
}
if (!subreq) {
return;
}
}
{
goto done;
}
goto done;
}
done:
return ret;
}
{
goto done;
}
/* if TLS was used, the sdap handle is already marked as connected */
/* we need to mark handle as connected to allow anonymous bind */
goto done;
}
}
/* End request if reconnecting failed to avoid endless loop */
goto done;
}
done:
}
}
{
struct tevent_req);
struct sdap_cli_connect_state);
int ret;
if (ret) {
return;
}
/* We weren't able to read rootDSE during unauthenticated bind.
* Let's try again now that we are authenticated */
if (!subreq) {
return;
}
return;
}
}
{
struct tevent_req);
struct sdap_cli_connect_state);
if (ret) {
/* The server we authenticated against went down. Retry another
* one */
}
return;
}
/* RootDSE was not available on
* the server.
* Continue, and just assume that the
* features requested by the config
* work properly.
*/
state->use_rootdse = false;
return;
}
/* We were able to get rootDSE after authentication */
return;
}
}
bool *can_retry,
struct sdap_handle **gsh,
struct sdap_server_opts **srv_opts)
{
struct sdap_cli_connect_state);
int err;
if (can_retry) {
*can_retry = true;
}
/* mark the server as bad if connection failed */
} else {
if (can_retry) {
*can_retry = false;
}
}
if (tstate == TEVENT_REQ_USER_ERROR) {
err = (int)err_uint64;
return EINVAL;
}
return err;
}
return EIO;
}
if (gsh) {
if (*gsh) {
talloc_zfree(*gsh);
}
if (!*gsh) {
return ENOMEM;
}
} else {
}
if (srv_opts) {
}
return EOK;
}
{
int lret;
int optret;
int ldaperr;
int msgid;
char *diag_msg;
if (!tmp_ctx) return LDAP_NO_MEMORY;
if (lret != LDAP_SUCCESS) {
if (optret == LDAP_SUCCESS) {
} else {
"Check for certificate issues.");
}
goto done;
}
if (lret != LDAP_RES_EXTENDED) {
"Unexpected ldap_result, expected [%lu] got [%d].\n",
goto done;
}
0);
if (lret != LDAP_SUCCESS) {
goto done;
}
if (ldap_tls_inplace(ldap)) {
lret = LDAP_SUCCESS;
goto done;
}
if (lret != LDAP_SUCCESS) {
if (optret == LDAP_SUCCESS) {
} else {
"Check for certificate issues.");
}
goto done;
}
lret = LDAP_SUCCESS;
done:
return lret;
}
{
struct sdap_rebind_proc_params);
const char *sasl_mech;
const char *user_dn;
int ret;
"Trying LDAP rebind while not connected.\n");
return ERR_NETWORK_IO;
}
if (p->use_start_tls) {
if (ret != LDAP_SUCCESS) {
return ret;
}
}
return LDAP_NO_MEMORY;
}
"sss_ldap_control_create failed to create "
"Password Policy control.\n");
goto done;
}
&password);
"sdap_auth_get_authtok failed.\n");
goto done;
}
}
if (ret != LDAP_SUCCESS) {
"ldap_sasl_bind_s failed (%d)[%s]\n", ret,
}
} else {
if (sasl_bind_state == NULL) {
goto done;
}
if (ret != LDAP_SUCCESS) {
"ldap_sasl_interactive_bind_s failed (%d)[%s]\n", ret,
}
}
done:
return ret;
}