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: 202481613a47404389e4d4f9c22afbc90abe0e2c $
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: 202481613a47404389e4d4f9c22afbc90abe0e2c $")
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  */
68  UNUSED fr_trunk_state_t state, void *uctx)
69 {
70  fr_ldap_referral_t *referral = talloc_get_type_abort(uctx, fr_ldap_referral_t);
71  fr_ldap_query_t *query = referral->query;
72  request_t *request = referral->request;
73 
74  /*
75  * If referral is set, then another LDAP trunk has gone active first and sent the referral
76  */
77  if (query->referral) return;
78 
79  /*
80  * Enqueue referral query on active trunk connection
81  */
82  query->referral = referral;
83  switch (fr_trunk_request_enqueue(&query->treq, referral->ttrunk->trunk, request, query, NULL)) {
86  break;
87 
88  default:
89  ROPTIONAL(RERROR, ERROR, "Failed enqueueing pending LDAP referral");
90  query->ret = LDAP_RESULT_ERROR;
91  if (request) unlang_interpret_mark_runnable(request);
92  return;
93  }
94 
95  ROPTIONAL(RDEBUG3, DEBUG3, "Pending LDAP referral query queued on active trunk");
96 }
97 
98 
99 /** Follow an LDAP referral
100  *
101  * The returned list of LDAP referrals should already be in query->referrals.
102  * We check all the possible referrals and look for one where there already
103  * is an active trunk connection.
104  *
105  * @param t Thread running the query.
106  * @param request related to the query.
107  * @param query whose result was one or more referral URLs.
108  * @return
109  * - 0 on success.
110  * - < 0 on failure.
111  */
113 {
115  fr_ldap_thread_trunk_t *ttrunk = NULL;
116  int referral_no = -1;
117  fr_ldap_referral_t *referral;
118  LDAPURLDesc temp_desc;
119 
121  query->treq = NULL;
122 
123  if (query->referral_depth > 1) {
124  /*
125  * If we've already parsed a referral, clear the existing list of followers.
126  */
128  query->referral = NULL;
129  } else {
130  /*
131  * Otherwise initialise the list header for followers.
132  */
134  }
135 
136  while (query->referral_urls[++referral_no]) {
137  if (!ldap_is_ldap_url(query->referral_urls[referral_no])) {
138  ROPTIONAL(RERROR, ERROR, "Referral %s does not look like an LDAP URL",
139  query->referral_urls[referral_no]);
140  continue;
141  }
142 
143  referral = fr_ldap_referral_alloc(query, request);
144  if (!referral) continue;
145 
146  referral->query = query;
147 
148  if (ldap_url_parse(query->referral_urls[referral_no], &referral->referral_url)) {
150  "Failed parsing referral LDAP URL %s", query->referral_urls[referral_no]);
151  free_referral:
152  talloc_free(referral);
153  continue;
154  }
155 
156  temp_desc = (LDAPURLDesc){
157  .lud_scheme = referral->referral_url->lud_scheme,
158  .lud_host = referral->referral_url->lud_host,
159  .lud_port = referral->referral_url->lud_port,
160  .lud_scope = -1
161  };
162  referral->host_uri = ldap_url_desc2str(&temp_desc);
163  if (!referral->host_uri) {
165  "Failed building LDAP host URI from %s", query->referral_urls[referral_no]);
166  }
167 
168  if (config->use_referral_credentials) {
169  char **ext;
170 
171  /*
172  * If there are no extensions, OpenLDAP doesn't
173  * bother allocating an array.
174  */
175  for (ext = referral->referral_url->lud_exts; ext && *ext; ext++) {
176  char const *p;
177  bool critical = false;
178 
179  p = *ext;
180 
181  if (*p == '!') {
182  critical = true;
183  p++;
184  }
185 
186  /*
187  * LDAP Parse URL unescapes the extensions for us
188  */
190  case LDAP_EXT_BINDNAME:
191  p = strchr(p, '=');
192  if (!p) {
193  bad_ext:
195  "Failed parsing extension \"%s\": "
196  "No attribute/value delimiter '='", *ext);
197  goto free_referral;
198  }
199  referral->identity = p + 1;
200  break;
201 
202  case LDAP_EXT_BINDPW:
203  p = strchr(p, '=');
204  if (!p) goto bad_ext;
205  referral->password = p + 1;
206  break;
207 
208  default:
209  if (critical) {
211  "Failed parsing critical extension \"%s\": "
212  "Not supported by FreeRADIUS", *ext);
213  goto free_referral;
214  }
215  ROPTIONAL(RDEBUG2, DEBUG2, "Skipping unsupported extension \"%s\"", *ext);
216  continue;
217  }
218  }
219  } else {
220  if (config->rebind) {
221  referral->identity = config->admin_identity;
222  referral->password = config->admin_password;
223  }
224  }
225 
226  fr_dlist_insert_tail(&query->referrals, referral);
227  if (fr_thread_ldap_trunk_state(t, referral->host_uri,
228  referral->identity) != FR_TRUNK_STATE_ACTIVE) {
230  "No active LDAP trunk for URI %s, bound as %s",
231  referral->host_uri, referral->identity);
232  continue;
233  }
234 
235  ttrunk = fr_thread_ldap_trunk_get(t, referral->host_uri, referral->identity,
236  referral->password, request, config);
237 
238  if (!ttrunk) {
239  ROPTIONAL(RERROR, ERROR, "Unable to connect to LDAP referral URL");
240  fr_dlist_talloc_free_item(&query->referrals, referral);
241  continue;
242  }
243 
244  /*
245  * We have an active trunk enqueue the request
246  */
247  query->referral = referral;
248  switch (fr_trunk_request_enqueue(&query->treq, ttrunk->trunk, request, query, NULL)) {
249  case FR_TRUNK_ENQUEUE_OK:
251  break;
252 
253  default:
254  ROPTIONAL(RERROR, ERROR, "Failed to enqueue request for referral");
255  goto free_referral;
256  }
257  return 0;
258  }
259 
260  /*
261  * None of the referrals parsed successfully
262  */
263  if (fr_dlist_num_elements(&query->referrals) == 0) {
264  ROPTIONAL(RERROR, ERROR, "No valid LDAP referrals to follow");
265  return -1;
266  }
267 
268  /*
269  * We have parsed referrals, but none of them matched an existing active connection.
270  * Launch new trunks with callbacks so the first to become active will run the query.
271  */
272  referral = NULL;
273  while ((referral = fr_dlist_next(&query->referrals, referral))) {
274  ttrunk = fr_thread_ldap_trunk_get(t, referral->host_uri, referral->identity,
275  referral->password, request, config);
276  if (!ttrunk) {
277  fr_dlist_talloc_free_item(&query->referrals, referral);
278  continue;
279  }
280  referral->ttrunk = ttrunk;
282  ROPTIONAL(RDEBUG4, DEBUG4, "Watch inserted to send referral query on active trunk");
283  }
284 
285  return 0;
286 }
287 
288 /** Follow an alternative LDAP referral
289  *
290  * If an initial chase of an LDAP referral results in an error being returned
291  * this function can be used to attempt one of the other referral URLs given
292  * in the initial query results.
293  *
294  * The initial use of fr_ldap_referral_follow may have launched trunks for
295  * any referral URLs which parsed successfully, so this starts by looking
296  * for the first which has an active state and sends the query that way.
297  *
298  * If no active trunks match the remaining servers listed in referrals then
299  * new trunks are launched with watchers to send the query on the first
300  * active trunk.
301  *
302  * @param t Thread running the query.
303  * @param request the query relates to.
304  * @param query whose referrals are being chased.
305  * @return
306  * - 0 on success.
307  * - < 0 on failure.
308  */
310 {
312  fr_ldap_referral_t *referral = NULL;
313  fr_ldap_thread_trunk_t *ttrunk;
314 
316  query->treq = NULL;
317 
318  while ((referral = fr_dlist_next(&query->referrals, referral))) {
319  if (fr_thread_ldap_trunk_state(t, referral->host_uri,
320  referral->identity) != FR_TRUNK_STATE_ACTIVE) {
321  ROPTIONAL(RDEBUG3, DEBUG3, "No active LDAP trunk for URI %s, bind DN %s",
322  referral->host_uri, referral->identity);
323  continue;
324  }
325 
326  ttrunk = fr_thread_ldap_trunk_get(t, referral->host_uri, referral->identity,
327  referral->password, request, config);
328 
329  if (!ttrunk) {
330  ROPTIONAL(RERROR, ERROR, "Unable to connect to LDAP referral URL");
331  fr_dlist_talloc_free_item(&query->referrals, referral);
332  continue;
333  }
334 
335  /*
336  * We have an active trunk enqueue the request
337  */
338  query->referral = referral;
339  switch(fr_trunk_request_enqueue(&query->treq, ttrunk->trunk, request, query, NULL)) {
340  case FR_TRUNK_ENQUEUE_OK:
342  break;
343 
344  default:
345  ROPTIONAL(RERROR, ERROR, "Failed to enqueue request for referral");
346  continue;
347  }
348  return 0;
349  }
350 
351  /*
352  * None of the referrals parsed successfully
353  */
354  if (fr_dlist_num_elements(&query->referrals) == 0) {
355  ROPTIONAL(RERROR, ERROR, "No valid LDAP referrals to follow");
356  return -1;
357  }
358 
359  /*
360  * None of the remaining referrals have an active trunk.
361  * Launch new trunks with callbacks so the first to become active will run the query.
362  */
363  referral = NULL;
364  while ((referral = fr_dlist_next(&query->referrals, referral))) {
365  ttrunk = fr_thread_ldap_trunk_get(t, referral->host_uri, referral->identity,
366  referral->password, request, config);
367  if (!ttrunk) {
368  fr_dlist_talloc_free_item(&query->referrals, referral);
369  continue;
370  }
371  referral->ttrunk = ttrunk;
373  ROPTIONAL(RDEBUG4, DEBUG4, "Watch inserted to send referral query on active trunk");
374  }
375 
376  return 0;
377 }
#define RCSID(id)
Definition: build.h:444
#define UNUSED
Definition: build.h:313
#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:1340
LDAPURLDesc * referral_url
URL for the referral.
Definition: base.h:477
fr_trunk_request_t * treq
Trunk request this query is associated with.
Definition: base.h:451
char * host_uri
Host URI used for referral connection.
Definition: base.h:478
fr_ldap_config_t * config
Module instance config.
Definition: base.h:381
char ** referral_urls
Referral results to follow.
Definition: base.h:456
uint16_t referral_depth
How many referrals we have followed.
Definition: base.h:458
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:993
fr_dlist_head_t referrals
List of parsed referrals.
Definition: base.h:457
fr_ldap_result_code_t ret
Result code.
Definition: base.h:465
request_t * request
Request this referral relates to.
Definition: base.h:482
fr_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:1071
char const * identity
Bind identity for referral connection.
Definition: base.h:479
fr_ldap_query_t * query
Query this referral relates to.
Definition: base.h:476
@ LDAP_RESULT_ERROR
A general error occurred.
Definition: base.h:189
char const * password
Bind password for referral connection.
Definition: base.h:480
fr_trunk_t * trunk
Connection trunk.
Definition: base.h:403
fr_ldap_thread_trunk_t * ttrunk
Trunk this referral should use.
Definition: base.h:481
fr_ldap_referral_t * referral
Referral actually being followed.
Definition: base.h:459
@ 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:219
LDAP query structure.
Definition: base.h:420
Parsed LDAP referral structure.
Definition: base.h:474
Thread specific structure to manage LDAP trunk connections.
Definition: base.h:379
Thread LDAP trunk structure.
Definition: base.h:397
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:188
#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:112
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:309
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
static void _ldap_referral_send(UNUSED fr_trunk_t *trunk, UNUSED fr_trunk_state_t prev, UNUSED fr_trunk_state_t state, void *uctx)
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:174
fr_trunk_watch_entry_t * fr_trunk_add_watch(fr_trunk_t *trunk, fr_trunk_state_t state, fr_trunk_watch_t watch, bool oneshot, void const *uctx)
Add a watch entry to the trunk state list.
Definition: trunk.c:846
fr_trunk_enqueue_t fr_trunk_request_enqueue(fr_trunk_request_t **treq_out, fr_trunk_t *trunk, request_t *request, void *preq, void *rctx)
Enqueue a request that needs data written to the trunk.
Definition: trunk.c:2481
void fr_trunk_request_signal_complete(fr_trunk_request_t *treq)
Signal that a trunk request is complete.
Definition: trunk.c:1995
Main trunk management handle.
Definition: trunk.c:189
fr_trunk_state_t
Definition: trunk.h:62
@ FR_TRUNK_STATE_ACTIVE
Trunk has active connections.
Definition: trunk.h:64
@ FR_TRUNK_ENQUEUE_IN_BACKLOG
Request should be enqueued in backlog.
Definition: trunk.h:149
@ FR_TRUNK_ENQUEUE_OK
Operation was successful.
Definition: trunk.h:150