]> jfr.im git - irc/freenode/solanum.git/commitdiff
Track and inform modules of privset changes
authorEd Kellett <redacted>
Fri, 29 Jan 2021 11:54:12 +0000 (11:54 +0000)
committerEd Kellett <redacted>
Mon, 1 Mar 2021 15:45:03 +0000 (15:45 +0000)
include/hook.h
include/privilege.h
include/s_user.h
ircd/hook.c
ircd/privilege.c
ircd/s_conf.c
ircd/s_user.c
modules/m_grant.c
modules/m_privs.c

index c9dcf93e207eb2eb9c16a74f439462cf786089e1..c1971614c5cfea5a03e2f07086adffd77588491b 100644 (file)
@@ -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;
index 1176f2bb5336c767e142206229dd7ca33a24fc76..ab0931887300c2839b25016ead20b1eec0257ace 100644 (file)
@@ -2,6 +2,23 @@
  * 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
@@ -32,22 +49,28 @@ enum {
 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
index 9fb6f7edd7195e336d1aeb85af30cd6a6086756a..8e81f4a283b7d030d7676fd5980025a4cf428789 100644 (file)
@@ -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
index 2a6adaf5d9094409486d47042332bcb5ca7f1bcb..b77ceb1050fa81090641ff6d4d2c3929f85312e3 100644 (file)
@@ -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");
 }
 
index 18785db1b96e8570e738569c0c1de97ec4e1feb3..d7226f973bb9ee00537eb7379f6d147f7ac2c558 100644 (file)
@@ -2,6 +2,23 @@
  * 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 *
@@ -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("<unchanged>");
+               result_sets[1] = set_added = privilegeset_new_orphan("<added>");
+               result_sets[2] = set_removed = privilegeset_new_orphan("<removed>");
+       }
+
+       if (old == NULL)
+               old = &empty;
+       if (new == NULL)
+               new = &empty;
+
+       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);
        }
 }
index 0776f48fa261a1d7a616a3b4220460a6a5b48a95..c84047e452b7bd8848fdf44debfcbc647d2c6b33 100644 (file)
@@ -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... */
 }
 
index 7ea20e7ff81b996604741b6717e65203abc9780e..56b901344048f6fa02d2f418a8674c2ba510ad99 100644 (file)
@@ -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);
 }
 
index 0021b7c00348b04f43e768f37dbd1befc08b9a1b..0f41f4c72e1a609efea3c67cc1a68de651316f05 100644 (file)
@@ -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 (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;
 }
index 112f06a1f4dead686b6b4d8cbba6d9da80baa183..ea972e92448c496ae95455b1ee29ff38460a5ab3 100644 (file)
@@ -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))
        {