Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add chanset bind #1524

Open
wants to merge 17 commits into
base: develop
Choose a base branch
from
10 changes: 10 additions & 0 deletions doc/sphinx_source/using/tcl-commands.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3585,6 +3585,14 @@ The following is a list of bind types and how they work. Below each bind type is

Description: triggered when a server sends an IRCv3 spec CHGHOST message to change a user's hostmask. The new host is matched against mask in the form of "#channel nick!user\@host" and can contain wildcards. The specified proc will be called with the nick of the user whose hostmask changed; the hostmask the affected user had before the change, the handle of the affected user (or * if no handle is present), the channel the user was on when the bind triggered, and the new hostmask of the affected user. This bind will trigger once for each channel the user is on.

(57) CHANSET

bind chanset <flags> <mask> <proc>

procname <chan> <setting> <value>

Description: triggered when a channel setting is set via the partyline. flags is ignored, mask is the name of channel setting (not including any +/- prefix) and can contain wildcards. The proc will be called with the channel that the setting was set on, the text name of the setting that was changed, and the value it was set to (0/1 for -/+, string, or X:Y formatted value).

^^^^^^^^^^^^^
Return Values
^^^^^^^^^^^^^
Expand Down Expand Up @@ -3639,6 +3647,8 @@ Here's a list of the bindings that use the return value from procs they trigger:

(19) RAWT Return 1 to ask the bot not to process the server text. This can affet the bot's performance by causing it to miss things that it would normally act on -- you have been warned. Again.

(20) CHANSET Return 1 to prevent the channel setting from being changed.

Control Procedures
------------------

Expand Down
24 changes: 24 additions & 0 deletions src/mod/channels.mod/channels.c
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ static Function *global = NULL;
static char chanfile[121], glob_chanmode[65];
static char *lastdeletedmask;

static p_tcl_bind_list H_chanset;

static struct udef_struct *udef;

static int use_info, chan_hack, quiet_save, global_revenge_mode,
Expand Down Expand Up @@ -241,6 +243,27 @@ static void get_mode_protect(struct chanset_t *chan, char *s)
}
}

static int builtin_chanset STDVAR
{
Function F = (Function) cd;

BADARGS(3, 3, " chan setting value");

CHECKVALIDITY(builtin_chanset);
F(argv[1], argv[2], argv[3]);
return TCL_OK;
}

int check_tcl_chanset(const char *chan, const char *setting, const char *value)
{
Tcl_SetVar(interp, "_chanset1", (char *) chan, 0);
Tcl_SetVar(interp, "_chanset2", (char *) setting, 0);
Tcl_SetVar(interp, "_chanset3", (char *) value, 0);

return BIND_EXEC_LOG == check_tcl_bind(H_chanset, setting, 0, " $_chanset1 $_chanset2 $_chanset3",
MATCH_MASK | BIND_STACKABLE | BIND_STACKRET | BIND_WANTRET);
}

/* Returns true if this is one of the channel masks
*/
static int ismodeline(masklist *m, char *user)
Expand Down Expand Up @@ -1007,6 +1030,7 @@ char *channels_start(Function *global_funcs)
Tcl_TraceVar(interp, "default-chanset",
TCL_TRACE_READS | TCL_TRACE_WRITES | TCL_TRACE_UNSETS,
traced_globchanset, NULL);
H_chanset = add_bind_table("chanset", HT_STACKABLE, builtin_chanset);
add_builtins(H_chon, my_chon);
add_builtins(H_dcc, C_dcc_irc);
add_tcl_commands(channels_cmds);
Expand Down
2 changes: 2 additions & 0 deletions src/mod/channels.mod/channels.h
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,8 @@ static void setudef(struct udef_struct *, char *, intptr_t);
static void remove_channel(struct chanset_t *);
static intptr_t ngetudef(char *, char *);
static int expired_mask(struct chanset_t *chan, char *who);
static int check_tcl_chanset(const char *, const char *, const char *);


#else

Expand Down
14 changes: 8 additions & 6 deletions src/mod/channels.mod/cmdschan.c
Original file line number Diff line number Diff line change
Expand Up @@ -1472,10 +1472,10 @@ static void cmd_chaninfo(struct userrec *u, int idx, char *par)

static void cmd_chanset(struct userrec *u, int idx, char *par)
{
char *chname = NULL, answers[512], *parcpy;
char *list[2], *bak, *buf;
char *chname = NULL, answers[1024], *parcpy;
char *list[2], value[2], *bak, *buf;
struct chanset_t *chan = NULL;
int all = 0;
int len, all = 0;

if (!par[0])
dprintf(idx, "Usage: chanset [%schannel] <settings>\n", CHANMETA);
Expand Down Expand Up @@ -1534,8 +1534,10 @@ static void cmd_chanset(struct userrec *u, int idx, char *par)
return;
}
if (tcl_channel_modify(0, chan, 1, list) == TCL_OK) {
strcat(answers, list[0]);
strcat(answers, " ");
strlcpy(value, list[0], 2);
len = strlen(answers);
egg_snprintf(answers + len, (sizeof answers) - len,
(len == 0) ? "%s" : " %s", list[0]); /* Concatenation */
} else if (!all || !chan->next)
dprintf(idx, "Error trying to set %s for %s, invalid mode.\n",
list[0], all ? "all channels" : chname);
Expand All @@ -1562,7 +1564,7 @@ static void cmd_chanset(struct userrec *u, int idx, char *par)
strcpy(parcpy, par);
irp = Tcl_CreateInterp();
if (tcl_channel_modify(irp, chan, 2, list) == TCL_OK) {
int len = strlen(answers);
len = strlen(answers);
egg_snprintf(answers + len, (sizeof answers) - len, "%s { %s }", list[0], parcpy); /* Concatenation */
} else if (!all || !chan->next)
dprintf(idx, "Error trying to set %s for %s, %s\n",
Expand Down
42 changes: 42 additions & 0 deletions src/mod/channels.mod/tclchan.c
Original file line number Diff line number Diff line change
Expand Up @@ -1318,6 +1318,48 @@ static int tcl_channel_modify(Tcl_Interp *irp, struct chanset_t *chan,
module_entry *me;

for (i = 0; i < items; i++) {
if (item[i][0] == '+' || item[i][0] == '-') {
if (check_tcl_chanset(chan->dname, item[i] + 1, item[i][0] == '+' ? "1" : "0")) {
if (irp) {
Tcl_ResetResult(irp);
Tcl_AppendResult(irp, "Channel setting ", item[i], " rejected by Tcl script", NULL);
}
return TCL_ERROR;
}
} else {
// otherwise invalid later, missing value
if (i < items - 1) {
int free_value = 0;
char *value;

if (!strncmp("need-", item[i], 5)) {
value = item[i + 1];
} else {
char *sep = strchr(item[i + 1], ' ');

if (sep) {
value = nmalloc(sep - item[i + 1] + 1);
strlcpy(value, item[i + 1], sep - item[i + 1] + 1);
free_value = 1;
} else {
value = item[i + 1];
}
}
if (check_tcl_chanset(chan->dname, item[i], value)) {
if (free_value) {
nfree(value);
}
if (irp) {
Tcl_ResetResult(irp);
Tcl_AppendResult(irp, "Channel setting ", item[i], " to ", value, " rejected by Tcl script", NULL);
}
return TCL_ERROR;
}
if (free_value) {
nfree(value);
}
}
}
if (!strcmp(item[i], "need-op")) {
i++;
if (i >= items) {
Expand Down
25 changes: 18 additions & 7 deletions src/tcl.c
Original file line number Diff line number Diff line change
Expand Up @@ -319,19 +319,30 @@ static void tcl_cleanup_stringinfo(ClientData cd)
}

/* Compatibility wrapper that calls Tcl functions with String API */
/*
* can call itself recursively, so argv is dynamically allocated
* incrrefcount is needed to preserve the strings we get from Tcl_GetString from being cleaned up
* if Tcl is invoked from this
*/
static int tcl_call_stringproc_cd(ClientData cd, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[])
{
static int max;
static const char **argv;
int i;
const char **argv;
int i, ret;
struct tcl_call_stringinfo *info = cd;

/* The string API guarantees argv[argc] == NULL, unlike the obj API */
if (objc + 1 > max)
argv = nrealloc(argv, (objc + 1) * sizeof *argv);
for (i = 0; i < objc; i++)
argv = nmalloc((objc + 1) * sizeof *argv);
for (i = 0; i < objc; i++) {
Tcl_IncrRefCount(objv[i]);
argv[i] = Tcl_GetString(objv[i]);
}
argv[objc] = NULL;
return (info->proc)(info->cd, interp, objc, argv);
ret = (info->proc)(info->cd, interp, objc, argv);
for (i = 0; i < objc; i++) {
Tcl_DecrRefCount(objv[i]);
}
nfree(argv);
return ret;
}

/* The standard case of no actual cd */
Expand Down