Added a parameter to semaphore constructor to avoid ambiguity
[pwlib.git] / src / ptclib / ipacl.cxx
blobf69bb89eb723f8752c473435e7505ffca1373a31
1 /*
2 * ipacl.cxx
4 * IP Access Control Lists
6 * Portable Windows Library
8 * Copyright (c) 2002 Equivalence Pty. Ltd.
10 * The contents of this file are subject to the Mozilla Public License
11 * Version 1.0 (the "License"); you may not use this file except in
12 * compliance with the License. You may obtain a copy of the License at
13 * http://www.mozilla.org/MPL/
15 * Software distributed under the License is distributed on an "AS IS"
16 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
17 * the License for the specific language governing rights and limitations
18 * under the License.
20 * The Original Code is Portable Windows Library.
22 * The Initial Developer of the Original Code is Equivalence Pty. Ltd.
24 * Contributor(s): ______________________________________.
26 * $Log$
27 * Revision 1.14 2002/11/06 22:47:25 robertj
28 * Fixed header comment (copyright etc)
30 * Revision 1.13 2002/07/16 10:05:01 robertj
31 * Fixed GNU warning
33 * Revision 1.12 2002/07/16 10:02:49 robertj
34 * Fixed MSVC warning
36 * Revision 1.11 2002/07/16 09:58:51 robertj
37 * Fixed compatibility with unix htonl(), use platform independent function!
38 * Allow number of bits of 32 to be a full mask, ie a single host ip.
40 * Revision 1.10 2002/07/16 08:00:49 robertj
41 * Fixed correct endian-ness of mask when specifed in bits.
43 * Revision 1.9 2002/06/19 04:03:21 robertj
44 * Added default allowance boolean if ACL empty.
45 * Added ability to override the creation of ACL entry objects with descendents
46 * so an application can add information/functionality to each entry.
48 * Revision 1.8 2002/02/13 02:08:12 robertj
49 * Added const to IsAllowed() function.
50 * Added missing function that takes a socket.
52 * Revision 1.7 1999/02/25 13:01:11 robertj
53 * Fixed subtle bug in GNU compiler not automatically casting IP address.
55 * Revision 1.6 1999/02/25 11:10:52 robertj
56 * Fixed count of non-hidden entries in config file.
58 * Revision 1.5 1999/02/25 05:05:15 robertj
59 * Added missing test for hidden entries not to be written to config file
61 * Revision 1.4 1999/02/08 08:05:39 robertj
62 * Changed semantics of IP access control list for empty list.
64 * Revision 1.3 1999/01/31 10:14:07 robertj
65 * Changed about dialog to be full company name
67 * Revision 1.2 1999/01/31 08:10:33 robertj
68 * Fixed PConfig file save, out by one error in array subscript.
70 * Revision 1.1 1999/01/31 00:59:26 robertj
71 * Added IP Access Control List class to PTLib Components
75 #include <ptlib.h>
76 #include <ptclib/ipacl.h>
78 #define new PNEW
81 PIpAccessControlEntry::PIpAccessControlEntry(PIPSocket::Address addr,
82 PIPSocket::Address msk,
83 BOOL allow)
84 : address(addr), mask(msk)
86 allowed = allow;
87 hidden = FALSE;
91 PIpAccessControlEntry::PIpAccessControlEntry(const PString & description)
92 : address(0), mask(0xffffffff)
94 Parse(description);
98 PIpAccessControlEntry & PIpAccessControlEntry::operator=(const PString & description)
100 Parse(description);
101 return *this;
105 PIpAccessControlEntry & PIpAccessControlEntry::operator=(const char * description)
107 Parse(description);
108 return *this;
112 PObject::Comparison PIpAccessControlEntry::Compare(const PObject & obj) const
114 PAssert(obj.IsDescendant(Class()), PInvalidCast);
115 const PIpAccessControlEntry & other = (const PIpAccessControlEntry &)obj;
117 // The larger the mask value, th more specific the range, so earlier in list
118 if (mask > other.mask)
119 return LessThan;
120 if (mask < other.mask)
121 return GreaterThan;
123 if (!domain && !other.domain)
124 return domain.Compare(other.domain);
126 if (address > other.address)
127 return LessThan;
128 if (address < other.address)
129 return GreaterThan;
131 return EqualTo;
135 void PIpAccessControlEntry::PrintOn(ostream & strm) const
137 if (!allowed)
138 strm << '-';
140 if (hidden)
141 strm << '@';
143 if (domain.IsEmpty())
144 strm << address;
145 else if (domain[0] != '\xff')
146 strm << domain;
147 else {
148 strm << "ALL";
149 return;
152 if (mask != 0 && mask != 0xffffffff)
153 strm << '/' << mask;
157 void PIpAccessControlEntry::ReadFrom(istream & strm)
159 char buffer[200];
160 strm >> ws >> buffer;
161 Parse(buffer);
165 BOOL PIpAccessControlEntry::Parse(const PString & description)
167 domain = PString();
168 address = 0;
170 if (description.IsEmpty())
171 return FALSE;
173 // Check for the allow/deny indication in first character of description
174 BOOL offset = 1;
175 if (description[0] == '-')
176 allowed = FALSE;
177 else {
178 allowed = TRUE;
179 if (description[0] != '+')
180 offset = 0;
183 // Check for indication entry is from the hosts.allow/hosts.deny file
184 hidden = FALSE;
185 if (description[offset] == '@') {
186 offset++;
187 hidden = TRUE;
190 if (description.Mid(offset) *= "all") {
191 domain = "\xff";
192 mask = 0;
193 return TRUE;
196 PINDEX slash = description.Find('/', offset);
198 PString preSlash = description(offset, slash-1);
199 if (preSlash[0] == '.') {
200 // If has a leading dot then assume a domain, ignore anything after slash
201 domain = preSlash;
202 mask = 0;
203 return TRUE;
206 if (strspn(preSlash, "0123456789.") != (size_t)preSlash.GetLength()) {
207 // If is not all numbers and dots can't be an IP number so assume hostname
208 domain = preSlash;
210 else if (preSlash[preSlash.GetLength()-1] != '.') {
211 // Must be explicit IP number if doesn't end in dot
212 address = preSlash;
214 else {
215 // Must be partial IP number, count the dots!
216 PINDEX dot = preSlash.Find('.', preSlash.Find('.')+1);
217 if (dot == P_MAX_INDEX) {
218 // One dot
219 preSlash += "0.0.0";
220 mask = "255.0.0.0";
222 else if ((dot = preSlash.Find('.', dot+1)) == P_MAX_INDEX) {
223 // has two dots
224 preSlash += "0.0";
225 mask = "255.255.0.0";
227 else if (preSlash.Find('.', dot+1) == P_MAX_INDEX) {
228 // has three dots
229 preSlash += "0";
230 mask = "255.255.255.0";
232 else {
233 // Has more than three dots!
234 return FALSE;
237 address = preSlash;
238 return TRUE;
241 if (slash == P_MAX_INDEX) {
242 // No slash so assume a full mask
243 mask = 0xffffffff;
244 return TRUE;
247 PString postSlash = description.Mid(slash+1);
248 if (strspn(postSlash, "0123456789.") != (size_t)postSlash.GetLength()) {
249 domain = PString();
250 address = 0;
251 return FALSE;
254 if (postSlash.Find('.') != P_MAX_INDEX)
255 mask = postSlash;
256 else {
257 DWORD bits = postSlash.AsUnsigned();
258 if (bits > 32)
259 mask = PSocket::Host2Net(bits);
260 else
261 mask = PSocket::Host2Net((DWORD)(0xffffffff << (32 - bits)));
264 if (mask == 0)
265 domain = "\xff";
267 address = (DWORD)address & (DWORD)mask;
269 return TRUE;
273 PString PIpAccessControlEntry::AsString() const
275 PStringStream str;
276 str << *this;
277 return str;
281 BOOL PIpAccessControlEntry::IsValid()
283 return address != 0 || !domain;
287 BOOL PIpAccessControlEntry::Match(PIPSocket::Address & addr)
289 switch (domain[0]) {
290 case '\0' : // Must have address field set
291 break;
293 case '.' : // Are a domain name
294 return PIPSocket::GetHostName(addr).Right(domain.GetLength()) *= domain;
296 case '\xff' : // Match all
297 return TRUE;
299 default : // All else must be a hostname
300 if (!PIPSocket::GetHostAddress(domain, address))
301 return FALSE;
304 return (address & mask) == (addr & mask);
308 ///////////////////////////////////////////////////////////////////////////////
310 PIpAccessControlList::PIpAccessControlList(BOOL defAllow)
311 : defaultAllowance(defAllow)
316 static BOOL ReadConfigFileLine(PTextFile & file, PString & line)
318 line = PString();
320 do {
321 if (!file.ReadLine(line))
322 return FALSE;
323 } while (line.IsEmpty() || line[0] == '#');
325 PINDEX lastCharPos;
326 while (line[lastCharPos = line.GetLength()-1] == '\\') {
327 PString str;
328 if (!file.ReadLine(str))
329 return FALSE;
330 line[lastCharPos] = ' ';
331 line += str;
334 return TRUE;
338 static void ParseConfigFileExcepts(const PString & str,
339 PStringList & entries,
340 PStringList & exceptions)
342 PStringArray terms = str.Tokenise(' ', FALSE);
344 BOOL hadExcept = FALSE;
345 PINDEX d;
346 for (d = 0; d < terms.GetSize(); d++) {
347 if (terms[d] == "EXCEPT")
348 hadExcept = TRUE;
349 else if (hadExcept)
350 exceptions.AppendString(terms[d]);
351 else
352 entries.AppendString(terms[d]);
357 static BOOL SplitConfigFileLine(const PString & line, PString & daemons, PString & clients)
359 PINDEX colon = line.Find(':');
360 if (colon == P_MAX_INDEX)
361 return FALSE;
363 daemons = line.Left(colon).Trim();
365 PINDEX other_colon = line.Find(':', ++colon);
366 clients = line(colon, other_colon-1).Trim();
368 return TRUE;
372 static BOOL IsDaemonInConfigFileLine(const PString & daemon, const PString & daemons)
374 PStringList daemonsIn, daemonsOut;
375 ParseConfigFileExcepts(daemons, daemonsIn, daemonsOut);
377 for (PINDEX in = 0; in < daemonsIn.GetSize(); in++) {
378 if (daemonsIn[in] == "ALL" || daemonsIn[in] == daemon) {
379 PINDEX out;
380 for (out = 0; out < daemonsOut.GetSize(); out++) {
381 if (daemonsOut[out] == daemon)
382 break;
384 if (out >= daemonsOut.GetSize())
385 return TRUE;
389 return FALSE;
393 static BOOL ReadConfigFile(PTextFile & file,
394 const PString & daemon,
395 PStringList & clientsIn,
396 PStringList & clientsOut)
398 PString line;
399 while (ReadConfigFileLine(file, line)) {
400 PString daemons, clients;
401 if (SplitConfigFileLine(line, daemons, clients) &&
402 IsDaemonInConfigFileLine(daemon, daemons)) {
403 ParseConfigFileExcepts(clients, clientsIn, clientsOut);
404 return TRUE;
408 return FALSE;
412 BOOL PIpAccessControlList::InternalLoadHostsAccess(const PString & daemonName,
413 const char * filename,
414 BOOL allowance)
416 PTextFile file;
417 if (!file.Open(PProcess::GetOSConfigDir() + filename, PFile::ReadOnly))
418 return TRUE;
420 BOOL ok = TRUE;
422 PStringList clientsIn;
423 PStringList clientsOut;
424 while (ReadConfigFile(file, daemonName, clientsIn, clientsOut)) {
425 PINDEX i;
426 for (i = 0; i < clientsOut.GetSize(); i++) {
427 if (!Add((allowance ? "-@" : "+@") + clientsOut[i]))
428 ok = FALSE;
430 for (i = 0; i < clientsIn.GetSize(); i++) {
431 if (!Add((allowance ? "+@" : "-@") + clientsIn[i]))
432 ok = FALSE;
436 return ok;
440 BOOL PIpAccessControlList::LoadHostsAccess(const char * daemonName)
442 PString daemon;
443 if (daemonName != NULL)
444 daemon = daemonName;
445 else
446 daemon = PProcess::Current().GetName();
448 return InternalLoadHostsAccess(daemon, "hosts.allow", TRUE) & // Really is a single &
449 InternalLoadHostsAccess(daemon, "hosts.deny", FALSE);
453 static const char DefaultConfigName[] = "IP Access Control List";
455 BOOL PIpAccessControlList::Load(PConfig & cfg)
457 return Load(cfg, DefaultConfigName);
461 BOOL PIpAccessControlList::Load(PConfig & cfg, const PString & baseName)
463 BOOL ok = TRUE;
464 PINDEX count = cfg.GetInteger(baseName & "Array Size");
465 for (PINDEX i = 1; i <= count; i++) {
466 if (!Add(cfg.GetString(baseName & PString(PString::Unsigned, i))))
467 ok = FALSE;
470 return ok;
474 void PIpAccessControlList::Save(PConfig & cfg)
476 Save(cfg, DefaultConfigName);
480 void PIpAccessControlList::Save(PConfig & cfg, const PString & baseName)
482 PINDEX count = 0;
484 for (PINDEX i = 0; i < GetSize(); i++) {
485 PIpAccessControlEntry & entry = operator[](i);
486 if (!entry.IsHidden()) {
487 count++;
488 cfg.SetString(baseName & PString(PString::Unsigned, count), entry.AsString());
492 cfg.SetInteger(baseName & "Array Size", count);
496 BOOL PIpAccessControlList::Add(PIpAccessControlEntry * entry)
498 if (!entry->IsValid()) {
499 delete entry;
500 return FALSE;
503 PINDEX idx = GetValuesIndex(*entry);
504 if (idx == P_MAX_INDEX) {
505 Append(entry);
506 return TRUE;
509 // Return TRUE if the newly added entry is identical to an existing one
510 PIpAccessControlEntry & existing = operator[](idx);
511 BOOL ok = existing.IsClass(PIpAccessControlEntry::Class()) &&
512 entry->IsClass(PIpAccessControlEntry::Class()) &&
513 existing.IsAllowed() == entry->IsAllowed();
515 delete entry;
516 return ok;
520 BOOL PIpAccessControlList::Add(const PString & description)
522 return Add(CreateControlEntry(description));
526 BOOL PIpAccessControlList::Add(PIPSocket::Address addr, PIPSocket::Address mask, BOOL allow)
528 PStringStream description;
529 description << (allow ? '+' : '-') << addr << '/' << mask;
530 return Add(description);
534 BOOL PIpAccessControlList::Remove(const PString & description)
536 PIpAccessControlEntry entry(description);
538 if (!entry.IsValid())
539 return FALSE;
541 return InternalRemoveEntry(entry);
545 BOOL PIpAccessControlList::Remove(PIPSocket::Address addr, PIPSocket::Address mask)
547 PIpAccessControlEntry entry(addr, mask, TRUE);
548 return InternalRemoveEntry(entry);
552 BOOL PIpAccessControlList::InternalRemoveEntry(PIpAccessControlEntry & entry)
554 PINDEX idx = GetValuesIndex(entry);
555 if (idx == P_MAX_INDEX)
556 return FALSE;
558 RemoveAt(idx);
559 return TRUE;
563 PIpAccessControlEntry * PIpAccessControlList::CreateControlEntry(const PString & description)
565 return new PIpAccessControlEntry(description);
569 PIpAccessControlEntry * PIpAccessControlList::Find(PIPSocket::Address address) const
571 PINDEX size = GetSize();
572 if (size == 0)
573 return NULL;
575 for (PINDEX i = 0; i < GetSize(); i++) {
576 PIpAccessControlEntry & entry = operator[](i);
577 if (entry.Match(address))
578 return &entry;
581 return NULL;
585 BOOL PIpAccessControlList::IsAllowed(PTCPSocket & socket) const
587 if (IsEmpty())
588 return defaultAllowance;
590 PIPSocket::Address address;
591 if (socket.GetPeerAddress(address))
592 return IsAllowed(address);
594 return FALSE;
598 BOOL PIpAccessControlList::IsAllowed(PIPSocket::Address address) const
600 if (IsEmpty())
601 return defaultAllowance;
603 PIpAccessControlEntry * entry = Find(address);
604 if (entry == NULL)
605 return FALSE;
607 return entry->IsAllowed();
611 // End of File ///////////////////////////////////////////////////////////////