added support for independent "zero-bots" (bots without an own source file)
[NeonServV5.git] / src / modcmd.c
index ecb788adce3586d542c1c6d024074a4bc88f11ad..50f71c86c138c4fea960643a6c3e8ef2908eeb03 100644 (file)
@@ -68,6 +68,8 @@ static const struct default_language_entry msgtab[] = {
     {"MODCMD_PRIVILEGED",       "$b%s$b is a privileged command."}, /* {ARGS: "god"} */
     {"MODCMD_PUBCMD",           "Public commands in $b%s$b are restricted."}, /* {ARGS: "#TestChan"} */
     {"MODCMD_ACCESS_DENIED",    "Access denied."},
+    {"MODCMD_SUBCOMMANDS",      "Subcommands of %s: %s"}, /* {ARGS: "bot", "ADD, DEL, EDIT"} */
+    {"MODCMD_CROSSCHAN",        "You must be in %s (or on its userlist) to use this command."},
     {NULL, NULL}
 };
 
@@ -78,10 +80,10 @@ static int get_binds_index(char first_char) {
     return 26;
 }
 
-struct ClientSocket* get_prefered_bot(int botid) {
+struct ClientSocket* get_botwise_prefered_bot(int botid, int clientid) {
     struct ClientSocket *client, *lowbot = NULL;
     for(client = getBots(SOCKET_FLAG_READY, NULL); client; client = getBots(SOCKET_FLAG_READY, client)) {
-        if(client->botid == botid) {
+        if(client->botid == botid && (botid || clientid == client->clientid)) {
             if((client->flags & SOCKET_FLAG_PREFERRED))
                 return client;
             else
@@ -91,10 +93,10 @@ struct ClientSocket* get_prefered_bot(int botid) {
     return lowbot;
 }
 
-static char* get_channel_trigger(int botid, struct ChanNode *chan) {
+static char* get_channel_trigger(int botid, int clientid, struct ChanNode *chan) {
     struct trigger_cache *trigger;
     for(trigger = chan->trigger; trigger; trigger = trigger->next) {
-        if(trigger->botid == botid)
+        if(trigger->botid == botid && (botid || trigger->clientid == clientid))
             return trigger->trigger;
     }
     struct trigger_callback *cb;
@@ -104,15 +106,16 @@ static char* get_channel_trigger(int botid, struct ChanNode *chan) {
     }
     char triggerStr[TRIGGERLEN];
     if(cb)
-        cb->func(chan, triggerStr);
+        cb->func(clientid, chan, triggerStr);
     else
-        strcpy(triggerStr, "+");
+        triggerStr[0] = '\0';
     trigger = malloc(sizeof(*trigger));
     if (!trigger) {
         perror("malloc() failed");
         return 0;
     }
     trigger->botid = botid;
+    trigger->clientid = clientid;
     trigger->trigger = (triggerStr[0] ? strdup(triggerStr) : NULL);
     trigger->next = chan->trigger;
     chan->trigger = trigger;
@@ -132,6 +135,11 @@ static USERAUTH_CALLBACK(command_checked_auth) {
     free(cache);
 }
 
+static CMD_BIND(modcmd_linker) {
+    //fake command for subcommands
+    //just empty
+}
+
 static void handle_command(struct ClientSocket *client, struct UserNode *user, struct ChanNode *chan, char *message) {
     struct ChanNode *sent_chan = chan;
     if(message[0] == '#') {
@@ -154,22 +162,56 @@ static void handle_command(struct ClientSocket *client, struct UserNode *user, s
     }
     struct cmd_binding *cbind;
     for(cbind = cmd_binds[bind_index]; cbind; cbind = cbind->next) {
-        if(cbind->botid == client->botid && stricmp(cbind->cmd, message) == 0) {
+        if(cbind->botid == client->botid && (cbind->botid || cbind->clientid == client->clientid) && stricmp(cbind->cmd, message) == 0) {
+            if(cbind->func->func == modcmd_linker) {
+                //links subcommands
+                char command[MAXLEN];
+                struct cmd_binding *parent_bind = cbind;
+                if(args) {
+                    char *subcmd = args;
+                    args = strstr(args, " ");
+                    if(args) {
+                        *args = '\0';
+                        args++;
+                    }
+                    sprintf(command, "%s %s", parent_bind->cmd, subcmd);
+                    for(cbind = cmd_binds[bind_index]; cbind; cbind = cbind->next) {
+                        if(cbind->botid == client->botid && (cbind->botid || cbind->clientid == client->clientid) && stricmp(cbind->cmd, command) == 0)
+                            break;
+                    }
+                    if(!cbind)
+                        break;
+                } else {
+                    //list all sub commands
+                    int commandlen = sprintf(command, "%s ", parent_bind->cmd);
+                    char subcommands[MAXLEN];
+                    int subcompos = 0;
+                    for(cbind = cmd_binds[bind_index]; cbind; cbind = cbind->next) {
+                        if(cbind->botid == client->botid && (cbind->botid || cbind->clientid == client->clientid) && stricmplen(cbind->cmd, command, commandlen) == 0) {
+                            subcompos += sprintf(subcommands + subcompos, (subcompos ? ", %s" : "%s"), cbind->cmd + commandlen);
+                        }
+                    }
+                    reply(tmp_text_client, user, "MODCMD_SUBCOMMANDS", parent_bind->cmd, subcommands);
+                    break;
+                }
+            }
             if(statistics_enabled)
                 statistics_commands++;
             total_triggered++;
-            cbind->func->triggered++;
+            cbind->triggered++;
             if((cbind->func->flags & CMDFLAG_FUNCMD)) {
                 if(!sent_chan)
                     break;
                 chan = sent_chan;
             }
             //get a text bot
-            tmp_text_client = get_prefered_bot(client->botid);
+            tmp_text_client = get_botwise_prefered_bot(client->botid, (client->botid == 0 ? client->clientid : 0));
             //parse the arguments...
             char *arga[MAXNUMPARAMS];
             char **argv;
             int argc = 0;
+            int escape = 0;
+            int offset = 0;
             if(args) {
                 while(*args) {
                     //skip leading spaces
@@ -178,8 +220,22 @@ static void handle_command(struct ClientSocket *client, struct UserNode *user, s
                     arga[argc++] = args;
                     if (argc >= MAXNUMPARAMS)
                         break;
-                    while (*args != ' ' && *args)
+                    while ((escape || *args != ' ') && *args) {
+                        if((cbind->func->flags & CMDFLAG_ESCAPE_ARGS) && *args == '\\') {
+                            escape = 1;
+                            offset++;
+                        } else if(escape)
+                            escape = 0;
+                        if(!escape && offset) {
+                            args[0 - offset] = args[0];
+                        }
                         args++;
+                        
+                    }
+                    if(offset) {
+                        args[0-offset] = '\0';
+                        offset = 0;
+                    }
                 }
             }
             argv = arga;
@@ -265,7 +321,7 @@ static void handle_command(struct ClientSocket *client, struct UserNode *user, s
                 reply(tmp_text_client, user, "MODCMD_CHAN_REQUIRED");
                 break;
             }
-            if((cbind->func->flags & CMDFLAG_CHECK_AUTH) && !(user->flags & USERFLAG_ISAUTHED)) {
+            if(((cbind->func->flags & CMDFLAG_CHECK_AUTH) || (chan && chan != sent_chan && !isUserOnChan(user, chan))) && !(user->flags & USERFLAG_ISAUTHED)) {
                 //check auth...
                 struct command_check_user_cache *data = malloc(sizeof(*data));
                 char **temp_argv = malloc(argc*sizeof(*temp_argv));
@@ -300,18 +356,41 @@ static void handle_command_async(struct ClientSocket *client, struct UserNode *u
     MYSQL_RES *res;
     MYSQL_ROW row;
     int uaccess;
+    char requested_uaccess = 0;
     int eventflags = (cbind->func->flags & (CMDFLAG_LOG | CMDFLAG_OPLOG));
     if((cbind->func->flags & CMDFLAG_REQUIRE_AUTH) && !(user->flags & USERFLAG_ISAUTHED)) {
         reply(tmp_text_client, user, "MODCMD_AUTH_REQUIRED");
         return;
     }
+    if(chan && sent_chan != chan && !isUserOnChan(user, chan)) {
+        char user_in_chan = 0;
+        if((user->flags & USERFLAG_ISAUTHED)) {
+            //maybe there's another user authed to user->auth on the channel...
+            struct ChanUser *cchanuser;
+            for(cchanuser = getChannelUsers(chan, NULL); cchanuser; cchanuser = getChannelUsers(chan, cchanuser)) {
+                if((cchanuser->user->flags & USERFLAG_ISAUTHED) && !stricmp(user->auth, cchanuser->user->auth)) {
+                    user_in_chan = 1;
+                    break;
+                }
+            }
+        }
+        if(!user_in_chan) {
+            //check if we are allowed to execute commands in this channel
+            requested_uaccess = 1;
+            uaccess = getChannelAccess(user, chan);
+            if(!uaccess) {
+                reply(tmp_text_client, user, "MODCMD_CROSSCHAN", chan->name);
+                return;
+            }
+        }
+    }
     if(sent_chan && sent_chan != chan) {
         //check pubcmd of this channel
         printf_mysql_query("SELECT `channel_pubcmd` FROM `channels` WHERE `channel_name` = '%s'", escape_string(sent_chan->name));
         res = mysql_use();
         if ((row = mysql_fetch_row(res)) != NULL) {
-            uaccess = getChannelAccess(user, sent_chan);
-            if(row[0] && uaccess < atoi(row[0]) && !isGodMode(user)) { //NOTE: HARDCODED DEFAULT: pubcmd = 0
+            int saccess = getChannelAccess(user, sent_chan);
+            if(row[0] && saccess < atoi(row[0]) && !isGodMode(user)) { //NOTE: HARDCODED DEFAULT: pubcmd = 0
                 reply(tmp_text_client, user, "MODCMD_PUBCMD", sent_chan->name);
                 return;
             }
@@ -370,7 +449,7 @@ static void handle_command_async(struct ClientSocket *client, struct UserNode *u
                 chan->flags |= CHANFLAG_CHAN_REGISTERED;
                 chan->channel_id = atoi(row[0]);
                 if((sent_chan && sent_chan == chan) || access_count || minaccess) {
-                    uaccess = getChannelAccess(user, chan);
+                    if(!requested_uaccess) uaccess = getChannelAccess(user, chan);
                     if(uaccess < minaccess && isGodMode(user)) {
                         eventflags |= CMDFLAG_OPLOG;
                     } else if(uaccess < minaccess) {
@@ -428,28 +507,31 @@ static void handle_command_async(struct ClientSocket *client, struct UserNode *u
         reply(tmp_text_client, user, "MODCMD_PRIVILEGED", cbind->cmd);
         return;
     }
-    struct Event *event = createEvent(client, user, chan, cbind->func->name, argv, argc, eventflags);
+    struct Event *event = createEvent(client, user, chan, cbind, argv, argc, eventflags);
     cbind->func->func(client, user, chan, argv, argc, event);
 }
 
 static void got_chanmsg(struct UserNode *user, struct ChanNode *chan, char *message) {
-    fd_set fds;
+    fd_set fds, fds2;
     char *trigger;
     struct ClientSocket *client;
     FD_ZERO(&fds);
+    FD_ZERO(&fds2);
     for(client = getBots(SOCKET_FLAG_READY, NULL); client; client = getBots(SOCKET_FLAG_READY, client)) {
-        if(isUserOnChan(client->user, chan) && (client->flags & SOCKET_FLAG_PREFERRED) && !FD_ISSET(client->botid, &fds)) {
-            FD_SET(client->botid, &fds);
-            trigger = get_channel_trigger(client->botid, chan);
+        if(isUserOnChan(client->user, chan) && (client->flags & SOCKET_FLAG_PREFERRED) && ((client->botid == 0 && !FD_ISSET(client->clientid, &fds)) || (client->botid && !FD_ISSET(client->botid, &fds2))))  {
+            FD_SET(client->clientid, &fds);
+            FD_SET(client->botid, &fds2);
+            trigger = get_channel_trigger(client->botid, client->clientid, chan);
             if(trigger && stricmplen(message, trigger, strlen(trigger)) == 0) {
                 handle_command(client, user, chan, message + strlen(trigger));
             }
         }
     }
     for(client = getBots(SOCKET_FLAG_READY, NULL); client; client = getBots(SOCKET_FLAG_READY, client)) {
-        if(isUserOnChan(client->user, chan) && !FD_ISSET(client->botid, &fds)) {
+        if(isUserOnChan(client->user, chan) && ((client->botid == 0 && !FD_ISSET(client->clientid, &fds)) || (client->botid && !FD_ISSET(client->botid, &fds2)))) {
             FD_SET(client->botid, &fds);
-            trigger = get_channel_trigger(client->botid, chan);
+            FD_SET(client->botid, &fds2);
+            trigger = get_channel_trigger(client->botid, client->clientid, chan);
             if(trigger && stricmplen(message, trigger, strlen(trigger)) == 0) {
                 handle_command(client, user, chan, message + strlen(trigger));
             }
@@ -484,7 +566,6 @@ int register_command(int botid, char *name, cmd_bind_t *func, int paramcount, ch
     cmdfunc->paramcount = paramcount;
     cmdfunc->channel_access = channel_access;
     cmdfunc->global_access = global_access;
-    cmdfunc->triggered = 0;
     cmdfunc->next = cmd_functions;
     cmd_functions = cmdfunc;
     return 1;
@@ -510,10 +591,10 @@ int set_trigger_callback(int botid, trigger_callback_t *func) {
     return 1;
 }
 
-int changeChannelTrigger(int botid, struct ChanNode *chan, char *new_trigger) {
+int changeBotwiseChannelTrigger(int botid, int clientid, struct ChanNode *chan, char *new_trigger) {
     struct trigger_cache *trigger;
     for(trigger = chan->trigger; trigger; trigger = trigger->next) {
-        if(trigger->botid == botid) {
+        if(trigger->botid == botid && (botid || trigger->clientid == clientid)) {
             free(trigger->trigger);
             trigger->trigger = strdup(new_trigger);
             return 1;
@@ -522,11 +603,11 @@ int changeChannelTrigger(int botid, struct ChanNode *chan, char *new_trigger) {
     return 0;
 }
 
-int bind_cmd_to_function(int botid, char *cmd, struct cmd_function *func) {
+int bind_botwise_cmd_to_function(int botid, int clientid, char *cmd, struct cmd_function *func) {
     int bind_index = get_binds_index(cmd[0]);
     struct cmd_binding *cbind;
     for(cbind = cmd_binds[bind_index]; cbind; cbind = cbind->next) {
-        if(cbind->botid == botid && strcmp(cbind->cmd, cmd) == 0)
+        if(((botid && cbind->botid == botid) || (botid == 0 && clientid == cbind->clientid)) && strcmp(cbind->cmd, cmd) == 0)
             return 0;
     }
     cbind = malloc(sizeof(*cbind));
@@ -535,18 +616,20 @@ int bind_cmd_to_function(int botid, char *cmd, struct cmd_function *func) {
         return 0;
     }
     cbind->botid = botid;
+    cbind->clientid = clientid;
     cbind->cmd = strdup(cmd);
     cbind->func = func;
     cbind->paramcount = 0;
     cbind->global_access = 0;
     cbind->channel_access = NULL;
     cbind->flags = 0;
+    cbind->triggered = 0;
     cbind->next = cmd_binds[bind_index];
     cmd_binds[bind_index] = cbind;
     return 1;
 }
 
-int bind_cmd_to_command(int botid, char *cmd, char *func) {
+int bind_botwise_cmd_to_command(int botid, int clientid, char *cmd, char *func) {
     struct cmd_function *cmdfunc;
     int fbotid = botid;
     char *c;
@@ -570,7 +653,7 @@ int bind_cmd_to_command(int botid, char *cmd, char *func) {
     int bind_index = get_binds_index(cmd[0]);
     struct cmd_binding *cbind;
     for(cbind = cmd_binds[bind_index]; cbind; cbind = cbind->next) {
-        if(cbind->botid == botid && strcmp(cbind->cmd, cmd) == 0)
+        if(((botid && cbind->botid == botid) || (botid == 0 && clientid == cbind->clientid)) && strcmp(cbind->cmd, cmd) == 0)
             return 0;
     }
     cbind = malloc(sizeof(*cbind));
@@ -579,6 +662,7 @@ int bind_cmd_to_command(int botid, char *cmd, char *func) {
         return 0;
     }
     cbind->botid = botid;
+    cbind->clientid = clientid;
     cbind->cmd = strdup(cmd);
     cbind->func = cmdfunc;
     cbind->next = cmd_binds[bind_index];
@@ -586,15 +670,16 @@ int bind_cmd_to_command(int botid, char *cmd, char *func) {
     cbind->global_access = 0;
     cbind->channel_access = NULL;
     cbind->flags = 0;
+    cbind->triggered = 0;
     cmd_binds[bind_index] = cbind;
     return 1;
 }
 
-int unbind_cmd(int botid, char *cmd) {
+int unbind_botwise_cmd(int botid, int clientid, char *cmd) {
     int bind_index = get_binds_index(cmd[0]);
     struct cmd_binding *cbind, *last = NULL;
     for(cbind = cmd_binds[bind_index]; cbind; cbind = cbind->next) {
-        if(cbind->botid == botid && strcmp(cbind->cmd, cmd) == 0) {
+        if(cbind->botid == botid && (botid || clientid == cbind->clientid) && strcmp(cbind->cmd, cmd) == 0) {
             if(last)
                 last->next = cbind->next;
             else
@@ -644,6 +729,7 @@ void init_modcmd() {
     bind_chanmsg(got_chanmsg);
     bind_privmsg(got_privmsg);
     register_default_language_table(msgtab);
+    register_command(0, "linker", modcmd_linker, 0, 0, 0, 0); //fake command for subcommands
 }
 
 void free_modcmd() {
@@ -686,11 +772,11 @@ void free_modcmd() {
     bot_aliases = NULL;
 }
 
-void bind_set_parameters(int botid, char *cmd, char *parameters) {
+void bind_botwise_set_parameters(int botid, int clientid, char *cmd, char *parameters) {
     int bind_index = get_binds_index(cmd[0]);
     struct cmd_binding *cbind;
     for(cbind = cmd_binds[bind_index]; cbind; cbind = cbind->next) {
-        if(cbind->botid == botid && strcmp(cbind->cmd, cmd) == 0) {
+        if(cbind->botid == botid && (botid || clientid == cbind->clientid) && strcmp(cbind->cmd, cmd) == 0) {
             if(cbind->paramcount) {
                 int i;
                 for(i = 0; i < cbind->paramcount; i++)
@@ -709,11 +795,11 @@ void bind_set_parameters(int botid, char *cmd, char *parameters) {
     }
 }
 
-void bind_set_global_access(int botid, char *cmd, int gaccess) {
+void bind_botwise_set_global_access(int botid, int clientid, char *cmd, int gaccess) {
     int bind_index = get_binds_index(cmd[0]);
     struct cmd_binding *cbind;
     for(cbind = cmd_binds[bind_index]; cbind; cbind = cbind->next) {
-        if(cbind->botid == botid && strcmp(cbind->cmd, cmd) == 0) {
+        if(cbind->botid == botid && (botid || clientid == cbind->clientid) && strcmp(cbind->cmd, cmd) == 0) {
             if(gaccess > -1) {
                 cbind->global_access = gaccess;
                 cbind->flags |= CMDFLAG_OVERRIDE_GLOBAL_ACCESS;
@@ -725,11 +811,11 @@ void bind_set_global_access(int botid, char *cmd, int gaccess) {
     }
 }
 
-void bind_set_channel_access(int botid, char *cmd, char *chanaccess) {
+void bind_botwise_set_channel_access(int botid, int clientid, char *cmd, char *chanaccess) {
     int bind_index = get_binds_index(cmd[0]);
     struct cmd_binding *cbind;
     for(cbind = cmd_binds[bind_index]; cbind; cbind = cbind->next) {
-        if(cbind->botid == botid && strcmp(cbind->cmd, cmd) == 0) {
+        if(cbind->botid == botid && (botid || clientid == cbind->clientid) && strcmp(cbind->cmd, cmd) == 0) {
             if(cbind->channel_access)
                 free(cbind->channel_access);
             if(chanaccess) {
@@ -744,18 +830,18 @@ void bind_set_channel_access(int botid, char *cmd, char *chanaccess) {
     }
 }
 
-struct cmd_binding *find_cmd_binding(int botid, char *cmd) {
+struct cmd_binding *find_botwise_cmd_binding(int botid, int clientid, char *cmd) {
     int bind_index = get_binds_index(cmd[0]);
     struct cmd_binding *cbind;
     for(cbind = cmd_binds[bind_index]; cbind; cbind = cbind->next) {
-        if(cbind->botid == botid && strcmp(cbind->cmd, cmd) == 0) {
+        if(cbind->botid == botid && (botid || clientid == cbind->clientid) && strcmp(cbind->cmd, cmd) == 0) {
             return cbind;
         }
     }
     return NULL;
 }
 
-void bind_unbound_required_functions(int botid) {
+void bind_botwise_unbound_required_functions(int botid, int clientid) {
     struct cmd_function *cmdfunc;
     int i, found;
     struct cmd_binding *cbind;
@@ -764,7 +850,7 @@ void bind_unbound_required_functions(int botid) {
             found = 0;
             for(i = 0; i < 27; i++) {
                 for(cbind = cmd_binds[i]; cbind; cbind = cbind->next) {
-                    if(cbind->botid == botid && cbind->func == cmdfunc) {
+                    if(cbind->botid == botid && (botid || clientid == cbind->clientid) && cbind->func == cmdfunc) {
                         found = 1;
                         break;
                     }
@@ -772,8 +858,8 @@ void bind_unbound_required_functions(int botid) {
                 if(found)
                     break;
             }
-            if(!found && bind_cmd_to_function(botid, cmdfunc->name, cmdfunc)) {
-                cbind = find_cmd_binding(botid, cmdfunc->name);
+            if(!found && bind_botwise_cmd_to_function(botid, clientid, cmdfunc->name, cmdfunc)) {
+                cbind = find_botwise_cmd_binding(botid, clientid, cmdfunc->name);
                 cbind->flags |= CMDFLAG_TEMPONARY_BIND;
             }
         }