* Solanum: a slightly advanced ircd
* privilege.h: Dynamic privileges API.
*
+ * Copyright (c) 2021 Ed Kellett <e@kellett.im>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
* Copyright (c) 2008 William Pitcock <nenolod@dereferenced.org>
*
* Permission to use, copy, modify, and/or distribute this software for any
typedef unsigned int PrivilegeFlags;
struct PrivilegeSet {
- unsigned int status; /* If CONF_ILLEGAL, delete when no refs */
- int refs;
+ rb_dlink_node node;
+ size_t size;
+ const char **privs;
+ size_t stored_size, allocated_size;
+ char *priv_storage;
char *name;
- char *privs;
+ struct PrivilegeSet *shadow;
PrivilegeFlags flags;
- rb_dlink_node node;
+ unsigned int status; /* If CONF_ILLEGAL, delete when no refs */
+ int refs;
};
-int privilegeset_in_set(struct PrivilegeSet *set, const char *priv);
+bool privilegeset_in_set(const struct PrivilegeSet *set, const char *priv);
struct PrivilegeSet *privilegeset_set_new(const char *name, const char *privs, PrivilegeFlags flags);
-struct PrivilegeSet *privilegeset_extend(struct PrivilegeSet *parent, const char *name, const char *privs, PrivilegeFlags flags);
+struct PrivilegeSet *privilegeset_extend(const struct PrivilegeSet *parent, const char *name, const char *privs, PrivilegeFlags flags);
struct PrivilegeSet *privilegeset_get(const char *name);
struct PrivilegeSet *privilegeset_ref(struct PrivilegeSet *set);
void privilegeset_unref(struct PrivilegeSet *set);
-void privilegeset_mark_all_illegal(void);
-void privilegeset_delete_all_illegal(void);
+void privilegeset_prepare_rehash(void);
+void privilegeset_cleanup_rehash(void);
void privilegeset_report(struct Client *source_p);
+const struct PrivilegeSet **privilegeset_diff(const struct PrivilegeSet *, const struct PrivilegeSet *);
+
#endif
* Solanum: a slightly advanced ircd
* privilege.c: Dynamic privileges API.
*
+ * Copyright (c) 2021 Ed Kellett <e@kellett.im>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
* Copyright (c) 2008 William Pitcock <nenolod@dereferenced.org>
*
* Permission to use, copy, modify, and/or distribute this software for any
static rb_dlink_list privilegeset_list = {NULL, NULL, 0};
-int
-privilegeset_in_set(struct PrivilegeSet *set, const char *priv)
+bool
+privilegeset_in_set(const struct PrivilegeSet *set, const char *priv)
{
s_assert(set != NULL);
s_assert(priv != NULL);
- return strstr(set->privs, priv) != NULL;
+ if (set->privs == NULL)
+ return false;
+
+ for (const char **s = set->privs; *s != NULL; s++)
+ if (strcmp(*s, priv) == 0) return true;
+
+ return false;
}
static struct PrivilegeSet *
return NULL;
}
+static int
+privilegeset_cmp_priv(const void *a_, const void *b_)
+{
+ const char *const *a = a_, *const *b = b_;
+ return strcmp(*a, *b);
+}
+
+static void
+privilegeset_index(struct PrivilegeSet *set)
+{
+ size_t n;
+ const char *s;
+ const char **p;
+
+ rb_free(set->privs);
+
+ set->privs = rb_malloc(sizeof *set->privs * (set->size + 1));
+ p = set->privs;
+
+ for (n = 0, s = set->priv_storage; n < set->size; n++, s += strlen(s) + 1)
+ *p++ = s;
+ qsort(set->privs, set->size, sizeof *set->privs, privilegeset_cmp_priv);
+ set->privs[set->size] = NULL;
+}
+
+static void
+privilegeset_add_privs(struct PrivilegeSet *dst, const char *privs)
+{
+ size_t alloc_size;
+ size_t n;
+
+ if (dst->priv_storage == NULL)
+ {
+ dst->stored_size = dst->allocated_size = 0;
+ alloc_size = 256;
+ }
+ else
+ {
+ alloc_size = dst->allocated_size;
+ }
+
+ dst->stored_size += strlen(privs) + 1;
+
+ while (alloc_size < dst->stored_size)
+ alloc_size *= 2;
+
+ if (alloc_size > dst->allocated_size)
+ dst->priv_storage = rb_realloc(dst->priv_storage, alloc_size);
+
+ dst->allocated_size = alloc_size;
+
+ const char *s;
+ char *d;
+ for (s = privs, d = dst->priv_storage; s < privs + strlen(privs); s += n , d += n)
+ {
+ const char *e = strchr(s, ' ');
+ /* up to space if there is one, else up to end of string */
+ n = 1 + (e != NULL ? e - s : strlen(s));
+ rb_strlcpy(d, s, n);
+
+ dst->size += 1;
+ }
+
+ privilegeset_index(dst);
+}
+
+static void
+privilegeset_add_privilegeset(struct PrivilegeSet *dst, const struct PrivilegeSet *src)
+{
+ size_t cur_size, alloc_size;
+
+ if (dst->priv_storage == NULL)
+ {
+ dst->stored_size = dst->allocated_size = 0;
+ cur_size = 0;
+ alloc_size = 256;
+ }
+ else
+ {
+ cur_size = dst->stored_size;
+ alloc_size = dst->allocated_size;
+ }
+
+ dst->stored_size = cur_size + src->stored_size;
+
+ while (alloc_size < dst->stored_size)
+ alloc_size *= 2;
+
+ if (alloc_size > dst->allocated_size)
+ dst->priv_storage = rb_realloc(dst->priv_storage, alloc_size);
+
+ dst->allocated_size = alloc_size;
+
+ memcpy(dst->priv_storage + cur_size, src->priv_storage, src->stored_size);
+ dst->size += src->size;
+
+ privilegeset_index(dst);
+}
+
+static struct PrivilegeSet *
+privilegeset_new_orphan(const char *name)
+{
+ struct PrivilegeSet *set;
+ set = rb_malloc(sizeof *set);
+ *set = (struct PrivilegeSet) {
+ .size = 0,
+ .privs = NULL,
+ .priv_storage = NULL,
+ .shadow = NULL,
+ .status = 0,
+ .refs = 0,
+ .name = rb_strdup(name),
+ };
+ return set;
+}
+
+static void
+privilegeset_free(struct PrivilegeSet *set)
+{
+ if (set == NULL)
+ return;
+
+ privilegeset_free(set->shadow);
+ rb_free(set->name);
+ rb_free(set->privs);
+ rb_free(set->priv_storage);
+ rb_free(set);
+}
+
+static void
+privilegeset_shade(struct PrivilegeSet *set)
+{
+ privilegeset_free(set->shadow);
+
+ set->shadow = privilegeset_new_orphan(set->name);
+ set->shadow->privs = set->privs;
+ set->shadow->size = set->size;
+ set->shadow->priv_storage = set->priv_storage;
+ set->shadow->stored_size = set->stored_size;
+ set->shadow->allocated_size = set->allocated_size;
+
+ set->privs = NULL;
+ set->size = 0;
+ set->priv_storage = NULL;
+ set->stored_size = 0;
+ set->allocated_size = 0;
+}
+
+static void
+privilegeset_clear(struct PrivilegeSet *set)
+{
+ rb_free(set->privs);
+ set->privs = NULL;
+ set->size = 0;
+ set->stored_size = 0;
+}
+
struct PrivilegeSet *
privilegeset_set_new(const char *name, const char *privs, PrivilegeFlags flags)
{
if (!(set->status & CONF_ILLEGAL))
ilog(L_MAIN, "Duplicate privset %s", name);
set->status &= ~CONF_ILLEGAL;
- rb_free(set->privs);
+ privilegeset_clear(set);
}
else
{
- set = rb_malloc(sizeof(struct PrivilegeSet));
- set->status = 0;
- set->refs = 0;
- set->name = rb_strdup(name);
-
+ set = privilegeset_new_orphan(name);
rb_dlinkAdd(set, &set->node, &privilegeset_list);
}
- set->privs = rb_strdup(privs);
+ privilegeset_add_privs(set, privs);
set->flags = flags;
return set;
}
struct PrivilegeSet *
-privilegeset_extend(struct PrivilegeSet *parent, const char *name, const char *privs, PrivilegeFlags flags)
+privilegeset_extend(const struct PrivilegeSet *parent, const char *name, const char *privs, PrivilegeFlags flags)
{
struct PrivilegeSet *set;
s_assert(name != NULL);
s_assert(privs != NULL);
- set = privilegeset_get_any(name);
- if (set != NULL)
- {
- if (!(set->status & CONF_ILLEGAL))
- ilog(L_MAIN, "Duplicate privset %s", name);
- set->status &= ~CONF_ILLEGAL;
- rb_free(set->privs);
- }
- else
- {
- set = rb_malloc(sizeof(struct PrivilegeSet));
- set->status = 0;
- set->refs = 0;
- set->name = rb_strdup(name);
-
- rb_dlinkAdd(set, &set->node, &privilegeset_list);
- }
+ set = privilegeset_set_new(name, privs, flags);
+ privilegeset_add_privilegeset(set, parent);
set->flags = flags;
- set->privs = rb_malloc(strlen(parent->privs) + 1 + strlen(privs) + 1);
- strcpy(set->privs, parent->privs);
- strcat(set->privs, " ");
- strcat(set->privs, privs);
return set;
}
{
rb_dlinkDelete(&set->node, &privilegeset_list);
- rb_free(set->name);
- rb_free(set->privs);
- rb_free(set);
+ privilegeset_free(set);
+ }
+}
+
+const struct PrivilegeSet **
+privilegeset_diff(const struct PrivilegeSet *old, const struct PrivilegeSet *new)
+{
+ static const char *no_privs[] = { NULL };
+ static const struct PrivilegeSet empty = { .size = 0, .privs = no_privs };
+ static struct PrivilegeSet *set_unchanged = NULL,
+ *set_added = NULL,
+ *set_removed = NULL;
+ static const struct PrivilegeSet *result_sets[3];
+ static size_t n_privs = 0;
+ size_t new_size = n_privs ? n_privs : 32;
+ size_t i = 0, j = 0;
+
+ if (result_sets[0] == NULL)
+ {
+ result_sets[0] = set_unchanged = privilegeset_new_orphan("<unchanged>");
+ result_sets[1] = set_added = privilegeset_new_orphan("<added>");
+ result_sets[2] = set_removed = privilegeset_new_orphan("<removed>");
+ }
+
+ if (old == NULL)
+ old = ∅
+ if (new == NULL)
+ new = ∅
+
+ while (new_size < MAX(old->size, new->size) + 1)
+ new_size *= 2;
+
+ if (new_size > n_privs)
+ {
+ set_unchanged->privs = rb_realloc(set_unchanged->privs, sizeof *set_unchanged->privs * new_size);
+ set_added->privs = rb_realloc(set_added->privs, sizeof *set_added->privs * new_size);
+ set_removed->privs = rb_realloc(set_removed->privs, sizeof *set_removed->privs * new_size);
+ }
+
+ const char **res_unchanged = set_unchanged->privs;
+ const char **res_added = set_added->privs;
+ const char **res_removed = set_removed->privs;
+
+ while (i < old->size || j < new->size)
+ {
+ const char *oldpriv = NULL, *newpriv = NULL;
+ int ord = 0;
+ if (i < old->size)
+ oldpriv = old->privs[i];
+ if (j < new->size)
+ newpriv = new->privs[j];
+
+ if (oldpriv && newpriv)
+ ord = strcmp(oldpriv, newpriv);
+
+ if (newpriv == NULL || ord < 0)
+ {
+ *res_removed++ = oldpriv;
+ i++;
+ }
+ else if (oldpriv == NULL || ord > 0)
+ {
+ *res_added++ = newpriv;
+ j++;
+ }
+ else
+ {
+ *res_unchanged++ = oldpriv;
+ i++; j++;
+ }
}
+
+ *res_removed = *res_added = *res_unchanged = NULL;
+ set_unchanged->size = res_unchanged - set_unchanged->privs;
+ set_added->size = res_added - set_added->privs;
+ set_removed->size = res_removed - set_removed->privs;
+
+ return result_sets;
}
void
-privilegeset_mark_all_illegal(void)
+privilegeset_prepare_rehash()
{
rb_dlink_node *iter;
RB_DLINK_FOREACH(iter, privilegeset_list.head)
{
- struct PrivilegeSet *set = (struct PrivilegeSet *) iter->data;
+ struct PrivilegeSet *set = iter->data;
/* the "default" privset is special and must remain available */
if (!strcmp(set->name, "default"))
continue;
set->status |= CONF_ILLEGAL;
- rb_free(set->privs);
- set->privs = rb_strdup("");
- /* but do not free it yet */
+ privilegeset_shade(set);
}
}
void
-privilegeset_delete_all_illegal(void)
+privilegeset_cleanup_rehash()
{
rb_dlink_node *iter, *next;
RB_DLINK_FOREACH_SAFE(iter, next, privilegeset_list.head)
{
- struct PrivilegeSet *set = (struct PrivilegeSet *) iter->data;
+ struct PrivilegeSet *set = iter->data;
+
+ if (set->shadow)
+ {
+ privilegeset_free(set->shadow);
+ set->shadow = NULL;
+ }
privilegeset_ref(set);
privilegeset_unref(set);
struct PrivilegeSet *set = ptr->data;
/* use RPL_STATSDEBUG for now -- jilles */
- sendto_one_numeric(source_p, RPL_STATSDEBUG,
- "O :%s %s",
- set->name,
- set->privs);
+ send_multiline_init(source_p, " ", ":%s %03d %s O :%s ",
+ get_id(&me, source_p),
+ RPL_STATSDEBUG,
+ get_id(source_p, source_p),
+ set->name);
+ send_multiline_remote_pad(source_p, &me);
+ send_multiline_remote_pad(source_p, source_p);
+ for (const char **s = set->privs; s && *s; s++)
+ send_multiline_item(source_p, "%s", *s);
+ send_multiline_fini(source_p, NULL);
}
}
static int do_grant(struct Client *source_p, struct Client *target_p, const char *new_privset)
{
int dooper = 0, dodeoper = 0;
- struct PrivilegeSet *privset = 0;
+ struct PrivilegeSet *privset = NULL, *old_privset = NULL;
if (!strcasecmp(new_privset, "deoper"))
{
modeparv[2] = "-o";
modeparv[3] = NULL;
user_mode(target_p, target_p, 3, modeparv);
+
+ return 0;
}
if (dooper)
oper_up(target_p, &oper);
}
- else if (privset != NULL)
+ else
{
- privilegeset_ref(privset);
- }
+ if (privset != NULL)
+ privilegeset_ref(privset);
+
+ if (target_p->user->privset != NULL)
+ old_privset = target_p->user->privset;
+
+ target_p->user->privset = privset;
- if (target_p->user->privset != NULL)
- privilegeset_unref(target_p->user->privset);
+ if (privset != NULL)
+ sendto_server(NULL, NULL, CAP_TS6, NOCAPS, ":%s OPER %s %s",
+ use_id(target_p), target_p->user->opername, privset->name);
- target_p->user->privset = privset;
+ report_priv_change(target_p, old_privset, privset);
- if (privset != NULL)
- sendto_server(NULL, NULL, CAP_TS6, NOCAPS, ":%s OPER %s %s",
- use_id(target_p), target_p->user->opername, privset->name);
+ if (old_privset != NULL)
+ privilegeset_unref(old_privset);
- const char *modeparv[4];
- modeparv[0] = modeparv[1] = target_p->name;
- modeparv[2] = "+";
- modeparv[3] = NULL;
- user_mode(target_p, target_p, 3, modeparv);
+ const char *modeparv[4];
+ modeparv[0] = modeparv[1] = target_p->name;
+ modeparv[2] = "+";
+ modeparv[3] = NULL;
+ user_mode(target_p, target_p, 3, modeparv);
+ }
return 0;
}