]>
Commit | Line | Data |
---|---|---|
9c3f080b | 1 | /* |
a6f63a82 | 2 | * Solanum: a slightly advanced ircd |
9c3f080b WP |
3 | * privilege.c: Dynamic privileges API. |
4 | * | |
8aadf0ce EK |
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 | * | |
9c3f080b WP |
22 | * Copyright (c) 2008 William Pitcock <nenolod@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> | |
422bb0b5 | 42 | #include "s_conf.h" |
9c3f080b | 43 | #include "privilege.h" |
3a177354 | 44 | #include "numeric.h" |
77d3d2db KB |
45 | #include "s_assert.h" |
46 | #include "logger.h" | |
47 | #include "send.h" | |
9c3f080b | 48 | |
29c92cf9 | 49 | static rb_dlink_list privilegeset_list = {NULL, NULL, 0}; |
9c3f080b | 50 | |
8aadf0ce EK |
51 | bool |
52 | privilegeset_in_set(const struct PrivilegeSet *set, const char *priv) | |
9c3f080b WP |
53 | { |
54 | s_assert(set != NULL); | |
55 | s_assert(priv != NULL); | |
56 | ||
8aadf0ce EK |
57 | if (set->privs == NULL) |
58 | return false; | |
59 | ||
60 | for (const char **s = set->privs; *s != NULL; s++) | |
61 | if (strcmp(*s, priv) == 0) return true; | |
62 | ||
63 | return false; | |
9c3f080b WP |
64 | } |
65 | ||
422bb0b5 JT |
66 | static struct PrivilegeSet * |
67 | privilegeset_get_any(const char *name) | |
68 | { | |
69 | rb_dlink_node *iter; | |
70 | ||
71 | s_assert(name != NULL); | |
72 | ||
73 | RB_DLINK_FOREACH(iter, privilegeset_list.head) | |
74 | { | |
75 | struct PrivilegeSet *set = (struct PrivilegeSet *) iter->data; | |
76 | ||
f956cb0f | 77 | if (!rb_strcasecmp(set->name, name)) |
422bb0b5 JT |
78 | return set; |
79 | } | |
80 | ||
81 | return NULL; | |
82 | } | |
83 | ||
8aadf0ce EK |
84 | static int |
85 | privilegeset_cmp_priv(const void *a_, const void *b_) | |
86 | { | |
87 | const char *const *a = a_, *const *b = b_; | |
88 | return strcmp(*a, *b); | |
89 | } | |
90 | ||
91 | static void | |
92 | privilegeset_index(struct PrivilegeSet *set) | |
93 | { | |
94 | size_t n; | |
95 | const char *s; | |
96 | const char **p; | |
97 | ||
98 | rb_free(set->privs); | |
99 | ||
100 | set->privs = rb_malloc(sizeof *set->privs * (set->size + 1)); | |
101 | p = set->privs; | |
102 | ||
103 | for (n = 0, s = set->priv_storage; n < set->size; n++, s += strlen(s) + 1) | |
104 | *p++ = s; | |
105 | qsort(set->privs, set->size, sizeof *set->privs, privilegeset_cmp_priv); | |
106 | set->privs[set->size] = NULL; | |
107 | } | |
108 | ||
109 | static void | |
110 | privilegeset_add_privs(struct PrivilegeSet *dst, const char *privs) | |
111 | { | |
112 | size_t alloc_size; | |
113 | size_t n; | |
114 | ||
115 | if (dst->priv_storage == NULL) | |
116 | { | |
117 | dst->stored_size = dst->allocated_size = 0; | |
118 | alloc_size = 256; | |
119 | } | |
120 | else | |
121 | { | |
122 | alloc_size = dst->allocated_size; | |
123 | } | |
124 | ||
125 | dst->stored_size += strlen(privs) + 1; | |
126 | ||
127 | while (alloc_size < dst->stored_size) | |
128 | alloc_size *= 2; | |
129 | ||
130 | if (alloc_size > dst->allocated_size) | |
131 | dst->priv_storage = rb_realloc(dst->priv_storage, alloc_size); | |
132 | ||
133 | dst->allocated_size = alloc_size; | |
134 | ||
135 | const char *s; | |
136 | char *d; | |
137 | for (s = privs, d = dst->priv_storage; s < privs + strlen(privs); s += n , d += n) | |
138 | { | |
139 | const char *e = strchr(s, ' '); | |
140 | /* up to space if there is one, else up to end of string */ | |
141 | n = 1 + (e != NULL ? e - s : strlen(s)); | |
142 | rb_strlcpy(d, s, n); | |
143 | ||
144 | dst->size += 1; | |
145 | } | |
146 | ||
147 | privilegeset_index(dst); | |
148 | } | |
149 | ||
150 | static void | |
151 | privilegeset_add_privilegeset(struct PrivilegeSet *dst, const struct PrivilegeSet *src) | |
152 | { | |
153 | size_t cur_size, alloc_size; | |
154 | ||
155 | if (dst->priv_storage == NULL) | |
156 | { | |
157 | dst->stored_size = dst->allocated_size = 0; | |
158 | cur_size = 0; | |
159 | alloc_size = 256; | |
160 | } | |
161 | else | |
162 | { | |
163 | cur_size = dst->stored_size; | |
164 | alloc_size = dst->allocated_size; | |
165 | } | |
166 | ||
167 | dst->stored_size = cur_size + src->stored_size; | |
168 | ||
169 | while (alloc_size < dst->stored_size) | |
170 | alloc_size *= 2; | |
171 | ||
172 | if (alloc_size > dst->allocated_size) | |
173 | dst->priv_storage = rb_realloc(dst->priv_storage, alloc_size); | |
174 | ||
175 | dst->allocated_size = alloc_size; | |
176 | ||
177 | memcpy(dst->priv_storage + cur_size, src->priv_storage, src->stored_size); | |
178 | dst->size += src->size; | |
179 | ||
180 | privilegeset_index(dst); | |
181 | } | |
182 | ||
183 | static struct PrivilegeSet * | |
184 | privilegeset_new_orphan(const char *name) | |
185 | { | |
186 | struct PrivilegeSet *set; | |
187 | set = rb_malloc(sizeof *set); | |
188 | *set = (struct PrivilegeSet) { | |
189 | .size = 0, | |
190 | .privs = NULL, | |
191 | .priv_storage = NULL, | |
192 | .shadow = NULL, | |
193 | .status = 0, | |
194 | .refs = 0, | |
195 | .name = rb_strdup(name), | |
196 | }; | |
197 | return set; | |
198 | } | |
199 | ||
200 | static void | |
201 | privilegeset_free(struct PrivilegeSet *set) | |
202 | { | |
203 | if (set == NULL) | |
204 | return; | |
205 | ||
206 | privilegeset_free(set->shadow); | |
207 | rb_free(set->name); | |
208 | rb_free(set->privs); | |
209 | rb_free(set->priv_storage); | |
210 | rb_free(set); | |
211 | } | |
212 | ||
213 | static void | |
214 | privilegeset_shade(struct PrivilegeSet *set) | |
215 | { | |
216 | privilegeset_free(set->shadow); | |
217 | ||
218 | set->shadow = privilegeset_new_orphan(set->name); | |
219 | set->shadow->privs = set->privs; | |
220 | set->shadow->size = set->size; | |
221 | set->shadow->priv_storage = set->priv_storage; | |
222 | set->shadow->stored_size = set->stored_size; | |
223 | set->shadow->allocated_size = set->allocated_size; | |
224 | ||
225 | set->privs = NULL; | |
226 | set->size = 0; | |
227 | set->priv_storage = NULL; | |
228 | set->stored_size = 0; | |
229 | set->allocated_size = 0; | |
230 | } | |
231 | ||
232 | static void | |
233 | privilegeset_clear(struct PrivilegeSet *set) | |
234 | { | |
235 | rb_free(set->privs); | |
236 | set->privs = NULL; | |
237 | set->size = 0; | |
238 | set->stored_size = 0; | |
239 | } | |
240 | ||
9c3f080b WP |
241 | struct PrivilegeSet * |
242 | privilegeset_set_new(const char *name, const char *privs, PrivilegeFlags flags) | |
243 | { | |
244 | struct PrivilegeSet *set; | |
245 | ||
422bb0b5 JT |
246 | set = privilegeset_get_any(name); |
247 | if (set != NULL) | |
248 | { | |
249 | if (!(set->status & CONF_ILLEGAL)) | |
250 | ilog(L_MAIN, "Duplicate privset %s", name); | |
251 | set->status &= ~CONF_ILLEGAL; | |
8aadf0ce | 252 | privilegeset_clear(set); |
422bb0b5 JT |
253 | } |
254 | else | |
255 | { | |
8aadf0ce | 256 | set = privilegeset_new_orphan(name); |
422bb0b5 JT |
257 | rb_dlinkAdd(set, &set->node, &privilegeset_list); |
258 | } | |
8aadf0ce | 259 | privilegeset_add_privs(set, privs); |
9c3f080b WP |
260 | set->flags = flags; |
261 | ||
9c3f080b WP |
262 | return set; |
263 | } | |
264 | ||
353f8625 | 265 | struct PrivilegeSet * |
8aadf0ce | 266 | privilegeset_extend(const struct PrivilegeSet *parent, const char *name, const char *privs, PrivilegeFlags flags) |
353f8625 WP |
267 | { |
268 | struct PrivilegeSet *set; | |
269 | ||
270 | s_assert(parent != NULL); | |
271 | s_assert(name != NULL); | |
272 | s_assert(privs != NULL); | |
353f8625 | 273 | |
8aadf0ce EK |
274 | set = privilegeset_set_new(name, privs, flags); |
275 | privilegeset_add_privilegeset(set, parent); | |
353f8625 | 276 | set->flags = flags; |
353f8625 WP |
277 | |
278 | return set; | |
279 | } | |
280 | ||
9c3f080b WP |
281 | struct PrivilegeSet * |
282 | privilegeset_get(const char *name) | |
283 | { | |
422bb0b5 | 284 | struct PrivilegeSet *set; |
9c3f080b | 285 | |
422bb0b5 JT |
286 | set = privilegeset_get_any(name); |
287 | if (set != NULL && set->status & CONF_ILLEGAL) | |
288 | set = NULL; | |
289 | return set; | |
9c3f080b WP |
290 | } |
291 | ||
598b4cf1 | 292 | struct PrivilegeSet * |
9c3f080b WP |
293 | privilegeset_ref(struct PrivilegeSet *set) |
294 | { | |
295 | s_assert(set != NULL); | |
296 | ||
297 | set->refs++; | |
598b4cf1 WP |
298 | |
299 | return set; | |
9c3f080b WP |
300 | } |
301 | ||
302 | void | |
303 | privilegeset_unref(struct PrivilegeSet *set) | |
304 | { | |
305 | s_assert(set != NULL); | |
306 | ||
422bb0b5 JT |
307 | if (set->refs > 0) |
308 | set->refs--; | |
309 | else | |
310 | ilog(L_MAIN, "refs on privset %s is already 0", | |
311 | set->name); | |
312 | if (set->refs == 0 && set->status & CONF_ILLEGAL) | |
9c3f080b WP |
313 | { |
314 | rb_dlinkDelete(&set->node, &privilegeset_list); | |
315 | ||
8aadf0ce EK |
316 | privilegeset_free(set); |
317 | } | |
318 | } | |
319 | ||
320 | const struct PrivilegeSet ** | |
321 | privilegeset_diff(const struct PrivilegeSet *old, const struct PrivilegeSet *new) | |
322 | { | |
323 | static const char *no_privs[] = { NULL }; | |
324 | static const struct PrivilegeSet empty = { .size = 0, .privs = no_privs }; | |
325 | static struct PrivilegeSet *set_unchanged = NULL, | |
326 | *set_added = NULL, | |
327 | *set_removed = NULL; | |
328 | static const struct PrivilegeSet *result_sets[3]; | |
329 | static size_t n_privs = 0; | |
330 | size_t new_size = n_privs ? n_privs : 32; | |
331 | size_t i = 0, j = 0; | |
332 | ||
333 | if (result_sets[0] == NULL) | |
334 | { | |
335 | result_sets[0] = set_unchanged = privilegeset_new_orphan("<unchanged>"); | |
336 | result_sets[1] = set_added = privilegeset_new_orphan("<added>"); | |
337 | result_sets[2] = set_removed = privilegeset_new_orphan("<removed>"); | |
338 | } | |
339 | ||
340 | if (old == NULL) | |
341 | old = ∅ | |
342 | if (new == NULL) | |
343 | new = ∅ | |
344 | ||
345 | while (new_size < MAX(old->size, new->size) + 1) | |
346 | new_size *= 2; | |
347 | ||
348 | if (new_size > n_privs) | |
349 | { | |
350 | set_unchanged->privs = rb_realloc(set_unchanged->privs, sizeof *set_unchanged->privs * new_size); | |
351 | set_added->privs = rb_realloc(set_added->privs, sizeof *set_added->privs * new_size); | |
352 | set_removed->privs = rb_realloc(set_removed->privs, sizeof *set_removed->privs * new_size); | |
353 | } | |
354 | ||
355 | const char **res_unchanged = set_unchanged->privs; | |
356 | const char **res_added = set_added->privs; | |
357 | const char **res_removed = set_removed->privs; | |
358 | ||
359 | while (i < old->size || j < new->size) | |
360 | { | |
361 | const char *oldpriv = NULL, *newpriv = NULL; | |
362 | int ord = 0; | |
363 | if (i < old->size) | |
364 | oldpriv = old->privs[i]; | |
365 | if (j < new->size) | |
366 | newpriv = new->privs[j]; | |
367 | ||
368 | if (oldpriv && newpriv) | |
369 | ord = strcmp(oldpriv, newpriv); | |
370 | ||
371 | if (newpriv == NULL || ord < 0) | |
372 | { | |
373 | *res_removed++ = oldpriv; | |
374 | i++; | |
375 | } | |
376 | else if (oldpriv == NULL || ord > 0) | |
377 | { | |
378 | *res_added++ = newpriv; | |
379 | j++; | |
380 | } | |
381 | else | |
382 | { | |
383 | *res_unchanged++ = oldpriv; | |
384 | i++; j++; | |
385 | } | |
9c3f080b | 386 | } |
8aadf0ce EK |
387 | |
388 | *res_removed = *res_added = *res_unchanged = NULL; | |
389 | set_unchanged->size = res_unchanged - set_unchanged->privs; | |
390 | set_added->size = res_added - set_added->privs; | |
391 | set_removed->size = res_removed - set_removed->privs; | |
392 | ||
393 | return result_sets; | |
9c3f080b | 394 | } |
422bb0b5 JT |
395 | |
396 | void | |
8aadf0ce | 397 | privilegeset_prepare_rehash() |
422bb0b5 JT |
398 | { |
399 | rb_dlink_node *iter; | |
400 | ||
401 | RB_DLINK_FOREACH(iter, privilegeset_list.head) | |
402 | { | |
8aadf0ce | 403 | struct PrivilegeSet *set = iter->data; |
422bb0b5 | 404 | |
a1d2fafd JT |
405 | /* the "default" privset is special and must remain available */ |
406 | if (!strcmp(set->name, "default")) | |
407 | continue; | |
408 | ||
422bb0b5 | 409 | set->status |= CONF_ILLEGAL; |
8aadf0ce | 410 | privilegeset_shade(set); |
422bb0b5 JT |
411 | } |
412 | } | |
413 | ||
414 | void | |
8aadf0ce | 415 | privilegeset_cleanup_rehash() |
422bb0b5 JT |
416 | { |
417 | rb_dlink_node *iter, *next; | |
418 | ||
419 | RB_DLINK_FOREACH_SAFE(iter, next, privilegeset_list.head) | |
420 | { | |
8aadf0ce EK |
421 | struct PrivilegeSet *set = iter->data; |
422 | ||
423 | if (set->shadow) | |
424 | { | |
425 | privilegeset_free(set->shadow); | |
426 | set->shadow = NULL; | |
427 | } | |
422bb0b5 JT |
428 | |
429 | privilegeset_ref(set); | |
430 | privilegeset_unref(set); | |
431 | } | |
432 | } | |
3a177354 JT |
433 | |
434 | void | |
435 | privilegeset_report(struct Client *source_p) | |
436 | { | |
437 | rb_dlink_node *ptr; | |
438 | ||
439 | RB_DLINK_FOREACH(ptr, privilegeset_list.head) | |
440 | { | |
441 | struct PrivilegeSet *set = ptr->data; | |
442 | ||
443 | /* use RPL_STATSDEBUG for now -- jilles */ | |
8aadf0ce EK |
444 | send_multiline_init(source_p, " ", ":%s %03d %s O :%s ", |
445 | get_id(&me, source_p), | |
446 | RPL_STATSDEBUG, | |
447 | get_id(source_p, source_p), | |
448 | set->name); | |
449 | send_multiline_remote_pad(source_p, &me); | |
450 | send_multiline_remote_pad(source_p, source_p); | |
451 | for (const char **s = set->privs; s && *s; s++) | |
452 | send_multiline_item(source_p, "%s", *s); | |
453 | send_multiline_fini(source_p, NULL); | |
3a177354 JT |
454 | } |
455 | } |