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