1 //--------------------------------------------------------------------------
2 // Copyright (C) 2014-2021 Cisco and/or its affiliates. All rights reserved.
3 //
4 // This program is free software; you can redistribute it and/or modify it
5 // under the terms of the GNU General Public License Version 2 as published
6 // by the Free Software Foundation. You may not use, modify or distribute
7 // this program under any other version of the GNU General Public License.
8 //
9 // This program is distributed in the hope that it will be useful, but
10 // WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 // General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License along
15 // with this program; if not, write to the Free Software Foundation, Inc.,
16 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 //--------------------------------------------------------------------------
18 // shell.cc author Russ Combs <rucombs@cisco.com>
19
20 #ifdef HAVE_CONFIG_H
21 #include "config.h"
22 #endif
23
24 #include "shell.h"
25
26 #include <unistd.h>
27
28 #include <cassert>
29 #include <fstream>
30 #include <stdexcept>
31
32 #include "dump_config/config_output.h"
33 #include "log/messages.h"
34 #include "lua/lua.h"
35 #include "main/policy.h"
36 #include "main/snort_config.h"
37 #include "managers/module_manager.h"
38 #include "parser/parse_conf.h"
39 #include "parser/parser.h"
40 #include "utils/stats.h"
41
42 #include "lua_bootstrap.h"
43 #include "lua_finalize.h"
44
45 using namespace snort;
46 using namespace std;
47
48 //-------------------------------------------------------------------------
49 // helper functions
50 //-------------------------------------------------------------------------
51
52 static const char* versions[] = {
53 "SNORT_VERSION",
54 "SNORT_MAJOR_VERSION",
55 "SNORT_MINOR_VERSION",
56 "SNORT_PATCH_VERSION",
57 "SNORT_SUBLEVEL_VERSION",
58 nullptr
59 };
60
install_version_strings(lua_State * L)61 static void install_version_strings(lua_State* L)
62 {
63 assert(versions[0]);
64
65 #ifdef BUILD
66 lua_pushstring(L, VERSION "-" BUILD);
67 #else
68 lua_pushstring(L, VERSION);
69 #endif
70 lua_setglobal(L, versions[0]);
71
72 std::istringstream vs(VERSION);
73 for ( int i = 1 ; versions[i] ; i++ )
74 {
75 std::string tmp;
76 int num = 0;
77 std::getline(vs, tmp, '.');
78
79 if ( !tmp.empty() )
80 num = stoi(tmp);
81
82 lua_pushinteger(L, num);
83 lua_setglobal(L, versions[i]);
84 }
85 }
86
87 string Shell::fatal;
88 std::stack<Shell*> Shell::current_shells;
89 ConfigOutput* Shell::s_config_output = nullptr;
90 BaseConfigNode* Shell::s_current_node = nullptr;
91 bool Shell::s_close_table = true;
92 string Shell::lua_sandbox;
93
94 // FIXIT-M Shell::panic() works on Linux but on OSX we can't throw from lua
95 // to C++. unprotected lua calls could be wrapped in a pcall to ensure lua
96 // panics don't kill the process. or we can not use lua for the shell. :(
panic(lua_State * L)97 [[noreturn]] int Shell::panic(lua_State* L)
98 {
99 fatal = lua_tostring(L, -1);
100 throw runtime_error(fatal);
101 }
102
get_current_shell()103 Shell* Shell::get_current_shell()
104 {
105 if ( !current_shells.empty() )
106 return current_shells.top();
107
108 return nullptr;
109 }
110
set_config_output(ConfigOutput * output)111 void Shell::set_config_output(ConfigOutput* output)
112 { s_config_output = output; }
113
clear_config_output()114 void Shell::clear_config_output()
115 {
116 s_config_output = nullptr;
117 s_current_node = nullptr;
118 }
119
is_trusted(const std::string & key)120 bool Shell::is_trusted(const std::string& key)
121 {
122 Shell* sh = Shell::get_current_shell();
123
124 if ( !sh )
125 return false;
126
127 const Allowlist& allowlist = sh->get_allowlist();
128 const Allowlist& internal_allowlist = sh->get_internal_allowlist();
129 const Allowlist& allowlist_prefixes = sh->get_allowlist_prefixes();
130
131 for ( const auto& prefix : allowlist_prefixes )
132 {
133 if (key.compare(0, prefix.length(), prefix) == 0)
134 return true;
135 }
136
137 if ( allowlist.find(key) != allowlist.end() )
138 return true;
139
140 if ( internal_allowlist.find(key) != internal_allowlist.end() )
141 return true;
142
143 return false;
144 }
145
allowlist_append(const char * keyword,bool is_prefix)146 void Shell::allowlist_append(const char* keyword, bool is_prefix)
147 {
148 Shell* sh = Shell::get_current_shell();
149
150 if ( !sh )
151 return;
152
153 sh->allowlist_update(keyword, is_prefix);
154 }
155
config_open_table(bool is_root_node,bool is_list,int idx,const std::string & table_name,const Parameter * p)156 void Shell::config_open_table(bool is_root_node, bool is_list, int idx,
157 const std::string& table_name, const Parameter* p)
158 {
159 if ( !s_config_output )
160 return;
161
162 Parameter::Type node_type = is_list ? Parameter::PT_LIST : Parameter::PT_TABLE;
163 if ( is_root_node )
164 add_config_root_node(table_name, node_type);
165 else
166 {
167 if ( p )
168 node_type = p->type;
169
170 if ( node_type == Parameter::PT_MULTI )
171 {
172 s_close_table = false;
173 return;
174 }
175
176 if ( node_type == Parameter::PT_TABLE )
177 update_current_config_node(table_name);
178 else
179 {
180 if ( idx )
181 node_type = Parameter::PT_TABLE;
182
183 add_config_child_node(table_name, node_type, idx);
184 }
185 }
186 }
187
add_config_child_node(const std::string & node_name,snort::Parameter::Type type,bool is_root_list_item)188 void Shell::add_config_child_node(const std::string& node_name, snort::Parameter::Type type,
189 bool is_root_list_item)
190 {
191 if ( !s_config_output || !s_current_node )
192 return;
193
194 // element of the top-level list is anonymous
195 std::string name = ( !is_root_list_item && s_current_node->get_name() != node_name ) ?
196 node_name : "";
197
198 auto new_node = new TreeConfigNode(s_current_node, name, type);
199 s_current_node->add_child_node(new_node);
200 s_current_node = new_node;
201 }
202
add_config_root_node(const std::string & root_name,snort::Parameter::Type node_type)203 void Shell::add_config_root_node(const std::string& root_name, snort::Parameter::Type node_type)
204 {
205 if ( !s_config_output )
206 return;
207
208 Shell* sh = Shell::get_current_shell();
209
210 if ( !sh )
211 return;
212
213 sh->s_current_node = new TreeConfigNode(nullptr, root_name, node_type);
214 sh->config_data.add_config_tree(sh->s_current_node);
215 }
216
update_current_config_node(const std::string & node_name)217 void Shell::update_current_config_node(const std::string& node_name)
218 {
219 if ( !s_config_output || !s_current_node )
220 return;
221
222 // node has been added during setting default options
223 if ( !node_name.empty() )
224 s_current_node = s_current_node->get_node(node_name);
225 else if ( s_current_node->get_parent_node() and
226 s_current_node->get_type() == Parameter::PT_TABLE and
227 !s_current_node->get_name().empty() )
228 s_current_node = s_current_node->get_parent_node();
229
230 assert(s_current_node);
231 }
232
config_close_table()233 void Shell::config_close_table()
234 {
235 if ( !s_config_output )
236 return;
237
238 if ( !s_close_table )
239 {
240 s_close_table = true;
241 return;
242 }
243
244 if ( !s_current_node )
245 return;
246
247 s_current_node = s_current_node->get_parent_node();
248 }
249
set_config_value(const std::string & fqn,const snort::Value & value)250 void Shell::set_config_value(const std::string& fqn, const snort::Value& value)
251 {
252 if ( !s_config_output || !s_current_node )
253 return;
254
255 // don't give names to list elements
256 if ( s_current_node->get_type() == Parameter::PT_LIST )
257 {
258 s_current_node->add_child_node(new ValueConfigNode(s_current_node, value, ""));
259 return;
260 }
261
262 BaseConfigNode* child_node = nullptr;
263
264 std::string custom_name;
265 if ( strchr(value.get_name(), '$') )
266 custom_name = fqn.substr(fqn.find_last_of(".") + 1);
267
268 for ( auto node : s_current_node->get_children() )
269 {
270 if ( (node->get_type() == Parameter::PT_MULTI) and (node->get_name() == value.get_name()) )
271 {
272 child_node = node;
273 break;
274 }
275
276 if ( !custom_name.empty() )
277 child_node = node->get_node(custom_name);
278 else
279 child_node = node->get_node(value.get_name());
280
281 if ( child_node )
282 break;
283 }
284
285 if ( !child_node )
286 s_current_node->add_child_node(new ValueConfigNode(s_current_node, value, custom_name));
287 else
288 child_node->set_value(value);
289 }
290
291 // FIXIT-L shell --pause should stop before loading config so Lua state
292 // can be examined and modified.
293
294 #if 0
295 // :( it does not look possible to get file and line after load
296 static int get_line_number(lua_State* L)
297 {
298 lua_Debug ar;
299 lua_getstack(L, 1, &ar);
300 lua_getinfo(L, "nSl", &ar);
301 return ar.currentline;
302 }
303
304 #endif
305
set_sandbox_env()306 bool Shell::set_sandbox_env()
307 {
308 lua_getglobal(lua, "sandbox_env");
309
310 if ( lua_istable(lua, -1) )
311 {
312 if ( !lua_setfenv(lua, -2) )
313 {
314 ParseError("can't set sandbox environment\n");
315 return false;
316 }
317 }
318 else
319 {
320 ParseError("sandbox environment not defined\n");
321 return false;
322 }
323 return true;
324 }
325
load_lua_sandbox()326 bool Shell::load_lua_sandbox()
327 {
328 Lua::ManageStack ms(lua);
329
330 LogMessage("Loading lua sandbox %s:\n", lua_sandbox.c_str());
331 if ( luaL_loadfile(lua, lua_sandbox.c_str()) )
332 {
333 ParseError("can't load lua sandbox %s: %s\n", lua_sandbox.c_str(), lua_tostring(lua, -1));
334 return false;
335 }
336
337 if ( lua_pcall(lua, 0, 0, 0) )
338 {
339 ParseError("can't init lua sandbox %s: %s\n", lua_sandbox.c_str(), lua_tostring(lua, -1));
340 return false;
341 }
342 LogMessage("Finished %s:\n", lua_sandbox.c_str());
343
344 lua_getglobal(lua, "sandbox_env");
345 if ( !lua_istable(lua, -1) )
346 {
347 ParseError("sandbox_env table doesn't exist in %s: %s\n", lua_sandbox.c_str(),
348 lua_tostring(lua, -1));
349 return false;
350 }
351
352 lua_getglobal(lua, "create_sandbox_env");
353 if ( lua_pcall(lua, 0, 0, 0) != 0 )
354 {
355 ParseError("can't create sandbox environment %s: %s\n", lua_sandbox.c_str(),
356 lua_tostring(lua, -1));
357 return false;
358 }
359
360 return true;
361 }
362
load_string(const char * s,bool load_in_sandbox,const char * message)363 bool Shell::load_string(const char* s, bool load_in_sandbox, const char* message)
364 {
365 Lua::ManageStack ms(lua);
366
367 if ( luaL_loadstring(lua, s) )
368 {
369 ParseError("can't load %s: %s\n", message, lua_tostring(lua, -1));
370 return false;
371 }
372
373 if ( load_in_sandbox && !set_sandbox_env() )
374 return false;
375
376
377 if ( lua_pcall(lua, 0, 0, 0) )
378 {
379 ParseError("can't init %s: %s\n", message, lua_tostring(lua, -1));
380 return false;
381 }
382
383 return true;
384 }
385
load_config(const char * file,bool load_in_sandbox)386 bool Shell::load_config(const char* file, bool load_in_sandbox)
387 {
388 if ( load_in_sandbox )
389 {
390 ifstream in_file(file, ifstream::in);
391 if (in_file.get() == 27 )
392 {
393 ParseError("bytecode is not allowed %s\n", file);
394 return false;
395 }
396 }
397
398 Lua::ManageStack ms(lua);
399
400 if ( luaL_loadfile(lua, file) )
401 {
402 ParseError("can't load %s: %s\n", file, lua_tostring(lua, -1));
403 return false;
404 }
405
406 if ( load_in_sandbox && !set_sandbox_env() )
407 return false;
408
409 if ( lua_pcall(lua, 0, 0, 0) )
410 {
411 ParseError("can't init %s: %s\n", file, lua_tostring(lua, -1));
412 return false;
413 }
414
415 return true;
416 }
417
418 //-------------------------------------------------------------------------
419 // public methods
420 //-------------------------------------------------------------------------
421
Shell(const char * s,bool load_defaults)422 Shell::Shell(const char* s, bool load_defaults) :
423 config_data(s), load_defaults(load_defaults)
424 {
425 // FIXIT-M should wrap in Lua::State
426 lua = luaL_newstate();
427
428 if ( !lua )
429 FatalError("Lua state instantiation failed\n");
430
431 current_shells.push(this);
432
433 lua_atpanic(lua, Shell::panic);
434 luaL_openlibs(lua);
435
436 if ( s )
437 file = s;
438
439 parse_from = get_parse_file();
440
441 loaded = false;
442 load_string(lua_bootstrap, false, "bootstrap");
443 install_version_strings(lua);
444 bootstrapped = true;
445
446 current_shells.pop();
447 }
448
~Shell()449 Shell::~Shell()
450 {
451 lua_close(lua);
452 }
453
set_file(const char * s)454 void Shell::set_file(const char* s)
455 {
456 file = s;
457 config_data.file_name = file;
458 }
459
set_overrides(const char * s)460 void Shell::set_overrides(const char* s)
461 {
462 overrides += s;
463 }
464
set_overrides(Shell * sh)465 void Shell::set_overrides(Shell* sh)
466 {
467 overrides += sh->overrides;
468 }
469
configure(SnortConfig * sc,bool is_root)470 bool Shell::configure(SnortConfig* sc, bool is_root)
471 {
472 assert(file.size());
473 ModuleManager::set_config(sc);
474
475 //set_*_policy can set to null. this is used
476 //to tell which pieces to pick from sub policy
477 auto pt = sc->policy_map->get_policies(this);
478 if ( pt.get() == nullptr )
479 set_default_policy(sc);
480 else
481 {
482 set_inspection_policy(pt->inspection);
483 set_ips_policy(pt->ips);
484 set_network_policy(pt->network);
485 }
486
487 if (!sc->tweaks.empty())
488 {
489 lua_pushstring(lua, sc->tweaks.c_str());
490 lua_setglobal(lua, "tweaks");
491 }
492
493 bool load_in_sandbox = true;
494
495 if ( lua_sandbox.empty() )
496 load_in_sandbox = false;
497 else if ( !load_lua_sandbox() )
498 return false;
499
500 if ( load_defaults )
501 load_string(ModuleManager::get_lua_coreinit(), load_in_sandbox, "coreinit");
502
503 std::string path = parse_from;
504 const char* code;
505
506 if ( !is_root )
507 code = get_config_file(file.c_str(), path);
508 else
509 {
510 code = "W";
511 path = file;
512 }
513
514 if ( !code )
515 {
516 ParseError("can't find %s\n", file.c_str());
517 return false;
518 }
519
520 push_parse_location(code, path.c_str(), file.c_str(), 0);
521
522 current_shells.push(this);
523
524 if ( !path.empty() and
525 !load_config(path.c_str(), load_in_sandbox) )
526 {
527 current_shells.pop();
528 return false;
529 }
530
531 if ( !overrides.empty() )
532 load_string(overrides.c_str(), load_in_sandbox, "overrides");
533
534 if ( SnortConfig::log_verbose() )
535 print_allowlist();
536
537 load_string(lua_finalize, false, "finalize");
538
539 clear_allowlist();
540
541 auto config_output = Shell::get_current_shell()->s_config_output;
542 if ( config_output )
543 config_output->dump_config(config_data);
544
545 current_shells.pop();
546
547 set_default_policy(sc);
548 ModuleManager::set_config(nullptr);
549 loaded = true;
550
551 pop_parse_location();
552
553 return true;
554 }
555
install(const char * name,const luaL_Reg * reg)556 void Shell::install(const char* name, const luaL_Reg* reg)
557 {
558 if ( !strcmp(name, "snort") )
559 luaL_register(lua, "_G", reg);
560
561 luaL_register(lua, name, reg);
562 }
563
execute(const char * cmd,string & rsp)564 void Shell::execute(const char* cmd, string& rsp)
565 {
566 int err = 0;
567 Lua::ManageStack ms(lua);
568
569 try
570 {
571 // FIXIT-L shares logic with chunk
572 err = luaL_loadbuffer(lua, cmd, strlen(cmd), "shell");
573
574 if ( !err )
575 err = lua_pcall(lua, 0, 0, 0);
576 }
577 catch (...)
578 {
579 rsp = fatal;
580 }
581
582 if (err)
583 {
584 rsp = lua_tostring(lua, -1);
585 rsp += "\n";
586 lua_pop(lua, 1);
587 }
588 }
589
590 //-------------------------------------------------------------------------
591 // Helper methods
592 //-------------------------------------------------------------------------
593
print_list(const Shell::Allowlist & wlist,const std::string & msg)594 static void print_list(const Shell::Allowlist& wlist, const std::string& msg)
595 {
596 LogMessage("\t%s\n", msg.c_str());
597 std::string list;
598
599 for ( const auto& wl : wlist )
600 {
601 list += wl;
602 list += ", ";
603 }
604
605 if ( !list.empty() )
606 list.erase(list.end() - 2, list.end());
607
608 ConfigLogger::log_list(list.c_str());
609 }
610
611 //-------------------------------------------------------------------------
612 // private methods
613 //-------------------------------------------------------------------------
614
print_allowlist() const615 void Shell::print_allowlist() const
616 {
617 std::string output;
618 if ( !allowlist.empty() )
619 {
620 output = "Lua Allowlist Keywords for " + file + ":";
621 print_list(allowlist, output);
622 }
623
624 if ( !allowlist_prefixes.empty() )
625 {
626 output = "Lua Allowlist Prefixes for " + file + ":";
627 print_list(allowlist_prefixes, output);
628 }
629 }
630
allowlist_update(const char * s,bool is_prefix)631 void Shell::allowlist_update(const char* s, bool is_prefix)
632 {
633 Allowlist* wlist = nullptr;
634 if ( is_prefix )
635 wlist = &allowlist_prefixes;
636 else if ( !bootstrapped )
637 wlist = &internal_allowlist;
638 else
639 wlist = &allowlist;
640
641 if ( s )
642 wlist->emplace(s);
643 }
644
645