1 /**
2 * config/cfparser.cc
3 *
4 * (C) 2005-2008 Murat Deligonul
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * ----
12 * 7/06 -- Parser core factored out
13 */
14
15 #include "autoconf.h"
16
17 #include <vector>
18 #include <string>
19 #include <iterator>
20 #include <algorithm>
21 #include <exception>
22 #include <functional>
23 #include <cstdlib>
24 #include <cstdio>
25 #include <cstring>
26 #include <cstdarg>
27 #include <cerrno>
28 #include <unistd.h>
29 #include <sys/types.h>
30 #include <sys/stat.h>
31 #include "util/generic.h"
32 #include "util/hash.h"
33 #include "config/cfparser.h"
34 #include "config/node_searcher.h"
35 #include "config/node.h"
36 #include "logging/chatlog.h"
37 #include "ruleset.h"
38
39 #include "debug.h"
40
41 /**
42 * TODO:
43 * disable debug statements
44 * detection of illegal user blocks isn't verbose enough
45 * possible off-by-one index error? ("Illegal argument type" code)
46 */
47
48 using std::vector;
49 using std::string;
50
51 using config::node_searcher;
52
53 /* Custom flags for symbol table */
54 enum {
55 OBSOLETE = 0x8000,
56 };
57
58 /* IDs for symbols */
59 enum {
60 SYM_OPENBRACE = 20,
61 SYM_CLOSEBRACE,
62 CFG_CMD_SET,
63 CFG_CMD_LISTEN, CFG_CMD_ALLOW,
64 CFG_CMD_DENY, CFG_CMD_USER,
65 CFG_CMD_VHOSTS, CFG_CMD_SSL_LISTEN,
66 CFG_CMD_RS_FROM,
67 CFG_CMD_RS_TO,
68 };
69
70 const int config_parser::SET_ARG_PATTERN[] = { ARG_TYPE_OTHER | OBSOLETE, ARG_TYPE_STRING | ARG_TYPE_NUMBER | ARG_TYPE_BOOL | ARG_TYPE_OTHER };
71 const int config_parser::LISTEN_ARG_PATTERN[] = { ARG_TYPE_STRING | ARG_TYPE_OTHER | ARG_TYPE_NUMBER};
72 const int config_parser::BLOCK_ARG_PATTERN[] = { ARG_TYPE_BLOCK };
73 const int config_parser::USER_ARG_PATTERN[] = { ARG_TYPE_STRING | ARG_TYPE_OTHER, ARG_TYPE_BLOCK};
74
75
76 const struct parser_core::config_symbol config_parser::symtab[] = {
77 /* good contexts */ /* flags to enforce */
78 {"SET", CFG_CMD_SET, CTX_GLOBAL | CTX_USER, COMMAND | ARGS_SAME_LINE, 2, SET_ARG_PATTERN},
79 {"LISTEN", CFG_CMD_LISTEN, CTX_GLOBAL, COMMAND | ARGS_SAME_LINE, 1, LISTEN_ARG_PATTERN},
80 {"SSL-LISTEN", CFG_CMD_SSL_LISTEN, CTX_GLOBAL, COMMAND | ARGS_SAME_LINE, 1, LISTEN_ARG_PATTERN},
81 {"ALLOW", CFG_CMD_ALLOW, CTX_USER, CREATE_CONTEXT, 1, BLOCK_ARG_PATTERN},
82 {"DENY", CFG_CMD_DENY, CTX_GLOBAL | CTX_USER, CREATE_CONTEXT, 1, BLOCK_ARG_PATTERN},
83 {"USER", CFG_CMD_USER, CTX_GLOBAL, CREATE_CONTEXT, 2, USER_ARG_PATTERN},
84 {"VHOSTS", CFG_CMD_VHOSTS, CTX_GLOBAL | CTX_USER, CREATE_CONTEXT, 1, BLOCK_ARG_PATTERN},
85 {"FROM", CFG_CMD_RS_FROM, CTX_RULESET, COMMAND | ARGS_SAME_LINE, -1},
86 {"TO", CFG_CMD_RS_TO, CTX_RULESET, COMMAND | ARGS_SAME_LINE, -1},
87 {"{", SYM_OPENBRACE, CTX_ARG, ARG_TYPE_BLOCK },
88 {"}", SYM_CLOSEBRACE, CTX_USER | CTX_RULESET | CTX_VHOSTS, EXIT_CONTEXT },
89
90 /** obsolete options. will be removed one day. **/
91 { "PREVENT-SELF-CONNECTS", 0, CTX_ARG, OBSOLETE},
92 { "ENABLE-PRIVATE-LOGGING", 0, CTX_ARG, OBSOLETE},
93 { "ENABLE-PUBLIC-LOGGING", 0, CTX_ARG, OBSOLETE},
94 { "ENABLE-SEPARATE-LOGGING",0, CTX_ARG, OBSOLETE},
95 { "ENABLE-SEPERATE-LOGGING",0, CTX_ARG, OBSOLETE},
96 { "NO-REVERSE-LOOKUPS", 0, CTX_ARG, OBSOLETE},
97 { "MAX-DNS-WAIT-TIME", 0, CTX_ARG, OBSOLETE},
98 { "DEFAULT-LOG-OPTIONS", 0, CTX_ARG, OBSOLETE},
99 { "ALLOWED-LOG-OPTIONS", 0, CTX_ARG, OBSOLETE},
100 { "AUTO-FAKE-IDENTS", 0, CTX_ARG, OBSOLETE},
101 { "ENABLE-AUTO-DETACH", 0, CTX_ARG, OBSOLETE},
102 { "AUTO-SERVER", 0, CTX_ARG, OBSOLETE},
103 { "DEFAULT-VHOST", 0, CTX_ARG, OBSOLETE},
104 { "DROP-ON-DISCONNECT", 0, CTX_ARG, OBSOLETE},
105 { "MAX-LOGFILE-SIZE", 0, CTX_ARG, OBSOLETE},
106 { "KILL-ON-FULL-QUEUE", 0, CTX_ARG, OBSOLETE},
107 { "ENABLE-DCC-FILTER", 0, CTX_ARG, OBSOLETE}
108 };
109
config_parser(const char * filename)110 config_parser::config_parser(const char * filename) :
111 parser_core(filename),
112 users(7),
113 options("default", true),
114 chash(47)
115 {
116 /** Create symbol table hash **/
117 for (unsigned c = 0; c < (sizeof(symtab) / sizeof(struct config_symbol)); ++c) {
118 chash.insert(symtab[c].symbol, &symtab[c]);
119 }
120 /** Make sure these hash tables are ready too */
121 node_searcher<user_permissions>::populate_table();
122 node_searcher<proxy_options>::populate_table();
123 }
124
~config_parser()125 config_parser::~config_parser()
126 {
127 DEBUG("config_parser::~config_parser() [%p]\n", this);
128 using std::for_each;
129 for_each(shitlist.begin(), shitlist.end(), util::delete_ptr());
130 for_each(users.begin(), users.end(), util::apply_second<util::delete_ptr>());
131 node_searcher<user_permissions>::clear_table();
132 node_searcher<proxy_options>::clear_table();
133 }
134
symbol_table() const135 const parser_core::hash_table_t * config_parser::symbol_table() const
136 {
137 return &chash;
138 }
139
str_context(int ctx) const140 const char * config_parser::str_context(int ctx) const
141 {
142 switch (ctx) {
143 case CTX_USER:
144 return "user block";
145 case CTX_VHOSTS:
146 return "vhost block";
147 case CTX_ALLOW:
148 return "allow block";
149 case CTX_DENY:
150 return "deny block";
151 default:
152 break;
153 }
154 return parser_core::str_context(ctx);
155 }
156
str_argtype(int arg) const157 const char * config_parser::str_argtype(int arg) const
158 {
159 switch (arg) {
160 case OBSOLETE:
161 return "obsolete option";
162 default:
163 break;
164 }
165 return parser_core::str_argtype(arg);
166 }
167
168 /**
169 * Check legality of potential junk arguments right before
170 * '}' that ends a block. Currently legal only in the vhosts
171 * block (where each argument is a vhost to add to the list)
172 * return: 0 on success
173 */
resolve_trailing_args(std::vector<config_token> & pre_args)174 int config_parser::resolve_trailing_args(std::vector<config_token> &pre_args)
175 {
176 context &ctx = context_stack.top();
177 if (ctx.state == CTX_VHOSTS) {
178 vector<string> * vhosts = (vector<string> *) ctx.data;
179 vector<config_token>::const_iterator i = pre_args.begin();
180 for (; i != pre_args.end(); ++i) {
181 DEBUG("VHOST: inserting '%s' into block %p\n", (*i).token, vhosts);
182 vhosts->push_back((*i).token);
183 }
184 return 0;
185 }
186 error("unknown tokens before closing brace\n");
187 error(" starting with: '%s'\n", pre_args[0].token);
188 return -1;
189 }
190
191 /**
192 * Process a command.
193 */
do_command(const config_symbol * sym,std::vector<config_token> & pre_args,std::vector<config_token> & args)194 int config_parser::do_command(const config_symbol * sym,
195 std::vector<config_token>& pre_args,
196 std::vector<config_token>& args)
197 {
198 switch (sym->id) {
199 case CFG_CMD_SET:
200 return do_set_command(sym, pre_args, args);
201 case CFG_CMD_LISTEN:
202 case CFG_CMD_SSL_LISTEN:
203 return do_listen_command(sym, pre_args, args);
204 case CFG_CMD_RS_TO:
205 case CFG_CMD_RS_FROM:
206 return do_rs_command(sym, pre_args, args);
207 }
208 error("(!!) no handler for command '%s'\n", sym->symbol);
209 return -1;
210 }
211
212 /**
213 * Enter a new context.
214 * Parser will ensure proper # and type of arguments before calling,
215 * but we might still fail in some cases. (e.g. username already used)
216 */
enter_context(const config_symbol * sym,context & ctx,vector<config_token> & pre_args,vector<config_token> & args)217 int config_parser::enter_context(const config_symbol *sym,
218 context &ctx,
219 vector<config_token> &pre_args,
220 vector<config_token> &args)
221 {
222 /* currently none of these support pre-args */
223 if (!pre_args.empty()) {
224 error("unknown tokens before keyword '%s'\n", sym->symbol);
225 error(" starting with: '%s'\n", pre_args[0].token);
226 return -1;
227 }
228
229 switch (sym->id) {
230 case CFG_CMD_USER:
231 ctx.state = CTX_USER;
232 if (users.contains(args[0].token)) {
233 error("user '%s' is already defined!\n", args[0].token);
234 return -1;
235 }
236
237 if (!userdef::is_legal_name(args[0].token)) {
238 error("'%s' is not a legal username.\n", args[0].token);
239 return -1;
240 }
241 ctx.data = (void *) new userdef(args[0].token);
242 break;
243 case CFG_CMD_VHOSTS:
244 ctx.state = CTX_VHOSTS;
245 ctx.data = (void *) new vector<std::string>();
246 break;
247 case CFG_CMD_ALLOW:
248 ctx.state = CTX_ALLOW;
249 ctx.data = (void *) new allowed_ruleset();
250 break;
251 case CFG_CMD_DENY:
252 ctx.state = CTX_DENY;
253 ctx.data = (void *) new denied_ruleset();
254 break;
255 default:
256 return -1;
257 }
258
259 return 0;
260 }
261
262
263 /**
264 * Called after getting done with a user/ruleset/vhost block.
265 * ctx refers to context struct just popped off the stack.
266 * Verifies legality of the user/ruleset/vhost block just parsed.
267 * Also called to cleanup after a failed parse.
268 *
269 * return:
270 * 0 - no errors.
271 * < 0 - error condition
272 * (note: will always return 0 if success is false)
273 */
leave_context(context & ctx,bool success)274 int config_parser::leave_context(context &ctx, bool success)
275 {
276 const context& top = context_stack.top();
277 ruleset * rs;
278 userdef * user;
279 vector<string> * v;
280
281 switch (ctx.state) {
282 case CTX_USER:
283 user = (userdef *) ctx.data;
284 if (!success) {
285 delete user;
286 break;
287 }
288 /** verify and add to user collection **/
289 if (validate_user_config(user) < 0) {
290 delete user;
291 return -1;
292 }
293 DEBUG("leave_context(): adding user %s\n", user->name());
294 users.insert(user->name(), user);
295 break;
296
297 case CTX_ALLOW:
298 case CTX_DENY:
299 rs = (ruleset *) ctx.data;
300 if (!success) {
301 delete rs;
302 break;
303 }
304 /** verify and add to user's ruleset list **/
305 if (!rs->is_legal()) {
306 error("this ruleset is invalid\n");
307 error(" 'allow' rulesets MUST have at least one 'from' rule and at least one 'to' rule\n");
308 error(" 'deny' rulesets MUST have at least one rule of either type\n");
309 delete rs;
310 return -1;
311 }
312
313 if (top.state == CTX_GLOBAL) {
314 shitlist.push_back(rs);
315 }
316 else {
317 user = (userdef *) top.data;
318 user->rulesets().push_back(rs);
319 }
320 break;
321
322 case CTX_VHOSTS:
323 v = (vector<string> *) ctx.data;
324 if (!success) {
325 delete v;
326 break;
327 }
328 /** verify **/
329 if (v->size() == 0) {
330 error("vhost list is empty!\n");
331 delete v;
332 return -1;
333 }
334 /** add to proper list **/
335 if (top.state == CTX_GLOBAL) {
336 std::copy(v->begin(), v->end(), std::back_inserter(vhosts));
337 }
338 else {
339 user = (userdef *) top.data;
340 std::copy(v->begin(), v->end(), std::back_inserter(user->vhosts()));
341 }
342 delete v;
343 break;
344 default:
345 error("Unknown context '%d' -- unable to continue\n", ctx.state);
346 abort();
347 }
348 return 0;
349 }
350
351
do_set_command(const config_symbol * sym,vector<config_token> & pre_args,vector<config_token> & args)352 int config_parser::do_set_command(const config_symbol * sym,
353 vector<config_token> &pre_args,
354 vector<config_token> &args)
355 {
356 if (!pre_args.empty()) {
357 error("garbage before command\n");
358 return -1;
359 }
360
361 const context &ctx = context_stack.top();
362 const config_token &var = args[0];
363 const config_token &value = args[1];
364 const config_symbol * c = var.symbol;
365 const config::hash_entry * entry;
366
367
368 DEBUG("do_set_command(): set %s %s\n", var.token, value.token);
369
370 // XXX: dunno if this works
371 if (c->flags & OBSOLETE) {
372 warning("option '%s' is obsolete; it will be ignored\n", c->symbol);
373 return 0;
374 }
375
376 /**
377 * Lookup option being set.
378 */
379 bool is_proxy_opt = true;
380 entry = node_searcher<proxy_options>::lookup_option(var.token);
381
382 if (entry == NULL) {
383 is_proxy_opt = false;
384 entry = node_searcher<user_permissions>::lookup_option(var.token);
385 }
386
387 if (entry == NULL) {
388 error("unknown option '%s'\n", var.token);
389 return -1;
390 }
391
392 /* there should not be a 'user' to set this option */
393 if (is_proxy_opt && ctx.state != CTX_GLOBAL) {
394 error("found proxy option '%s', but not in global context\n", var.token);
395 return -1;
396 }
397
398 /* and there better be a 'user' if needed */
399 if (!is_proxy_opt && ctx.state != CTX_USER) {
400 error("found user option '%s', but not in user context\n", var.token);
401 return -1;
402 }
403
404 // verify legality of argument type.
405 // kinda messy.
406 int mapping = 0;
407 switch (entry->type) {
408 case config::BOOLEAN:
409 mapping = BOOL_ARG[0];
410 break;
411
412 case config::INTEGER:
413 mapping = NUMBER_ARG[0];
414 break;
415
416 case config::STRING:
417 mapping = STRING_ARG[0];
418 break;
419 }
420
421 if (!(value.symbol->flags & mapping)) {
422 // err: illegal argument type
423 error("illegal argument type for option '%s'\n", entry->item->name);
424 error(" Needed: (%s);\n", config::stroption(entry->type));
425 error(" Got: (%s)\n", str_argtypes(value.symbol->flags).c_str());
426 return -1;
427 }
428
429 const char * const string_value = value.token;
430 const int num_value = value.other_value;
431 const int opt = entry->option;
432 userdef * user = (userdef *) ctx.data;
433 using config::traits;
434
435 try {
436 switch (entry->type) {
437 case config::BOOLEAN:
438 if (is_proxy_opt) {
439 options.set<bool>( (traits<proxy_config, bool>::identifier_t) opt, num_value );
440 }
441 else {
442 user->config()->set<bool>( traits<user_perms, bool>::identifier_t(opt), num_value );
443 }
444 break;
445
446 case config::INTEGER:
447 if (is_proxy_opt) {
448 options.set<int>( traits<proxy_config, int>::identifier_t(opt), num_value );
449 }
450 else {
451 user->config()->set<int>( traits<user_perms, int>::identifier_t(opt), num_value );
452 }
453 break;
454
455 case config::STRING:
456 if (is_proxy_opt) {
457 options.set<string>( traits<proxy_config, string>::identifier_t(opt), string_value );
458 }
459 else {
460 user->config()->set<string>( traits<user_perms, string>::identifier_t(opt), string_value );
461 }
462 break;
463 }
464 }
465 catch (std::exception& e) {
466 error("exception while setting option '%s':\n", var.token);
467 error("illegal value '%s': %s\n", value.token, e.what());
468 return -1;
469 }
470 return 0;
471 }
472
473
do_listen_command(const config_symbol * sym,vector<config_token> & pre_args,vector<config_token> & args)474 int config_parser::do_listen_command(const config_symbol * sym,
475 vector<config_token> &pre_args,
476 vector<config_token> &args)
477 {
478 if (pre_args.size() > 0) {
479 error("garbage before '%s' command\n", sym->symbol);
480 return -1;
481 }
482
483 config::traits<proxy_config, string>::identifier_t opt =
484 (sym->id == CFG_CMD_LISTEN) ? proxy_config::PORTS : proxy_config::SSL_PORTS;
485 if (!options.get<string>(opt).empty()) {
486 warning("a value was already set for the '%s' command, ignoring\n", sym->symbol);
487 return 0;
488 }
489 try {
490 options.set<string>(opt, args[0].token);
491 }
492 catch (std::exception& e) {
493 error("fatal exception while performing '%s' command\n", sym->symbol);
494 error("value '%s' not valid: '%s'\n", args[0].token, e.what());
495 return -1;
496 }
497 return 0;
498 }
499
500 /**
501 * Parses indiviual statements inside ruleset blocks.
502 * Must handle:
503 * allow {
504 * 2 from *.domain.* on 6667
505 * 2 from 192.168.1.1
506 * from *otherdomain*
507 * to * on 6667
508 * to irc.*
509 */
510
do_rs_command(const config_symbol * sym,vector<config_token> & pre_args,vector<config_token> & args)511 int config_parser::do_rs_command(const config_symbol * sym,
512 vector<config_token> &pre_args,
513 vector<config_token> &args)
514 {
515 const context &ctx = context_stack.top();
516 ruleset * rs = (ruleset *) ctx.data;
517 /** optional pre- arg **/
518 const config_token * num_token = 0;
519 int num = ruleset::UNLIMITED;
520
521 /** sanity checks **/
522 if (args.size() == 0) {
523 error("you must specify a hostmask after 'to' or 'from'\n");
524 return -1;
525 }
526
527 /** sanity checks part II **/
528 if (pre_args.size() > 1) {
529 error("too many tokens before 'to' / 'from' command\n");
530 return -1;
531 }
532 else if (pre_args.size() == 1) {
533 num_token = &pre_args[0];
534 if (num_token->symbol->id != SYM_NUMBER) {
535 error("first token on this line must be a number\n");
536 return -1;
537 }
538 if (num_token->other_value < 1) {
539 error("must enter a positive value for this number\n");
540 return -1;
541 }
542 num = num_token->other_value;
543 }
544
545 /** mandatory args **/
546 const config_token& hostmask = args[0];
547 const char * ports = "all";
548 std::string reason;
549
550 if (args.size() > 1) {
551 unsigned int reason_idx = 1;
552 const config_token& ports_token = args[1];
553 if (!strcasecmp(ports_token.token, "on")) {
554 if (args.size() == 2) {
555 error("must specify ports after 'on' statement\n");
556 return -1;
557 }
558 ports = args[2].token;
559 reason_idx = 3;
560 }
561
562 if (args.size() >= reason_idx+1) {
563 /** Extract the reason for banning from
564 * the rest of the line **/
565 std::vector<config_token>::const_iterator i = args.begin();
566 for (i += reason_idx; i != args.end(); ++i) {
567 reason += (*i).token;
568 reason += ' ';
569 }
570 }
571 }
572
573 DEBUG("do_rs_command(): %d %s %s ports: %s reason: %s\n", num, sym->symbol, hostmask.token, ports, reason.c_str());
574 if (sym->id == CFG_CMD_RS_FROM) {
575 rs->add_host_from(hostmask.token, ports, reason.c_str(), num);
576 }
577 else {
578 rs->add_host_to(hostmask.token, ports, reason.c_str(), num);
579 }
580 return 0;
581 }
582
583
584 /**
585 * Ensures sanity of a newly defined user.
586 * Sets some default values if possible.
587 */
validate_user_config(userdef * user)588 int config_parser::validate_user_config(userdef * user)
589 {
590 int err = 0;
591
592 /** errors **/
593 if (user->rulesets().empty()) {
594 error("user `%s' has no 'allow' rulesets\n", user->name());
595 err = -1;
596 }
597 if ( user->config()->get<string>(user_perms::PASSWORD).empty() ) {
598 error ("user '%s': a password is required\n", user->name());
599 err = -1;
600 }
601 return err;
602 }
603
validate_config() const604 int config_parser::validate_config() const
605 {
606 int err = 0;
607
608 if (users.empty()) {
609 error("No users defined!\n");
610 err = -1;
611 }
612 if (options.get<string>(proxy_config::LOG_FILE).empty()) {
613 error("\"logfile\" option wasn't set\n");
614 err = -1;
615 }
616 if (options.get<string>(proxy_config::PORTS).empty() &&
617 options.get<string>(proxy_config::SSL_PORTS).empty()) {
618 error("No listening ports defined\n");
619 err = -1;
620 }
621 #ifdef HAVE_SSL
622 if (options.get<string>(proxy_config::CERT_FILE).empty() &&
623 !options.get<string>(proxy_config::SSL_PORTS).empty()) {
624 error("SSL listen ports set, but no \"certfile\" defined\n");
625 err = -1;
626 }
627 #endif
628
629 const string& vfs_dir = options.get<string>(proxy_config::VFS_DIR);
630 if (vfs_dir.empty()) {
631 error("No Virtual File System directory defined. Please set the \"vfs-dir\" option\n");
632 err = -1;
633 }
634 else if (access(vfs_dir.c_str(), R_OK | W_OK | X_OK) < 0) {
635 error( "Cannot access VFS base dir `%s': %s\n",
636 vfs_dir.c_str(), strerror(errno));
637 err = -1;
638 }
639 if (options.get<int>(proxy_config::MAX_BUFFER_SIZE)
640 <= options.get<int>(proxy_config::MIN_BUFFER_SIZE)) {
641 error("\"max-buffer-size\" must be greater than \"min_buffer_size\"\n");
642 err = -1;
643 }
644
645 // other warnings
646 // warn about unsafe oident fake ident support
647 const string& fmethod = options.get<string>(proxy_config::FAKE_IDENT_METHOD);
648 if (users.size() > 1 && strcasecmp(fmethod.c_str(), "oidentd-hack") == 0) {
649 warning("\"oidentd-hack\" fake ident support is not reliable with multiple users\n");
650 }
651 return err;
652 }
653
654