/src/freeradius-server/src/lib/server/request_data.c
Line | Count | Source |
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: a71a643df71c7440ef642ad35df0a242ee5b0b7b $ |
19 | | * |
20 | | * @brief Functions for allocating requests and storing internal data in them. |
21 | | * @file src/lib/server/request_data.c |
22 | | * |
23 | | * @copyright 2019 The FreeRADIUS server project |
24 | | */ |
25 | | RCSID("$Id: a71a643df71c7440ef642ad35df0a242ee5b0b7b $") |
26 | | |
27 | | #include <freeradius-devel/util/debug.h> |
28 | | #include <freeradius-devel/server/request_data.h> |
29 | | |
30 | | /** Per-request opaque data, added by modules |
31 | | * |
32 | | */ |
33 | | struct request_data_s { |
34 | | fr_dlist_t list; //!< Next opaque request data struct linked to this request. |
35 | | |
36 | | void const *unique_ptr; //!< Key to lookup request data. |
37 | | int unique_int; //!< Alternative key to lookup request data. |
38 | | char const *type; //!< Opaque type e.g. fr_pair_t, fr_dict_attr_t etc... |
39 | | void *opaque; //!< Opaque data. |
40 | | bool free_on_replace; //!< Whether to talloc_free(opaque) when the request data is removed. |
41 | | bool free_on_parent; //!< Whether to talloc_free(opaque) when the request is freed |
42 | | bool persist; //!< Whether this data should be transferred to a session_entry_t |
43 | | //!< after we're done processing this request. |
44 | | |
45 | | #ifndef NDEBUG |
46 | | char const *file; //!< File where this request data was added. |
47 | | int line; //!< Line where this request data was added. |
48 | | #endif |
49 | | }; |
50 | | |
51 | | static char *request_data_description(TALLOC_CTX *ctx, request_data_t *rd) |
52 | 0 | { |
53 | 0 | char *where; |
54 | 0 | char *what; |
55 | 0 | char *out; |
56 | | |
57 | | /* |
58 | | * Where was the request data added |
59 | | */ |
60 | 0 | #ifndef NDEBUG |
61 | 0 | where = talloc_typed_asprintf(NULL, " added at %s:%i", rd->file, rd->line); |
62 | | #else |
63 | | where = NULL; |
64 | | #endif |
65 | | |
66 | | /* |
67 | | * What was added |
68 | | */ |
69 | 0 | if (rd->type) { |
70 | 0 | what = talloc_typed_asprintf(NULL, "%p (%s)", rd->opaque, rd->type); |
71 | 0 | } else { |
72 | 0 | what = talloc_typed_asprintf(NULL, "%p", rd->opaque); |
73 | 0 | } |
74 | |
|
75 | 0 | out = talloc_typed_asprintf(ctx, "[0x%012"PRIxPTR":%i]%s %p, opaque %s%s", |
76 | 0 | (uintptr_t)rd->unique_ptr, |
77 | 0 | rd->unique_int, |
78 | 0 | rd->persist ? "[P]" : "", |
79 | 0 | rd, |
80 | 0 | what, |
81 | 0 | where ? where : ""); |
82 | 0 | talloc_free(what); |
83 | 0 | talloc_free(where); |
84 | |
|
85 | 0 | return out; |
86 | 0 | } |
87 | | |
88 | | /* Initialise a dlist for storing request data |
89 | | * |
90 | | * @param[in] list to initialise. |
91 | | */ |
92 | | void request_data_list_init(fr_dlist_head_t *data) |
93 | 0 | { |
94 | 0 | fr_dlist_talloc_init(data, request_data_t, list); |
95 | 0 | } |
96 | | |
97 | | /** Ensure opaque data is freed by binding its lifetime to the request_data_t |
98 | | * |
99 | | * @param rd Request data being freed. |
100 | | * @return |
101 | | * - 0 if free on parent is false or there's no opaque data. |
102 | | * - ...else whatever the destructor for the opaque data returned. |
103 | | */ |
104 | | static int _request_data_free(request_data_t *rd) |
105 | 0 | { |
106 | 0 | char *desc = NULL; |
107 | | |
108 | | /* |
109 | | * In the vast majority of cases the request data will |
110 | | * unlinked from its list before being freed. |
111 | | * But in case it's not, do this now. |
112 | | * |
113 | | * This helps in a very specific case where there's a list |
114 | | * of request_data_t, and the state_ctx that the |
115 | | * request_data_t is parented off is freed without the |
116 | | * request_data_t being unlinked explicitly, but before |
117 | | * the request itself is freed something attempts to access |
118 | | * the request_data_t list, and runs into freed memory. |
119 | | * |
120 | | * It's a similar pattern to structs removing themselves |
121 | | * from trees when they're freed, but with the added bonus |
122 | | * of never running into use after free errors/ |
123 | | */ |
124 | 0 | fr_dlist_entry_unlink(&rd->list); |
125 | |
|
126 | 0 | if (DEBUG_ENABLED4) desc = request_data_description(rd, rd); |
127 | |
|
128 | 0 | if (rd->free_on_parent && rd->opaque) { |
129 | 0 | int ret; |
130 | |
|
131 | 0 | DEBUG4("%s - freed with opaque data", desc); |
132 | |
|
133 | 0 | ret = talloc_free(rd->opaque); |
134 | 0 | rd->opaque = NULL; |
135 | |
|
136 | 0 | return ret; |
137 | 0 | } |
138 | | |
139 | 0 | DEBUG4("%s - freed, but leaving opaque data", desc); |
140 | |
|
141 | 0 | return 0; |
142 | 0 | } |
143 | | |
144 | | /** Allocate request data |
145 | | * |
146 | | * @param[in] ctx to allocate request data in. |
147 | | * @return new request data. |
148 | | */ |
149 | | static inline request_data_t *request_data_alloc(TALLOC_CTX *ctx) |
150 | 0 | { |
151 | 0 | request_data_t *rd; |
152 | |
|
153 | 0 | MEM(rd = talloc_zero(ctx, request_data_t)); |
154 | 0 | talloc_set_destructor(rd, _request_data_free); |
155 | |
|
156 | 0 | return rd; |
157 | 0 | } |
158 | | |
159 | | /** Add opaque data to a request_t |
160 | | * |
161 | | * The unique ptr is meant to be a module configuration, and the unique |
162 | | * integer allows the caller to have multiple opaque data associated with a request_t. |
163 | | * |
164 | | * @param[in] request to associate data with. |
165 | | * @param[in] unique_ptr Identifier for the data. |
166 | | * @param[in] unique_int Qualifier for the identifier. |
167 | | * @param[in] type Type of data (if talloced) |
168 | | * @param[in] opaque Data to associate with the request. May be NULL. |
169 | | * @param[in] free_on_replace Free opaque data if this request_data is replaced. |
170 | | * @param[in] free_on_parent Free opaque data if the request or session is freed. |
171 | | * Must not be set if the opaque data is also parented by |
172 | | * the request or state (double free). |
173 | | * @param[in] persist Transfer request data to an #fr_state_entry_t, and |
174 | | * add it back to the next request we receive for the |
175 | | * session. |
176 | | * @param[in] file request data was added in. |
177 | | * @param[in] line request data was added on. |
178 | | * @return |
179 | | * - -2 on bad arguments. |
180 | | * - -1 on memory allocation error. |
181 | | * - 0 on success. |
182 | | */ |
183 | | int _request_data_add(request_t *request, void const *unique_ptr, int unique_int, char const *type, void *opaque, |
184 | | bool free_on_replace, bool free_on_parent, bool persist, |
185 | | #ifndef NDEBUG |
186 | | char const *file, int line |
187 | | #else |
188 | | UNUSED char const *file, UNUSED int line |
189 | | #endif |
190 | | ) |
191 | 0 | { |
192 | 0 | request_data_t *rd = NULL; |
193 | | |
194 | | /* |
195 | | * Request must have a state ctx |
196 | | */ |
197 | 0 | fr_assert(request); |
198 | 0 | fr_assert(!persist || request->session_state_ctx); |
199 | 0 | fr_assert(!persist || |
200 | 0 | (talloc_parent(opaque) == request->session_state_ctx) || |
201 | 0 | (talloc_parent(opaque) == talloc_null_ctx())); |
202 | 0 | fr_assert(!free_on_parent || (talloc_parent(opaque) != request)); |
203 | |
|
204 | 0 | #ifndef TALLOC_GET_TYPE_ABORT_NOOP |
205 | 0 | if (type) opaque = _talloc_get_type_abort(opaque, type, __location__); |
206 | 0 | #endif |
207 | |
|
208 | 0 | while ((rd = fr_dlist_next(&request->data, rd))) { |
209 | 0 | if ((rd->unique_ptr != unique_ptr) || (rd->unique_int != unique_int)) continue; |
210 | | |
211 | 0 | fr_dlist_remove(&request->data, rd); /* Unlink from the list */ |
212 | | |
213 | | /* |
214 | | * If caller requires custom behaviour on free |
215 | | * they must set a destructor. |
216 | | */ |
217 | 0 | if (rd->free_on_replace && rd->opaque) { |
218 | 0 | RDEBUG4("%s: Freeing %s%s%p at %p:%i via replacement", |
219 | 0 | __FUNCTION__, |
220 | 0 | rd->type ? rd->type : "", rd->type ? " " : "", |
221 | 0 | rd->opaque, rd->unique_ptr, rd->unique_int); |
222 | 0 | talloc_free(rd->opaque); |
223 | 0 | } |
224 | | /* |
225 | | * Need a new one, rd one's parent is wrong. |
226 | | * And no, we can't just steal. |
227 | | */ |
228 | 0 | if (rd->persist != persist) { |
229 | 0 | rd->free_on_parent = false; |
230 | 0 | TALLOC_FREE(rd); |
231 | 0 | } |
232 | |
|
233 | 0 | break; /* replace the existing entry */ |
234 | 0 | } |
235 | | |
236 | | /* |
237 | | * Only alloc new memory if we're not replacing |
238 | | * an existing entry. |
239 | | * |
240 | | * Tie the lifecycle of the data to either the state_ctx |
241 | | * or the request, depending on whether it should |
242 | | * persist or not. |
243 | | */ |
244 | 0 | if (!rd) { |
245 | 0 | if (persist) { |
246 | 0 | fr_assert(request->session_state_ctx); |
247 | 0 | rd = request_data_alloc(request->session_state_ctx); |
248 | 0 | } else { |
249 | 0 | rd = request_data_alloc(request); |
250 | 0 | } |
251 | |
|
252 | 0 | } |
253 | 0 | if (!rd) return -1; |
254 | | |
255 | 0 | rd->unique_ptr = unique_ptr; |
256 | 0 | rd->unique_int = unique_int; |
257 | 0 | rd->type = type; |
258 | 0 | rd->opaque = opaque; |
259 | 0 | rd->free_on_replace = free_on_replace; |
260 | 0 | rd->free_on_parent = free_on_parent; |
261 | 0 | rd->persist = persist; |
262 | 0 | #ifndef NDEBUG |
263 | 0 | rd->file = file; |
264 | 0 | rd->line = line; |
265 | 0 | #endif |
266 | |
|
267 | 0 | fr_dlist_insert_head(&request->data, rd); |
268 | |
|
269 | 0 | RDEBUG4("%s: %s%s%p at %p:%i, free_on_replace: %s, free_on_parent: %s, persist: %s", |
270 | 0 | __FUNCTION__, |
271 | 0 | rd->type ? rd->type : "", rd->type ? " " : "", |
272 | 0 | rd->opaque, rd->unique_ptr, rd->unique_int, |
273 | 0 | free_on_replace ? "yes" : "no", |
274 | 0 | free_on_parent ? "yes" : "no", |
275 | 0 | persist ? "yes" : "no"); |
276 | |
|
277 | 0 | return 0; |
278 | 0 | } |
279 | | |
280 | | /** Get opaque data from a request |
281 | | * |
282 | | * @note The unique ptr is meant to be a module configuration, and the unique |
283 | | * integer allows the caller to have multiple opaque data associated with a request_t. |
284 | | * |
285 | | * @param[in] request to retrieve data from. |
286 | | * @param[in] unique_ptr Identifier for the data. |
287 | | * @param[in] unique_int Qualifier for the identifier. |
288 | | * @return |
289 | | * - NULL if no opaque data could be found. |
290 | | * - the opaque data. The entry holding the opaque data is removed from the request. |
291 | | */ |
292 | | void *request_data_get(request_t *request, void const *unique_ptr, int unique_int) |
293 | 0 | { |
294 | 0 | request_data_t *rd = NULL; |
295 | |
|
296 | 0 | if (!request) return NULL; |
297 | | |
298 | 0 | while ((rd = fr_dlist_next(&request->data, rd))) { |
299 | 0 | void *ptr; |
300 | |
|
301 | 0 | if ((rd->unique_ptr != unique_ptr) || (rd->unique_int != unique_int)) continue; |
302 | | |
303 | 0 | ptr = rd->opaque; |
304 | |
|
305 | 0 | rd->free_on_parent = false; /* Don't free opaque data we're handing back */ |
306 | 0 | fr_dlist_remove(&request->data, rd); |
307 | |
|
308 | 0 | #ifndef TALLOC_GET_TYPE_ABORT_NOOP |
309 | 0 | if (rd->type) ptr = _talloc_get_type_abort(ptr, rd->type, __location__); |
310 | 0 | #endif |
311 | |
|
312 | 0 | RDEBUG4("%s: %s%s%p at %p:%i retrieved and unlinked", |
313 | 0 | __FUNCTION__, |
314 | 0 | rd->type ? rd->type : "", rd->type ? " " : "", |
315 | 0 | rd->opaque, rd->unique_ptr, rd->unique_int); |
316 | |
|
317 | 0 | talloc_free(rd); |
318 | |
|
319 | 0 | return ptr; |
320 | 0 | } |
321 | | |
322 | 0 | RDEBUG4("%s: No request data found at %p:%i", __FUNCTION__, unique_ptr, unique_int); |
323 | |
|
324 | 0 | return NULL; /* wasn't found, too bad... */ |
325 | 0 | } |
326 | | |
327 | | /** Get opaque data from a request without removing it |
328 | | * |
329 | | * @note The unique ptr is meant to be a module configuration, and the unique |
330 | | * integer allows the caller to have multiple opaque data associated with a request_t. |
331 | | * |
332 | | * @param request to retrieve data from. |
333 | | * @param unique_ptr Identifier for the data. |
334 | | * @param unique_int Qualifier for the identifier. |
335 | | * @return |
336 | | * - NULL if no opaque data could be found. |
337 | | * - the opaque data. |
338 | | */ |
339 | | void *request_data_reference(request_t *request, void const *unique_ptr, int unique_int) |
340 | 0 | { |
341 | 0 | request_data_t *rd = NULL; |
342 | |
|
343 | 0 | if (!request) return NULL; |
344 | | |
345 | 0 | while ((rd = fr_dlist_next(&request->data, rd))) { |
346 | 0 | if ((rd->unique_ptr != unique_ptr) || (rd->unique_int != unique_int)) continue; |
347 | | |
348 | 0 | #ifndef TALLOC_GET_TYPE_ABORT_NOOP |
349 | 0 | if (rd->type) rd->opaque = _talloc_get_type_abort(rd->opaque, rd->type, __location__); |
350 | 0 | #endif |
351 | |
|
352 | 0 | RDEBUG4("%s: %s%s%p at %p:%i retrieved", |
353 | 0 | __FUNCTION__, |
354 | 0 | rd->type ? rd->type : "", rd->type ? " " : "", |
355 | 0 | rd->opaque, rd->unique_ptr, rd->unique_int); |
356 | |
|
357 | 0 | return rd->opaque; |
358 | 0 | } |
359 | | |
360 | 0 | RDEBUG4("%s: No request data found at %p:%i", __FUNCTION__, unique_ptr, unique_int); |
361 | |
|
362 | 0 | return NULL; /* wasn't found, too bad... */ |
363 | 0 | } |
364 | | |
365 | | /** Loop over all the request data, pulling out ones matching persist state |
366 | | * |
367 | | * @param[out] out Head of result list. |
368 | | * @param[in] request to search for request_data_t in. |
369 | | * @param[in] persist Whether to pull persistable or non-persistable data. |
370 | | * @return number of request_data_t retrieved. |
371 | | */ |
372 | | int request_data_by_persistance(fr_dlist_head_t *out, request_t *request, bool persist) |
373 | 0 | { |
374 | 0 | int count = 0; |
375 | 0 | request_data_t *rd = NULL, *prev; |
376 | |
|
377 | 0 | while ((rd = fr_dlist_next(&request->data, rd))) { |
378 | 0 | if (rd->persist != persist) continue; |
379 | | |
380 | 0 | prev = fr_dlist_remove(&request->data, rd); |
381 | 0 | fr_dlist_insert_tail(out, rd); |
382 | 0 | rd = prev; |
383 | 0 | count++; |
384 | 0 | } |
385 | |
|
386 | 0 | return count; |
387 | 0 | } |
388 | | |
389 | | /** Loop over all the request data, copying, then freeing ones matching persist state |
390 | | * |
391 | | * @param[in] ctx To allocate new request_data_t. |
392 | | * @param[out] out Head of result list. If NULL, data |
393 | | * will be reparented in place. |
394 | | * @param[in] request to search for request_data_t in. |
395 | | * @param[in] persist Whether to pull persistable or non-persistable data. |
396 | | * @return number of request_data_t retrieved. |
397 | | */ |
398 | | int request_data_by_persistance_reparent(TALLOC_CTX *ctx, fr_dlist_head_t *out, request_t *request, bool persist) |
399 | 0 | { |
400 | 0 | int count = 0; |
401 | 0 | request_data_t *rd = NULL, *new, *prev; |
402 | 0 | fr_dlist_head_t head; |
403 | |
|
404 | 0 | fr_dlist_talloc_init(&head, request_data_t, list); |
405 | |
|
406 | 0 | while ((rd = fr_dlist_next(&request->data, rd))) { |
407 | 0 | if (rd->persist != persist) continue; |
408 | | |
409 | 0 | prev = fr_dlist_remove(&request->data, rd); |
410 | |
|
411 | 0 | new = request_data_alloc(ctx); |
412 | 0 | memcpy(new, rd, sizeof(*new)); |
413 | | |
414 | | /* |
415 | | * Clear the list pointers... |
416 | | */ |
417 | 0 | memset(&new->list, 0, sizeof(new->list)); |
418 | 0 | rd->free_on_parent = false; |
419 | 0 | talloc_free(rd); |
420 | |
|
421 | 0 | if (out) { |
422 | 0 | fr_dlist_insert_tail(out, new); |
423 | 0 | } else { |
424 | 0 | fr_dlist_insert_tail(&head, new); |
425 | 0 | } |
426 | 0 | rd = prev; |
427 | 0 | count++; |
428 | 0 | } |
429 | |
|
430 | 0 | if (!out) fr_dlist_move(&request->data, &head); |
431 | |
|
432 | 0 | return count; |
433 | 0 | } |
434 | | |
435 | | /** Return how many request data entries exist of a given persistence |
436 | | * |
437 | | * @param[in] request to check in. |
438 | | * @param[in] persist Whether to count persistable or non-persistable data. |
439 | | * @return number of request_data_t that exist in persistable or non-persistable form |
440 | | */ |
441 | | int request_data_by_persistance_count(request_t *request, bool persist) |
442 | 0 | { |
443 | 0 | int count = 0; |
444 | 0 | request_data_t *rd = NULL; |
445 | |
|
446 | 0 | while ((rd = fr_dlist_next(&request->data, rd))) { |
447 | 0 | if (rd->persist != persist) continue; |
448 | | |
449 | 0 | count++; |
450 | 0 | } |
451 | |
|
452 | 0 | return count; |
453 | 0 | } |
454 | | |
455 | | /** Add request data back to a request |
456 | | * |
457 | | * @note May add multiple entries (if they're linked). |
458 | | * @note Will not check for duplicates. |
459 | | * |
460 | | * @param request to add data to. |
461 | | * @param in Data to add. |
462 | | */ |
463 | | void request_data_restore(request_t *request, fr_dlist_head_t *in) |
464 | 0 | { |
465 | 0 | fr_dlist_move(&request->data, in); |
466 | 0 | } |
467 | | |
468 | | /** Used for removing data from subrequests that are about to be freed |
469 | | * |
470 | | * @param[in] request to remove persistable data from. |
471 | | */ |
472 | | void request_data_persistable_free(request_t *request) |
473 | 0 | { |
474 | 0 | fr_dlist_head_t head; |
475 | |
|
476 | 0 | fr_dlist_talloc_init(&head, request_data_t, list); |
477 | |
|
478 | 0 | request_data_by_persistance(&head, request, true); |
479 | |
|
480 | 0 | fr_dlist_talloc_free(&head); |
481 | 0 | } |
482 | | |
483 | | |
484 | | void request_data_list_dump(request_t *request, fr_dlist_head_t *head) |
485 | 0 | { |
486 | 0 | request_data_t *rd = NULL; |
487 | |
|
488 | 0 | if (fr_dlist_empty(head)) return; |
489 | | |
490 | 0 | while ((rd = fr_dlist_next(head, rd))) { |
491 | 0 | char *desc; |
492 | |
|
493 | 0 | desc = request_data_description(NULL, rd); |
494 | 0 | ROPTIONAL(RDEBUG, DEBUG, "%s", desc); |
495 | 0 | talloc_free(desc); |
496 | 0 | } |
497 | 0 | } |
498 | | |
499 | | void request_data_dump(request_t *request) |
500 | 0 | { |
501 | 0 | request_data_list_dump(request, &request->data); |
502 | 0 | } |
503 | | |
504 | | #ifdef WITH_VERIFY_PTR |
505 | | bool request_data_persistable(request_data_t *rd) |
506 | 0 | { |
507 | 0 | return rd->persist; |
508 | 0 | } |
509 | | |
510 | | /** Verify all request data is parented by the specified context |
511 | | * |
512 | | * @note Only available if built with WITH_VERIFY_PTR |
513 | | * |
514 | | * @param parent that should hold the request data. |
515 | | * @param entry to verify. |
516 | | * @return |
517 | | * - true if chunk lineage is correct. |
518 | | * - false if one of the chunks is parented by something else. |
519 | | */ |
520 | | bool request_data_verify_parent(TALLOC_CTX *parent, fr_dlist_head_t *entry) |
521 | 0 | { |
522 | 0 | request_data_t *rd = NULL; |
523 | |
|
524 | 0 | while ((rd = fr_dlist_next(entry, rd))) if (talloc_parent(rd) != parent) return false; |
525 | | |
526 | 0 | return true; |
527 | 0 | } |
528 | | #endif |