Implement bot

This commit is contained in:
kageru 2020-01-02 23:10:28 +01:00
parent c5b440311c
commit ac4f180dd8
Signed by: kageru
GPG Key ID: 8282A2BEA4ADA3D2
8 changed files with 70 additions and 56 deletions

2
.gitignore vendored
View File

@ -1,3 +1,3 @@
/target /target
**/*.rs.bk **/*.rs.bk
secret prod_config.toml

View File

@ -5,8 +5,8 @@ authors = ["kageru <kageru@encode.moe>"]
edition = "2018" edition = "2018"
[dependencies] [dependencies]
serenity = "0.7" serenity = "0.7.4"
serde = "1.0.104" serde = "1.0.104"
toml = "0.5.5" toml = "0.5.5"
lazy_static = "1.4.0" lazy_static = "1.4.0"
itertools = "0.8.2" cmd_lib = "0.7.8"

View File

@ -1,7 +1,7 @@
# RCEAADB # RCEAADB
## I’m sorry? ## I’m sorry?
RCEAABD. Remote code execution as a discord bot. RCEAADB. Remote code execution as a discord bot.
Almost as good as ncmpcpp. Almost as good as ncmpcpp.
## But why? Do you hate security that much? ## But why? Do you hate security that much?

View File

@ -1,4 +1,22 @@
secret = "your login secret"
[[command]] [[command]]
trigger = ">restart_example" # The prefix (a constant in the source code)
command = "/usr/bin/restart_something" # does not need to be specified here.
users = [12345, 54321] # It is added automatically.
trigger = "create_file"
# Spaces are not escaped here
command = "touch /tmp/test-rce"
users = [137780880344088576]
[[command]]
trigger = "append_to_file"
# Pipes and redirection are allowed
command = "echo asd >> /tmp/test-rce"
users = [137780880344088576]
[[command]]
trigger = "delete_file"
command = "rm /tmp/test-rce"
# Multiple user IDs can be added here
users = [137780880344088576, 123456789098765]

View File

@ -1,41 +1,36 @@
use itertools::Itertools; use super::config::CONFIG;
use lazy_static; use cmd_lib::{CmdResult, Process};
use serde::Deserialize; use serde::Deserialize;
use serenity::model::channel::Message; use serenity::model::channel::Message;
#[derive(Deserialize, Debug)] #[derive(Deserialize, Debug)]
pub struct Command<'a> { pub struct Command {
trigger: &'a str, trigger: String,
command: &'a str, command: String,
users: Vec<u64>, users: Vec<u64>,
} }
#[derive(Deserialize, Debug)]
struct Config {
#[serde(rename = "command")]
commands: Vec<Command<'static>>,
}
lazy_static! {
static ref RAW_CONFIG: String = super::file::read_file("config.toml").join("\n");
static ref COMMANDS: Vec<Command<'static>> = {
toml::from_str::<Config>(&RAW_CONFIG).expect("Error in config").commands
};
}
pub fn print_commands() { pub fn print_commands() {
COMMANDS.iter().for_each(|c| println!("{:?}", c)); CONFIG.commands.iter().for_each(|c| println!("{:?}", c));
} }
pub fn find_matching(message: &str) -> Option<&Command> { pub fn find_matching(message: &str) -> Option<&Command> {
COMMANDS.iter().find(|&c| message.starts_with(&c.trigger)) CONFIG.commands.iter().find(|&c| message[1..] == c.trigger)
} }
impl<'a> Command<'a> { impl Command {
pub fn execute(&self, msg: &Message) -> Result<(), &'static str> { /// Check permissions for a command and execute it.
/// Returns an error for insufficient permissions or non-zero return codes.
pub fn execute(&self, msg: &Message) -> Result<(), String> {
println!(
"User {} tried to execute command {}",
&msg.author, &msg.content
);
if !self.users.contains(&msg.author.id.0) { if !self.users.contains(&msg.author.id.0) {
return Err("You don’t have the permissions to execute this command."); return Err("You don’t have the permissions to execute this command.".to_owned());
} }
unimplemented!() Process::new(self.command.clone())
.wait::<CmdResult>()
.map_err(|e| format!("{:?}", e))
} }
} }

18
src/config.rs Normal file
View File

@ -0,0 +1,18 @@
use super::commands::*;
use serde::Deserialize;
use std::fs;
pub fn read_config() -> String {
fs::read_to_string("config.toml").expect("Could not read config file. Make sure a file named config.toml exists in the current working directory.")
}
#[derive(Deserialize, Debug)]
pub struct Config {
#[serde(rename = "command")]
pub commands: Vec<Command>,
pub secret: String,
}
lazy_static! {
pub static ref CONFIG: Config = toml::from_str(&read_config()).unwrap();
}

View File

@ -1,9 +0,0 @@
use std::fs::File;
use std::io::{prelude::*, BufReader};
pub fn read_file(path: &'static str) -> impl Iterator<Item = String> {
let reader = BufReader::new(File::open(path).expect("File not found"));
reader
.lines()
.map(|l| l.expect(&format!("Could not read from file")))
}

View File

@ -5,24 +5,17 @@ use serenity::model::channel::Message;
use serenity::model::id::ChannelId; use serenity::model::id::ChannelId;
use serenity::prelude::*; use serenity::prelude::*;
mod commands; mod commands;
mod file; mod config;
pub fn main() { pub fn main() {
/*
Client::new(
file::read_file("secret")
.next()
.expect("The file containing the login secret is present but empty"),
Handler,
)
.expect("Error creating client")
.start()
.expect("Could not connect to discord");
*/
print_commands(); print_commands();
Client::new(&config::CONFIG.secret, Handler)
.expect("Error creating client")
.start()
.expect("Could not connect to discord");
} }
const PREFIX: char = '$'; const PREFIX: char = '>';
pub struct Handler; pub struct Handler;
impl EventHandler for Handler { impl EventHandler for Handler {
@ -33,10 +26,9 @@ impl EventHandler for Handler {
if let Some(command) = find_matching(&msg.content) { if let Some(command) = find_matching(&msg.content) {
let response = match command.execute(&msg) { let response = match command.execute(&msg) {
Err(e) => e, Err(e) => e,
// TODO: maybe check the return code here? Ok(()) => "Done".to_owned(),
Ok(()) => "Done",
}; };
send(msg.channel_id, response, &ctx); send(msg.channel_id, &response, &ctx);
} }
} }
} }