]>
Commit | Line | Data |
---|---|---|
1 | /* | |
2 | * Solanum: a slightly advanced ircd | |
3 | * privilege.c: Dynamic privileges API. | |
4 | * | |
5 | * Copyright (c) 2021 Ed Kellett <e@kellett.im> | |
6 | * | |
7 | * This program is free software; you can redistribute it and/or modify | |
8 | * it under the terms of the GNU General Public License as published by | |
9 | * the Free Software Foundation; either version 2 of the License, or | |
10 | * (at your option) any later version. | |
11 | * | |
12 | * This program is distributed in the hope that it will be useful, | |
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
15 | * GNU General Public License for more details. | |
16 | * | |
17 | * You should have received a copy of the GNU General Public License | |
18 | * along with this program; if not, write to the Free Software | |
19 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 | |
20 | * USA | |
21 | * | |
22 | * Copyright (c) 2008 Ariadne Conill <ariadne@dereferenced.org> | |
23 | * | |
24 | * Permission to use, copy, modify, and/or distribute this software for any | |
25 | * purpose with or without fee is hereby granted, provided that the above | |
26 | * copyright notice and this permission notice is present in all copies. | |
27 | * | |
28 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR | |
29 | * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | |
30 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |
31 | * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, | |
32 | * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | |
33 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | |
34 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) | |
35 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, | |
36 | * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING | |
37 | * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
38 | * POSSIBILITY OF SUCH DAMAGE. | |
39 | */ | |
40 | ||
41 | #include <stdinc.h> | |
42 | #include "s_conf.h" | |
43 | #include "privilege.h" | |
44 | #include "numeric.h" | |
45 | #include "s_assert.h" | |
46 | #include "logger.h" | |
47 | #include "send.h" | |
48 | ||
49 | static rb_dlink_list privilegeset_list = {NULL, NULL, 0}; | |
50 | ||
51 | static struct PrivilegeSet * | |
52 | privilegeset_get_any(const char *name) | |
53 | { | |
54 | rb_dlink_node *iter; | |
55 | ||
56 | s_assert(name != NULL); | |
57 | ||
58 | RB_DLINK_FOREACH(iter, privilegeset_list.head) | |
59 | { | |
60 | struct PrivilegeSet *set = (struct PrivilegeSet *) iter->data; | |
61 | ||
62 | if (!rb_strcasecmp(set->name, name)) | |
63 | return set; | |
64 | } | |
65 | ||
66 | return NULL; | |
67 | } | |
68 | ||
69 | static int | |
70 | privilegeset_cmp_priv(const void *a_, const void *b_) | |
71 | { | |
72 | const char *const *a = a_, *const *b = b_; | |
73 | return strcmp(*a, *b); | |
74 | } | |
75 | ||
76 | static void | |
77 | privilegeset_index(struct PrivilegeSet *set) | |
78 | { | |
79 | size_t n; | |
80 | const char *s; | |
81 | const char **p; | |
82 | ||
83 | rb_free(set->privs); | |
84 | ||
85 | set->privs = rb_malloc(sizeof *set->privs * (set->size + 1)); | |
86 | p = set->privs; | |
87 | ||
88 | for (n = 0, s = set->priv_storage; n < set->size; n++, s += strlen(s) + 1) | |
89 | *p++ = s; | |
90 | qsort(set->privs, set->size, sizeof *set->privs, privilegeset_cmp_priv); | |
91 | set->privs[set->size] = NULL; | |
92 | } | |
93 | ||
94 | void | |
95 | privilegeset_add_privs(struct PrivilegeSet *dst, const char *privs) | |
96 | { | |
97 | size_t alloc_size, old_stored_size; | |
98 | ||
99 | if (dst->priv_storage == NULL) | |
100 | { | |
101 | dst->stored_size = dst->allocated_size = 0; | |
102 | alloc_size = 256; | |
103 | } | |
104 | else | |
105 | { | |
106 | alloc_size = dst->allocated_size; | |
107 | } | |
108 | ||
109 | old_stored_size = dst->stored_size; | |
110 | dst->stored_size += strlen(privs) + 1; | |
111 | ||
112 | while (alloc_size < dst->stored_size) | |
113 | alloc_size *= 2; | |
114 | ||
115 | if (alloc_size > dst->allocated_size) | |
116 | dst->priv_storage = rb_realloc(dst->priv_storage, alloc_size); | |
117 | ||
118 | dst->allocated_size = alloc_size; | |
119 | ||
120 | const char *s; | |
121 | char *d; | |
122 | ||
123 | for (s = privs, d = dst->priv_storage + old_stored_size; | |
124 | s <= privs + strlen(privs); | |
125 | s++, d++) | |
126 | { | |
127 | *d = *s; | |
128 | if (*d == ' ' || *d == '\0') | |
129 | { | |
130 | *d = '\0'; | |
131 | if (s > privs) dst->size += 1; | |
132 | } | |
133 | } | |
134 | ||
135 | privilegeset_index(dst); | |
136 | } | |
137 | ||
138 | static void | |
139 | privilegeset_add_privilegeset(struct PrivilegeSet *dst, const struct PrivilegeSet *src) | |
140 | { | |
141 | size_t cur_size, alloc_size; | |
142 | ||
143 | if (dst->priv_storage == NULL) | |
144 | { | |
145 | dst->stored_size = dst->allocated_size = 0; | |
146 | cur_size = 0; | |
147 | alloc_size = 256; | |
148 | } | |
149 | else | |
150 | { | |
151 | cur_size = dst->stored_size; | |
152 | alloc_size = dst->allocated_size; | |
153 | } | |
154 | ||
155 | dst->stored_size = cur_size + src->stored_size; | |
156 | ||
157 | while (alloc_size < dst->stored_size) | |
158 | alloc_size *= 2; | |
159 | ||
160 | if (alloc_size > dst->allocated_size) | |
161 | dst->priv_storage = rb_realloc(dst->priv_storage, alloc_size); | |
162 | ||
163 | dst->allocated_size = alloc_size; | |
164 | ||
165 | memcpy(dst->priv_storage + cur_size, src->priv_storage, src->stored_size); | |
166 | dst->size += src->size; | |
167 | ||
168 | privilegeset_index(dst); | |
169 | } | |
170 | ||
171 | static struct PrivilegeSet * | |
172 | privilegeset_new_orphan(const char *name) | |
173 | { | |
174 | struct PrivilegeSet *set; | |
175 | set = rb_malloc(sizeof *set); | |
176 | *set = (struct PrivilegeSet) { | |
177 | .size = 0, | |
178 | .privs = NULL, | |
179 | .priv_storage = NULL, | |
180 | .shadow = NULL, | |
181 | .status = 0, | |
182 | .refs = 0, | |
183 | .name = rb_strdup(name), | |
184 | }; | |
185 | return set; | |
186 | } | |
187 | ||
188 | static void | |
189 | privilegeset_free(struct PrivilegeSet *set) | |
190 | { | |
191 | if (set == NULL) | |
192 | return; | |
193 | ||
194 | privilegeset_free(set->shadow); | |
195 | rb_free(set->name); | |
196 | rb_free(set->privs); | |
197 | rb_free(set->priv_storage); | |
198 | rb_free(set); | |
199 | } | |
200 | ||
201 | static void | |
202 | privilegeset_shade(struct PrivilegeSet *set) | |
203 | { | |
204 | privilegeset_free(set->shadow); | |
205 | ||
206 | set->shadow = privilegeset_new_orphan(set->name); | |
207 | set->shadow->privs = set->privs; | |
208 | set->shadow->size = set->size; | |
209 | set->shadow->priv_storage = set->priv_storage; | |
210 | set->shadow->stored_size = set->stored_size; | |
211 | set->shadow->allocated_size = set->allocated_size; | |
212 | ||
213 | set->privs = NULL; | |
214 | set->size = 0; | |
215 | set->priv_storage = NULL; | |
216 | set->stored_size = 0; | |
217 | set->allocated_size = 0; | |
218 | } | |
219 | ||
220 | static void | |
221 | privilegeset_clear(struct PrivilegeSet *set) | |
222 | { | |
223 | rb_free(set->privs); | |
224 | set->privs = NULL; | |
225 | set->size = 0; | |
226 | set->stored_size = 0; | |
227 | } | |
228 | ||
229 | bool | |
230 | privilegeset_in_set(const struct PrivilegeSet *set, const char *priv) | |
231 | { | |
232 | s_assert(set != NULL); | |
233 | s_assert(priv != NULL); | |
234 | ||
235 | const char **found = bsearch(&priv, set->privs, set->size, sizeof *set->privs, privilegeset_cmp_priv); | |
236 | return found != NULL; | |
237 | } | |
238 | ||
239 | const char *const * | |
240 | privilegeset_privs(const struct PrivilegeSet *set) | |
241 | { | |
242 | static const char *no_privs[] = { NULL }; | |
243 | return set->privs != NULL ? set->privs : no_privs; | |
244 | } | |
245 | ||
246 | struct PrivilegeSet * | |
247 | privilegeset_set_new(const char *name, const char *privs, PrivilegeFlags flags) | |
248 | { | |
249 | struct PrivilegeSet *set; | |
250 | ||
251 | set = privilegeset_get_any(name); | |
252 | if (set != NULL) | |
253 | { | |
254 | if (!(set->status & CONF_ILLEGAL)) | |
255 | ilog(L_MAIN, "Duplicate privset %s", name); | |
256 | set->status &= ~CONF_ILLEGAL; | |
257 | privilegeset_clear(set); | |
258 | } | |
259 | else | |
260 | { | |
261 | set = privilegeset_new_orphan(name); | |
262 | rb_dlinkAdd(set, &set->node, &privilegeset_list); | |
263 | } | |
264 | privilegeset_add_privs(set, privs); | |
265 | set->flags = flags; | |
266 | ||
267 | return set; | |
268 | } | |
269 | ||
270 | struct PrivilegeSet * | |
271 | privilegeset_extend(const struct PrivilegeSet *parent, const char *name, const char *privs, PrivilegeFlags flags) | |
272 | { | |
273 | struct PrivilegeSet *set; | |
274 | ||
275 | s_assert(parent != NULL); | |
276 | s_assert(name != NULL); | |
277 | s_assert(privs != NULL); | |
278 | ||
279 | set = privilegeset_set_new(name, privs, flags); | |
280 | privilegeset_add_privilegeset(set, parent); | |
281 | set->flags = flags; | |
282 | ||
283 | return set; | |
284 | } | |
285 | ||
286 | struct PrivilegeSet * | |
287 | privilegeset_get(const char *name) | |
288 | { | |
289 | struct PrivilegeSet *set; | |
290 | ||
291 | set = privilegeset_get_any(name); | |
292 | if (set != NULL && set->status & CONF_ILLEGAL) | |
293 | set = NULL; | |
294 | return set; | |
295 | } | |
296 | ||
297 | struct PrivilegeSet * | |
298 | privilegeset_ref(struct PrivilegeSet *set) | |
299 | { | |
300 | s_assert(set != NULL); | |
301 | ||
302 | set->refs++; | |
303 | ||
304 | return set; | |
305 | } | |
306 | ||
307 | void | |
308 | privilegeset_unref(struct PrivilegeSet *set) | |
309 | { | |
310 | s_assert(set != NULL); | |
311 | ||
312 | if (set->refs > 0) | |
313 | set->refs--; | |
314 | else | |
315 | ilog(L_MAIN, "refs on privset %s is already 0", | |
316 | set->name); | |
317 | if (set->refs == 0 && set->status & CONF_ILLEGAL) | |
318 | { | |
319 | rb_dlinkDelete(&set->node, &privilegeset_list); | |
320 | ||
321 | privilegeset_free(set); | |
322 | } | |
323 | } | |
324 | ||
325 | struct privset_diff | |
326 | privilegeset_diff(const struct PrivilegeSet *old, const struct PrivilegeSet *new) | |
327 | { | |
328 | static const char *no_privs[] = { NULL }; | |
329 | static const struct PrivilegeSet empty = { .size = 0, .privs = no_privs }; | |
330 | static struct PrivilegeSet *set_unchanged = NULL, | |
331 | *set_added = NULL, | |
332 | *set_removed = NULL; | |
333 | static size_t n_privs = 0; | |
334 | size_t new_size = n_privs ? n_privs : 32; | |
335 | size_t i = 0, j = 0; | |
336 | ||
337 | if (set_unchanged == NULL) | |
338 | { | |
339 | set_unchanged = privilegeset_new_orphan("<unchanged>"); | |
340 | set_added = privilegeset_new_orphan("<added>"); | |
341 | set_removed = privilegeset_new_orphan("<removed>"); | |
342 | } | |
343 | ||
344 | if (old == NULL) | |
345 | old = ∅ | |
346 | if (new == NULL) | |
347 | new = ∅ | |
348 | ||
349 | while (new_size < MAX(old->size, new->size) + 1) | |
350 | new_size *= 2; | |
351 | ||
352 | if (new_size > n_privs) | |
353 | { | |
354 | set_unchanged->privs = rb_realloc(set_unchanged->privs, sizeof *set_unchanged->privs * new_size); | |
355 | set_added->privs = rb_realloc(set_added->privs, sizeof *set_added->privs * new_size); | |
356 | set_removed->privs = rb_realloc(set_removed->privs, sizeof *set_removed->privs * new_size); | |
357 | } | |
358 | ||
359 | const char **res_unchanged = set_unchanged->privs; | |
360 | const char **res_added = set_added->privs; | |
361 | const char **res_removed = set_removed->privs; | |
362 | ||
363 | while (i < old->size || j < new->size) | |
364 | { | |
365 | const char *oldpriv = NULL, *newpriv = NULL; | |
366 | int ord = 0; | |
367 | oldpriv = privilegeset_privs(old)[i]; | |
368 | newpriv = privilegeset_privs(new)[j]; | |
369 | ||
370 | if (oldpriv && newpriv) | |
371 | ord = strcmp(oldpriv, newpriv); | |
372 | ||
373 | if (newpriv == NULL || ord < 0) | |
374 | { | |
375 | *res_removed++ = oldpriv; | |
376 | i++; | |
377 | } | |
378 | else if (oldpriv == NULL || ord > 0) | |
379 | { | |
380 | *res_added++ = newpriv; | |
381 | j++; | |
382 | } | |
383 | else | |
384 | { | |
385 | *res_unchanged++ = oldpriv; | |
386 | i++; j++; | |
387 | } | |
388 | } | |
389 | ||
390 | *res_removed = *res_added = *res_unchanged = NULL; | |
391 | set_unchanged->size = res_unchanged - set_unchanged->privs; | |
392 | set_added->size = res_added - set_added->privs; | |
393 | set_removed->size = res_removed - set_removed->privs; | |
394 | ||
395 | return (struct privset_diff){ | |
396 | .unchanged = set_unchanged, | |
397 | .added = set_added, | |
398 | .removed = set_removed, | |
399 | }; | |
400 | } | |
401 | ||
402 | void | |
403 | privilegeset_prepare_rehash() | |
404 | { | |
405 | rb_dlink_node *iter; | |
406 | ||
407 | RB_DLINK_FOREACH(iter, privilegeset_list.head) | |
408 | { | |
409 | struct PrivilegeSet *set = iter->data; | |
410 | ||
411 | /* the "default" privset is special and must remain available */ | |
412 | if (!strcmp(set->name, "default")) | |
413 | continue; | |
414 | ||
415 | set->status |= CONF_ILLEGAL; | |
416 | privilegeset_shade(set); | |
417 | } | |
418 | } | |
419 | ||
420 | void | |
421 | privilegeset_cleanup_rehash() | |
422 | { | |
423 | rb_dlink_node *iter, *next; | |
424 | ||
425 | RB_DLINK_FOREACH_SAFE(iter, next, privilegeset_list.head) | |
426 | { | |
427 | struct PrivilegeSet *set = iter->data; | |
428 | ||
429 | if (set->shadow) | |
430 | { | |
431 | privilegeset_free(set->shadow); | |
432 | set->shadow = NULL; | |
433 | } | |
434 | ||
435 | privilegeset_ref(set); | |
436 | privilegeset_unref(set); | |
437 | } | |
438 | } | |
439 | ||
440 | void | |
441 | privilegeset_report(struct Client *source_p) | |
442 | { | |
443 | rb_dlink_node *ptr; | |
444 | ||
445 | RB_DLINK_FOREACH(ptr, privilegeset_list.head) | |
446 | { | |
447 | struct PrivilegeSet *set = ptr->data; | |
448 | ||
449 | /* use RPL_STATSDEBUG for now -- jilles */ | |
450 | send_multiline_init(source_p, " ", ":%s %03d %s O :%s ", | |
451 | get_id(&me, source_p), | |
452 | RPL_STATSDEBUG, | |
453 | get_id(source_p, source_p), | |
454 | set->name); | |
455 | send_multiline_remote_pad(source_p, &me); | |
456 | send_multiline_remote_pad(source_p, source_p); | |
457 | for (const char *const *s = privilegeset_privs(set); *s != NULL; s++) | |
458 | send_multiline_item(source_p, "%s", *s); | |
459 | send_multiline_fini(source_p, NULL); | |
460 | } | |
461 | } |