From ed3ca2ff16a1dc921d90c0a67093de8f47209176 Mon Sep 17 00:00:00 2001 From: Ed Kellett Date: Sun, 7 Jul 2019 02:36:58 +0100 Subject: [PATCH] Propagate OPER Move opername and privset storage to struct User, so it can exist for remote opers. On /oper and when bursting opers, send: :foo OPER opername privset which sets foo's opername and privset. The contents of the privset on remote servers come from the remote server's config, so the potential for confusion exists if these do not match. If an oper's privset does not exist on a server that sees it, it will complain, but create a placeholder privset. If the privset is created by a rehash, this will be reflected properly. /privs is udpated to take an optional argument, the server to query, and is now local by default: /privs [[nick_or_server] nick] --- doc/technical/ts6-protocol.txt | 8 ++++- extensions/extb_oper.c | 2 +- include/client.h | 6 ++-- include/s_newconf.h | 2 +- ircd/client.c | 6 ++-- ircd/s_conf.c | 2 +- ircd/s_serv.c | 6 ++++ ircd/s_user.c | 23 ++++++++++----- modules/m_challenge.c | 16 +++++----- modules/m_grant.c | 4 +-- modules/m_oper.c | 33 ++++++++++++++++++++- modules/m_privs.c | 53 +++++++++++++++++++++++----------- modules/m_whois.c | 7 +++-- tests/send1.c | 16 +++++----- 14 files changed, 129 insertions(+), 55 deletions(-) diff --git a/doc/technical/ts6-protocol.txt b/doc/technical/ts6-protocol.txt index 05a4955c..cbc87986 100644 --- a/doc/technical/ts6-protocol.txt +++ b/doc/technical/ts6-protocol.txt @@ -670,6 +670,13 @@ and most error messages are suppressed. Servers may not send '$$', '$#' and opers@server notices. Older servers may not allow servers to send to specific statuses on a channel. +OPER +source: user +parameters: opername, privset + +Sets the source user's oper name and privset. Sent after the +o mode change, or +during burst, to inform other servers of an oper's privileges. + OPERSPY encap only encap target: * @@ -1222,7 +1229,6 @@ MODRESTART MODUNLOAD MONITOR NAMES -OPER POST PUT RESTART diff --git a/extensions/extb_oper.c b/extensions/extb_oper.c index 72908b29..0400e724 100644 --- a/extensions/extb_oper.c +++ b/extensions/extb_oper.c @@ -42,7 +42,7 @@ static int eb_oper(const char *data, struct Client *client_p, if (data != NULL) { struct PrivilegeSet *set = privilegeset_get(data); - if (set != NULL && client_p->localClient->privset == set) + if (set != NULL && client_p->user->privset == set) return EXTBAN_MATCH; /* $o:admin or whatever */ diff --git a/include/client.h b/include/client.h index 9ded2c87..ccc3c2e9 100644 --- a/include/client.h +++ b/include/client.h @@ -79,6 +79,9 @@ struct User char *away; /* pointer to away message */ int refcnt; /* Number of times this block is referenced */ + char *opername; /* name of operator{} block being used or tried (challenge) */ + struct PrivilegeSet *privset; + char suser[NICKLEN+1]; }; @@ -225,7 +228,6 @@ struct LocalUser */ char *passwd; char *auth_user; - char *opername; /* name of operator{} block being used or tried (challenge) */ char *challenge; char *fullcaps; char *cipher_string; @@ -282,8 +284,6 @@ struct LocalUser uint16_t cork_count; /* used for corking/uncorking connections */ struct ev_entry *event; /* used for associated events */ - struct PrivilegeSet *privset; /* privset... */ - char sasl_agent[IDLEN]; unsigned char sasl_out; unsigned char sasl_complete; diff --git a/include/s_newconf.h b/include/s_newconf.h index 41e74442..cced2d07 100644 --- a/include/s_newconf.h +++ b/include/s_newconf.h @@ -146,7 +146,7 @@ extern void cluster_generic(struct Client *, const char *, int cltype, #define IsOperConfEncrypted(x) ((x)->flags & OPER_ENCRYPTED) #define IsOperConfNeedSSL(x) ((x)->flags & OPER_NEEDSSL) -#define HasPrivilege(x, y) ((x)->localClient != NULL && (x)->localClient->privset != NULL && privilegeset_in_set((x)->localClient->privset, (y))) +#define HasPrivilege(x, y) ((x)->user != NULL && (x)->user->privset != NULL && privilegeset_in_set((x)->user->privset, (y))) #define IsOperGlobalKill(x) (HasPrivilege((x), "oper:global_kill")) #define IsOperLocalKill(x) (HasPrivilege((x), "oper:local_kill")) diff --git a/ircd/client.c b/ircd/client.c index 76cb826b..a19c664f 100644 --- a/ircd/client.c +++ b/ircd/client.c @@ -300,10 +300,7 @@ free_local_client(struct Client *client_p) rb_free(client_p->localClient->auth_user); rb_free(client_p->localClient->challenge); rb_free(client_p->localClient->fullcaps); - rb_free(client_p->localClient->opername); rb_free(client_p->localClient->mangledhost); - if (client_p->localClient->privset) - privilegeset_unref(client_p->localClient->privset); if (IsSSL(client_p)) ssld_decrement_clicount(client_p->localClient->ssl_ctl); @@ -1920,6 +1917,9 @@ free_user(struct User *user, struct Client *client_p) { if(user->away) rb_free((char *) user->away); + rb_free(user->opername); + if (user->privset) + privilegeset_unref(user->privset); /* * sanity check */ diff --git a/ircd/s_conf.c b/ircd/s_conf.c index dbed64d0..4cbb0d5e 100644 --- a/ircd/s_conf.c +++ b/ircd/s_conf.c @@ -1267,7 +1267,7 @@ get_oper_name(struct Client *client_p) { snprintf(buffer, sizeof(buffer), "%s!%s@%s{%s}", client_p->name, client_p->username, - client_p->host, client_p->localClient->opername); + client_p->host, client_p->user->opername); return buffer; } diff --git a/ircd/s_serv.c b/ircd/s_serv.c index bc77b43c..a001cd16 100644 --- a/ircd/s_serv.c +++ b/ircd/s_serv.c @@ -669,6 +669,12 @@ burst_TS6(struct Client *client_p) use_id(target_p), target_p->user->away); + if(IsOper(target_p) && target_p->user && target_p->user->opername && target_p->user->privset) + sendto_one(client_p, ":%s OPER %s %s", + use_id(target_p), + target_p->user->opername, + target_p->user->privset->name); + hclientinfo.target = target_p; call_hook(h_burst_client, &hclientinfo); } diff --git a/ircd/s_user.c b/ircd/s_user.c index 0c69f528..3a3dc8b6 100644 --- a/ircd/s_user.c +++ b/ircd/s_user.c @@ -1121,12 +1121,19 @@ user_mode(struct Client *client_p, struct Client *source_p, int parc, const char } source_p->flags &= ~OPER_FLAGS; - rb_free(source_p->localClient->opername); - source_p->localClient->opername = NULL; - rb_dlinkFindDestroy(source_p, &local_oper_list); - privilegeset_unref(source_p->localClient->privset); - source_p->localClient->privset = NULL; + } + + if(source_p->user->opername != NULL) + { + rb_free(source_p->user->opername); + source_p->user->opername = NULL; + } + + if(source_p->user->privset != NULL) + { + privilegeset_unref(source_p->user->privset); + source_p->user->privset = NULL; } rb_dlinkFindDestroy(source_p, &oper_list); @@ -1413,8 +1420,8 @@ oper_up(struct Client *source_p, struct oper_conf *oper_p) SetExemptKline(source_p); source_p->flags |= oper_p->flags; - source_p->localClient->opername = rb_strdup(oper_p->name); - source_p->localClient->privset = privilegeset_ref(oper_p->privset); + source_p->user->opername = rb_strdup(oper_p->name); + source_p->user->privset = privilegeset_ref(oper_p->privset); rb_dlinkAddAlloc(source_p, &local_oper_list); rb_dlinkAddAlloc(source_p, &oper_list); @@ -1433,6 +1440,8 @@ oper_up(struct Client *source_p, struct oper_conf *oper_p) sendto_realops_snomask(SNO_GENERAL, L_ALL, "%s (%s!%s@%s) is now an operator", oper_p->name, source_p->name, source_p->username, source_p->host); + sendto_server(NULL, NULL, CAP_TS6, NOCAPS, ":%s OPER %s %s", + use_id(source_p), oper_p->name, oper_p->privset->name); if(!(old & UMODE_INVISIBLE) && IsInvisible(source_p)) ++Count.invisi; if((old & UMODE_INVISIBLE) && !IsInvisible(source_p)) diff --git a/modules/m_challenge.c b/modules/m_challenge.c index d9a285ac..8174acbd 100644 --- a/modules/m_challenge.c +++ b/modules/m_challenge.c @@ -93,9 +93,9 @@ cleanup_challenge(struct Client *target_p) return; rb_free(target_p->localClient->challenge); - rb_free(target_p->localClient->opername); + rb_free(target_p->user->opername); target_p->localClient->challenge = NULL; - target_p->localClient->opername = NULL; + target_p->user->opername = NULL; target_p->localClient->chal_time = 0; } @@ -131,7 +131,7 @@ m_challenge(struct MsgBuf *msgbuf_p, struct Client *client_p, struct Client *sou { sendto_one(source_p, form_str(ERR_PASSWDMISMATCH), me.name, source_p->name); ilog(L_FOPER, "EXPIRED CHALLENGE (%s) by (%s!%s@%s) (%s)", - source_p->localClient->opername, source_p->name, + source_p->user->opername, source_p->name, source_p->username, source_p->host, source_p->sockhost); if(ConfigFileEntry.failed_oper_notice) @@ -151,7 +151,7 @@ m_challenge(struct MsgBuf *msgbuf_p, struct Client *client_p, struct Client *sou { sendto_one(source_p, form_str(ERR_PASSWDMISMATCH), me.name, source_p->name); ilog(L_FOPER, "FAILED CHALLENGE (%s) by (%s!%s@%s) (%s)", - source_p->localClient->opername, source_p->name, + source_p->user->opername, source_p->name, source_p->username, source_p->host, source_p->sockhost); if(ConfigFileEntry.failed_oper_notice) @@ -169,13 +169,13 @@ m_challenge(struct MsgBuf *msgbuf_p, struct Client *client_p, struct Client *sou oper_p = find_oper_conf(source_p->username, source_p->orighost, source_p->sockhost, - source_p->localClient->opername); + source_p->user->opername); if(oper_p == NULL) { sendto_one_numeric(source_p, ERR_NOOPERHOST, form_str(ERR_NOOPERHOST)); ilog(L_FOPER, "FAILED OPER (%s) by (%s!%s@%s) (%s)", - source_p->localClient->opername, source_p->name, + source_p->user->opername, source_p->name, source_p->username, source_p->host, source_p->sockhost); @@ -192,7 +192,7 @@ m_challenge(struct MsgBuf *msgbuf_p, struct Client *client_p, struct Client *sou oper_up(source_p, oper_p); ilog(L_OPERED, "OPER %s by %s!%s@%s (%s)", - source_p->localClient->opername, source_p->name, + source_p->user->opername, source_p->name, source_p->username, source_p->host, source_p->sockhost); return; } @@ -274,7 +274,7 @@ m_challenge(struct MsgBuf *msgbuf_p, struct Client *client_p, struct Client *sou sendto_one(source_p, form_str(RPL_ENDOFRSACHALLENGE2), me.name, source_p->name); rb_free(challenge); - source_p->localClient->opername = rb_strdup(oper_p->name); + source_p->user->opername = rb_strdup(oper_p->name); } else sendto_one_notice(source_p, ":Failed to generate challenge."); diff --git a/modules/m_grant.c b/modules/m_grant.c index 20c3a622..3788fb95 100644 --- a/modules/m_grant.c +++ b/modules/m_grant.c @@ -61,7 +61,7 @@ void set_privset(struct Client *const source, return; } - if(IsOper(target) && target->localClient->privset == privset) + if(IsOper(target) && target->user->privset == privset) { sendto_one_notice(source, ":%s already has role of %s.", target->name, privset_name); return; @@ -71,7 +71,7 @@ void set_privset(struct Client *const source, { sendto_one_notice(target, ":%s has changed your role to %s.", source->name, privset_name); sendto_realops_snomask(SNO_GENERAL, L_NETWIDE, "%s has changed %s's role to %s.", get_oper_name(source), target->name, privset_name); - target->localClient->privset = privset; + target->user->privset = privset; return; } diff --git a/modules/m_oper.c b/modules/m_oper.c index 9afef1f8..66bd1f78 100644 --- a/modules/m_oper.c +++ b/modules/m_oper.c @@ -31,6 +31,7 @@ #include "s_newconf.h" #include "logger.h" #include "s_user.h" +#include "s_serv.h" #include "send.h" #include "msg.h" #include "parse.h" @@ -41,12 +42,13 @@ static const char oper_desc[] = "Provides the OPER command to become an IRC operator"; static void m_oper(struct MsgBuf *, struct Client *, struct Client *, int, const char **); +static void mc_oper(struct MsgBuf *, struct Client *, struct Client *, int, const char **); static bool match_oper_password(const char *password, struct oper_conf *oper_p); struct Message oper_msgtab = { "OPER", 0, 0, 0, 0, - {mg_unreg, {m_oper, 3}, mg_ignore, mg_ignore, mg_ignore, {m_oper, 3}} + {mg_unreg, {m_oper, 3}, {mc_oper, 3}, mg_ignore, mg_ignore, {m_oper, 3}} }; mapi_clist_av1 oper_clist[] = { &oper_msgtab, NULL }; @@ -161,6 +163,35 @@ m_oper(struct MsgBuf *msgbuf_p, struct Client *client_p, struct Client *source_p } } +/* + * mc_oper - server-to-server OPER propagation + * parv[1] = opername + * parv[2] = privset + */ +static void +mc_oper(struct MsgBuf *msgbuf_p, struct Client *client_p, struct Client *source_p, int parc, const char *parv[]) +{ + struct PrivilegeSet *privset; + sendto_server(client_p, NULL, CAP_TS6, NOCAPS, ":%s OPER %s %s", use_id(source_p), parv[1], parv[2]); + + privset = privilegeset_get(parv[2]); + if(privset == NULL) + { + /* if we don't have a matching privset, we'll create an empty one and + * mark it illegal, so it gets picked up on a rehash later */ + sendto_realops_snomask(SNO_GENERAL, L_NETWIDE, "Received OPER for %s with unknown privset %s", source_p->name, parv[2]); + privset = privilegeset_set_new(parv[2], "", 0); + privset->status |= CONF_ILLEGAL; + } + + privset = privilegeset_ref(privset); + if (source_p->user->privset != NULL) + privilegeset_unref(source_p->user->privset); + + source_p->user->privset = privset; + source_p->user->opername = rb_strdup(parv[1]); +} + /* * match_oper_password * diff --git a/modules/m_privs.c b/modules/m_privs.c index c71327dc..a650203c 100644 --- a/modules/m_privs.c +++ b/modules/m_privs.c @@ -38,6 +38,7 @@ #include "modules.h" #include "s_conf.h" #include "s_newconf.h" +#include "hash.h" static const char privs_desc[] = "Provides the PRIVS command to inspect an operator's privileges"; @@ -86,21 +87,24 @@ static void show_privs(struct Client *source_p, struct Client *target_p) struct mode_table *p; buf[0] = '\0'; - if (target_p->localClient->privset) - rb_strlcat(buf, target_p->localClient->privset->privs, sizeof buf); + if (target_p->user->privset) + rb_strlcat(buf, target_p->user->privset->privs, sizeof buf); if (IsOper(target_p)) { - if (buf[0] != '\0') - rb_strlcat(buf, " ", sizeof buf); - rb_strlcat(buf, "operator:", sizeof buf); - rb_strlcat(buf, target_p->localClient->opername, sizeof buf); + if (target_p->user->opername) + { + if (buf[0] != '\0') + rb_strlcat(buf, " ", sizeof buf); + rb_strlcat(buf, "operator:", sizeof buf); + rb_strlcat(buf, target_p->user->opername, sizeof buf); + } - if (target_p->localClient->privset) + if (target_p->user->privset) { if (buf[0] != '\0') rb_strlcat(buf, " ", sizeof buf); rb_strlcat(buf, "privset:", sizeof buf); - rb_strlcat(buf, target_p->localClient->privset->name, sizeof buf); + rb_strlcat(buf, target_p->user->privset->name, sizeof buf); } } p = &auth_client_table[0]; @@ -126,8 +130,9 @@ me_privs(struct MsgBuf *msgbuf_p, struct Client *client_p, struct Client *source if (!IsOper(source_p) || parc < 2 || EmptyString(parv[1])) return; - /* we cannot show privs for remote clients */ - if((target_p = find_person(parv[1])) && MyClient(target_p)) + target_p = find_person(parv[1]); + + if (target_p != NULL) show_privs(source_p, target_p); } @@ -135,13 +140,24 @@ static void mo_privs(struct MsgBuf *msgbuf_p, struct Client *client_p, struct Client *source_p, int parc, const char *parv[]) { struct Client *target_p; + struct Client *server_p; if (parc < 2 || EmptyString(parv[1])) - target_p = source_p; + { + server_p = target_p = source_p; + } else { - target_p = find_named_person(parv[1]); - if (target_p == NULL) + if (parc >= 3) + { + server_p = find_named_client(parv[1]); + target_p = find_named_person(parv[2]); + } + else + { + server_p = target_p = find_named_person(parv[1]); + } + if (server_p == NULL || target_p == NULL) { sendto_one_numeric(source_p, ERR_NOSUCHNICK, form_str(ERR_NOSUCHNICK), parv[1]); @@ -149,12 +165,15 @@ mo_privs(struct MsgBuf *msgbuf_p, struct Client *client_p, struct Client *source } } - if (MyClient(target_p)) + if (!IsServer(server_p)) + server_p = server_p->servptr; + + if (IsMe(server_p)) show_privs(source_p, target_p); else - sendto_one(target_p, ":%s ENCAP %s PRIVS %s", - get_id(source_p, target_p), - target_p->servptr->name, + sendto_one(server_p, ":%s ENCAP %s PRIVS %s", + get_id(source_p, server_p), + server_p->name, use_id(target_p)); } diff --git a/modules/m_whois.c b/modules/m_whois.c index d3384ed3..0fc0f4f7 100644 --- a/modules/m_whois.c +++ b/modules/m_whois.c @@ -318,11 +318,14 @@ single_whois(struct Client *source_p, struct Client *target_p, int operspy) GlobalSetOptions.operstring)); } - if(MyClient(target_p) && !EmptyString(target_p->localClient->opername) && IsOper(target_p) && IsOper(source_p)) + if(!EmptyString(target_p->user->opername) && IsOper(target_p) && IsOper(source_p)) { char buf[512]; + const char *privset = "(missing)"; + if (target_p->user->privset != NULL) + privset = target_p->user->privset->name; snprintf(buf, sizeof(buf), "is opered as %s, privset %s", - target_p->localClient->opername, target_p->localClient->privset->name); + target_p->user->opername, privset); sendto_one_numeric(source_p, RPL_WHOISSPECIAL, form_str(RPL_WHOISSPECIAL), target_p->name, buf); } diff --git a/tests/send1.c b/tests/send1.c index e80aeccb..266e7a44 100644 --- a/tests/send1.c +++ b/tests/send1.c @@ -3898,8 +3898,8 @@ static void sendto_realops_snomask1(void) oper3->snomask = SNO_BOTS | SNO_SKILL; oper4->snomask = SNO_GENERAL | SNO_REJ; - oper3->localClient->privset = privilegeset_get("admin"); - oper4->localClient->privset = privilegeset_get("admin"); + oper3->user->privset = privilegeset_get("admin"); + oper4->user->privset = privilegeset_get("admin"); server->localClient->caps = CAP_ENCAP | CAP_TS6; server2->localClient->caps = 0; @@ -4125,8 +4125,8 @@ static void sendto_realops_snomask1__tags(void) oper3->snomask = SNO_BOTS | SNO_SKILL; oper4->snomask = SNO_GENERAL | SNO_REJ; - oper3->localClient->privset = privilegeset_get("admin"); - oper4->localClient->privset = privilegeset_get("admin"); + oper3->user->privset = privilegeset_get("admin"); + oper4->user->privset = privilegeset_get("admin"); server->localClient->caps = CAP_ENCAP | CAP_TS6; server2->localClient->caps = 0; @@ -4340,8 +4340,8 @@ static void sendto_realops_snomask_from1(void) oper3->snomask = SNO_BOTS | SNO_SKILL; oper4->snomask = SNO_GENERAL | SNO_REJ; - oper3->localClient->privset = privilegeset_get("admin"); - oper4->localClient->privset = privilegeset_get("admin"); + oper3->user->privset = privilegeset_get("admin"); + oper4->user->privset = privilegeset_get("admin"); sendto_realops_snomask_from(SNO_BOTS, L_ALL, &me, "Hello %s!", "World"); is_client_sendq(":" TEST_ME_NAME " NOTICE * :*** Notice -- Hello World!" CRLF, oper1, "Matches mask; " MSG); @@ -4460,8 +4460,8 @@ static void sendto_realops_snomask_from1__tags(void) oper3->snomask = SNO_BOTS | SNO_SKILL; oper4->snomask = SNO_GENERAL | SNO_REJ; - oper3->localClient->privset = privilegeset_get("admin"); - oper4->localClient->privset = privilegeset_get("admin"); + oper3->user->privset = privilegeset_get("admin"); + oper4->user->privset = privilegeset_get("admin"); sendto_realops_snomask_from(SNO_BOTS, L_ALL, &me, "Hello %s!", "World"); is_client_sendq("@time=" ADVENTURE_TIME " :" TEST_ME_NAME " NOTICE * :*** Notice -- Hello World!" CRLF, oper1, "Matches mask; " MSG);