cpp-eval-command-discord

# Making an eval Command in C++

## What is an eval command anyway?

Many times people will ask: "How do I make a command like 'eval' in C++?". For the uninitiated, an `eval` command is a command often found in interpreted languages such as JavaScript and Python, which allows the developer to pass in raw interpreter statements which are then executed within the context of the running program, without any sandboxing. Eval commands are plain evil, if not properly coded in.

Needless to say, this is very dangerous. If you are asking how to do this, and want to put this into your bot, we trust that you have a very good reason to do so and have considered alternatives before resorting to this. The code below is for educational purposes only and makes several assumptions:

1. This code will only operate on UNIX-like systems such as Linux (not Darwin).

2. It assumes you use GCC, and have `g++` installed on your server and in your `${PATH}`.

3. The program will attempt to write to the current directory.

4. No security checks will be done against the code, except for to check that it is being run by the bot's developer by snowflake id. It is entirely possible to send an `!eval exit(0);` and make the bot quit, for example, or delete files from the host operating system, if misused or misconfigured.

5. You're willing to wait a few seconds for compilation before your evaluated code runs. There isn't a way around this, as C++ is a compiled language.

To create this program you must create two files, `eval.h` and `eval.cpp`. The header file lists forward declarations of functions that you will be able to use directly within your `eval` code. As well as this the entirety of D++ will be available to the eval command via the local variable `bot`, and the entire `on_message_create` event variable via a local variable called `event`.

The evaluated code will run within its own thread, so can execute for as long as it needs (but use common sense, don't go spawning a tight `while` loop that runs forever, you'll lock a thread at 100% CPU that won't ever end!).

## Implementation details

This code operates by outputting your provided code to be evaluated into a simple boilerplate program which can be compiled to a shared object library (`.so`) file. This `.so` file is then compiled with `g++`, using the `-shared` and `-fPIC` flags. If the program can be successfully compiled, it is then loaded using `dlopen()`, and the symbol `so_exec()` searched for within it, and called. This `so_exec()` function will contain the body of the code given to the eval command. Once this has been called and it has returned, the `dlclose()` function is called to unload the library, and finally any temporary files (such as the `.so` file and its corresponding `.cpp` file) are cleaned up. Docker is definitely recommended if you code on Windows/macOS, because Docker desktop still uses a Linux VM, so your code can easily use the `.so` file and your code runs the same on your VPS (if it also uses Linux distro).

## Source code

If you manage to get your system, network, or anything else harmed by use or misuse of this code, we are not responsible. Don't say we didn't warn you! Find another way to solve your problem!

### eval.h

Remember that `eval.h` contains forward-declarations of any functions you want to expose to the eval command. It is included both by the bot itself, and by any shared object files compiled for evaluation.

    #pragmaonce

    /*ThisisthesnowflakeIDofthebot'sdeveloper.
    *Theevalcommandwillberestrictedtothisuser.
    */
    #defineMY_DEVELOPER189759562910400512ULL

    /*Anyfunctionsyouwanttobeusablefromwithinaneval,
    *thatarenotpartofD++itselforthemessageevent,you
    *canputhereasforwarddeclarations.Thetest_function()
    *servesasanexample.
    */

    inttest_function();

### eval.cpp

This is the main body of the example program.

    #include<dpp/dpp.h>
    #include<fmt/format.h>
    #include<fstream>
    #include<iostream>
    /*Wehavetodefinethistomakecertainfunctionsvisible*/
    #ifndef_GNU_SOURCE
    #define_GNU_SOURCE
    #endif
    #include<link.h>
    #include<dlfcn.h>
    #include"eval.h"

    /*Thisisanexamplefunctionyoucanexposetoyourevalcommand*/
    inttest_function(){
    return42;
    }

    /*Important:ThiscodeisforUNIX-likesystemsonly,e.g.
    *Linux,BSD,OSX.ItwillNOTworkonWindows!
    *NoteforOSXyou'llprobablyhavetochangeallreferences
    *from.soto.dylib.
    */
    intmain(){
    dpp::clusterbot("token",dpp::i_default_intents|dpp::i_message_content);

    bot.on_log(dpp::utility::cout_logger());

    /*Thiswon'tworkinaslashcommandverywellyet,asthereisnotyet
    *amulti-lineslashcommandinputtype.
    */
    bot.on_message_create([&bot](constauto&event){
    if(dpp::utility::utf8substr(event.msg.content,0,5)=="!eval"){

    if(event.msg.author.id!=dpp::snowflake(MY_DEVELOPER)){
    bot.message_create(dpp::message(event.msg.channel_id,"Onthedayidothisforyou,Satanwillbeiceskatingtowork."));
    return;
    }

    /*Westartbycreatingastringthatcontainsacppprogramforasimplelibrary.
    *Thelibrarywillcontainoneexportedfunctioncalledso_exec()thatiscalled
    *containingtherawC++codetoeval.
    */
    std::stringcode="#include<iostream>\n\
    #include<string>\n\
    #include<map>\n\
    #include<unordered_map>\n\
    #include<stdint.h>\n\
    #include<dpp/dpp.h>\n\
    #include<dpp/nlohmann/json.hpp>\n\
    #include<fmt/format.h>\n\
    #include\"eval.h\"\n\
    extern\"C\"voidso_exec(dpp::cluster&bot,dpp::message_create_tevent){\n\
    "+dpp::utility::utf8substr(
    event.msg.content,
    6,
    dpp::utility::utf8len(event.msg.content)
    )+";\n\
    return;\n\
    }";

    /*NextweoutputthisstringfullofC++toacppfileondisk.
    *Thiscodeassumesthecurrentdirectoryiswriteable.Thefilewillhavea
    *uniquenamemadefromtheuser'sidandthemessageid.
    */
    std::stringsource_filename=std::to_string(event.msg.author.id)+"_"+std::to_string(event.msg.id)+".cpp";
    std::fstreamcode_file(source_filename,std::fstream::binary|std::fstream::out);
    if(!code_file.is_open()){
    bot.message_create(dpp::message(event.msg.channel_id,"Unabletocreatesourcefilefor`eval`"));
    return;
    }
    code_file<<code;
    code_file.close();

    /*Nowtoactuallycompilethefile.Weusedpp::utility::execto
    *invokeacompiler.Thisassumesyouareusingg++,anditisinyourpath.
    */
    doublecompile_start=dpp::utility::time_f();
    dpp::utility::exec("g++",{
    "-std=c++20",
    "-shared",/*Buildtheoutputasa.sofile*/
    "-fPIC",
    std::string("-o")+std::to_string(event.msg.author.id)+"_"+std::to_string(event.msg.id)+".so",
    std::to_string(event.msg.author.id)+"_"+std::to_string(event.msg.id)+".cpp",
    "-ldpp",
    "-ldl"
    },[event,&bot,source_filename,compile_start](conststd::string&output){

    /*Afterg++isranweendupinsidethislambdawiththeoutputasastring*/
    doublecompile_time=dpp::utility::time_f()-compile_start;

    /*Deleteourcppfile,wedon'tneeditanymore*/
    std::stringdel_file=std::string(getenv("PWD"))+std::to_string(event.msg.author.id)+"_"+std::to_string(event.msg.id)+".cpp";
    unlink(del_file.c_str());

    /*Onsuccessfulcompilationg++outputsnothing,soanyoutputhereiserroroutput*/
    if(output.length()){
    bot.message_create(dpp::message(event.msg.channel_id,"Compileerror:```\n"+output+"\n```"));
    }else{

    /*Nowforthemeatofthefunction.Toactuallyload
    *oursharedobjectweusedlopen()toloaditintothe
    *memoryspaceofourbot.Ifdlopen()returnsanullptr,
    *thesharedobjectcouldnotbeloaded.Theuserprobably
    *didsomethingoddwiththesymbolsinsidetheireval.
    */
    std::stringdl=std::string(getenv("PWD"))+std::to_string(event.msg.author.id)+"_"+std::to_string(event.msg.id)+".so";
    autoshared_object_handle=dlopen(dl.c_str(),RTLD_NOW);
    if(!shared_object_handle){
    constchar*dlsym_error=dlerror();
    bot.message_create(dpp::message(event.msg.channel_id,"Sharedobjectloaderror:```\n"+
    std::string(dlsym_error?dlsym_error:"Unknownerror")+"\n```"));
    return;
    }

    /*Thistyperepresentsthe"voidso_exec()"functioninside
    *thesharedobjectlibraryfile.
    */
    usingfunction_pointer=void(*)(dpp::cluster&,dpp::message_create_t);

    /*Attempttofindthefunctioncalledso_exec()insidethe
    *librarywejustloaded.Ifwecan'tfindit,thentheuser
    *didsomethingreallystrangeintheireval.Alsonoteit's
    *importantwecalldlerror()heretoresetitbeforetrying
    *touseitasecondtime.It'sweird-assCcodeandisjust
    *likethat.
    */
    dlerror();
    function_pointerexec_run=(function_pointer)dlsym(shared_object_handle,"so_exec");
    constchar*dlsym_error=dlerror();
    if(dlsym_error){
    bot.message_create(dpp::message(event.msg.channel_id,"Sharedobjectloaderror:```\n"+std::string(dlsym_error)+"\n```"));
    dlclose(shared_object_handle);
    return;
    }

    /*Nowwehaveafunctionpointertoouractualexeccodein
    *'exec_run',soletscallit,andpassitareferenceto
    *thecluster,andalsoacopyofthemessage_create_t.
    */
    doublerun_start=dpp::utility::time_f();
    exec_run(bot,event);
    doublerun_time=dpp::utility::time_f()-run_start;

    /*Whenwe'redonewitha.sofilewemustalwaysdlclose()it*/
    dlclose(shared_object_handle);

    /*Wearenowdonewiththecompiledcodetoo*/
    unlink(dl.c_str());

    /*Outputsomestatistics*/
    bot.message_create(dpp::message(event.msg.channel_id,
    "Executioncompleted.Compiletime:"+std::to_string(compile_time)+
    "s,executiontime"+std::to_string(run_time)+"s"));
    }
    });
    }
    });

    bot.start(dpp::st_wait);

    return0;
    }

## Compilation

To compile this program you must link against `libdl`. It is also critically important to include the `-rdynamic` flag. For example:

    g++-std=c++20-rdynamic-oevaleval.cpp-ldpp-ldl

## Example usage