4b1d2cc1eeeb767b7fb9663874980c5e00f4cec9
[NeonServV5.git] / src / modcmd.c
1
2 #include "modcmd.h"
3 #include "IRCEvents.h"
4 #include "IRCParser.h"
5 #include "ClientSocket.h"
6 #include "UserNode.h"
7 #include "ChanNode.h"
8 #include "ChanUser.h"
9 #include "WHOHandler.h"
10 #include "lang.h"
11 #include "mysqlConn.h"
12 #include "DBHelper.h"
13 #include "EventLogger.h"
14
15 struct trigger_callback {
16     int botid;
17     trigger_callback_t *func;
18     
19     struct trigger_callback *next;
20 };
21
22 struct command_check_user_cache {
23     struct ClientSocket *client, *textclient;
24     struct UserNode *user;
25     struct ChanNode *chan, *sent_chan;
26     char **argv;
27     int argc;
28     char *message;
29     struct cmd_binding *cbind;
30 };
31
32 static struct cmd_binding **cmd_binds;
33 static struct cmd_function *cmd_functions = NULL;
34 static struct trigger_callback *trigger_callbacks = NULL;
35 static struct ClientSocket *tmp_text_client;
36
37 static const struct default_language_entry msgtab[] = {
38     {"MODCMD_LESS_PARAM_COUNT", "This command requires more parameters."},
39     {"MODCMD_CHAN_REQUIRED",    "You must provide the name of a channel that exists and the bot is on."},
40     {"MODCMD_AUTH_REQUIRED",    "You need to be authenticated with AuthServ to use this command."},
41     {"MODCMD_CHAN_SUSPENDED",   "This channel is currently suspended."},
42     {"MODCMD_PRIVILEGED",       "$b%s$b is a privileged command."}, /* {ARGS: "god"} */
43     {"MODCMD_PUBCMD",           "Public commands in $b%s$b are restricted."}, /* {ARGS: "#TestChan"} */
44     {"MODCMD_ACCESS_DENIED",    "Access denied."},
45     {NULL, NULL}
46 };
47
48 static int get_binds_index(char first_char) {
49     if(tolower(first_char) >= 'a' && tolower(first_char) <= 'z') {
50         return tolower(first_char) - 'a';
51     }
52     return 26;
53 }
54
55 struct ClientSocket* get_prefered_bot(int botid) {
56     struct ClientSocket *client, *lowbot = NULL;
57     for(client = getBots(SOCKET_FLAG_READY, NULL); client; client = getBots(SOCKET_FLAG_READY, client)) {
58         if(client->botid == botid) {
59             if((client->flags & SOCKET_FLAG_PREFERRED))
60                 return client;
61             else
62                 lowbot = client;
63         }
64     }
65     return lowbot;
66 }
67
68 static char* get_channel_trigger(int botid, struct ChanNode *chan) {
69     struct trigger_cache *trigger;
70     for(trigger = chan->trigger; trigger; trigger = trigger->next) {
71         if(trigger->botid == botid)
72             return trigger->trigger;
73     }
74     struct trigger_callback *cb;
75     for(cb = trigger_callbacks; cb; cb = cb->next) {
76         if(cb->botid == botid)
77             break;
78     }
79     char triggerStr[TRIGGERLEN];
80     if(cb)
81         cb->func(chan, triggerStr);
82     else
83         strcpy(triggerStr, "+");
84     trigger = malloc(sizeof(*trigger));
85     if (!trigger) {
86         perror("malloc() failed");
87         return 0;
88     }
89     trigger->botid = botid;
90     trigger->trigger = strdup(triggerStr);
91     trigger->next = chan->trigger;
92     chan->trigger = trigger;
93     return trigger->trigger;
94 }
95
96 static void handle_command_async(struct ClientSocket *client, struct UserNode *user, struct ChanNode *chan, struct ChanNode *sent_chan, struct cmd_binding *cbind, char **argv, int argc);
97
98 static USERAUTH_CALLBACK(command_checked_auth) {
99     struct command_check_user_cache *cache = data;
100     tmp_text_client = cache->textclient;
101     handle_command_async(cache->client, user, cache->chan, cache->sent_chan, cache->cbind, cache->argv, cache->argc);
102     free(cache->message);
103     free(cache);
104 }
105
106 static void handle_command(struct ClientSocket *client, struct UserNode *user, struct ChanNode *chan, char *message) {
107     struct ChanNode *sent_chan = chan;
108     if(message[0] == '#') {
109         char *chanName = message;
110         message = strstr(message, " ");
111         if(!message) return;
112         *message = '\0';
113         message++;
114         struct ChanNode *chan2 = getChanByName(chanName);
115         if(chan2)
116             chan = chan2;
117     }
118     message = strdup(message);
119     int bind_index = get_binds_index(message[0]);
120     char *args = strstr(message, " ");
121     if(args) {
122         *args = '\0';
123         args++;
124     }
125     struct cmd_binding *cbind;
126     for(cbind = cmd_binds[bind_index]; cbind; cbind = cbind->next) {
127         if(cbind->botid == client->botid && stricmp(cbind->cmd, message) == 0) {
128             //get a text bot
129             tmp_text_client = get_prefered_bot(client->botid);
130             //parse the arguments...
131             char *arga[MAXNUMPARAMS];
132             char **argv;
133             int argc = 0;
134             if(args) {
135                 while(*args) {
136                     //skip leading spaces
137                     while (*args == ' ')
138                         *args++ = 0;
139                     arga[argc++] = args;
140                     if (argc >= MAXNUMPARAMS)
141                         break;
142                     while (*args != ' ' && *args)
143                         args++;
144                 }
145             }
146             argv = arga;
147             if(argc != 0 && argv[0][0] == '#' && !(cbind->func->flags & CMDFLAG_CHAN_PARAM)) {
148                 struct ChanNode *chan2 = getChanByName(argv[0]);
149                 if(chan2) {
150                     argv += 1;
151                     argc -= 1;
152                     chan = chan2;
153                 }
154             }
155             if(cbind->parameters) {
156                 //userdefined parameters...
157                 char *uargs[MAXNUMPARAMS];
158                 int uargc = 0;
159                 char *a,*b = cbind->parameters;
160                 int allargs, argi;
161                 do {
162                     a = strstr(b, " ");
163                     if(a) *a = '\0';
164                     if(b[0] == '%') {
165                         b++;
166                         if(b[strlen(b)-1] == '-') {
167                             allargs = 1;
168                             b[strlen(b)-1] = '\0';
169                             argi = atoi(b);
170                             b[strlen(b)-1] = '-';
171                         } else {
172                             allargs = 0;
173                             argi = atoi(b);
174                         }
175                         if(argi > 0) {
176                             if(argi <= argc) {
177                                 uargs[uargc++] = argv[argi-1];
178                                 if(allargs) {
179                                     for(argi++; argi <= argc; argi++)
180                                         uargs[uargc++] = argv[argi-1];
181                                 }
182                             }
183                         } else if(!strcmp(b, "c")) {
184                             uargs[uargc++] = (chan ? chan->name : NULL);
185                         } else if(!strcmp(b, "n")) {
186                             uargs[uargc++] = user->nick;
187                         }
188                     } else {
189                         uargs[uargc++] = b;
190                     }
191                     if(a) {
192                         *a = ' ';
193                         b = a+1;
194                     }
195                 } while(a);
196                 argv = uargs;
197                 argc = uargc;
198             }
199             if(argc < cbind->func->paramcount) {
200                 reply(tmp_text_client, user, "MODCMD_LESS_PARAM_COUNT");
201                 break;
202             }
203             if((cbind->func->flags & CMDFLAG_REQUIRE_CHAN) && !chan) {
204                 reply(tmp_text_client, user, "MODCMD_CHAN_REQUIRED");
205                 break;
206             }
207             if((cbind->func->flags & CMDFLAG_CHECK_AUTH) && !(user->flags & USERFLAG_ISAUTHED)) {
208                 //check auth...
209                 struct command_check_user_cache *data = malloc(sizeof(*data));
210                 char **temp_argv = malloc(argc*sizeof(*temp_argv));
211                 if (!data || !temp_argv) {
212                     perror("malloc() failed");
213                     break;
214                 }
215                 memcpy(temp_argv, argv, argc*sizeof(*temp_argv));
216                 data->argv = temp_argv;
217                 data->argc = argc;
218                 data->client = client;
219                 data->user = user;
220                 data->chan = chan;
221                 data->sent_chan = sent_chan;
222                 data->message = message;
223                 data->cbind = cbind;
224                 data->textclient = tmp_text_client;
225                 get_userauth(user, command_checked_auth, data);
226                 return;
227             } else
228                 handle_command_async(client, user, chan, sent_chan, cbind, argv, argc);
229             break;
230         }
231     }
232     free(message);
233 }
234
235 static void handle_command_async(struct ClientSocket *client, struct UserNode *user, struct ChanNode *chan, struct ChanNode *sent_chan, struct cmd_binding *cbind, char **argv, int argc) {
236     MYSQL_RES *res;
237     MYSQL_ROW row;
238     int uaccess;
239     int eventflags = (cbind->func->flags & (CMDFLAG_LOG | CMDFLAG_OPLOG));
240     if((cbind->func->flags & CMDFLAG_REQUIRE_AUTH) && !(user->flags & USERFLAG_ISAUTHED)) {
241         reply(tmp_text_client, user, "MODCMD_AUTH_REQUIRED");
242         return;
243     }
244     if(sent_chan && sent_chan != chan) {
245         //check pubcmd of this channel
246         printf_mysql_query("SELECT `channel_pubcmd` FROM `channels` WHERE `channel_name` = '%s'", escape_string(sent_chan->name));
247         res = mysql_use();
248         if ((row = mysql_fetch_row(res)) != NULL) {
249             uaccess = getChannelAccess(user, sent_chan, 1);
250             if(row[0] && uaccess < atoi(row[0])) { //NOTE: HARDCODED DEFAULT: pubcmd = 0
251                 reply(tmp_text_client, user, "MODCMD_PUBCMD", sent_chan->name);
252                 return;
253             }
254         }
255     }
256     int global_access = ((cbind->flags & CMDFLAG_OVERRIDE_GLOBAL_ACCESS) ? cbind->global_access : cbind->func->global_access);
257     if(global_access > 0) {
258         int user_global_access = 0;
259         printf_mysql_query("SELECT `user_access` FROM `users` WHERE `user_user` = '%s'", escape_string(user->auth));
260         res = mysql_use();
261         if ((row = mysql_fetch_row(res)) != NULL) {
262             user_global_access = atoi(row[0]);
263         }
264         if(user_global_access < global_access) {
265             if(!user_global_access)
266                 reply(tmp_text_client, user, "MODCMD_PRIVILEGED", cbind->cmd);
267             else
268                 reply(tmp_text_client, user, "MODCMD_ACCESS_DENIED");
269             return;
270         }
271     }
272     if((cbind->func->flags & CMDFLAG_REGISTERED_CHAN)) {
273         MYSQL_ROW defaults = NULL;
274         char access_list[256];
275         int access_pos = 0;
276         int access_count = 0;
277         int minaccess = 0;
278         char *str_a, *str_b = cbind->func->channel_access, *str_c;
279         if(cbind->flags & CMDFLAG_OVERRIDE_CHANNEL_ACCESS)
280             str_b = cbind->channel_access;
281         access_list[0] = '\0';
282         if(str_b) {
283             str_c = strdup(str_b);
284             str_b = str_c;
285             while((str_a = str_b)) {
286                 str_b = strstr(str_a, ",");
287                 if(str_b) {
288                     *str_b = '\0';
289                     str_b++;
290                 }
291                 if(*str_a == '#') {
292                     str_a++;
293                     access_pos += sprintf(access_list+access_pos, ", `%s`", str_a);
294                     access_count++;
295                 } else {
296                     if(atoi(str_a) > minaccess)
297                         minaccess = atoi(str_a);
298                 }
299             }
300             free(str_c);
301         }
302         if(!(chan->flags & CHANFLAG_REQUESTED_CHANINFO) || (sent_chan && sent_chan == chan) || access_count || minaccess) {
303             printf_mysql_query("SELECT `channel_id`, `channel_pubcmd` %s FROM `channels` WHERE `channel_name` = '%s'", access_list, escape_string(chan->name));
304             res = mysql_use();
305             if ((row = mysql_fetch_row(res)) != NULL) {
306                 chan->flags |= CHANFLAG_CHAN_REGISTERED;
307                 chan->channel_id = atoi(row[0]);
308                 if((sent_chan && sent_chan == chan) || access_count || minaccess) {
309                     uaccess = getChannelAccess(user, chan, 0);
310                     if(uaccess < minaccess && isGodMode(user)) {
311                         eventflags |= CMDFLAG_OPLOG;
312                     } else if(uaccess < minaccess) {
313                         //ACCESS DENIED
314                         reply(tmp_text_client, user, "MODCMD_ACCESS_DENIED");
315                         return;
316                     }
317                     if(!row[1] && !defaults) {
318                         printf_mysql_query("SELECT `channel_id`, `channel_pubcmd` %s FROM `channels` WHERE `channel_name` = 'defaults'", access_list);
319                         defaults = mysql_fetch_row(mysql_use());
320                     }
321                     if(sent_chan && (sent_chan == chan) && uaccess < (row[1] ? atoi(row[1]) : atoi(defaults[1]))) {
322                         if(isGodMode(user)) {
323                             eventflags |= CMDFLAG_OPLOG;
324                         } else {
325                             //PUBCMD
326                             reply(tmp_text_client, user, "MODCMD_PUBCMD", chan->name);
327                             return;
328                         }
329                     }
330                     int i;
331                     for(i = 0; i < access_count; i++) {
332                         if(!row[2+i] && !defaults) {
333                             printf_mysql_query("SELECT `channel_id`, `channel_pubcmd` %s FROM `channels` WHERE `channel_name` = 'defaults'", access_list);
334                             defaults = mysql_fetch_row(mysql_use());
335                         }
336                         if(uaccess < (row[2+i] ? atoi(row[2+i]) : atoi(defaults[2+i]))) {
337                             if(isGodMode(user)) {
338                                 eventflags |= CMDFLAG_OPLOG;
339                             } else {
340                                 reply(tmp_text_client, user, "MODCMD_ACCESS_DENIED");
341                                 return;
342                             }
343                         }
344                     }
345                 }
346             }
347             chan->flags |= CHANFLAG_REQUESTED_CHANINFO;
348         }
349         if(!(chan->flags & CHANFLAG_CHAN_REGISTERED)) {
350             reply(tmp_text_client, user, "MODCMD_CHAN_REQUIRED");
351             return;
352         }
353         printf_mysql_query("SELECT `botid`, `suspended` FROM `bot_channels` LEFT JOIN `bots` ON `bot_channels`.`botid` = `bots`.`id` WHERE `chanid` = '%d' AND `botclass` = '%d'", chan->channel_id, client->botid);
354         res = mysql_use();
355         if ((row = mysql_fetch_row(res)) == NULL) {
356             reply(tmp_text_client, user, "MODCMD_CHAN_REQUIRED");
357             return;
358         } else if(!strcmp(row[1], "1")) {
359             reply(tmp_text_client, user, "MODCMD_CHAN_SUSPENDED");
360             return;
361         }
362     }
363     if((cbind->func->flags & CMDFLAG_REQUIRE_GOD) && !isGodMode(user)) {
364         reply(tmp_text_client, user, "MODCMD_PRIVILEGED", cbind->cmd);
365         return;
366     }
367     struct Event *event = createEvent(client, user, chan, cbind->func->name, argv, argc, eventflags);
368     cbind->func->func(client, user, chan, argv, argc, event);
369 }
370
371 static void got_chanmsg(struct UserNode *user, struct ChanNode *chan, char *message) {
372     fd_set fds;
373     char *trigger;
374     struct ClientSocket *client;
375     FD_ZERO(&fds);
376     for(client = getBots(SOCKET_FLAG_READY, NULL); client; client = getBots(SOCKET_FLAG_READY, client)) {
377         if(isUserOnChan(client->user, chan) && (client->flags & SOCKET_FLAG_PREFERRED) && !FD_ISSET(client->botid, &fds)) {
378             FD_SET(client->botid, &fds);
379             trigger = get_channel_trigger(client->botid, chan);
380             if(stricmplen(message, trigger, strlen(trigger)) == 0) {
381                 handle_command(client, user, chan, message + strlen(trigger));
382             }
383         }
384     }
385     for(client = getBots(SOCKET_FLAG_READY, NULL); client; client = getBots(SOCKET_FLAG_READY, client)) {
386         if(isUserOnChan(client->user, chan) && !FD_ISSET(client->botid, &fds)) {
387             FD_SET(client->botid, &fds);
388             trigger = get_channel_trigger(client->botid, chan);
389             if(stricmplen(message, trigger, strlen(trigger)) == 0) {
390                 handle_command(client, user, chan, message + strlen(trigger));
391             }
392         }
393     }
394 }
395
396 static void got_privmsg(struct UserNode *user, struct UserNode *target, char *message) {
397     struct ClientSocket *client;
398     for(client = getBots(SOCKET_FLAG_READY, NULL); client; client = getBots(SOCKET_FLAG_READY, client)) {
399         if(client->user == target) {
400             handle_command(client, user, NULL, message);
401         }
402     }
403 }
404
405 int register_command(int botid, char *name, cmd_bind_t *func, int paramcount, char *channel_access, int global_access, unsigned int flags) {
406     struct cmd_function *cmdfunc;
407     for(cmdfunc = cmd_functions; cmdfunc; cmdfunc = cmdfunc->next) {
408         if(cmdfunc->botid == botid && strcmp(cmdfunc->name, name) == 0)
409             return 0;
410     }
411     cmdfunc = malloc(sizeof(*cmdfunc));
412     if (!cmdfunc) {
413         perror("malloc() failed");
414         return 0;
415     }
416     cmdfunc->botid = botid;
417     cmdfunc->name = strdup(name);
418     cmdfunc->func = func;
419     cmdfunc->flags = flags;
420     cmdfunc->paramcount = paramcount;
421     cmdfunc->channel_access = channel_access;
422     cmdfunc->global_access = global_access;
423     cmdfunc->next = cmd_functions;
424     cmd_functions = cmdfunc;
425     return 1;
426 }
427
428 int set_trigger_callback(int botid, trigger_callback_t *func) {
429     static struct trigger_callback *cb = NULL;
430     for(cb = trigger_callbacks; cb; cb = cb->next) {
431         if(cb->botid == botid)
432             break;
433     }
434     if(!cb) {
435         cb = malloc(sizeof(*cb));
436         if (!cb) {
437             perror("malloc() failed");
438             return 0;
439         }
440         cb->botid = botid;
441         cb->next = trigger_callbacks;
442         trigger_callbacks = cb;
443     }
444     cb->func = func;
445     return 1;
446 }
447
448 int changeChannelTrigger(int botid, struct ChanNode *chan, char *new_trigger) {
449     struct trigger_cache *trigger;
450     for(trigger = chan->trigger; trigger; trigger = trigger->next) {
451         if(trigger->botid == botid) {
452             free(trigger->trigger);
453             trigger->trigger = strdup(new_trigger);
454             return 1;
455         }
456     }
457     return 0;
458 }
459
460 int bind_cmd_to_function(int botid, char *cmd, struct cmd_function *func) {
461     int bind_index = get_binds_index(cmd[0]);
462     struct cmd_binding *cbind;
463     for(cbind = cmd_binds[bind_index]; cbind; cbind = cbind->next) {
464         if(cbind->botid == botid && strcmp(cbind->cmd, cmd) == 0)
465             return 0;
466     }
467     cbind = malloc(sizeof(*cbind));
468     if (!cbind) {
469         perror("malloc() failed");
470         return 0;
471     }
472     cbind->botid = botid;
473     cbind->cmd = strdup(cmd);
474     cbind->func = func;
475     cbind->parameters = NULL;
476     cbind->global_access = 0;
477     cbind->channel_access = NULL;
478     cbind->flags = 0;
479     cbind->next = cmd_binds[bind_index];
480     cmd_binds[bind_index] = cbind;
481     return 1;
482 }
483
484 int bind_cmd_to_command(int botid, char *cmd, char *func) {
485     struct cmd_function *cmdfunc;
486     for(cmdfunc = cmd_functions; cmdfunc; cmdfunc = cmdfunc->next) {
487         if(cmdfunc->botid == botid && strcmp(cmdfunc->name, func) == 0)
488             break;
489     }
490     if(!cmdfunc) return 0;
491     int bind_index = get_binds_index(cmd[0]);
492     struct cmd_binding *cbind;
493     for(cbind = cmd_binds[bind_index]; cbind; cbind = cbind->next) {
494         if(cbind->botid == botid && strcmp(cbind->cmd, cmd) == 0)
495             return 0;
496     }
497     cbind = malloc(sizeof(*cbind));
498     if (!cbind) {
499         perror("malloc() failed");
500         return 0;
501     }
502     cbind->botid = botid;
503     cbind->cmd = strdup(cmd);
504     cbind->func = cmdfunc;
505     cbind->next = cmd_binds[bind_index];
506     cbind->parameters = NULL;
507     cbind->global_access = 0;
508     cbind->channel_access = NULL;
509     cbind->flags = 0;
510     cmd_binds[bind_index] = cbind;
511     return 1;
512 }
513
514 int unbind_cmd(int botid, char *cmd) {
515     int bind_index = get_binds_index(cmd[0]);
516     struct cmd_binding *cbind, *last = NULL;
517     for(cbind = cmd_binds[bind_index]; cbind; cbind = cbind->next) {
518         if(cbind->botid == botid && strcmp(cbind->cmd, cmd) == 0) {
519             if(last)
520                 last->next = cbind->next;
521             else
522                 cmd_binds[bind_index] = cbind->next;
523             free(cbind->cmd);
524             if(cbind->parameters)
525                 free(cbind->parameters);
526             free(cbind);
527             return 1;
528         } else
529             last = cbind;
530     }
531     return 0;
532 }
533
534 struct cmd_function *find_cmd_function(int botid, char *name) {
535     struct cmd_function *cmdfunc;
536     for(cmdfunc = cmd_functions; cmdfunc; cmdfunc = cmdfunc->next) {
537         if(cmdfunc->botid == botid && stricmp(cmdfunc->name, name) == 0)
538             break;
539     }
540     return cmdfunc;
541 }
542
543 struct ClientSocket *getTextBot() {
544     return tmp_text_client;
545 }
546
547 void init_modcmd() {
548     cmd_binds = calloc(27, sizeof(*cmd_binds));
549     bind_chanmsg(got_chanmsg);
550     bind_privmsg(got_privmsg);
551     register_default_language_table(msgtab);
552 }
553
554 void free_modcmd() {
555     int i;
556     for(i = 0; i < 27; i++) {
557         struct cmd_binding *cbind, *next;
558         for(cbind = cmd_binds[i]; cbind; cbind = next) {
559             next = cbind->next;
560             free(cbind->cmd);
561             if(cbind->parameters)
562                 free(cbind->parameters);
563             if(cbind->channel_access)
564                 free(cbind->channel_access);
565             free(cbind);
566         }
567     }
568     free(cmd_binds);
569     struct cmd_function *cmdfunct, *next;
570     for(cmdfunct = cmd_functions; cmdfunct; cmdfunct = next) {
571         next = cmdfunct->next;
572         free(cmdfunct->name);
573         free(cmdfunct);
574     }
575     struct trigger_callback *cb, *next_cb;
576     for(cb = trigger_callbacks; cb; cb = next_cb) {
577         next_cb = cb->next;
578         free(next_cb);
579     }
580     cmd_functions = NULL;
581     trigger_callbacks = NULL;
582 }
583
584 void bind_set_parameters(int botid, char *cmd, char *parameters) {
585     int bind_index = get_binds_index(cmd[0]);
586     struct cmd_binding *cbind;
587     for(cbind = cmd_binds[bind_index]; cbind; cbind = cbind->next) {
588         if(cbind->botid == botid && strcmp(cbind->cmd, cmd) == 0) {
589             if(cbind->parameters)
590                 free(cbind->parameters);
591             cbind->parameters = strdup(parameters);
592             return;
593         }
594     }
595 }
596
597 void bind_set_global_access(int botid, char *cmd, int gaccess) {
598     int bind_index = get_binds_index(cmd[0]);
599     struct cmd_binding *cbind;
600     for(cbind = cmd_binds[bind_index]; cbind; cbind = cbind->next) {
601         if(cbind->botid == botid && strcmp(cbind->cmd, cmd) == 0) {
602             if(gaccess > -1) {
603                 cbind->global_access = gaccess;
604                 cbind->flags |= CMDFLAG_OVERRIDE_GLOBAL_ACCESS;
605             } else {
606                 cbind->flags &= ~CMDFLAG_OVERRIDE_GLOBAL_ACCESS;
607             }
608             return;
609         }
610     }
611 }
612
613 void bind_set_channel_access(int botid, char *cmd, char *chanaccess) {
614     int bind_index = get_binds_index(cmd[0]);
615     struct cmd_binding *cbind;
616     for(cbind = cmd_binds[bind_index]; cbind; cbind = cbind->next) {
617         if(cbind->botid == botid && strcmp(cbind->cmd, cmd) == 0) {
618             if(cbind->channel_access)
619                 free(cbind->channel_access);
620             if(chanaccess) {
621                 cbind->channel_access = strdup(chanaccess);
622                 cbind->flags |= CMDFLAG_OVERRIDE_CHANNEL_ACCESS;
623             } else {
624                 cbind->channel_access = NULL;
625                 cbind->flags &= ~CMDFLAG_OVERRIDE_CHANNEL_ACCESS;
626             }
627             return;
628         }
629     }
630 }
631
632 struct cmd_binding *find_cmd_binding(int botid, char *cmd) {
633     int bind_index = get_binds_index(cmd[0]);
634     struct cmd_binding *cbind;
635     for(cbind = cmd_binds[bind_index]; cbind; cbind = cbind->next) {
636         if(cbind->botid == botid && strcmp(cbind->cmd, cmd) == 0) {
637             return cbind;
638         }
639     }
640     return NULL;
641 }
642