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]
This commit is contained in:
Ed Kellett 2019-07-07 02:36:58 +01:00
parent 742ddc8fac
commit ed3ca2ff16
No known key found for this signature in database
GPG key ID: CB9986DEF342FABC
14 changed files with 129 additions and 55 deletions

View file

@ -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

View file

@ -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 */

View file

@ -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;

View file

@ -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"))

View file

@ -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
*/

View file

@ -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;
}

View file

@ -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);
}

View file

@ -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))

View file

@ -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.");

View file

@ -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;
}

View file

@ -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
*

View file

@ -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 (target_p->user->opername)
{
if (buf[0] != '\0')
rb_strlcat(buf, " ", sizeof buf);
rb_strlcat(buf, "operator:", sizeof buf);
rb_strlcat(buf, target_p->localClient->opername, 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));
}

View file

@ -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);
}

View file

@ -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);