1 /**
2  * This program is free software; you can redistribute it and/or modify
3  * it under the terms of the GNU General Public License as published by
4  * the Free Software Foundation; either version 2 of the License, or
5  * (at your option) any later version.
6  *
7  * ruleset.cc
8  * (C) 1998-2005 Murat Deligonul
9  */
10 
11 #include "autoconf.h"
12 
13 #include <vector>
14 #include <algorithm>
15 #include <cstdlib>
16 #include <cstdio>
17 #include <cstring>
18 #include <cctype>
19 #include <unistd.h>
20 #include "util/tokenizer.h"
21 #include "ruleset.h"
22 #include "debug.h"
23 
24 using namespace util::strings;
25 
26 const int  ruleset::UNLIMITED = -1;
27 
28 /*
29  *  Checks if port is in set 'set'.
30  */
port_in_set(const char * set,unsigned short port)31 bool ruleset::port_in_set(const char * set, unsigned short port)
32 {
33 	if (!set) {
34 		return false;
35 	}
36 	if (strcasecmp(set, "all") == 0) {
37 		return true;
38 	}
39 	/*
40 	 * Find tokens seperated by commas.
41 	 * Will also get the first and only one if there are no commas.
42 	 */
43 	using std::vector;
44 	using std::string;
45 	vector<string> tokens;
46 	tokenize(set, ",", tokens);
47 
48 	for (vector<string>::const_iterator i = tokens.begin(), e = tokens.end();
49 			i != e; ++i) {
50 		const string& tok = *i;
51 		// look for range
52 		string values[2];
53 		tokenize(tok.c_str(), "-", &values[0], 2);
54 
55 		if (!values[1].empty()) {
56 			unsigned int min, max;
57 			/* we are kind enough to trim down min/max if they
58 			   are too big */
59 			min = (unsigned) atoi(tokens[0].c_str());
60 			max = (unsigned) atoi(tokens[1].c_str());
61 			min = (min > 65536) ? 65536 : min;
62 			max = (max > 65536) ? 65536 : max;
63 
64 			if (port >= (unsigned short) min && port <= (unsigned short) max) {
65 				return true;
66 			}
67 		}
68 		else {
69 			if (port == (unsigned short) atoi(tok.c_str())) {
70 				return true;
71 			}
72 		}
73 	}
74 	return false;
75 }
76 
77 /**
78  * Tries to determine if a wildcard pattern is meant for matching an IP address
79  * or a full hostname.
80  */
is_ip_pattern(const char * address)81 bool ruleset::is_ip_pattern(const char *address)
82 {
83 	bool legal = false;
84 
85 	int num_colons = 0;
86 	int num_letters = 0;
87 	int num_wild = 0;
88 
89 	do {
90 		if (*address >= '0' && *address <= '9') {
91 			legal = true;
92 		}
93 		else if (toupper(*address) >= 'A' && toupper(*address) <= 'F') {
94 			++num_letters;
95 			legal = true;
96 		}
97 		else if (*address == '?' || *address == '*') {
98 			++num_wild;
99 		}
100 		else if (*address == ':') {
101 			++num_colons;
102 		}
103  		else if (*address == '.') {
104 			continue;
105 		}
106 		else {
107 			return 0;
108 		}
109 	} while (*++address);
110 
111 	if (num_letters && !num_colons) {
112 		legal = false;
113 	}
114 	else if (num_wild && !num_letters) {
115 		legal = true;
116 	}
117 
118 	return legal;
119 }
120 
121 /**
122  * mar 05: updated to not perform dns lookups anymore.
123  * 	   change return value to 0 on match
124  */
smart_match(const char * host,const char * ip,const char * pattern)125 /* static */ int ruleset::smart_match(const char * host, const char * ip, const char *pattern)
126 {
127 	bool have_host = (host && *host);
128 	bool is_pattern_ip = is_ip_pattern(pattern);
129 
130 	/* pattern is of an IP address */
131 	if (is_pattern_ip)
132 		return wild_match(pattern, ip);
133 	/* pattern was not an IP address and we have host */
134 	else if (!is_pattern_ip && have_host)
135 		return wild_match(pattern, host);
136 	return 1;
137 }
138 
~ruleset()139 ruleset::~ruleset()
140 {
141 	DEBUG("ruleset::~ruleset(): %p\n", this);
142 }
143 
144 /**
145  *   Code for allowed_ruleset class:
146  *   a config file block can be :
147  *  allow {
148  *     [num] from (address) [ports] : Num is optional.
149  *                                     Max # of clients
150  *                                     allowed to connect from that address.
151  *                                     Defaults to -1, which means unlimited.
152  *                              address:
153  *                                    Address to permit clients from
154  *                              ports:
155  *                                    What ports should it allow/deny to:
156  *                                    "all"
157  *                                    "6667"
158  *                                    "6660-7000"
159  *                                    "7000,4000,40355,29421,500-2403,6660-6666"
160  *
161  *     [num] to (address) [ports]  : blah blah
162  *   }
163  *   There must be at least one `from' and one 'to' entry.
164  *   `Reason' arguments to below functions are ignored.
165  *
166  */
does_match(const char * from,const char * ip,unsigned short port,host_type t) const167 bool allowed_ruleset::does_match(const char * from, const char * ip,
168 					unsigned short port, host_type t) const
169 {
170 	const std::vector<rs_host> & l = ((t == FROM) ? from_hosts : to_hosts);
171 	return std::find(l.begin(), l.end(), rsh_comp_helper(from, ip, port)) != l.end();
172 }
173 
174 /**
175  *  Checks if this host is allowed entry by this rule list.
176  *  return values:
177  *    1 - User is allowed
178  *    0 - No matching hosts found
179  *   -1 - User is banned - 'buff' is filled w/ the reason..
180  *        for the case of the allowed_ruleset class, it will only be returned
181  *        in case user limit for rule set was exceeded
182  *
183  *  buff = buffer to hold reason for banning, if any.
184  *
185  */
is_allowed(const char * address,const char * ip,unsigned short port,char * buff,size_t bufflen) const186 int allowed_ruleset::is_allowed(const char * address, const char * ip, unsigned short port,
187                                  char *buff, size_t bufflen) const
188 {
189 	std::vector<rs_host>::const_iterator i,
190 			beg = from_hosts.begin(),
191 			end = from_hosts.end();
192     	rsh_comp_helper h(address, ip, port);
193 	i = beg;
194 
195 	while (i != end) {
196 		i = std::find(i, end, h);
197 		if (i != end) {
198 			const rs_host& r = *i;
199 			if (r.num >= r.max && r.max != UNLIMITED) {
200 				my_strlcpy(buff, "No more connections allowed from your host", bufflen);
201 				return -1;
202 			}
203 			else if (r.num < r.max || r.max == UNLIMITED) {
204 				return 1;
205 			}
206 		}
207 	}
208 	return 0;
209 }
210 
211 /**
212  * Like above but checks if it 'from' may connect to 'to' on port.
213  * Even though there is a 'from' argument, we assume that the caller
214  * belongs to this rule set (for now)..
215  */
is_allowed_to(const char * to,const char * to_ip,unsigned short port,char * buff,size_t bufflen) const216 int allowed_ruleset::is_allowed_to(const char * to, const char * to_ip,
217 					unsigned short port,
218 					char *buff, size_t bufflen) const
219 {
220 	std::vector<rs_host>::const_iterator i,
221 			beg = to_hosts.begin(),
222 			end = to_hosts.end();
223 	rsh_comp_helper h(to, to_ip, port);
224 	i = beg;
225 
226 	while (i != end) {
227 		i = std::find(i, end, h);
228 		if (i != end) {
229 			const rs_host& r = *i;
230 			if (r.num >= r.max && r.max != UNLIMITED) {
231 				my_strlcpy(buff, "No more connections allowed to destination from your host", bufflen);
232 				return -1;
233 			}
234 			else if (r.num < r.max || r.max == UNLIMITED) {
235 				return 1;
236 			}
237 		}
238 	}
239 	return 0;
240 }
241 
242 /**
243  *  Return values by register_connection and friends:
244  *  0 - success
245  * -1 - full ?
246  */
register_connection(host_type t,const char * host,const char * ip,unsigned short port)247 int allowed_ruleset::register_connection(host_type t, const char * host, const char *ip,
248                                           unsigned short port)
249 {
250 	std::vector<rs_host>& target = (t == FROM) ? from_hosts : to_hosts;
251 	std::vector<rs_host>::iterator i,
252 			beg = target.begin(),
253 			end = target.end();
254 	rsh_comp_helper h(host, ip, port);
255 	i = beg;
256 
257 	while (true) {
258 		i = std::find(i, end, h);
259 		if (i == end) {
260 			break;
261 		}
262 
263 		rs_host& r = *i;
264 		if (r.num >= r.max && r.max != UNLIMITED) {
265 			/** check again if limit exceeded **/
266 			return -1;
267 		}
268 		else {
269 			++r.num;
270 	                return ruleset::register_connection(t, host, ip, port);
271 		}
272 	}
273 	return 0;
274 }
275 
unregister_connection(host_type t,const char * host,const char * ip,unsigned short port)276 int allowed_ruleset::unregister_connection(host_type t, const char * host, const char * ip,
277 						unsigned short port)
278 {
279 	std::vector<rs_host>& target = (t == FROM) ? from_hosts : to_hosts;
280 	std::vector<rs_host>::iterator i,
281 				beg = target.begin(),
282 				end = target.end();
283 	rsh_comp_helper h(host, ip, port);
284 	i = beg;
285 
286 	while (true) {
287 		i = std::find(i, end, h);
288 		if (i == end) {
289 			break;
290 		}
291 
292 		rs_host& r = *i;
293 		--r.num;
294 		return ruleset::unregister_connection(t, host, ip, port);
295 	}
296 	return 0;
297 }
298 
is_legal() const299 bool allowed_ruleset::is_legal() const
300 {
301 	return (from_hosts.size() > 0 && to_hosts.size() > 0);
302 }
303 
304 /*
305  *  denied_ruleset -- syntax is basically same but:
306  *
307  *     * A from w/o a to or vice versa is permitted
308  *     * In a rule set w/ no from entries is_allowed will always
309  *        return 1. is_allowed_to will check as usual. does_match() will
310  *        also always return 1 in such case.
311  *     * max # nubmers are ignored here
312  *     * If a rule set has both from and tos, the tos are only
313  *        applied to the from addresses. The addresses in the form fields
314  *        will be granted passage
315  */
316 
317 /**
318  *  NOTE: in the case that there are no entries of type 't',
319  *        1 will be returned.
320  */
does_match(const char * host,const char * ip,unsigned short port,host_type t) const321 bool denied_ruleset::does_match(const char * host, const char * ip,
322 				unsigned short port, host_type t) const
323 {
324 	const std::vector<rs_host> * l = ((t == FROM) ? &from_hosts : &to_hosts);
325 	if (l->empty()) {
326 		return true;
327 	}
328 	return std::find(l->begin(), l->end(), rsh_comp_helper(host, ip, port)) != l->end();
329 }
330 
331 /**
332  *  Returns:
333  *    -1 - if banned
334  *     0 - not found in list (not banned)
335  */
is_allowed(const char * host,const char * ip,unsigned short port,char * buff,size_t bufflen) const336 int denied_ruleset::is_allowed(const char * host, const char * ip, unsigned short port,
337                                 char *buff, size_t bufflen) const
338 {
339 	std::vector<rs_host>::const_iterator i,
340 			beg = from_hosts.begin(),
341 			end = from_hosts.end();
342     	rsh_comp_helper h(host, ip, port);
343 	i = beg;
344 
345 	while (i != end) {
346 		i = std::find(i, end, h);
347 		if (i != end) {
348 			my_strlcpy(buff, (*i).reason , bufflen);
349 			return -1;
350 		}
351 	}
352 	return 0;
353 }
354 
355 /**
356  *  return:
357  *     -1: not allowed to connect
358  *      0: not in list, does not necesssarily mean he may connect there tho
359  */
is_allowed_to(const char * to,const char * to_ip,unsigned short port,char * buff,size_t bufflen) const360 int denied_ruleset::is_allowed_to(const char * to, const char * to_ip, unsigned short port,
361                                    char *buff, size_t bufflen) const
362 {
363 	std::vector<rs_host>::const_iterator i,
364 			beg = to_hosts.begin(),
365 			end = to_hosts.end();
366     	rsh_comp_helper h(to, to_ip, port);
367 	i = beg;
368 
369 	while (i != end)
370 	{
371 		i = std::find(i, end, h);
372 		if (i != end) {
373 			my_strlcpy(buff, (*i).reason , bufflen);
374 			return -1;
375 		}
376 	}
377 	return 0;
378 }
379 
is_legal() const380 bool denied_ruleset::is_legal() const
381 {
382 	return (!to_hosts.empty() || !from_hosts.empty());
383 }
384 
385 /**
386  * Compare two rulesets
387  * Ignores num_registered_xxx and obsolete and rs_host.num
388  * Note! - Since this operates on the ruleset base class, there
389  *     is no way of knowing for sure if the *type* of the object we are being compared
390  *     to is the same type as us. (Actually each rs_host of an Allowed_ruleset has
391  *     reason set to NULL, and each denined_ruleset should have it set to something
392  *     other than that, because the parsing code will give 'No reason was given!'..)
393  *
394  */
operator ==(const ruleset & r2) const395 bool ruleset::operator == (const ruleset& r2) const
396 {
397 
398 	if (! (from_hosts.size() == r2.from_hosts.size()) ||
399 		! (to_hosts.size() == r2.to_hosts.size())) {
400 		return false;
401 	}
402 	using std::vector;
403 
404 	const vector<rs_host> *vec1, *vec2;
405 
406 	vec1 = &from_hosts;
407 	vec2 = &r2.from_hosts;
408 	for (int j = 0; j < 2; ++j) {
409 		vector<const rs_host *> seen;
410 		for (vector<rs_host>::const_iterator i = vec1->begin(), e = vec1->end(); i != e; ++i) {
411 			vector<rs_host>::const_iterator ptr = vec2->begin();
412 
413 			while (ptr != vec2->end()) {
414 				vector<rs_host>::const_iterator match = std::find(ptr, vec2->end(), *i);
415 				if (match == vec2->end()) {
416 					return false;
417 				}
418 
419 				vector<const rs_host *>::const_iterator
420 					pos_seen = std::find(seen.begin(), seen.end(), &(*match));
421 				if (pos_seen != seen.end()) {
422 					++ptr;
423 					continue;
424 				}
425 				seen.push_back(&(*match));
426 				break;
427 			}
428 
429 		}
430 
431 		// repeat for 'to' hosts
432 		vec1 = &to_hosts;
433 		vec2 = &r2.to_hosts;
434 	}
435 
436 	return true;
437 }
438 
~rs_host()439 ruleset::rs_host::~rs_host()
440 {
441 	delete[] reason;
442 	delete[] pattern;
443 	delete[] ports;
444 	DEBUG("\trs_host::~rs_host(): [%p] destructed\n", this);
445 }
446 
rs_host(host_type _type,const char * _addr,const char * _ports,const char * _reason,int _max)447 ruleset::rs_host::rs_host(host_type _type, const char *_addr,
448                            const char *_ports , const char *_reason, int _max) :
449 			   	pattern(my_strdup(_addr)),
450 				ports(my_strdup(_ports)),
451 			   	reason(my_strdup(_reason)),
452 			   	type(_type), max(_max),
453 				num(0)
454 {
455 	DEBUG("\trs_host::rs_host(): [%p] created -- type %d, addr: %s, ports: %s, max: %d reason: %s\n",
456 				this, type, pattern,
457 				ports, max, reason);
458 }
459 
rs_host(const ruleset::rs_host & that)460 ruleset::rs_host::rs_host(const ruleset::rs_host &that)
461 {
462 	DEBUG("rs_host::[copy constructor] invoked for [%p]\n", this);
463 	pattern = my_strdup(that.pattern);
464 	ports = my_strdup(that.ports);
465 	reason = my_strdup(that.reason);
466 	type = that.type;
467 	max = that.max;
468 	num = that.num;
469 }
470 
471 
register_connection(host_type t,const char *,const char *,unsigned short)472 int ruleset::register_connection(host_type t, const char *, const char *, unsigned short)
473 {
474 	DEBUG("Increasing %s usage count of %p to %d\n", ((t == FROM) ? "FROM" : "TO"), this, (t == FROM ? num_registered_from + 1 : num_registered_to + 1));
475 	return (t == FROM) ? ++num_registered_from : ++num_registered_to;
476 }
477 
unregister_connection(host_type t,const char *,const char *,unsigned short)478 int ruleset::unregister_connection(host_type t, const char *, const char *, unsigned short)
479 {
480 	DEBUG("Decreasing %s usage count of %p to %d\n", ((t == FROM) ? "FROM" : "TO"), this,(t == FROM ? num_registered_from - 1 : num_registered_to - 1));
481 	return (t == FROM) ? --num_registered_from : --num_registered_to;
482 }
483 
add_host_to(const char * address_to,const char * ports,const char * reason,int max_num)484 int ruleset::add_host_to(const char *address_to, const char *ports,
485                                    const char * reason, int max_num)
486 {
487 	to_hosts.push_back(rs_host(TO, address_to, ports, reason, max_num));
488 	return to_hosts.size();
489 }
490 
add_host_from(const char * address_from,const char * ports,const char * reason,int max_num)491 int ruleset::add_host_from(const char *address_from, const char *ports,
492                                      const char * reason, int max_num)
493 {
494 	from_hosts.push_back(rs_host(FROM, address_from, ports, reason, max_num));
495 	return from_hosts.size();
496 }
497 
498 
499 /**
500  * Used during rehash.
501  * if a new ruleset:
502  *      doesn't exist in the old set, move it over to new list
503  *      exists in the old list re-add the old one
504  * if an old ruleset:
505  *      doesn't exist in the new set:
506  *              if it's being used: mark as obsolete, add
507  *              if it's not being used: destroy it
508  *  post conditions:
509  *      newlist -> will be empty; all contents either destroyed
510  *                  or moved to list3
511  *      oldlist -> empty
512  *      list3   -> ready for use
513  **/
sync_lists(std::vector<ruleset * > * oldlist,std::vector<ruleset * > * newlist)514 std::vector<ruleset *> * ruleset::sync_lists(std::vector<ruleset *> * oldlist, std::vector<ruleset *> * newlist)
515 {
516 	DEBUG("ruleset::sync_lists(): entering\n");
517 	using std::vector;
518 	vector<ruleset *> * out = new vector<ruleset *>();
519 
520 	for (vector<ruleset *>::iterator i = oldlist->begin();
521 		i != oldlist->end();
522 		i = oldlist->erase(i)) {
523 
524 		ruleset * r = *i;
525 
526 		for (vector<ruleset *>::iterator i2 = newlist->begin();
527 				i2 != newlist->end();
528 				++i2) {
529 			ruleset * r2 = *i2;
530 			if (*r == *r2) {
531 				/* Old ruleset exists in new list.
532 				 * Delete new one, transfer old on to out list. */
533                 		DEBUG("         RULESET: Found match for %p == %p, keeping\n", r, r2);
534 				delete r2;
535 				newlist->erase(i2);
536 				out->push_back(r);
537 				goto next;
538 			}
539 		}
540 
541 		/* Ruleset no longer exists in the configuration */
542 		if (r->num_registered_from == 0 && r->num_registered_to == 0) {
543 			DEBUG("		RULESET: deleting non-matching: %p\n", r);
544 			delete r;
545 			continue;
546 		}
547 
548 		/* Final case: no longer exists, but we have
549 		 * users registered it */
550 		DEBUG("		RULESET: keeping ruleset as obsolete: %p\n", r);
551 		r->obsolete = true;
552 		out->push_back(r);
553 		next:;
554 	}
555 
556 	DEBUG("		RULESET: %zd more new rulesets to add\n", newlist->size());
557 	std::copy(newlist->begin(), newlist->end(), std::back_inserter(*out));
558 
559 	newlist->clear();
560 
561 	assert(newlist->empty());
562 	assert(oldlist->empty());
563 	return out;
564 }
565