Merge pull request #282 from edk0/propagate-oper

Propagate OPER
This commit is contained in:
Aaron Jones 2019-09-13 12:15:06 +00:00 committed by GitHub
commit b9da417b4e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
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 (target_p->user->opername)
{ {
if (buf[0] != '\0') if (buf[0] != '\0')
rb_strlcat(buf, " ", sizeof buf); rb_strlcat(buf, " ", sizeof buf);
rb_strlcat(buf, "operator:", 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') 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);