The FreeRADIUS server  $Id: 15bac2a4c627c01d1aa2047687b3418955ac7f00 $
persistent_search.c
Go to the documentation of this file.
1 /*
2  * This program 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
5  * (at 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: 314b29c12381543e4d43a48a035240c02b54c5f7 $
19  * @file persistent_search.c
20  * @brief LDAP sync callback functions for servers implementing persistent search.
21  *
22  * @copyright 2022 Network RADIUS SAS (legal@networkradius.com)
23  */
24 
25 #define LOG_PREFIX "ldap_sync_persistent"
26 
27 #include "persistent_search.h"
28 #include "proto_ldap_sync_ldap.h"
29 #include <freeradius-devel/ldap/base.h>
30 #include <freeradius-devel/util/debug.h>
31 
32 /** Allocate and initialise sync queries for persistent searches.
33  *
34  * Servers implementing https://tools.ietf.org/id/draft-ietf-ldapext-psearch-03.txt
35  *
36  * The persisntent search control is defined as
37  *
38  * PersistentSearch ::= SEQUENCE {
39  * changeTypes INTEGER,
40  * changesOnly BOOLEAN
41  * returnECs BOOLEAN
42  * }
43  *
44  * The sync structure is parented off the conn. When the sync is no longer needed, or
45  * an error has occurred, it should be freed with talloc_free(), which will result in
46  * an ldap_abandon message to the server to tell it to cancel the search.
47  *
48  * @param[in] conn Connection to issue the search request on.
49  * @param[in] sync_no number of the sync in the array of configs.
50  * @param[in] inst instance of ldap_sync this query relates to.
51  * @param[in] cookie not applicable to persistent search LDAP servers.
52  */
53 int persistent_sync_state_init(fr_ldap_connection_t *conn, size_t sync_no, proto_ldap_sync_t const *inst, UNUSED uint8_t const *cookie)
54 {
55  static char const *notify_oid = LDAP_CONTROL_PERSIST_REQUEST;
56  LDAPControl ctrl = {0}, *ctrls[2] = { &ctrl, NULL };
57  BerElement *ber = NULL;
58  int ret;
59  sync_state_t *sync;
60  fr_rb_tree_t *tree;
61  sync_config_t *config = inst->sync_config[sync_no];
62 
63  fr_assert(conn);
65 
66  /*
67  * Allocate or retrieve the tree of outstanding msgids
68  * these are specific to the connection.
69  */
70  if (!conn->uctx) {
71  MEM(tree = fr_rb_inline_talloc_alloc(conn, sync_state_t, node, sync_state_cmp, NULL));
72  conn->uctx = tree;
73  } else {
74  tree = talloc_get_type_abort(conn->uctx, fr_rb_tree_t);
75  }
76 
77  /*
78  * Allocate the sync request control
79  */
80  ber = ber_alloc_t(LBER_USE_DER);
81  if (!ber) {
82  ERROR("Failed allocating sync control");
83  return -1;
84  }
85 
86  sync = sync_state_alloc(tree, conn, inst, sync_no, config);
87 
88  memcpy(&ctrl.ldctl_oid, &notify_oid, sizeof(ctrl.ldctl_oid));
89 
90  /*
91  * The value for the search control is
92  * - changeTypes - what changes to receive notifications of
93  * - changesOnly - don't send initial directory contents first
94  * - returnECs - send Entry Change Notification control with change responses.
95  */
96  ber_printf(ber, "{ibb}", LDAP_CONTROL_PERSIST_ENTRY_CHANGE_ADD |
97  LDAP_CONTROL_PERSIST_ENTRY_CHANGE_DELETE |
98  LDAP_CONTROL_PERSIST_ENTRY_CHANGE_MODIFY |
99  LDAP_CONTROL_PERSIST_ENTRY_CHANGE_RENAME,
100  config->changes_only, true );
101  ret = ber_flatten2(ber, &ctrl.ldctl_value, 0);
102  if (ret < 0) {
103  ERROR("Failed creating sync control");
104  ber_free(ber, 1);
105  error:
106  talloc_free(sync);
107  return -1;
108  }
109  memcpy(&ctrl.ldctl_oid, &notify_oid, sizeof(ctrl.ldctl_oid));
110 
111  /*
112  * Mark the control as critical
113  */
114  ctrl.ldctl_iscritical = 1;
115 
116  ret = fr_ldap_search_async(&sync->msgid, NULL, conn, config->base_dn, config->scope,
117  config->filter, config->attrs, ctrls, NULL);
118  ber_free(ber, 1);
119 
120  if (ret != LDAP_PROC_SUCCESS) {
121  ERROR("Failed to start persistent search query");
122  goto error;
123  }
124 
125  if (!fr_rb_insert(tree, sync)) {
126  ERROR("Duplicate sync (msgid %i)", sync->msgid);
127  goto error;
128  }
129 
130  DEBUG3("Sync created with base dn \"%s\", filter \"%s\", msgid %i",
131  sync->config->base_dn, sync->config->filter, sync->msgid);
132 
133  trigger_exec(unlang_interpret_get_thread_default(), config->cs, "ldap_sync.start", true, &sync->trigger_args);
134 
135  /*
136  * Register event to store cookies at a regular interval
137  * Whilst persistent search LDAP servers don't provide cookies as such
138  * we treat change numbers, if provided, as cookies.
139  */
140  fr_event_timer_in(sync, conn->conn->el, &sync->cookie_ev, inst->cookie_interval, ldap_sync_cookie_event, sync);
141 
142  return 0;
143 }
144 
145 /** Handle a SearchResultEntry response from Persistent Search LDAP servers
146  *
147  * Upon receipt of a search request containing the PersistentSearch control, if changesOnly is
148  * false, the server provides the initial content using zero or more SearchResultEntries
149  * without EntryChangeNotification controls.
150  *
151  * Changes subsequent to the initial search request, result in SearchResultEntry or SearchResultReference
152  * with the EntryChangeNotification control which indicates what type of change is being reported.
153  *
154  * The Entry Change Notification is an LDAP Control where the controlType is the object identifier
155  * 2.16.840.1.113730.3.4.3 and the controlValue, an OCTET STRING.
156  * It contains a BER-encoded syncStateValue.
157  *
158  * EntryChangeNotification ::= SEQUENCE {
159  * changeType ENUMERATED {
160  * add (1),
161  * delete (2)
162  * modify (4),
163  * modDN (8)
164  * },
165  * previousDN LDAPDN OPTIONAL, -- only when the changeType is modDN
166  * changeNumber INTEGER OPTIONAL -- if supported, the changeNumber from the change log.
167  * }
168  *
169  * The Sync State Control is only included in SearchResultEntry and SearchResultReference Messages.
170  *
171  * @param[in] sync message was associated with.
172  * @param[in] msg containing an entry to process.
173  * @param[in] ctrls associated with the msg.
174  * @return
175  * - 0 on success.
176  * - -1 on failure.
177  */
178 int persistent_sync_search_entry(sync_state_t *sync, LDAPMessage *msg, LDAPControl **ctrls)
179 {
180  int ret = 0, i;
181  ber_len_t len;
182  BerElement *ber = NULL;
184  int change_type, change_no;
185  struct berval orig_dn = { 0, NULL };
186 
187  fr_assert(sync);
188  fr_assert(sync->conn);
189  fr_assert(msg);
190 
191  if (!ctrls) {
192  missing_control:
193  /*
194  * Initial directory sync does not have Entry Change
195  * Notification control in the returned messages.
196  */
197  if (sync->phase == SYNC_PHASE_INIT) {
198  op = SYNC_OP_ADD;
199  goto process_entry;
200  }
201  ERROR("searchResEntry missing EntryChangeNotification control");
202  error:
203  ldap_msgfree(msg);
204  return -1;
205  }
206 
207  /*
208  * Every SearchResultEntry must contain a Entry Change Notification Control
209  * describing the state of an object/the changes that should be made to it.
210  *
211  * EntryChangeNotification ::= SEQUENCE {
212  * changeType ENUMERATED {
213  * add (1),
214  * delete (2),
215  * modify (4),
216  * modDN (8)
217  * },
218  * previousDN LDAPDN OPTIONAL, -- modifyDN ops. only
219  * changeNumber INTEGER OPTIONAL -- if supported
220  * }
221  *
222  * If the entry doesn't have this control then the LDAP server is broken.
223  */
224  for (i = 0; ctrls[i] != NULL; i++) {
225  if (strcmp(ctrls[i]->ldctl_oid, LDAP_CONTROL_PERSIST_ENTRY_CHANGE_NOTICE) == 0) break;
226  }
227  if (!ctrls[i]) goto missing_control;
228 
229  if (sync->phase == SYNC_PHASE_INIT) sync->phase = SYNC_PHASE_DONE;
230 
231  /*
232  * Get the value of the control.
233  */
234  ber = ber_init(&ctrls[i]->ldctl_value);
235  if (!ber) {
236  ERROR("Failed allocating ber to handle syncStateValue control");
237 
238  free_ber:
239  if (ber) ber_free(ber, 1);
240  goto error;
241  }
242 
243  /*
244  * Extract the change type - the only non-optional value.
245  */
246  if (ber_scanf(ber, "{e", &change_type) == LBER_ERROR) {
247  ERROR("Maformed EntryChangeNotification control");
248  goto free_ber;
249  }
250 
251  /*
252  * Modifications provide object previous DN.
253  */
254  if ((change_type == LDAP_CONTROL_PERSIST_ENTRY_CHANGE_RENAME) &&
255  (ber_scanf(ber, "m", &orig_dn) == LBER_ERROR)) {
256  ERROR("Maformed EntryChangeNotification for entry modification");
257  goto free_ber;
258  }
259 
260  if (ber_peek_tag(ber, &len) == 0x02) {
261  if (ber_scanf(ber, "i", &change_no) == LBER_ERROR) {
262  ERROR("Malformed changeNumber control");
263  goto free_ber;
264  }
265  /*
266  * The server has returned a changeNumber, treat it as a new cookie
267  */
268  if (sync->cookie) talloc_free(sync->cookie);
269  sync->cookie = (uint8_t *)talloc_asprintf(sync, "%d", change_no);
270  if (ldap_sync_cookie_store(sync, false) < 0) goto error;
271  }
272 
273  if (ber_scanf(ber, "}") == LBER_ERROR) {
274  ERROR("Malformed syncStatevalue sequence");
275  goto free_ber;
276  }
277 
278  /*
279  * Map persistent change types to sync states
280  */
281  switch(change_type) {
282  case LDAP_CONTROL_PERSIST_ENTRY_CHANGE_ADD:
283  op = SYNC_OP_ADD;
284  break;
285  case LDAP_CONTROL_PERSIST_ENTRY_CHANGE_DELETE:
286  op = SYNC_OP_DELETE;
287  break;
288  case LDAP_CONTROL_PERSIST_ENTRY_CHANGE_MODIFY:
289  op = SYNC_OP_MODIFY;
290  break;
291  case LDAP_CONTROL_PERSIST_ENTRY_CHANGE_RENAME:
292  op = SYNC_OP_MODIFY;
293  break;
294  default:
295  ERROR("Invalid changeType returned");
296  goto free_ber;
297  }
298 
299 process_entry:
300  if (DEBUG_ENABLED3) {
301  char *entry_dn = ldap_get_dn(sync->conn->handle, msg);
302 
303  DEBUG3("Processing searchResEntry (%s), dn \"%s\"",
304  fr_table_str_by_value(sync_op_table, op, "<unknown>"),
305  entry_dn ? entry_dn : "<unknown>");
306 
307  ldap_memfree(entry_dn);
308  }
309 
310  /*
311  * Send the packet with the entry change notification
312  */
313  ret = ldap_sync_entry_send(sync, NULL, &orig_dn, msg, op);
314 
315  ber_free(ber, 1);
316 
317  return ret;
318 }
log_entry msg
Definition: acutest.h:794
#define UNUSED
Definition: build.h:313
#define ERROR(fmt,...)
Definition: dhcpclient.c:41
#define fr_event_timer_in(...)
Definition: event.h:255
unlang_interpret_t * unlang_interpret_get_thread_default(void)
Get the default interpreter for this thread.
Definition: interpret.c:1787
LDAP * handle
libldap handle.
Definition: base.h:333
void * uctx
User data associated with the handle.
Definition: base.h:354
connection_t * conn
Connection state handle.
Definition: base.h:345
@ LDAP_PROC_SUCCESS
Operation was successful.
Definition: base.h:585
Tracks the state of a libldap connection handle.
Definition: base.h:332
fr_ldap_rcode_t fr_ldap_search_async(int *msgid, request_t *request, fr_ldap_connection_t *pconn, char const *dn, int scope, char const *filter, char const *const *attrs, LDAPControl **serverctrls, LDAPControl **clientctrls)
Search for something in the LDAP directory.
Definition: base.c:528
#define DEBUG3(_fmt,...)
Definition: log.h:266
#define DEBUG_ENABLED3
True if global debug level 1-3 messages are enabled.
Definition: log.h:259
talloc_free(reap)
unsigned char uint8_t
Definition: merged_model.c:30
int persistent_sync_search_entry(sync_state_t *sync, LDAPMessage *msg, LDAPControl **ctrls)
Handle a SearchResultEntry response from Persistent Search LDAP servers.
int persistent_sync_state_init(fr_ldap_connection_t *conn, size_t sync_no, proto_ldap_sync_t const *inst, UNUSED uint8_t const *cookie)
Allocate and initialise sync queries for persistent searches.
static const conf_parser_t config[]
Definition: base.c:183
char const * filter
Filter to retrieve only user objects.
sync_op_t
Operations to perform on entries.
@ SYNC_OP_ADD
Entry should be added to our copy.
@ SYNC_OP_MODIFY
Entry should be updated in our copy.
@ SYNC_OP_INVALID
Invalid sync operation.
@ SYNC_OP_DELETE
Entry should be deleted from our copy.
char const * base_dn
DN to search for users under.
An instance of a proto_ldap_sync listen section.
Areas of the directory to receive notifications for.
int ldap_sync_cookie_store(sync_state_t *sync, bool refresh)
Add a new cookie packet ctx to the pending list.
int ldap_sync_entry_send(sync_state_t *sync, uint8_t const uuid[SYNC_UUID_LENGTH], struct berval *orig_dn, LDAPMessage *msg, sync_op_t op)
Enqueue a new entry change packet.
fr_table_num_sorted_t const sync_op_table[]
Operations performed on entries.
sync_state_t * sync_state_alloc(TALLOC_CTX *ctx, fr_ldap_connection_t *conn, proto_ldap_sync_t const *inst, size_t sync_no, sync_config_t const *config)
Allocate a sync state.
void ldap_sync_cookie_event(fr_event_list_t *el, UNUSED fr_time_t now, void *uctx)
Event to handle storing of cookies on a timed basis.
int8_t sync_state_cmp(void const *one, void const *two)
Compare two sync state structures on msgid.
uint8_t * cookie
Opaque cookie, used to resume synchronisation.
sync_phases_t phase
Phase this sync is in.
int msgid
The unique identifier for this sync session.
sync_config_t const * config
Configuration for this sync.
@ SYNC_PHASE_DONE
Refresh phase is complete.
@ SYNC_PHASE_INIT
We haven't entered any of the refresh phases.
fr_event_timer_t const * cookie_ev
Timer event for sending cookies.
fr_ldap_connection_t * conn
Connection the sync is running on.
fr_pair_list_t trigger_args
Arguments to make available in triggers.
State of an individual sync.
#define fr_rb_inline_talloc_alloc(_ctx, _type, _field, _data_cmp, _data_free)
Allocs a red black that verifies elements are of a specific talloc type.
Definition: rb.h:246
bool fr_rb_insert(fr_rb_tree_t *tree, void const *data)
The main red black tree structure.
Definition: rb.h:73
fr_assert(0)
MEM(pair_append_request(&vp, attr_eap_aka_sim_identity) >=0)
eap_aka_sim_process_conf_t * inst
#define fr_table_str_by_value(_table, _number, _def)
Convert an integer to a string.
Definition: table.h:772
int trigger_exec(unlang_interpret_t *intp, CONF_SECTION const *cs, char const *name, bool rate_limit, fr_pair_list_t *args)
Execute a trigger - call an executable to process an event.
Definition: trigger.c:233