#include "debashify.hpp" #include #include "processing.hpp" #include "recursive.hpp" #include "util.hpp" #include "parse.hpp" #include "struc_helper.hpp" #include "g_shellcode.h" /* [[ ]] debashifying: [[ EXPRESSION && EXPRESSION ]] separated into two parts EXPRESSION : gen_bashtest_cmd && , || : debashify_bashtest */ // [[ $a = b ]] : quote vars // [[ a == b ]] : replace == with = // [[ a = b* ]] : case a in b*) true;; *) false;; esac // [[ a =~ b ]] : expr a : "b" >/dev/null block* gen_bashtest_cmd(std::vector args) { block* ret = nullptr; std::string arg1replace; if(args.size() == 3) { if(args[1]->string() == "==") arg1replace="="; else if(args[1]->string() == "<") arg1replace="-lt"; else if(args[1]->string() == "<=") arg1replace="-le"; else if(args[1]->string() == ">") arg1replace="-gt"; else if(args[1]->string() == ">=") arg1replace="-ge"; } if(arg1replace != "") { delete args[1]; args[1] = new arg(arg1replace); } if(args.size() == 3 && args[1]->string() == "=" && (arg_has_char('*', args[2]) || arg_has_char('?', args[2])) ) { // glob matcher: do a case delete args[1]; args[1]=nullptr; case_block* tc = new case_block(args[0]); tc->cases.push_back( std::make_pair(std::vector({args[2]}), make_list("true")) ); tc->cases.push_back( std::make_pair(std::vector({new arg("*")}), make_list("false")) ); ret = tc; } else if(args.size() == 3 && args[1]->string() == "=~") { // regex matcher: use expr delete args[1]; args[1]=nullptr; add_quotes(args[2]); ret = make_cmd( std::vector({ new arg("expr"), args[0], new arg(":"), args[2] }) ); ret->redirs.push_back(new redirect(">", new arg("/dev/null") )); } else { // regular [ ] cmd* t = make_cmd(args); t->args->insert(0, new arg("[")); t->add(new arg("]")); ret = t; } // arg oblivious replacements: // quote variables for(auto it: args) { if(it!=nullptr) force_quotes(it); } return ret; } // [[ a && b ]] : [ a ] && [ b ] bool debashify_bashtest(pipeline* pl) { if(pl->cmds.size()<=0) return false; if(pl->cmds[0]->type != _obj::block_cmd) return false; cmd* in = dynamic_cast(pl->cmds[0]); if(in->arg_string(0) == "[[") { brace* br = new brace(new list); condlist* cl = new condlist; br->lst->add(cl); arg *a=nullptr; uint32_t j=1; bool or_op=false; for(uint32_t i=1 ; iargs->size() ; i++) { a = in->args->args[i]; if(i >= in->args->size()-1 || a->string() == "&&" || a->string() == "||") { block* tbl = gen_bashtest_cmd(std::vector(in->args->args.begin()+j, in->args->args.begin()+i)); cl->add(new pipeline(tbl), or_op); or_op = a->string() == "||"; j=i+1; } } delete in->args->args[0]; delete in->args->args[in->args->args.size()-1]; in->args->args.resize(0); delete in; pl->cmds[0] = br; return true; } return false; } void warn(std::string const& in) { std::cerr << "WARN: " << in << std::endl; } std::string get_declare_opt(cmd* in) { if(in->cmd_var_assigns[0].second!=nullptr) { return in->cmd_var_assigns[0].second->string(); } return ""; } ztd::option_set gen_echo_opts() { ztd::option_set ret( std::vector({ ztd::option('e'), ztd::option('E'), ztd::option('n') }) ); return ret; } bool debashify_echo(pipeline* pl) { if(pl->cmds[0]->type != _obj::block_cmd) return false; cmd* in = dynamic_cast(pl->cmds[0]); std::string const& cmdstr=in->arg_string(0); if(cmdstr == "echo") { bool skip=false; ztd::option_set opts=gen_echo_opts(); std::vector args=in->args->strargs(1); std::vector postargs; try { postargs=opts.process(args, {.ignore_numbers=true, .stop_on_argument=true} ); } catch(ztd::option_error& e) { skip=true; } bool enable_interpretation=false; bool newline=true; bool has_escape_sequence=false; bool has_processed_options=false; if(!skip && postargs.size() != args.size()) { has_processed_options=true; // delete the number of args that were processed for(uint32_t i=0; iargs->args[1]; in->args->args.erase(in->args->args.begin()+1); } if(opts['e']) enable_interpretation=true; else if(opts['n']) newline=false; } for(auto it=in->args->args.begin()+1; it!=in->args->args.end(); it++) { if(!(*it)->is_string() || (*it)->string().find('\\') != std::string::npos) { has_escape_sequence=true; break; } } if(newline && !has_escape_sequence) { // newline and no potential escape: don't replace, keep echo return has_processed_options; } else { // replace by printf if(!in->args->can_expand()) { // no potential expansion: static number of args std::string format_str = "'"; for(uint32_t i=1; iargs->args.size(); i++) { if(enable_interpretation) format_str += "%b "; else format_str += "%s "; } format_str.pop_back(); if(newline) format_str += "\\n"; format_str += '\''; in->args->insert(1, new arg(format_str)); delete in->args->args[0]; in->args->args[0] = new arg("printf"); } else { std::string format_str; if(enable_interpretation) format_str = "%b"; else format_str = "%s"; list* lst=nullptr; // more than 1 arg and first arg can't expand: can split into two printf // printf '%s' arg1 // printf ' %s' args... if(in->args->args.size()>2 && !in->args->args[1]->can_expand()) { // extract arg 1 arg* arg1 = in->args->args[1]; in->args->args.erase(in->args->args.begin()+1); delete in->args->args[0]; in->args->args[0] = new arg("printf"); lst = new list; lst->add(new condlist(make_cmd({new arg("printf"), new arg(format_str), arg1 }))); in->args->insert(1, new arg("\\ "+format_str) ); lst->add(new condlist(in)); } else { // can't reliable replace: keep echo if newline if(newline) return has_processed_options; in->args->insert(1, new arg(format_str+"\\ ")); delete in->args->args[0]; in->args->args[0] = new arg("printf"); } if(newline) { if(lst == nullptr) { lst = new list; lst->add(new condlist(in)); } lst->add(make_condlist("echo")); } if(lst != nullptr) { pl->cmds[0] = new brace(lst); } } } return true; } return false; } bool debashify_readonly(list* in) { bool has_found=false; for(uint32_t i=0; icls.size(); i++) { // not a cmd: go to next if(in->cls[i]->pls[0]->cmds[0]->type != _obj::block_cmd) continue; cmd* c1 = dynamic_cast(in->cls[i]->pls[0]->cmds[0]); std::string const& cmdstr=c1->arg_string(0); if(cmdstr == "readonly") { has_found=true; c1->is_cmdvar=false; for(uint32_t i=0; icmd_var_assigns.size(); i++) { if(c1->cmd_var_assigns[i].first == nullptr || c1->cmd_var_assigns[i].second == nullptr) { if(c1->cmd_var_assigns[i].first != nullptr) delete c1->cmd_var_assigns[i].first; if(c1->cmd_var_assigns[i].second != nullptr) delete c1->cmd_var_assigns[i].second; c1->cmd_var_assigns.erase(c1->cmd_var_assigns.begin()+i); i--; } } if(c1->cmd_var_assigns.size() == 0) { delete in->cls[i]; in->cls.erase(in->cls.begin()+i); i--; } else { delete c1->args; c1->args = new arglist; c1->var_assigns=c1->cmd_var_assigns; c1->cmd_var_assigns.resize(0); } } } return has_found; } bool debashify_declare(list* in, debashify_params* params) { bool has_found=false; for(uint32_t i=0; icls.size(); i++) { // not a cmd: go to next if(in->cls[i]->pls[0]->cmds[0]->type != _obj::block_cmd) continue; cmd* c1 = dynamic_cast(in->cls[i]->pls[0]->cmds[0]); std::string const& cmdstr=c1->arg_string(0); if(cmdstr == "declare" || cmdstr == "typeset") { std::string const& op = get_declare_opt(c1); if(op == "-a") { for(auto it: c1->cmd_var_assigns) { if(it.first != nullptr) params->arrays[it.first->varname] = false; } } else if(op == "-A") { for(auto it: c1->cmd_var_assigns) { if(it.first != nullptr) params->arrays[it.first->varname] = true; } } has_found=true; delete in->cls[i]; in->cls.erase(in->cls.begin()+i); i--; } } return has_found; } cmd* make_cmd_varindex(std::string const& strcmd, std::string const& varname, arg* index) { cmd* c = new cmd(new arglist); // cmd c->args->add( new arg(strcmd) ); // cmd "$VAR" c->args->add( make_arg("\"$"+varname+"\"") ); // cmd "$VAR" N c->args->add( index ); return c; } subshell* do_debashify_array_var_get(variable* in, debashify_params* params) { if(in->manip != nullptr) throw std::runtime_error("Cannot debashify manipulations on ${VAR[]}"); std::string varname = in->varname; arg* index = in->index; in->index=nullptr; if(index->string() == "*") { delete index; index = new arg("\\*"); } cmd* c; if(params->arrays[varname]) { c = make_cmd_varindex("_lxsh_map_get", varname, index); params->require_fct("_lxsh_map_get"); } else { c = make_cmd_varindex("_lxsh_array_get", varname, index); params->require_fct("_lxsh_array_get"); } return new subshell(c); } subshell* do_debashify_random(variable* in, debashify_params* params) { if(in->manip != nullptr) throw std::runtime_error("Cannot debashify manipulations on ${RANDOM}"); cmd* c = make_cmd("_lxsh_random"); params->require_fct("_lxsh_random"); return new subshell(c); } // does multiple debashifies: // - array // - RANDOM subshell_arithmetic* do_debashify_arithmetic(arithmetic* in, debashify_params* params) { subshell_arithmetic* ret = nullptr; if(in->type == _obj::arithmetic_variable) { variable_arithmetic* t = dynamic_cast(in); if(t->var != nullptr && t->var->varname == "RANDOM") { ret = new subshell_arithmetic(do_debashify_random(t->var, params)); } else if(t->var != nullptr && t->var->index != nullptr) { ret = new subshell_arithmetic(do_debashify_array_var_get(t->var, params)); } } return ret; } bool debashify_arithmetic_replace(_obj* o, debashify_params* params) { bool ret=false; switch(o->type) { case _obj::subarg_arithmetic: { arithmetic_subarg* t = dynamic_cast(o); arithmetic* r = do_debashify_arithmetic(t->arith, params); if(r!=nullptr) { ret=true; delete t->arith; t->arith = r; } } break; case _obj::arithmetic_operation: { operation_arithmetic* t = dynamic_cast(o); arithmetic* r = do_debashify_arithmetic(t->val1, params); if(r!=nullptr) { ret=true; delete t->val1; t->val1 = r; } r = do_debashify_arithmetic(t->val2, params); if(r!=nullptr) { ret=true; delete t->val2; t->val2 = r; } } break; case _obj::arithmetic_parenthesis: { parenthesis_arithmetic* t = dynamic_cast(o); arithmetic* r = do_debashify_arithmetic(t->val, params); if(r!=nullptr) { ret=true; delete t->val; t->val = r; } } break; default: break; } return ret; } bool debashify_subarg_replace(arg* in, debashify_params* params) { bool has_replaced=false; for(auto it=in->sa.begin() ; it!=in->sa.end() ; it++) { subarg* replacer=nullptr; bool quoted=(*it)->quoted; if((*it)->type == _obj::subarg_variable) { variable_subarg* t = dynamic_cast(*it); if(t->var != nullptr && t->var->varname == "RANDOM") { replacer = new subshell_subarg(do_debashify_random(t->var, params)); } if(t->var != nullptr && t->var->is_manip && t->var->index != nullptr) { replacer = new subshell_subarg(do_debashify_array_var_get(t->var, params)); } } if(replacer != nullptr) { replacer->quoted=quoted; delete *it; *it = replacer; has_replaced=true; } } return has_replaced; } bool debashify_array_set(cmd* in, debashify_params* params) { bool has_replaced=false; for(auto it = in->var_assigns.begin() ; it != in->var_assigns.end() ; it++) { if(it->first!=nullptr && it->second != nullptr && it->second->size()>0 && it->second->first_sa_string().substr(0,2) == "=(") { // array creation: VAR=() // extract arguments from =(ARGS...) std::string gen=it->second->generate(0); std::string varname=it->first->varname; gen=gen.substr(2); gen.pop_back(); // create cmd out of arguments arglist* args = parse_arglist( make_context(gen) ).first; cmd* c = new cmd(args); // cmd first argument is _lxsh_X_create if(params->arrays[varname]) { c->args->insert(0, new arg("_lxsh_map_create") ); params->require_fct("_lxsh_map_create"); } else { c->args->insert(0, new arg("_lxsh_array_create") ); params->require_fct("_lxsh_array_create"); } subshell_subarg* sb = new subshell_subarg(new subshell(c)); // insert new value delete it->second; it->second = new arg("="); it->second->add(sb); has_replaced=true; } else if(it->first != nullptr && it->first->index != nullptr) { // array value set: VAR[]= force_quotes(it->second); force_quotes(it->first->index); string_subarg* tt=dynamic_cast(it->second->sa[0]); std::string varname = it->first->varname; arg* index = it->first->index; arg* value = it->second; it->first->index = nullptr; it->second = nullptr; if(tt->val.substr(0,2) == "+=") { tt->val = tt->val.substr(2); // remove += // create array get of value cmd* c; if(params->arrays[varname]) { c = make_cmd_varindex("_lxsh_map_get", varname, copy(index)); params->require_fct("_lxsh_map_get"); } else { c = make_cmd_varindex("_lxsh_array_get", varname, copy(index)); params->require_fct("_lxsh_array_get"); } subshell_subarg* sb = new subshell_subarg(new subshell(c)); sb->quoted=true; value->insert(0, "\""); value->insert(0, sb); value->insert(0, "\""); } else tt->val = tt->val.substr(1); // remove = cmd* c = new cmd(new arglist); if(params->arrays[varname]) { c->args->add(new arg("_lxsh_map_set") ); params->require_fct("_lxsh_map_set"); } else { c->args->add(new arg("_lxsh_array_set") ); params->require_fct("_lxsh_array_set"); } // _lxsh_array_set "$VAR" c->args->add( make_arg("\"$"+varname+"\"") ); // _lxsh_array_set "$VAR" N c->args->add( index ); // _lxsh_array_set "$VAR" N value c->args->add( value ); // $(_lxsh_array_set "$VAR" N value) subshell_subarg* sb = new subshell_subarg(new subshell(c)); it->second = new arg("="); it->second->add(sb); has_replaced=true; } else if(it->first != nullptr && it->second!=nullptr && it->second->first_sa_string().substr(0,3) == "+=(") { // array add: VAR+=() // can be done by creating a new array with old array + new std::string varname = it->first->varname; // extract arguments from =+(ARGS...) std::string gen=it->second->generate(0); gen=gen.substr(3); gen.pop_back(); // create cmd out of arguments arglist* args = parse_arglist( make_context(gen) ).first; cmd* c = new cmd(args); // cmd first argument is _lxsh_array_create if(params->arrays[varname]) { throw std::runtime_error("Cannot debashify VAR+=() on associative arrays"); } else { c->args->insert(0, new arg("_lxsh_array_create") ); params->require_fct("_lxsh_array_create"); } // second arg is varname c->args->insert(1, make_arg("\"$"+varname+"\"") ); subshell_subarg* sb = new subshell_subarg(new subshell(c)); // insert new value delete it->second; it->second = new arg("="); it->second->add(sb); has_replaced=true; } } return has_replaced; } bool debashify_plusequal(cmd* in, debashify_params* params) { bool has_replaced=false; for(auto it = in->var_assigns.begin() ; it != in->var_assigns.end() ; it++) { if(it->first != nullptr && it->second != nullptr && it->second->first_sa_string().substr(0,2) == "+=") { string_subarg* tt=dynamic_cast(it->second->sa[0]); variable* v = new variable(it->first->varname); v->is_manip=true; tt->val = tt->val.substr(2); // remove += it->second->insert(0, new variable_subarg(v) ); it->second->insert(0, "="); } } return has_replaced; } // replace <<< foo by printf %s\n "foo" | bool debashify_herestring(pipeline* pl) { if(pl->cmds.size()>0) { block* c=pl->cmds[0]; for(uint32_t i=0; iredirs.size() ; i++) { if(c->redirs[i]->op == "<<<") { force_quotes(c->redirs[i]->target); cmd* printcmd = make_cmd("printf %s\\\\n"); printcmd->add(c->redirs[i]->target); pl->cmds.insert(pl->cmds.begin(), printcmd); // cleanup c->redirs[i]->target=nullptr; delete c->redirs[i]; c->redirs.erase(pl->cmds[1]->redirs.begin()+i); return true; } } } return false; } // replace &>, &>> and >&: // add 2>&1 as redirect bool debashify_combined_redirects(block* in) { bool has_replaced=false; for(uint32_t i=0; iredirs.size() ; i++) { if(in->redirs[i]->op == "&>" || in->redirs[i]->op == "&>>" || in->redirs[i]->op == ">&") { // resolve new operator std::string newop = ">"; if( in->redirs[i]->op == "&>>" ) newop = ">>"; // create new redir with target redirect* newredir = new redirect(newop, in->redirs[i]->target); in->redirs[i]->target=nullptr; // replace old redir delete in->redirs[i]; in->redirs[i] = newredir; // insert merge redir i++; in->redirs.insert(in->redirs.begin()+i, new redirect("2>&1")); has_replaced=true; } } return has_replaced; } // replace <() and >() /* REPLACE TO: fifoN=${TMPDIR-/tmp}/lxshfifo_$(_lxsh_random_string 10) mkfifo "$fifoN" ( {PSUB;} [>|<] "$fifoN" ; rm "$fifoN") & CMD "$fifoN" */ bool debashify_procsub(list* lst, debashify_params* params) { bool has_replaced=false; for(uint32_t li=0; licls.size(); li++) { std::vector> affected_args; // iterate all applicable args of the cl for(auto plit: lst->cls[li]->pls) { for(auto cmit: plit->cmds) { if(cmit->type == _obj::block_cmd) { cmd* t = dynamic_cast(cmit); if(t->args != nullptr) { for(auto ait: t->args->args) { if(ait->size() == 1 && ait->sa[0]->type == _obj::subarg_procsub) { procsub_subarg* st = dynamic_cast(ait->sa[0]); affected_args.push_back( std::make_pair(ait, st->is_output) ); } } } } } } // perform the replace if(affected_args.size()>0) { params->require_fct("_lxsh_random_tmpfile"); has_replaced=true; list* lst_insert = new list; std::string mkfifocmd="mkfifo"; for(uint32_t i=0; iadd( make_condlist( strf("_lxshfifo%u=$(_lxsh_random_tmpfile lxshfifo)", i) ) ); mkfifocmd += strf(" \"$_lxshfifo%u\"", i); } // mkfifo "$fifoN" lst_insert->add( make_condlist(mkfifocmd) ); for(uint32_t i=0; i "$fifoN" ; rm "$fifoN") & subshell* psub = new subshell(new list); procsub_subarg* st = dynamic_cast(affected_args[i].first->sa[0]); // {PSUB;} brace* cbr = new brace(st->sbsh->lst); // deindex list for delete st->sbsh->lst=nullptr; // {PSUB;} > "$_lxshfifoN" cbr->redirs.push_back( new redirect( affected_args[i].second ? "<" : ">", make_arg(strf("\"$_lxshfifo%u\"", i)) ) ); // ( {PSUB;} > "$_lxshfifoN" ) psub->lst->add( new condlist(cbr) ); // ( {PSUB;} > "$_lxshfifoN" ; rm "$_lxshfifoN" ) psub->lst->add( make_condlist(strf("rm \"$_lxshfifo%u\"", i)) ); // ( {PSUB;} > "$_lxshfifoN" ; rm "$_lxshfifoN" ) & condlist* pscl = new condlist(psub); pscl->parallel=true; lst_insert->add( pscl ); // replace the arg delete affected_args[i].first->sa[0]; affected_args[i].first->sa[0] = new string_subarg("\""); affected_args[i].first->add( new variable_subarg( new variable(strf("_lxshfifo%u", i)) ) ); affected_args[i].first->add( new string_subarg("\"") ); } lst->insert(li, *lst_insert ); li+= lst_insert->size(); //cleanup lst_insert->cls.resize(0); delete lst_insert; } } return has_replaced; } bool debashify_var(variable* in, debashify_params* params) { if(in->is_manip && in->precedence && in->manip->string() == "!") throw std::runtime_error("Cannot debashify ${!VAR}"); return false; } bool r_debashify(_obj* o, debashify_params* params) { // global debashifies debashify_arithmetic_replace(o, params); switch(o->type) { case _obj::_variable: { variable* t = dynamic_cast(o); debashify_var(t, params); } break; case _obj::_arg: { arg* t = dynamic_cast(o); debashify_subarg_replace(t, params); } break; case _obj::_list: { list* t = dynamic_cast(o); debashify_declare(t, params); debashify_readonly(t); debashify_procsub(t, params); } break; case _obj::_pipeline: { pipeline* t = dynamic_cast(o); debashify_echo(t); debashify_herestring(t); debashify_bashtest(t); } break; case _obj::block_cmd: { cmd* t = dynamic_cast(o); debashify_combined_redirects(t); debashify_array_set(t, params); debashify_plusequal(t, params); } break; case _obj::block_subshell: { subshell* t = dynamic_cast(o); debashify_combined_redirects(t); } break; case _obj::block_brace: { brace* t = dynamic_cast(o); debashify_combined_redirects(t); } break; case _obj::block_main: { shmain* t = dynamic_cast(o); debashify_combined_redirects(t); } break; case _obj::block_function: { function* t = dynamic_cast(o); debashify_combined_redirects(t); } break; case _obj::block_case: { case_block* t = dynamic_cast(o); debashify_combined_redirects(t); } break; case _obj::block_if: { if_block* t = dynamic_cast(o); debashify_combined_redirects(t); } break; case _obj::block_while: { while_block* t = dynamic_cast(o); debashify_combined_redirects(t); } break; case _obj::block_for: { for_block* t = dynamic_cast(o); debashify_combined_redirects(t); } break; default: break; } return true; } // return value: dependencies std::set debashify(_obj* o, debashify_params* params) { recurse(r_debashify, o, params); return params->required_fcts; } // return value: dependencies std::set debashify(shmain* sh) { debashify_params params; sh->shebang = "#!/bin/sh"; recurse(r_debashify, sh, ¶ms); return params.required_fcts; }