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 Servers may not send '$$', '$#' and opers@server notices. Older servers may
not allow servers to send to specific statuses on a channel. 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 OPERSPY
encap only encap only
encap target: * encap target: *
@ -1222,7 +1229,6 @@ MODRESTART
MODUNLOAD MODUNLOAD
MONITOR MONITOR
NAMES NAMES
OPER
POST POST
PUT PUT
RESTART RESTART

View file

@ -42,7 +42,7 @@ static int eb_oper(const char *data, struct Client *client_p,
if (data != NULL) if (data != NULL)
{ {
struct PrivilegeSet *set = privilegeset_get(data); 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; return EXTBAN_MATCH;
/* $o:admin or whatever */ /* $o:admin or whatever */

View file

@ -79,6 +79,9 @@ struct User
char *away; /* pointer to away message */ char *away; /* pointer to away message */
int refcnt; /* Number of times this block is referenced */ 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]; char suser[NICKLEN+1];
}; };
@ -225,7 +228,6 @@ struct LocalUser
*/ */
char *passwd; char *passwd;
char *auth_user; char *auth_user;
char *opername; /* name of operator{} block being used or tried (challenge) */
char *challenge; char *challenge;
char *fullcaps; char *fullcaps;
char *cipher_string; char *cipher_string;
@ -282,8 +284,6 @@ struct LocalUser
uint16_t cork_count; /* used for corking/uncorking connections */ uint16_t cork_count; /* used for corking/uncorking connections */
struct ev_entry *event; /* used for associated events */ struct ev_entry *event; /* used for associated events */
struct PrivilegeSet *privset; /* privset... */
char sasl_agent[IDLEN]; char sasl_agent[IDLEN];
unsigned char sasl_out; unsigned char sasl_out;
unsigned char sasl_complete; 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 IsOperConfEncrypted(x) ((x)->flags & OPER_ENCRYPTED)
#define IsOperConfNeedSSL(x) ((x)->flags & OPER_NEEDSSL) #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 IsOperGlobalKill(x) (HasPrivilege((x), "oper:global_kill"))
#define IsOperLocalKill(x) (HasPrivilege((x), "oper:local_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->auth_user);
rb_free(client_p->localClient->challenge); rb_free(client_p->localClient->challenge);
rb_free(client_p->localClient->fullcaps); rb_free(client_p->localClient->fullcaps);
rb_free(client_p->localClient->opername);
rb_free(client_p->localClient->mangledhost); rb_free(client_p->localClient->mangledhost);
if (client_p->localClient->privset)
privilegeset_unref(client_p->localClient->privset);
if (IsSSL(client_p)) if (IsSSL(client_p))
ssld_decrement_clicount(client_p->localClient->ssl_ctl); ssld_decrement_clicount(client_p->localClient->ssl_ctl);
@ -1920,6 +1917,9 @@ free_user(struct User *user, struct Client *client_p)
{ {
if(user->away) if(user->away)
rb_free((char *) user->away); rb_free((char *) user->away);
rb_free(user->opername);
if (user->privset)
privilegeset_unref(user->privset);
/* /*
* sanity check * sanity check
*/ */

View file

@ -1267,7 +1267,7 @@ get_oper_name(struct Client *client_p)
{ {
snprintf(buffer, sizeof(buffer), "%s!%s@%s{%s}", snprintf(buffer, sizeof(buffer), "%s!%s@%s{%s}",
client_p->name, client_p->username, client_p->name, client_p->username,
client_p->host, client_p->localClient->opername); client_p->host, client_p->user->opername);
return buffer; return buffer;
} }

View file

@ -669,6 +669,12 @@ burst_TS6(struct Client *client_p)
use_id(target_p), use_id(target_p),
target_p->user->away); 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; hclientinfo.target = target_p;
call_hook(h_burst_client, &hclientinfo); 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; source_p->flags &= ~OPER_FLAGS;
rb_free(source_p->localClient->opername);
source_p->localClient->opername = NULL;
rb_dlinkFindDestroy(source_p, &local_oper_list); 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); rb_dlinkFindDestroy(source_p, &oper_list);
@ -1413,8 +1420,8 @@ oper_up(struct Client *source_p, struct oper_conf *oper_p)
SetExemptKline(source_p); SetExemptKline(source_p);
source_p->flags |= oper_p->flags; source_p->flags |= oper_p->flags;
source_p->localClient->opername = rb_strdup(oper_p->name); source_p->user->opername = rb_strdup(oper_p->name);
source_p->localClient->privset = privilegeset_ref(oper_p->privset); source_p->user->privset = privilegeset_ref(oper_p->privset);
rb_dlinkAddAlloc(source_p, &local_oper_list); rb_dlinkAddAlloc(source_p, &local_oper_list);
rb_dlinkAddAlloc(source_p, &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, sendto_realops_snomask(SNO_GENERAL, L_ALL,
"%s (%s!%s@%s) is now an operator", oper_p->name, source_p->name, "%s (%s!%s@%s) is now an operator", oper_p->name, source_p->name,
source_p->username, source_p->host); 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)) if(!(old & UMODE_INVISIBLE) && IsInvisible(source_p))
++Count.invisi; ++Count.invisi;
if((old & UMODE_INVISIBLE) && !IsInvisible(source_p)) if((old & UMODE_INVISIBLE) && !IsInvisible(source_p))

View file

@ -93,9 +93,9 @@ cleanup_challenge(struct Client *target_p)
return; return;
rb_free(target_p->localClient->challenge); 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->challenge = NULL;
target_p->localClient->opername = NULL; target_p->user->opername = NULL;
target_p->localClient->chal_time = 0; 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); sendto_one(source_p, form_str(ERR_PASSWDMISMATCH), me.name, source_p->name);
ilog(L_FOPER, "EXPIRED CHALLENGE (%s) by (%s!%s@%s) (%s)", 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); source_p->username, source_p->host, source_p->sockhost);
if(ConfigFileEntry.failed_oper_notice) 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); sendto_one(source_p, form_str(ERR_PASSWDMISMATCH), me.name, source_p->name);
ilog(L_FOPER, "FAILED CHALLENGE (%s) by (%s!%s@%s) (%s)", 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); source_p->username, source_p->host, source_p->sockhost);
if(ConfigFileEntry.failed_oper_notice) 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, oper_p = find_oper_conf(source_p->username, source_p->orighost,
source_p->sockhost, source_p->sockhost,
source_p->localClient->opername); source_p->user->opername);
if(oper_p == NULL) if(oper_p == NULL)
{ {
sendto_one_numeric(source_p, ERR_NOOPERHOST, form_str(ERR_NOOPERHOST)); sendto_one_numeric(source_p, ERR_NOOPERHOST, form_str(ERR_NOOPERHOST));
ilog(L_FOPER, "FAILED OPER (%s) by (%s!%s@%s) (%s)", 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->username, source_p->host,
source_p->sockhost); 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); oper_up(source_p, oper_p);
ilog(L_OPERED, "OPER %s by %s!%s@%s (%s)", 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); source_p->username, source_p->host, source_p->sockhost);
return; 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), sendto_one(source_p, form_str(RPL_ENDOFRSACHALLENGE2),
me.name, source_p->name); me.name, source_p->name);
rb_free(challenge); rb_free(challenge);
source_p->localClient->opername = rb_strdup(oper_p->name); source_p->user->opername = rb_strdup(oper_p->name);
} }
else else
sendto_one_notice(source_p, ":Failed to generate challenge."); sendto_one_notice(source_p, ":Failed to generate challenge.");

View file

@ -61,7 +61,7 @@ void set_privset(struct Client *const source,
return; 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); sendto_one_notice(source, ":%s already has role of %s.", target->name, privset_name);
return; 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_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); 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; return;
} }

View file

@ -31,6 +31,7 @@
#include "s_newconf.h" #include "s_newconf.h"
#include "logger.h" #include "logger.h"
#include "s_user.h" #include "s_user.h"
#include "s_serv.h"
#include "send.h" #include "send.h"
#include "msg.h" #include "msg.h"
#include "parse.h" #include "parse.h"
@ -41,12 +42,13 @@
static const char oper_desc[] = "Provides the OPER command to become an IRC operator"; 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 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); static bool match_oper_password(const char *password, struct oper_conf *oper_p);
struct Message oper_msgtab = { struct Message oper_msgtab = {
"OPER", 0, 0, 0, 0, "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 }; 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 * match_oper_password
* *

View file

@ -38,6 +38,7 @@
#include "modules.h" #include "modules.h"
#include "s_conf.h" #include "s_conf.h"
#include "s_newconf.h" #include "s_newconf.h"
#include "hash.h"
static const char privs_desc[] = "Provides the PRIVS command to inspect an operator's privileges"; 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; struct mode_table *p;
buf[0] = '\0'; buf[0] = '\0';
if (target_p->localClient->privset) if (target_p->user->privset)
rb_strlcat(buf, target_p->localClient->privset->privs, sizeof buf); rb_strlcat(buf, target_p->user->privset->privs, sizeof buf);
if (IsOper(target_p)) if (IsOper(target_p))
{ {
if (buf[0] != '\0') if (target_p->user->opername)
rb_strlcat(buf, " ", sizeof buf); {
rb_strlcat(buf, "operator:", sizeof buf); if (buf[0] != '\0')
rb_strlcat(buf, target_p->localClient->opername, sizeof buf); 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') if (buf[0] != '\0')
rb_strlcat(buf, " ", sizeof buf); rb_strlcat(buf, " ", sizeof buf);
rb_strlcat(buf, "privset:", 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]; 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])) if (!IsOper(source_p) || parc < 2 || EmptyString(parv[1]))
return; return;
/* we cannot show privs for remote clients */ target_p = find_person(parv[1]);
if((target_p = find_person(parv[1])) && MyClient(target_p))
if (target_p != NULL)
show_privs(source_p, target_p); 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[]) 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 *target_p;
struct Client *server_p;
if (parc < 2 || EmptyString(parv[1])) if (parc < 2 || EmptyString(parv[1]))
target_p = source_p; {
server_p = target_p = source_p;
}
else else
{ {
target_p = find_named_person(parv[1]); if (parc >= 3)
if (target_p == NULL) {
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, sendto_one_numeric(source_p, ERR_NOSUCHNICK,
form_str(ERR_NOSUCHNICK), parv[1]); 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); show_privs(source_p, target_p);
else else
sendto_one(target_p, ":%s ENCAP %s PRIVS %s", sendto_one(server_p, ":%s ENCAP %s PRIVS %s",
get_id(source_p, target_p), get_id(source_p, server_p),
target_p->servptr->name, server_p->name,
use_id(target_p)); use_id(target_p));
} }

View file

@ -318,11 +318,14 @@ single_whois(struct Client *source_p, struct Client *target_p, int operspy)
GlobalSetOptions.operstring)); 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]; 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", 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), sendto_one_numeric(source_p, RPL_WHOISSPECIAL, form_str(RPL_WHOISSPECIAL),
target_p->name, buf); target_p->name, buf);
} }

View file

@ -3898,8 +3898,8 @@ static void sendto_realops_snomask1(void)
oper3->snomask = SNO_BOTS | SNO_SKILL; oper3->snomask = SNO_BOTS | SNO_SKILL;
oper4->snomask = SNO_GENERAL | SNO_REJ; oper4->snomask = SNO_GENERAL | SNO_REJ;
oper3->localClient->privset = privilegeset_get("admin"); oper3->user->privset = privilegeset_get("admin");
oper4->localClient->privset = privilegeset_get("admin"); oper4->user->privset = privilegeset_get("admin");
server->localClient->caps = CAP_ENCAP | CAP_TS6; server->localClient->caps = CAP_ENCAP | CAP_TS6;
server2->localClient->caps = 0; server2->localClient->caps = 0;
@ -4125,8 +4125,8 @@ static void sendto_realops_snomask1__tags(void)
oper3->snomask = SNO_BOTS | SNO_SKILL; oper3->snomask = SNO_BOTS | SNO_SKILL;
oper4->snomask = SNO_GENERAL | SNO_REJ; oper4->snomask = SNO_GENERAL | SNO_REJ;
oper3->localClient->privset = privilegeset_get("admin"); oper3->user->privset = privilegeset_get("admin");
oper4->localClient->privset = privilegeset_get("admin"); oper4->user->privset = privilegeset_get("admin");
server->localClient->caps = CAP_ENCAP | CAP_TS6; server->localClient->caps = CAP_ENCAP | CAP_TS6;
server2->localClient->caps = 0; server2->localClient->caps = 0;
@ -4340,8 +4340,8 @@ static void sendto_realops_snomask_from1(void)
oper3->snomask = SNO_BOTS | SNO_SKILL; oper3->snomask = SNO_BOTS | SNO_SKILL;
oper4->snomask = SNO_GENERAL | SNO_REJ; oper4->snomask = SNO_GENERAL | SNO_REJ;
oper3->localClient->privset = privilegeset_get("admin"); oper3->user->privset = privilegeset_get("admin");
oper4->localClient->privset = privilegeset_get("admin"); oper4->user->privset = privilegeset_get("admin");
sendto_realops_snomask_from(SNO_BOTS, L_ALL, &me, "Hello %s!", "World"); 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); 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; oper3->snomask = SNO_BOTS | SNO_SKILL;
oper4->snomask = SNO_GENERAL | SNO_REJ; oper4->snomask = SNO_GENERAL | SNO_REJ;
oper3->localClient->privset = privilegeset_get("admin"); oper3->user->privset = privilegeset_get("admin");
oper4->localClient->privset = privilegeset_get("admin"); oper4->user->privset = privilegeset_get("admin");
sendto_realops_snomask_from(SNO_BOTS, L_ALL, &me, "Hello %s!", "World"); 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); is_client_sendq("@time=" ADVENTURE_TIME " :" TEST_ME_NAME " NOTICE * :*** Notice -- Hello World!" CRLF, oper1, "Matches mask; " MSG);