The FreeRADIUS server  $Id: 15bac2a4c627c01d1aa2047687b3418955ac7f00 $
referral.c
Go to the documentation of this file.
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 /**
18  * $Id: 8afee84520f0d2c547b6d8c0d5b36d51d7f51d52 $
19  * @file lib/ldap/referral.c
20  * @brief Functions to handle ldap referrals
21  *
22  * @author Nick Porter <nick.porter@networkradius.com>
23  * @copyright 2021 The FreeRADIUS Server Project.
24  */
25 RCSID("$Id: 8afee84520f0d2c547b6d8c0d5b36d51d7f51d52 $")
26 
27 #include <freeradius-devel/ldap/base.h>
28 
29 
30 /** Clear up a fr_ldap_referral_t
31  *
32  * If there is a parsed referral_url, that must be freed using libldap's ldap_free_urldesc
33  */
35 {
36  if (referral->referral_url) ldap_free_urldesc(referral->referral_url);
37  if (referral->host_uri) ldap_memfree(referral->host_uri);
38  return 0;
39 }
40 
41 /** Allocate a new structure to handle an LDAP referral, setting the destructor
42  *
43  * @param[in] ctx to allocate the referral in
44  * @param[in] request the LDAP query relates to.
45  * @return
46  * - a new referral structure on success
47  * - NULL on failure
48  */
50 {
51  fr_ldap_referral_t *referral;
52 
53  referral = talloc_zero(ctx, fr_ldap_referral_t);
54  if (!referral) {
55  PERROR("Failed to allocate LDAP referral container");
56  return NULL;
57  }
58  referral->request = request;
59  talloc_set_destructor(referral, _fr_ldap_referral_free);
60 
61  return referral;
62 }
63 
64 /** Callback to send LDAP referral queries when a trunk becomes active
65  *
66  */
67 CC_NO_UBSAN(function) /* UBSAN: false positive - public vs private trunk_t trips --fsanitize=function*/
68 static void _ldap_referral_send(UNUSED trunk_t *trunk, UNUSED trunk_state_t prev,
69  UNUSED trunk_state_t state, void *uctx)
70 {
71  fr_ldap_referral_t *referral = talloc_get_type_abort(uctx, fr_ldap_referral_t);
72  fr_ldap_query_t *query = referral->query;
73  request_t *request = referral->request;
74 
75  /*
76  * If referral is set, then another LDAP trunk has gone active first and sent the referral
77  */
78  if (query->referral) return;
79 
80  /*
81  * Enqueue referral query on active trunk connection
82  */
83  query->referral = referral;
84  switch (trunk_request_enqueue(&query->treq, referral->ttrunk->trunk, request, query, NULL)) {
85  case TRUNK_ENQUEUE_OK:
87  break;
88 
89  default:
90  ROPTIONAL(RERROR, ERROR, "Failed enqueueing pending LDAP referral");
91  query->ret = LDAP_RESULT_ERROR;
92  if (request) unlang_interpret_mark_runnable(request);
93  return;
94  }
95 
96  ROPTIONAL(RDEBUG3, DEBUG3, "Pending LDAP referral query queued on active trunk");
97 }
98 
99 
100 /** Follow an LDAP referral
101  *
102  * The returned list of LDAP referrals should already be in query->referrals.
103  * We check all the possible referrals and look for one where there already
104  * is an active trunk connection.
105  *
106  * @param t Thread running the query.
107  * @param request related to the query.
108  * @param query whose result was one or more referral URLs.
109  * @return
110  * - 0 on success.
111  * - < 0 on failure.
112  */
114 {
116  fr_ldap_thread_trunk_t *ttrunk = NULL;
117  int referral_no = -1;
118  fr_ldap_referral_t *referral;
119  LDAPURLDesc temp_desc;
120 
122  query->treq = NULL;
123 
124  if (query->referral_depth > 1) {
125  /*
126  * If we've already parsed a referral, clear the existing list of followers.
127  */
129  query->referral = NULL;
130  } else {
131  /*
132  * Otherwise initialise the list header for followers.
133  */
135  }
136 
137  while (query->referral_urls[++referral_no]) {
138  if (!ldap_is_ldap_url(query->referral_urls[referral_no])) {
139  ROPTIONAL(RERROR, ERROR, "Referral %s does not look like an LDAP URL",
140  query->referral_urls[referral_no]);
141  continue;
142  }
143 
144  referral = fr_ldap_referral_alloc(query, request);
145  if (!referral) continue;
146 
147  referral->query = query;
148 
149  if (ldap_url_parse(query->referral_urls[referral_no], &referral->referral_url)) {
151  "Failed parsing referral LDAP URL %s", query->referral_urls[referral_no]);
152  free_referral:
153  talloc_free(referral);
154  continue;
155  }
156 
157  temp_desc = (LDAPURLDesc){
158  .lud_scheme = referral->referral_url->lud_scheme,
159  .lud_host = referral->referral_url->lud_host,
160  .lud_port = referral->referral_url->lud_port,
161  .lud_scope = -1
162  };
163  referral->host_uri = ldap_url_desc2str(&temp_desc);
164  if (!referral->host_uri) {
166  "Failed building LDAP host URI from %s", query->referral_urls[referral_no]);
167  }
168 
169  if (config->use_referral_credentials) {
170  char **ext;
171 
172  /*
173  * If there are no extensions, OpenLDAP doesn't
174  * bother allocating an array.
175  */
176  for (ext = referral->referral_url->lud_exts; ext && *ext; ext++) {
177  char const *p;
178  bool critical = false;
179 
180  p = *ext;
181 
182  if (*p == '!') {
183  critical = true;
184  p++;
185  }
186 
187  /*
188  * LDAP Parse URL unescapes the extensions for us
189  */
191  case LDAP_EXT_BINDNAME:
192  p = strchr(p, '=');
193  if (!p) {
194  bad_ext:
196  "Failed parsing extension \"%s\": "
197  "No attribute/value delimiter '='", *ext);
198  goto free_referral;
199  }
200  referral->identity = p + 1;
201  break;
202 
203  case LDAP_EXT_BINDPW:
204  p = strchr(p, '=');
205  if (!p) goto bad_ext;
206  referral->password = p + 1;
207  break;
208 
209  default:
210  if (critical) {
212  "Failed parsing critical extension \"%s\": "
213  "Not supported by FreeRADIUS", *ext);
214  goto free_referral;
215  }
216  ROPTIONAL(RDEBUG2, DEBUG2, "Skipping unsupported extension \"%s\"", *ext);
217  continue;
218  }
219  }
220  } else {
221  if (config->rebind) {
222  referral->identity = config->admin_identity;
223  referral->password = config->admin_password;
224  }
225  }
226 
227  fr_dlist_insert_tail(&query->referrals, referral);
228  if (fr_thread_ldap_trunk_state(t, referral->host_uri,
229  referral->identity) != TRUNK_STATE_ACTIVE) {
231  "No active LDAP trunk for URI %s, bound as %s",
232  referral->host_uri, referral->identity);
233  continue;
234  }
235 
236  ttrunk = fr_thread_ldap_trunk_get(t, referral->host_uri, referral->identity,
237  referral->password, request, config);
238 
239  if (!ttrunk) {
240  ROPTIONAL(RERROR, ERROR, "Unable to connect to LDAP referral URL");
241  fr_dlist_talloc_free_item(&query->referrals, referral);
242  continue;
243  }
244 
245  /*
246  * We have an active trunk enqueue the request
247  */
248  query->referral = referral;
249  switch (trunk_request_enqueue(&query->treq, ttrunk->trunk, request, query, NULL)) {
250  case TRUNK_ENQUEUE_OK:
252  break;
253 
254  default:
255  ROPTIONAL(RERROR, ERROR, "Failed to enqueue request for referral");
256  goto free_referral;
257  }
258  return 0;
259  }
260 
261  /*
262  * None of the referrals parsed successfully
263  */
264  if (fr_dlist_num_elements(&query->referrals) == 0) {
265  ROPTIONAL(RERROR, ERROR, "No valid LDAP referrals to follow");
266  return -1;
267  }
268 
269  /*
270  * We have parsed referrals, but none of them matched an existing active connection.
271  * Launch new trunks with callbacks so the first to become active will run the query.
272  */
273  referral = NULL;
274  while ((referral = fr_dlist_next(&query->referrals, referral))) {
275  ttrunk = fr_thread_ldap_trunk_get(t, referral->host_uri, referral->identity,
276  referral->password, request, config);
277  if (!ttrunk) {
278  fr_dlist_talloc_free_item(&query->referrals, referral);
279  continue;
280  }
281  referral->ttrunk = ttrunk;
282  trunk_add_watch(ttrunk->trunk, TRUNK_STATE_ACTIVE, _ldap_referral_send, true, referral);
283  ROPTIONAL(RDEBUG4, DEBUG4, "Watch inserted to send referral query on active trunk");
284  }
285 
286  return 0;
287 }
288 
289 /** Follow an alternative LDAP referral
290  *
291  * If an initial chase of an LDAP referral results in an error being returned
292  * this function can be used to attempt one of the other referral URLs given
293  * in the initial query results.
294  *
295  * The initial use of fr_ldap_referral_follow may have launched trunks for
296  * any referral URLs which parsed successfully, so this starts by looking
297  * for the first which has an active state and sends the query that way.
298  *
299  * If no active trunks match the remaining servers listed in referrals then
300  * new trunks are launched with watchers to send the query on the first
301  * active trunk.
302  *
303  * @param t Thread running the query.
304  * @param request the query relates to.
305  * @param query whose referrals are being chased.
306  * @return
307  * - 0 on success.
308  * - < 0 on failure.
309  */
311 {
313  fr_ldap_referral_t *referral = NULL;
314  fr_ldap_thread_trunk_t *ttrunk;
315 
317  query->treq = NULL;
318 
319  while ((referral = fr_dlist_next(&query->referrals, referral))) {
320  if (fr_thread_ldap_trunk_state(t, referral->host_uri,
321  referral->identity) != TRUNK_STATE_ACTIVE) {
322  ROPTIONAL(RDEBUG3, DEBUG3, "No active LDAP trunk for URI %s, bind DN %s",
323  referral->host_uri, referral->identity);
324  continue;
325  }
326 
327  ttrunk = fr_thread_ldap_trunk_get(t, referral->host_uri, referral->identity,
328  referral->password, request, config);
329 
330  if (!ttrunk) {
331  ROPTIONAL(RERROR, ERROR, "Unable to connect to LDAP referral URL");
332  fr_dlist_talloc_free_item(&query->referrals, referral);
333  continue;
334  }
335 
336  /*
337  * We have an active trunk enqueue the request
338  */
339  query->referral = referral;
340  switch(trunk_request_enqueue(&query->treq, ttrunk->trunk, request, query, NULL)) {
341  case TRUNK_ENQUEUE_OK:
343  break;
344 
345  default:
346  ROPTIONAL(RERROR, ERROR, "Failed to enqueue request for referral");
347  continue;
348  }
349  return 0;
350  }
351 
352  /*
353  * None of the referrals parsed successfully
354  */
355  if (fr_dlist_num_elements(&query->referrals) == 0) {
356  ROPTIONAL(RERROR, ERROR, "No valid LDAP referrals to follow");
357  return -1;
358  }
359 
360  /*
361  * None of the remaining referrals have an active trunk.
362  * Launch new trunks with callbacks so the first to become active will run the query.
363  */
364  referral = NULL;
365  while ((referral = fr_dlist_next(&query->referrals, referral))) {
366  ttrunk = fr_thread_ldap_trunk_get(t, referral->host_uri, referral->identity,
367  referral->password, request, config);
368  if (!ttrunk) {
369  fr_dlist_talloc_free_item(&query->referrals, referral);
370  continue;
371  }
372  referral->ttrunk = ttrunk;
373  trunk_add_watch(ttrunk->trunk, TRUNK_STATE_ACTIVE, _ldap_referral_send, true, referral);
374  ROPTIONAL(RDEBUG4, DEBUG4, "Watch inserted to send referral query on active trunk");
375  }
376 
377  return 0;
378 }
#define RCSID(id)
Definition: build.h:481
#define UNUSED
Definition: build.h:313
fr_dcursor_eval_t void const * uctx
Definition: dcursor.h:546
#define ERROR(fmt,...)
Definition: dhcpclient.c:41
static void * fr_dlist_talloc_free_item(fr_dlist_head_t *list_head, void *ptr)
Free the item specified.
Definition: dlist.h:876
static void * fr_dlist_next(fr_dlist_head_t const *list_head, void const *ptr)
Get the next item in a list.
Definition: dlist.h:555
static void fr_dlist_talloc_free(fr_dlist_head_t *head)
Free all items in a doubly linked list (with talloc)
Definition: dlist.h:908
static unsigned int fr_dlist_num_elements(fr_dlist_head_t const *head)
Return the number of elements in the dlist.
Definition: dlist.h:939
static int fr_dlist_insert_tail(fr_dlist_head_t *list_head, void *ptr)
Insert an item into the tail of a list.
Definition: dlist.h:378
#define fr_dlist_talloc_init(_head, _type, _field)
Initialise the head structure of a doubly linked list.
Definition: dlist.h:275
void unlang_interpret_mark_runnable(request_t *request)
Mark a request as resumable.
Definition: interpret.c:1359
LDAPURLDesc * referral_url
URL for the referral.
Definition: base.h:482
char * host_uri
Host URI used for referral connection.
Definition: base.h:483
fr_ldap_config_t * config
Module instance config.
Definition: base.h:383
char ** referral_urls
Referral results to follow.
Definition: base.h:461
uint16_t referral_depth
How many referrals we have followed.
Definition: base.h:463
fr_ldap_thread_trunk_t * fr_thread_ldap_trunk_get(fr_ldap_thread_t *thread, char const *uri, char const *bind_dn, char const *bind_password, request_t *request, fr_ldap_config_t const *config)
Find a thread specific LDAP connection for a specific URI / bind DN.
Definition: connection.c:918
fr_dlist_head_t referrals
List of parsed referrals.
Definition: base.h:462
fr_ldap_result_code_t ret
Result code.
Definition: base.h:470
request_t * request
Request this referral relates to.
Definition: base.h:487
trunk_request_t * treq
Trunk request this query is associated with.
Definition: base.h:456
char const * identity
Bind identity for referral connection.
Definition: base.h:484
fr_ldap_query_t * query
Query this referral relates to.
Definition: base.h:481
@ LDAP_RESULT_ERROR
A general error occurred.
Definition: base.h:191
trunk_state_t fr_thread_ldap_trunk_state(fr_ldap_thread_t *thread, char const *uri, char const *bind_dn)
Lookup the state of a thread specific LDAP connection trunk for a specific URI / bind DN.
Definition: connection.c:996
char const * password
Bind password for referral connection.
Definition: base.h:485
trunk_t * trunk
Connection trunk.
Definition: base.h:405
fr_ldap_thread_trunk_t * ttrunk
Trunk this referral should use.
Definition: base.h:486
fr_ldap_referral_t * referral
Referral actually being followed.
Definition: base.h:464
@ LDAP_EXT_BINDPW
Specifies the password for an LDAP bind.
Definition: base.h:125
@ LDAP_EXT_UNSUPPORTED
Unsupported extension.
Definition: base.h:123
@ LDAP_EXT_BINDNAME
Specifies the user DN or name for an LDAP bind.
Definition: base.h:124
Connection configuration.
Definition: base.h:221
LDAP query structure.
Definition: base.h:422
Parsed LDAP referral structure.
Definition: base.h:479
Thread specific structure to manage LDAP trunk connections.
Definition: base.h:381
Thread LDAP trunk structure.
Definition: base.h:399
fr_table_num_sorted_t const fr_ldap_supported_extensions[]
Definition: base.c:60
#define PERROR(_fmt,...)
Definition: log.h:228
#define DEBUG3(_fmt,...)
Definition: log.h:266
#define ROPTIONAL(_l_request, _l_global, _fmt,...)
Use different logging functions depending on whether request is NULL or not.
Definition: log.h:528
#define RDEBUG3(fmt,...)
Definition: log.h:343
#define RERROR(fmt,...)
Definition: log.h:298
#define DEBUG4(_fmt,...)
Definition: log.h:267
#define RDEBUG4(fmt,...)
Definition: log.h:344
talloc_free(reap)
static const conf_parser_t config[]
Definition: base.c:183
#define RDEBUG2(fmt,...)
Definition: radclient.h:54
#define DEBUG2(fmt,...)
Definition: radclient.h:43
static int _fr_ldap_referral_free(fr_ldap_referral_t *referral)
Clear up a fr_ldap_referral_t.
Definition: referral.c:34
int fr_ldap_referral_follow(fr_ldap_thread_t *t, request_t *request, fr_ldap_query_t *query)
Follow an LDAP referral.
Definition: referral.c:113
int fr_ldap_referral_next(fr_ldap_thread_t *t, request_t *request, fr_ldap_query_t *query)
Follow an alternative LDAP referral.
Definition: referral.c:310
fr_ldap_referral_t * fr_ldap_referral_alloc(TALLOC_CTX *ctx, request_t *request)
Allocate a new structure to handle an LDAP referral, setting the destructor.
Definition: referral.c:49
CC_NO_UBSAN(function)
Callback to send LDAP referral queries when a trunk becomes active.
Definition: referral.c:67
#define fr_table_value_by_substr(_table, _name, _name_len, _def)
Convert a partial string to a value using an ordered or sorted table.
Definition: table.h:693
trunk_watch_entry_t * trunk_add_watch(trunk_t *trunk, trunk_state_t state, trunk_watch_t watch, bool oneshot, void const *uctx)
Add a watch entry to the trunk state list.
Definition: trunk.c:861
trunk_enqueue_t trunk_request_enqueue(trunk_request_t **treq_out, trunk_t *trunk, request_t *request, void *preq, void *rctx)
Enqueue a request that needs data written to the trunk.
Definition: trunk.c:2575
void trunk_request_signal_complete(trunk_request_t *treq)
Signal that a trunk request is complete.
Definition: trunk.c:2087
Main trunk management handle.
Definition: trunk.c:195
trunk_state_t
Definition: trunk.h:62
@ TRUNK_STATE_ACTIVE
Trunk has active connections.
Definition: trunk.h:64
@ TRUNK_ENQUEUE_OK
Operation was successful.
Definition: trunk.h:150
@ TRUNK_ENQUEUE_IN_BACKLOG
Request should be enqueued in backlog.
Definition: trunk.h:149