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