]> jfr.im git - irc/freenode/solanum.git/blame - ircd/privilege.c
Track and inform modules of privset changes
[irc/freenode/solanum.git] / ircd / privilege.c
CommitLineData
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 49static rb_dlink_list privilegeset_list = {NULL, NULL, 0};
9c3f080b 50
8aadf0ce
EK
51bool
52privilegeset_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
66static struct PrivilegeSet *
67privilegeset_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
84static int
85privilegeset_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
91static void
92privilegeset_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
109static void
110privilegeset_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
150static void
151privilegeset_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
183static struct PrivilegeSet *
184privilegeset_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
200static void
201privilegeset_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
213static void
214privilegeset_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
232static void
233privilegeset_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
241struct PrivilegeSet *
242privilegeset_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 265struct PrivilegeSet *
8aadf0ce 266privilegeset_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
281struct PrivilegeSet *
282privilegeset_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 292struct PrivilegeSet *
9c3f080b
WP
293privilegeset_ref(struct PrivilegeSet *set)
294{
295 s_assert(set != NULL);
296
297 set->refs++;
598b4cf1
WP
298
299 return set;
9c3f080b
WP
300}
301
302void
303privilegeset_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
320const struct PrivilegeSet **
321privilegeset_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 = &empty;
342 if (new == NULL)
343 new = &empty;
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
396void
8aadf0ce 397privilegeset_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
414void
8aadf0ce 415privilegeset_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
434void
435privilegeset_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}