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