summaryrefslogtreecommitdiffstats
path: root/src/modules/rlm_ldap/sasl.c
blob: 17a635676c7c6d58df5e853cb492d47304bd0d23 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
/*
 *   This program is is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 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, write to the Free Software
 *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 */

#include "ldap.h"

/**
 * $Id$
 * @file sasl.c
 * @brief Functions to perform SASL binds against an LDAP directory.
 *
 * @author Arran Cudbard-Bell <a.cudbardb@freeradius.org>
 * @copyright 2015 Arran Cudbard-Bell <a.cudbardb@freeradius.org>
 * @copyright 2015 The FreeRADIUS Server Project.
 */
#include <freeradius-devel/radiusd.h>
#include <freeradius-devel/rad_assert.h>

#include <sasl/sasl.h>

/** Data passed to the _sasl interact callback.
 *
 */
typedef struct rlm_ldap_sasl_ctx {
	rlm_ldap_t const	*inst;		//!< LDAP instance
	REQUEST			*request;	//!< The current request.

	char const		*identity;	//!< User's DN or identity.
	char const		*password;	//!< Bind password.

	ldap_sasl		*extra;		//!< Extra fields (realm and proxy id).
} rlm_ldap_sasl_ctx_t;

/** Callback for ldap_sasl_interactive_bind
 *
 * @param handle used for the SASL bind.
 * @param flags data as provided to ldap_sasl_interactive_bind.
 * @param ctx Our context data, containing the identity, password, realm and various other things.
 * @param sasl_callbacks Array of challenges to provide responses for.
 * @return SASL_OK.
 */
static int _sasl_interact(UNUSED LDAP *handle, UNUSED unsigned flags, void *ctx, void *sasl_callbacks)
{
	rlm_ldap_sasl_ctx_t	*this = ctx;
	REQUEST			*request = this->request;
	rlm_ldap_t const	*inst = this->inst;
	sasl_interact_t		*cb = sasl_callbacks;
	sasl_interact_t		*cb_p;

	for (cb_p = cb; cb_p->id != SASL_CB_LIST_END; cb_p++) {
		MOD_ROPTIONAL(RDEBUG3, DEBUG3, "SASL challenge : %s", cb_p->challenge);
		MOD_ROPTIONAL(RDEBUG3, DEBUG3, "SASL prompt    : %s", cb_p->prompt);

		switch (cb_p->id) {
		case SASL_CB_AUTHNAME:
			cb_p->result = this->identity;
			cb_p->len = strlen(this->identity);
			break;

		case SASL_CB_PASS:
			cb_p->result = this->password;
			cb_p->len = strlen(this->password);
			break;

		case SASL_CB_USER:
			cb_p->result = this->extra->proxy ? this->extra->proxy : this->identity;
			cb_p->len = this->extra->proxy ? strlen(this->extra->proxy) : strlen(this->identity);
			break;

		case SASL_CB_GETREALM:
			if (this->extra->realm) {
				cb_p->result = this->extra->realm;
				cb_p->len = strlen(this->extra->realm);
			}
			break;

		default:
			break;
		}
		MOD_ROPTIONAL(RDEBUG3, DEBUG3, "SASL result    : %s", cb_p->result ? (char const *)cb_p->result : "");
	}
	return SASL_OK;
}

/** Initiate an LDAP interactive bind
 *
 * @param[in] inst rlm_ldap configuration.
 * @param[in] request Current request, this may be NULL, in which case all debug logging is done with radlog.
 * @param[in] conn to use. May change as this function calls functions which auto re-connect.
 * @param[in] identity of the user.
 * @param[in] password of the user.
 * @param[in] sasl mechanism to use for bind, and additional parameters.
 * @param[out] error message resulting from bind.
 * @param[out] extra information about the error.
 * @return One of the LDAP_PROC_* (#ldap_rcode_t) values.
 */
ldap_rcode_t rlm_ldap_sasl_interactive(rlm_ldap_t const *inst, REQUEST *request,
				       ldap_handle_t *conn, char const *identity,
				       char const *password, ldap_sasl *sasl,
				       char const **error, char **extra)
{
	ldap_rcode_t		status;
	int			ret = 0;
	int			msgid;
	char const		*mech;
	LDAPMessage		*result = NULL;
	rlm_ldap_sasl_ctx_t	sasl_ctx;		/* SASL defaults */

	/* rlm_ldap_result may not be called */
	if (error) *error = NULL;
	if (extra) *extra = NULL;

	sasl_ctx.inst = inst;
	sasl_ctx.request = request;
	sasl_ctx.identity = identity;
	sasl_ctx.password = password;
	sasl_ctx.extra = sasl;

	MOD_ROPTIONAL(RDEBUG2, DEBUG2, "Starting SASL mech(s): %s", sasl->mech);
	for (;;) {
		ret = ldap_sasl_interactive_bind(conn->handle, NULL, sasl->mech,
						 NULL, NULL, LDAP_SASL_AUTOMATIC,
						 _sasl_interact, &sasl_ctx, result,
						 &mech, &msgid);

		/*
		 *	If ldap_sasl_interactive_bind indicates it didn't want
		 *	to continue, then we're done.
		 *
		 *	Calling ldap_result here, results in a timeout in some
		 *	cases, so we need to figure out whether the bind was
		 *	successful without the help of ldap_result.
		 */
		if (ret != LDAP_SASL_BIND_IN_PROGRESS) {
			status = rlm_ldap_result(inst, conn, -1, identity, NULL, error, extra);
			break;		/* Old result gets freed on after exit */
		}

		ldap_msgfree(result);	/* We always need to free the old message */

		/*
		 *	If LDAP parse result indicates there was an error
		 *	then we're done.
		 */
		status = rlm_ldap_result(inst, conn, msgid, identity, &result, error, extra);
		switch (status) {
		case LDAP_PROC_SUCCESS:		/* ldap_sasl_interactive_bind should have indicated success */
		case LDAP_PROC_CONTINUE:
			break;

		default:
			goto done;
		}

		/*
		 *	...otherwise, the bind is still in progress.
		 */
		MOD_ROPTIONAL(RDEBUG3, DEBUG3, "Continuing SASL mech %s...", mech);

		/*
		 *	Write the servers response to the debug log
		 */
		if (((request && RDEBUG_ENABLED3) || DEBUG_ENABLED3) && result) {
			struct berval *srv_cred;

			if ((ldap_parse_sasl_bind_result(conn->handle, result, &srv_cred, 0) == LDAP_SUCCESS) &&
			    (srv_cred != NULL)) {
				char *escaped;

				escaped = fr_aprints(request, srv_cred->bv_val, srv_cred->bv_len, '\0');
				MOD_ROPTIONAL(RDEBUG3, DEBUG3, "SASL response  : %s", escaped);

				talloc_free(escaped);
				ber_bvfree(srv_cred);
			}
		}
	}
done:
	ldap_msgfree(result);

	return status;
}