diff --git a/include/hook.h b/include/hook.h index c9dcf93e..c1971614 100644 --- a/include/hook.h +++ b/include/hook.h @@ -45,6 +45,7 @@ extern int h_conf_read_start; extern int h_conf_read_end; extern int h_outbound_msgbuf; extern int h_rehash; +extern int h_priv_change; extern int h_cap_change; void init_hook(void); @@ -180,6 +181,16 @@ typedef struct int approved; } hook_data_privmsg_user; +typedef struct +{ + struct Client *client; + struct PrivilegeSet *old; + struct PrivilegeSet *new; + const struct PrivilegeSet *added; + const struct PrivilegeSet *removed; + const struct PrivilegeSet *unchanged; +} hook_data_priv_change; + typedef struct { bool signal; diff --git a/include/privilege.h b/include/privilege.h index 1176f2bb..ab093188 100644 --- a/include/privilege.h +++ b/include/privilege.h @@ -2,6 +2,23 @@ * Solanum: a slightly advanced ircd * privilege.h: Dynamic privileges API. * + * Copyright (c) 2021 Ed Kellett + * + * 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 * * Permission to use, copy, modify, and/or distribute this software for any @@ -32,22 +49,28 @@ enum { typedef unsigned int PrivilegeFlags; struct PrivilegeSet { + rb_dlink_node node; + size_t size; + const char **privs; + size_t stored_size, allocated_size; + char *priv_storage; + char *name; + struct PrivilegeSet *shadow; + PrivilegeFlags flags; unsigned int status; /* If CONF_ILLEGAL, delete when no refs */ int refs; - char *name; - char *privs; - PrivilegeFlags flags; - rb_dlink_node node; }; -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 diff --git a/include/s_user.h b/include/s_user.h index 9fb6f7ed..8e81f4a2 100644 --- a/include/s_user.h +++ b/include/s_user.h @@ -51,6 +51,8 @@ extern int user_modes[256]; extern unsigned int find_umode_slot(void); extern void construct_umodebuf(void); +struct PrivilegeSet; +extern void report_priv_change(struct Client *, struct PrivilegeSet *, struct PrivilegeSet *); extern void oper_up(struct Client *, struct oper_conf *); #endif diff --git a/ircd/hook.c b/ircd/hook.c index 2a6adaf5..b77ceb10 100644 --- a/ircd/hook.c +++ b/ircd/hook.c @@ -71,6 +71,7 @@ int h_conf_read_start; int h_conf_read_end; int h_outbound_msgbuf; int h_rehash; +int h_priv_change; int h_cap_change; void @@ -96,6 +97,7 @@ init_hook(void) h_conf_read_end = register_hook("conf_read_end"); h_outbound_msgbuf = register_hook("outbound_msgbuf"); h_rehash = register_hook("rehash"); + h_priv_change = register_hook("priv_change"); h_cap_change = register_hook("cap_change"); } diff --git a/ircd/privilege.c b/ircd/privilege.c index 18785db1..d7226f97 100644 --- a/ircd/privilege.c +++ b/ircd/privilege.c @@ -2,6 +2,23 @@ * Solanum: a slightly advanced ircd * privilege.c: Dynamic privileges API. * + * Copyright (c) 2021 Ed Kellett + * + * 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 * * Permission to use, copy, modify, and/or distribute this software for any @@ -31,13 +48,19 @@ 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 * @@ -58,6 +81,163 @@ privilegeset_get_any(const char *name) 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) { @@ -69,25 +249,21 @@ 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; @@ -95,28 +271,9 @@ privilegeset_extend(struct PrivilegeSet *parent, const char *name, const char *p 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; } @@ -156,40 +313,118 @@ privilegeset_unref(struct PrivilegeSet *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(""); + result_sets[1] = set_added = privilegeset_new_orphan(""); + result_sets[2] = set_removed = privilegeset_new_orphan(""); + } + + 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); @@ -206,9 +441,15 @@ privilegeset_report(struct Client *source_p) 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); } } diff --git a/ircd/s_conf.c b/ircd/s_conf.c index 0776f48f..c84047e4 100644 --- a/ircd/s_conf.c +++ b/ircd/s_conf.c @@ -654,6 +654,8 @@ rehash(bool sig) rehash_authd(); + privilegeset_prepare_rehash(); + /* don't close listeners until we know we can go ahead with the rehash */ read_conf_files(false); @@ -667,13 +669,12 @@ rehash(bool sig) RB_DLINK_FOREACH(n, local_oper_list.head) { struct Client *oper = n->data; - const char *modeparv[4]; - modeparv[0] = modeparv[1] = oper->name; - modeparv[2] = "+"; - modeparv[3] = NULL; - user_mode(oper, oper, 3, modeparv); + struct PrivilegeSet *privset = oper->user->privset; + report_priv_change(oper, privset ? privset->shadow : NULL, privset); } + privilegeset_cleanup_rehash(); + call_hook(h_rehash, &hdata); return false; } @@ -862,7 +863,6 @@ read_conf(void) validate_conf(); /* Check to make sure some values are still okay. */ /* Some global values are also loaded here. */ check_class(); /* Make sure classes are valid */ - privilegeset_delete_all_illegal(); construct_cflags_strings(); } @@ -1577,8 +1577,6 @@ clear_out_old_conf(void) del_dnsbl_entry_all(); - privilegeset_mark_all_illegal(); - /* OK, that should be everything... */ } diff --git a/ircd/s_user.c b/ircd/s_user.c index 7ea20e7f..56b90134 100644 --- a/ircd/s_user.c +++ b/ircd/s_user.c @@ -972,6 +972,26 @@ report_and_set_user_flags(struct Client *source_p, struct ConfItem *aconf) } } +void +report_priv_change(struct Client *client, struct PrivilegeSet *old, struct PrivilegeSet *new) +{ + const struct PrivilegeSet *added, *removed, *unchanged; + const struct PrivilegeSet **result = privilegeset_diff(old, new); + unchanged = result[0]; + added = result[1]; + removed = result[2]; + + hook_data_priv_change hdata = { + .client = client, + .new = new, + .old = old, + .unchanged = unchanged, + .added = added, + .removed = removed, + }; + call_hook(h_priv_change, &hdata); +} + static void show_other_user_mode(struct Client *source_p, struct Client *target_p) { @@ -1129,6 +1149,8 @@ user_mode(struct Client *client_p, struct Client *source_p, int parc, const char if(source_p->user->privset != NULL) { + report_priv_change(source_p, source_p->user->privset, NULL); + privilegeset_unref(source_p->user->privset); source_p->user->privset = NULL; } @@ -1435,6 +1457,8 @@ oper_up(struct Client *source_p, struct oper_conf *oper_p) source_p->user->opername = rb_strdup(oper_p->name); source_p->user->privset = privilegeset_ref(oper_p->privset); + report_priv_change(source_p, NULL, source_p->user->privset); + rb_dlinkAddAlloc(source_p, &local_oper_list); rb_dlinkAddAlloc(source_p, &oper_list); @@ -1471,7 +1495,10 @@ oper_up(struct Client *source_p, struct oper_conf *oper_p) construct_snobuf(source_p->snomask)); sendto_one(source_p, form_str(RPL_YOUREOPER), me.name, source_p->name); sendto_one_notice(source_p, ":*** Oper privilege set is %s", oper_p->privset->name); - sendto_one_notice(source_p, ":*** Oper privs are %s", oper_p->privset->privs); + send_multiline_init(source_p, " ", ":%s NOTICE %s :*** Oper privs are ", me.name, source_p->name); + for (const char **s = oper_p->privset->privs; s && *s; s++) + send_multiline_item(source_p, "%s", *s); + send_multiline_fini(source_p, NULL); send_oper_motd(source_p); } diff --git a/modules/m_grant.c b/modules/m_grant.c index 0021b7c0..0f41f4c7 100644 --- a/modules/m_grant.c +++ b/modules/m_grant.c @@ -82,7 +82,7 @@ me_grant(struct MsgBuf *msgbuf_p, struct Client *client_p, struct Client *source 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")) { @@ -144,6 +144,8 @@ static int do_grant(struct Client *source_p, struct Client *target_p, const char modeparv[2] = "-o"; modeparv[3] = NULL; user_mode(target_p, target_p, 3, modeparv); + + return 0; } if (dooper) @@ -154,25 +156,31 @@ static int do_grant(struct Client *source_p, struct Client *target_p, const char 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 (privset != NULL) + sendto_server(NULL, NULL, CAP_TS6, NOCAPS, ":%s OPER %s %s", + use_id(target_p), target_p->user->opername, privset->name); + + report_priv_change(target_p, old_privset, privset); + + 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); } - if (target_p->user->privset != NULL) - privilegeset_unref(target_p->user->privset); - - target_p->user->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); - - 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; } diff --git a/modules/m_privs.c b/modules/m_privs.c index 112f06a1..ea972e92 100644 --- a/modules/m_privs.c +++ b/modules/m_privs.c @@ -95,13 +95,8 @@ static void show_privs(struct Client *source_p, struct Client *target_p) send_multiline_remote_pad(source_p, source_p); if (target_p->user->privset) - for (char *s = target_p->user->privset->privs; s != NULL; (s = strchr(s, ' ')) && s++) - { - char *c = strchr(s, ' '); - if (c) *c = '\0'; - send_multiline_item(source_p, "%s", s); - if (c) *c = ' '; - } + for (const char **s = target_p->user->privset->privs; *s != NULL; s++) + send_multiline_item(source_p, "%s", *s); if (IsOper(target_p)) {