forked from noisytoot/rubyserv-iirc
524 lines
15 KiB
C
524 lines
15 KiB
C
// Used-executed commands part of HaxServ
|
||
//
|
||
// Written by: Test_User <hax@andrewyu.org>
|
||
//
|
||
// This is free and unencumbered software released into the public
|
||
// domain.
|
||
//
|
||
// Anyone is free to copy, modify, publish, use, compile, sell, or
|
||
// distribute this software, either in source code form or as a compiled
|
||
// binary, for any purpose, commercial or non-commercial, and by any
|
||
// means.
|
||
//
|
||
// In jurisdictions that recognize copyright laws, the author or authors
|
||
// of this software dedicate any and all copyright interest in the
|
||
// software to the public domain. We make this dedication for the benefit
|
||
// of the public at large and to the detriment of our heirs and
|
||
// successors. We intend this dedication to be an overt act of
|
||
// relinquishment in perpetuity of all present and future rights to this
|
||
// software under copyright law.
|
||
//
|
||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||
// IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||
// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||
// OTHER DEALINGS IN THE SOFTWARE.
|
||
|
||
#include <stdint.h>
|
||
#include <stdlib.h>
|
||
#include <string.h>
|
||
#include <stdio.h>
|
||
|
||
#include "types.h"
|
||
#include "table.h"
|
||
#include "commands.h"
|
||
#include "network.h"
|
||
#include "tls.h"
|
||
#include "config.h"
|
||
#include "utils.h"
|
||
|
||
#define MAX_SPAM_COUNT 65536
|
||
|
||
struct table user_commands = {0};
|
||
|
||
int help_command(struct string sender, struct string original_message, struct string to, uint64_t argc, struct string *argv, char is_local) {
|
||
for (uint64_t i = 0; i < user_commands.len; i++) {
|
||
struct command_def *def = user_commands.array[i].ptr;
|
||
|
||
struct string message[] = {
|
||
command_prefix,
|
||
user_commands.array[i].name,
|
||
STRING("\x0F" " "),
|
||
def->summary,
|
||
};
|
||
|
||
privmsg(STRING("1HC000000"), to, sizeof(message)/sizeof(*message), message);
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
static struct command_def help_command_def = {
|
||
.func = help_command,
|
||
.privs = {0},
|
||
.local_only = 0,
|
||
.summary = STRING("Shows a list of commands, or, when I write it up, help about a specific command"),
|
||
};
|
||
|
||
int raw_command(struct string sender, struct string original_message, struct string to, uint64_t argc, struct string *argv, char is_local) {
|
||
if (argv[0].len < original_message.len) {
|
||
original_message.data += argv[0].len + 1;
|
||
original_message.len -= argv[0].len + 1;
|
||
SEND(original_message);
|
||
}
|
||
SEND(STRING("\n"));
|
||
|
||
return 0;
|
||
}
|
||
static struct command_def raw_command_def = {
|
||
.func = raw_command,
|
||
.privs = STRING("NetAdmin"), // TODO: Make this configurable elsewhere
|
||
.local_only = 0,
|
||
.summary = STRING("Sends a raw message to the server"),
|
||
};
|
||
|
||
static struct pref_type_suff {
|
||
struct string pref;
|
||
uint8_t type;
|
||
struct string suff;
|
||
} sus_strings[] = {
|
||
{STRING(":1HC000000 PRIVMSG "), 0, STRING(" :Andrew is very sus.\n")},
|
||
{STRING(":1HC000000 PRIVMSG "), 0, STRING(" :I was the impostor, but you only know because I killed you.\n")},
|
||
{STRING(":1HC000000 PRIVMSG "), 0, STRING(" :\\x1b(0\n")},
|
||
{STRING(":1HC000000 KILL "), 1, STRING(" :Ejected (1 Impostor remains)\n")},
|
||
{STRING(":1HC000000 KILL "), 1, STRING(" :Ejected, and the crewmates have won.\n")},
|
||
}, cr_strings[] = {
|
||
{STRING(":1HC000000 PRIVMSG "), 0, STRING(" :You are now a cruxian toxicpod, kill the sharded crewmates.\n")},
|
||
{STRING(":1HC000000 PRIVMSG "), 0, STRING(" :You are now a cruxian omura, kill the sharded crewmates.\n")},
|
||
{STRING(":1HC000000 PRIVMSG "), 0, STRING(" :You are now a cruxian oct, but you ran out of reactors.\n")},
|
||
{STRING(":1HC000000 KILL "), 1, STRING(" :Eliminated (You became a cruxian eclipse, but were drawn to my bait reactor)\n")},
|
||
{STRING(":1HC000000 PRIVMSG "), 0, STRING(" :You attempted to change into a cruxian navanax, but were caught in the act.\n")},
|
||
};
|
||
|
||
int sus_command(struct string sender, struct string original_message, struct string to, uint64_t argc, struct string *argv, char is_local) {
|
||
uint64_t index = (uint64_t)random() % (sizeof(sus_strings)/sizeof(sus_strings[0]));
|
||
|
||
SEND(sus_strings[index].pref);
|
||
if (sus_strings[index].type == 0)
|
||
SEND(to);
|
||
else
|
||
SEND(sender);
|
||
SEND(sus_strings[index].suff);
|
||
|
||
return 0;
|
||
}
|
||
static struct command_def sus_command_def = {
|
||
.func = sus_command,
|
||
.privs = {0},
|
||
.local_only = 0,
|
||
.summary = STRING("You seem a bit sus today"),
|
||
};
|
||
|
||
int cr_command(struct string sender, struct string original_message, struct string to, uint64_t argc, struct string *argv, char is_local) {
|
||
uint64_t index = (uint64_t)random() % (sizeof(cr_strings)/sizeof(cr_strings[0]));
|
||
|
||
SEND(cr_strings[index].pref);
|
||
if (cr_strings[index].type == 0)
|
||
SEND(to);
|
||
else
|
||
SEND(sender);
|
||
SEND(cr_strings[index].suff);
|
||
|
||
return 0;
|
||
}
|
||
static struct command_def cr_command_def = {
|
||
.func = cr_command,
|
||
.privs = {0},
|
||
.local_only = 0,
|
||
.summary = STRING("Join the crux side"),
|
||
};
|
||
|
||
int spam_command(struct string sender, struct string original_message, struct string to, uint64_t argc, struct string *argv, char is_local) {
|
||
if (argc < 3) {
|
||
privmsg(STRING("1HC000000"), to, 1, (struct string[]){STRING("Missing args!")});
|
||
return 0;
|
||
}
|
||
|
||
char err;
|
||
uint64_t count = str_to_unsigned(argv[1], &err);
|
||
if (err || (count > MAX_SPAM_COUNT && !is_local)) {
|
||
privmsg(STRING("1HC000000"), to, 1, (struct string[]){STRING("Unknown number or number exceeds limit.")});
|
||
return 0;
|
||
}
|
||
|
||
char wasspace = 1;
|
||
uint64_t offset = 0;
|
||
char found = 0;
|
||
for (; offset < original_message.len; offset++) {
|
||
if (original_message.data[offset] == ' ' && !wasspace)
|
||
found++;
|
||
|
||
wasspace = (original_message.data[offset] == ' ');
|
||
|
||
if (found >= 2 && !wasspace)
|
||
break;
|
||
}
|
||
|
||
if (found < 2) {
|
||
WRITES(2, STRING("WARNING: Apparently there was no second argument... shouldn't happen.\n"));
|
||
return 0;
|
||
}
|
||
|
||
struct command_def *cmd = get_table_index(user_commands, argv[2]);
|
||
if (cmd) {
|
||
if (!cmd->local_only) {
|
||
if (cmd->privs.len != 0 && sender.len != 3) {
|
||
struct user_info *user = get_table_index(user_list, sender);
|
||
if (!user)
|
||
return 1; // really shouldn't happen
|
||
if (!STRING_EQ(user->opertype, cmd->privs)) {
|
||
SEND(STRING(":1HC000000 NOTICE "));
|
||
SEND(to);
|
||
SEND(STRING(" :You are not authorized to execute that command.\n"));
|
||
|
||
return 0;
|
||
}
|
||
|
||
if (cmd->func == spam_command) {
|
||
SEND(STRING(":1HC000000 NOTICE "));
|
||
SEND(to);
|
||
SEND(STRING(" :Spam recursion is not allowed. The limit is for your own sake, please do not violate it.\n"));
|
||
|
||
return 0;
|
||
}
|
||
}
|
||
} else {
|
||
SEND(STRING(":1HC000000 NOTICE "));
|
||
SEND(to);
|
||
SEND(STRING(" :Spamming of local-only commands is disabled.\n"));
|
||
|
||
return 0;
|
||
}
|
||
} else {
|
||
SEND(STRING(":1HC000000 NOTICE "));
|
||
SEND(to);
|
||
SEND(STRING(" :Unknown command.\n"));
|
||
|
||
return 0;
|
||
}
|
||
|
||
struct string fake_original_message = {.data = original_message.data + offset, .len = original_message.len - offset};
|
||
WRITES(2, fake_original_message);
|
||
WRITES(2, STRING("\n"));
|
||
|
||
for (uint64_t i = 0; i < count; i++) {
|
||
int ret = cmd->func(sender, fake_original_message, to, argc - 2, &(argv[2]), 0);
|
||
if (ret)
|
||
return ret;
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
static struct command_def spam_command_def = {
|
||
.func = spam_command,
|
||
.privs = STRING("NetAdmin"),
|
||
.local_only = 0,
|
||
.summary = STRING("Repeats a command <n> times"),
|
||
};
|
||
|
||
int clear_command(struct string sender, struct string original_message, struct string to, uint64_t argc, struct string *argv, char is_local) {
|
||
if (argc < 2) {
|
||
privmsg(STRING("1HC000000"), to, 1, (struct string[]){STRING("Missing args!")});
|
||
return 0;
|
||
}
|
||
|
||
struct channel_info *channel = get_table_index(channel_list, argv[1]);
|
||
if (!channel) {
|
||
privmsg(STRING("1HC000000"), to, 1, (struct string[]){STRING("That channel doesn't seem to exist, so is thereby already cleared.")});
|
||
return 0;
|
||
}
|
||
|
||
for (uint64_t i = 0; i < channel->user_list.len; i++) {
|
||
// TODO: Proper kick function, will prob come as a part of my next HaxServ rewrite
|
||
|
||
SEND(STRING(":1HC000000 KICK "));
|
||
SEND(argv[1]);
|
||
SEND(STRING(" "));
|
||
SEND(channel->user_list.array[i].name);
|
||
SEND(STRING(" :(B\n"));
|
||
|
||
if (client_connected) {
|
||
SENDCLIENT(STRING(":"));
|
||
SENDCLIENT(nick);
|
||
SENDCLIENT(STRING("!"));
|
||
SENDCLIENT(nick);
|
||
SENDCLIENT(STRING("@"));
|
||
SENDCLIENT(hostmask);
|
||
SENDCLIENT(STRING(" KICK "));
|
||
SENDCLIENT(argv[1]);
|
||
SENDCLIENT(STRING(" "));
|
||
|
||
struct user_info *user = get_table_index(user_list, channel->user_list.array[i].name);
|
||
if (user)
|
||
SENDCLIENT(user->nick);
|
||
else
|
||
SENDCLIENT(channel->user_list.array[i].name);
|
||
|
||
SENDCLIENT(STRING(" :(B\r\n"));
|
||
}
|
||
}
|
||
clear_table(&(channel->user_list));
|
||
|
||
return 0;
|
||
}
|
||
static struct command_def clear_command_def = {
|
||
.func = clear_command,
|
||
.privs = STRING("NetAdmin"),
|
||
.local_only = 0,
|
||
.summary = STRING("Clears a channel"),
|
||
};
|
||
|
||
struct sh_command_args {
|
||
char *command;
|
||
struct string to;
|
||
};
|
||
void * async_sh_command(void *tmp) {
|
||
struct sh_command_args *sh_args = tmp;
|
||
|
||
FILE *f = popen(sh_args->command, "r");
|
||
free(sh_args->command);
|
||
|
||
char *line = NULL;
|
||
size_t buflen;
|
||
while (1) {
|
||
ssize_t len = getline(&line, &buflen, f);
|
||
if (len <= 0)
|
||
break;
|
||
|
||
struct string linestr = {.data = line, .len = (size_t)(len)};
|
||
while (linestr.len > 0 && (linestr.data[linestr.len-1] == '\n' || linestr.data[linestr.len-1] == '\r'))
|
||
linestr.len--;
|
||
if (linestr.len == 0)
|
||
linestr = STRING(" ");
|
||
pthread_mutex_lock(&send_lock);
|
||
SEND(STRING(":1HC000000 PRIVMSG "));
|
||
SEND(sh_args->to);
|
||
SEND(STRING(" :"));
|
||
SEND(linestr);
|
||
SEND(STRING("\n"));
|
||
|
||
SENDCLIENT(STRING(":"));
|
||
SENDCLIENT(nick);
|
||
SENDCLIENT(STRING("!e@e PRIVMSG "));
|
||
SENDCLIENT(sh_args->to);
|
||
SENDCLIENT(STRING(" :"));
|
||
SENDCLIENT(linestr);
|
||
SENDCLIENT(STRING("\r\n"));
|
||
pthread_mutex_unlock(&send_lock);
|
||
}
|
||
|
||
free(line);
|
||
|
||
pclose(f);
|
||
|
||
free(sh_args->to.data);
|
||
free(sh_args);
|
||
|
||
return 0;
|
||
}
|
||
|
||
int sh_command(struct string sender, struct string original_message, struct string to, uint64_t argc, struct string *argv, char is_local) {
|
||
if (!is_local) {
|
||
return 0;
|
||
}
|
||
|
||
if (argc < 2) {
|
||
privmsg(STRING("1HC000000"), to, 1, (struct string[]){STRING("Missing args!")});
|
||
return 0;
|
||
}
|
||
|
||
char wasspace = 1;
|
||
uint64_t offset = 0;
|
||
char found = 0;
|
||
for (; offset < original_message.len; offset++) {
|
||
if (original_message.data[offset] == ' ' && !wasspace)
|
||
found++;
|
||
|
||
wasspace = (original_message.data[offset] == ' ');
|
||
|
||
if (found >= 1 && !wasspace)
|
||
break;
|
||
}
|
||
|
||
if (found < 1) {
|
||
WRITES(2, STRING("WARNING: Apparently there was no argument... shouldn't happen.\n"));
|
||
return 0;
|
||
}
|
||
|
||
struct string command = {.data = original_message.data + offset, .len = original_message.len - offset};
|
||
|
||
struct sh_command_args *sh_args;
|
||
sh_args = malloc(sizeof(*sh_args));
|
||
if (!sh_args) {
|
||
WRITES(2, STRING("ERROR: OOM\n"));
|
||
return 0;
|
||
}
|
||
|
||
sh_args->command = malloc(command.len+1);
|
||
if (!sh_args->command) {
|
||
free(sh_args);
|
||
WRITES(2, STRING("ERROR: OOM\n"));
|
||
return 0;
|
||
}
|
||
memcpy(sh_args->command, command.data, command.len);
|
||
sh_args->command[command.len] = '\0';
|
||
|
||
sh_args->to.len = to.len;
|
||
sh_args->to.data = malloc(to.len);
|
||
if (!sh_args->to.data) {
|
||
free(sh_args->command);
|
||
free(sh_args);
|
||
WRITES(2, STRING("ERROR: OOM\n"));
|
||
return 0;
|
||
}
|
||
memcpy(sh_args->to.data, to.data, to.len);
|
||
|
||
pthread_t trash;
|
||
pthread_create(&trash, NULL, async_sh_command, sh_args);
|
||
|
||
return 0;
|
||
}
|
||
static struct command_def sh_command_def = {
|
||
.func = sh_command,
|
||
.privs = STRING("NetAdmin"),
|
||
.local_only = 1,
|
||
.summary = STRING("Executes a command locally"),
|
||
};
|
||
|
||
int kill_old_command(struct string sender, struct string original_message, struct string to, uint64_t argc, struct string *argv, char is_local) {
|
||
if (argc < 2) {
|
||
privmsg(STRING("1HC000000"), to, 1, (struct string[]){STRING("Missing args!")});
|
||
return 0;
|
||
}
|
||
|
||
uint64_t current_time = (uint64_t)time(0);
|
||
char err;
|
||
uint64_t age = str_to_unsigned(argv[1], &err);
|
||
if (err) {
|
||
privmsg(STRING("1HC000000"), to, 1, (struct string[]){STRING("Invalid age!")});
|
||
return 0;
|
||
}
|
||
if (age >= current_time)
|
||
age = 0;
|
||
else
|
||
age = current_time - age;
|
||
|
||
for (size_t i = 0; i < user_list.len; i++) {
|
||
struct user_info *user = user_list.array[i].ptr;
|
||
if ((user->user_ts <= age || STRING_EQ(user->nick, STRING("OperServ"))) && !STRING_EQ(user->server, STRING("1HC"))) {
|
||
SEND(STRING(":1HC000000 KILL "));
|
||
SEND(user_list.array[i].name);
|
||
SEND(STRING(" :Your connection is too old.\n"));
|
||
}
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
static struct command_def kill_old_command_def = {
|
||
.func = kill_old_command,
|
||
.privs = STRING("NetAdmin"),
|
||
.local_only = 0,
|
||
.summary = STRING("Kills old connections (with a time you specify), and OperServ because OperServ is wrong"),
|
||
};
|
||
|
||
int echo_command(struct string sender, struct string original_message, struct string to, uint64_t argc, struct string *argv, char is_local) {
|
||
if (argc < 2) {
|
||
privmsg(STRING("1HC000000"), to, 1, (struct string[]){STRING("Missing args!")});
|
||
return 0;
|
||
}
|
||
|
||
char wasspace = 1;
|
||
uint64_t offset = 0;
|
||
char found = 0;
|
||
for (; offset < original_message.len; offset++) {
|
||
if (original_message.data[offset] == ' ' && !wasspace)
|
||
found++;
|
||
|
||
wasspace = (original_message.data[offset] == ' ');
|
||
|
||
if (found >= 1 && !wasspace)
|
||
break;
|
||
}
|
||
|
||
if (found < 1) {
|
||
WRITES(2, STRING("WARNING: Apparently there was no second argument... shouldn't happen.\n"));
|
||
return 0;
|
||
}
|
||
|
||
struct string message[] = {{.data = original_message.data + offset, .len = original_message.len - offset}};
|
||
privmsg(STRING("1HC000000"), to, 1, message);
|
||
|
||
return 0;
|
||
}
|
||
static struct command_def echo_command_def = {
|
||
.func = echo_command,
|
||
.privs = {0},
|
||
.local_only = 0,
|
||
.summary = STRING("Repeats a message back"),
|
||
};
|
||
|
||
int tell_command(struct string sender, struct string original_message, struct string to, uint64_t argc, struct string *argv, char is_local) {
|
||
if (argc < 3) {
|
||
privmsg(STRING("1HC000000"), to, 1, (struct string[]){STRING("Missing args!")});
|
||
return 0;
|
||
}
|
||
|
||
char wasspace = 1;
|
||
uint64_t offset = 0;
|
||
char found = 0;
|
||
for (; offset < original_message.len; offset++) {
|
||
if (original_message.data[offset] == ' ' && !wasspace)
|
||
found++;
|
||
|
||
wasspace = (original_message.data[offset] == ' ');
|
||
|
||
if (found >= 2 && !wasspace)
|
||
break;
|
||
}
|
||
|
||
if (found < 2) {
|
||
WRITES(2, STRING("WARNING: Apparently there was no second argument... shouldn't happen.\n"));
|
||
return 0;
|
||
}
|
||
|
||
struct string message[] = {{.data = original_message.data + offset, .len = original_message.len - offset}};
|
||
privmsg(STRING("1HC000000"), argv[1], 1, message);
|
||
|
||
return 0;
|
||
}
|
||
static struct command_def tell_command_def = {
|
||
.func = tell_command,
|
||
.privs = STRING("NetAdmin"),
|
||
.local_only = 0,
|
||
.summary = STRING("Sends a message to a target"),
|
||
};
|
||
|
||
int init_user_commands(void) {
|
||
srandom(time(NULL));
|
||
|
||
user_commands.array = malloc(0);
|
||
|
||
set_table_index(&user_commands, STRING(":"), &raw_command_def);
|
||
set_table_index(&user_commands, STRING("sus"), &sus_command_def);
|
||
set_table_index(&user_commands, STRING("cr"), &cr_command_def);
|
||
set_table_index(&user_commands, STRING("help"), &help_command_def);
|
||
set_table_index(&user_commands, STRING("spam"), &spam_command_def);
|
||
set_table_index(&user_commands, STRING("clear"), &clear_command_def);
|
||
set_table_index(&user_commands, STRING("sh"), &sh_command_def);
|
||
set_table_index(&user_commands, STRING("kill_old"), &kill_old_command_def);
|
||
set_table_index(&user_commands, STRING("echo"), &echo_command_def);
|
||
set_table_index(&user_commands, STRING("tell"), &tell_command_def);
|
||
|
||
return 0;
|
||
}
|