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