success may still return nothing. Should help with #1824
[freeradius.git] / src / modules / rlm_ldap / sasl.c
1 /*
2  *   This program is is free software; you can redistribute it and/or modify
3  *   it under the terms of the GNU General Public License as published by
4  *   the Free Software Foundation; either version 2 of the License, or (at
5  *   your option) any later version.
6  *
7  *   This program is distributed in the hope that it will be useful,
8  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
9  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10  *   GNU General Public License for more details.
11  *
12  *   You should have received a copy of the GNU General Public License
13  *   along with this program; if not, write to the Free Software
14  *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
15  */
16
17 #include "ldap.h"
18
19 /**
20  * $Id$
21  * @file sasl.c
22  * @brief Functions to perform SASL binds against an LDAP directory.
23  *
24  * @author Arran Cudbard-Bell <a.cudbardb@freeradius.org>
25  * @copyright 2015 Arran Cudbard-Bell <a.cudbardb@freeradius.org>
26  * @copyright 2015 The FreeRADIUS Server Project.
27  */
28 #include <freeradius-devel/radiusd.h>
29 #include <freeradius-devel/rad_assert.h>
30
31 #include <sasl/sasl.h>
32
33 /** Data passed to the _sasl interact callback.
34  *
35  */
36 typedef struct rlm_ldap_sasl_ctx {
37         rlm_ldap_t const        *inst;          //!< LDAP instance
38         REQUEST                 *request;       //!< The current request.
39
40         char const              *identity;      //!< User's DN or identity.
41         char const              *password;      //!< Bind password.
42
43         ldap_sasl               *extra;         //!< Extra fields (realm and proxy id).
44 } rlm_ldap_sasl_ctx_t;
45
46 /** Callback for ldap_sasl_interactive_bind
47  *
48  * @param handle used for the SASL bind.
49  * @param flags data as provided to ldap_sasl_interactive_bind.
50  * @param ctx Our context data, containing the identity, password, realm and various other things.
51  * @param sasl_callbacks Array of challenges to provide responses for.
52  * @return SASL_OK.
53  */
54 static int _sasl_interact(UNUSED LDAP *handle, UNUSED unsigned flags, void *ctx, void *sasl_callbacks)
55 {
56         rlm_ldap_sasl_ctx_t     *this = ctx;
57         REQUEST                 *request = this->request;
58         rlm_ldap_t const        *inst = this->inst;
59         sasl_interact_t         *cb = sasl_callbacks;
60         sasl_interact_t         *cb_p;
61
62         for (cb_p = cb; cb_p->id != SASL_CB_LIST_END; cb_p++) {
63                 MOD_ROPTIONAL(RDEBUG3, DEBUG3, "SASL challenge : %s", cb_p->challenge);
64                 MOD_ROPTIONAL(RDEBUG3, DEBUG3, "SASL prompt    : %s", cb_p->prompt);
65
66                 switch (cb_p->id) {
67                 case SASL_CB_AUTHNAME:
68                         cb_p->result = this->identity;
69                         break;
70
71                 case SASL_CB_PASS:
72                         cb_p->result = this->password;
73                         break;
74
75                 case SASL_CB_USER:
76                         cb_p->result = this->extra->proxy ? this->extra->proxy : this->identity;
77                         break;
78
79                 case SASL_CB_GETREALM:
80                         if (this->extra->realm) cb_p->result = this->extra->realm;
81                         break;
82
83                 default:
84                         break;
85                 }
86                 MOD_ROPTIONAL(RDEBUG3, DEBUG3, "SASL result    : %s", cb_p->result ? (char const *)cb_p->result : "");
87         }
88         return SASL_OK;
89 }
90
91 /** Initiate an LDAP interactive bind
92  *
93  * @param[in] inst rlm_ldap configuration.
94  * @param[in] request Current request, this may be NULL, in which case all debug logging is done with radlog.
95  * @param[in] conn to use. May change as this function calls functions which auto re-connect.
96  * @param[in] identity of the user.
97  * @param[in] password of the user.
98  * @param[in] sasl mechanism to use for bind, and additional parameters.
99  * @param[out] error message resulting from bind.
100  * @param[out] extra information about the error.
101  * @return One of the LDAP_PROC_* (#ldap_rcode_t) values.
102  */
103 ldap_rcode_t rlm_ldap_sasl_interactive(rlm_ldap_t const *inst, REQUEST *request,
104                                        ldap_handle_t *conn, char const *identity,
105                                        char const *password, ldap_sasl *sasl,
106                                        char const **error, char **extra)
107 {
108         ldap_rcode_t            status;
109         int                     ret = 0;
110         int                     msgid;
111         char const              *mech;
112         LDAPMessage             *result = NULL;
113         rlm_ldap_sasl_ctx_t     sasl_ctx;               /* SASL defaults */
114
115         /* rlm_ldap_result may not be called */
116         if (error) *error = NULL;
117         if (extra) *extra = NULL;
118
119         sasl_ctx.inst = inst;
120         sasl_ctx.request = request;
121         sasl_ctx.identity = identity;
122         sasl_ctx.password = password;
123         sasl_ctx.extra = sasl;
124
125         MOD_ROPTIONAL(RDEBUG2, DEBUG2, "Starting SASL mech(s): %s", sasl->mech);
126         for (;;) {
127                 ret = ldap_sasl_interactive_bind(conn->handle, NULL, sasl->mech,
128                                                  NULL, NULL, LDAP_SASL_AUTOMATIC,
129                                                  _sasl_interact, &sasl_ctx, result,
130                                                  &mech, &msgid);
131
132                 /*
133                  *      If ldap_sasl_interactive_bind indicates it didn't want
134                  *      to continue, then we're done.
135                  *
136                  *      Calling ldap_result here, results in a timeout in some
137                  *      cases, so we need to figure out whether the bind was
138                  *      successful without the help of ldap_result.
139                  */
140                 if (ret != LDAP_SASL_BIND_IN_PROGRESS) {
141                         status = rlm_ldap_result(inst, conn, -1, identity, NULL, error, extra);
142                         break;          /* Old result gets freed on after exit */
143                 }
144
145                 ldap_msgfree(result);   /* We always need to free the old message */
146
147                 /*
148                  *      If LDAP parse result indicates there was an error
149                  *      then we're done.
150                  */
151                 status = rlm_ldap_result(inst, conn, msgid, identity, &result, error, extra);
152                 switch (status) {
153                 case LDAP_PROC_SUCCESS:         /* ldap_sasl_interactive_bind should have indicated success */
154                 case LDAP_PROC_CONTINUE:
155                         break;
156
157                 default:
158                         goto done;
159                 }
160
161                 /*
162                  *      ...otherwise, the bind is still in progress.
163                  */
164                 MOD_ROPTIONAL(RDEBUG3, DEBUG3, "Continuing SASL mech %s...", mech);
165
166                 /*
167                  *      Write the servers response to the debug log
168                  */
169                 if (((request && RDEBUG_ENABLED3) || DEBUG_ENABLED3) && result) {
170                         struct berval *srv_cred;
171
172                         if ((ldap_parse_sasl_bind_result(conn->handle, result, &srv_cred, 0) == LDAP_SUCCESS) &&
173                             (srv_cred != NULL)) {
174                                 char *escaped;
175
176                                 escaped = fr_aprints(request, srv_cred->bv_val, srv_cred->bv_len, '\0');
177                                 MOD_ROPTIONAL(RDEBUG3, DEBUG3, "SASL response  : %s", escaped);
178
179                                 talloc_free(escaped);
180                                 ldap_memfree(srv_cred);
181                         }
182                 }
183         }
184 done:
185         ldap_msgfree(result);
186
187         return status;
188 }