The FreeRADIUS server $Id: 15bac2a4c627c01d1aa2047687b3418955ac7f00 $
Loading...
Searching...
No Matches
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"
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 */
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 */
178int 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
299process_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:315
#define MEM(x)
Definition debug.h:36
#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:529
#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
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.
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.
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.
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_assert(_expr)
Definition rad_assert.h:38
bool fr_rb_insert(fr_rb_tree_t *tree, void const *data)
Insert data into a tree.
Definition rb.c:626
#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
The main red black tree structure.
Definition rb.h:73
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