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
20 * The Original Code is Portable Windows Library.
22 * The Initial Developer of the Original Code is Equivalence Pty. Ltd.
24 * Contributor(s): ______________________________________.
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
33 * Revision 1.12 2002/07/16 10:02:49 robertj
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
76 #include <ptclib/ipacl.h>
81 PIpAccessControlEntry::PIpAccessControlEntry(PIPSocket::Address addr
,
82 PIPSocket::Address msk
,
84 : address(addr
), mask(msk
)
91 PIpAccessControlEntry::PIpAccessControlEntry(const PString
& description
)
92 : address(0), mask(0xffffffff)
98 PIpAccessControlEntry
& PIpAccessControlEntry::operator=(const PString
& description
)
105 PIpAccessControlEntry
& PIpAccessControlEntry::operator=(const char * description
)
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
)
120 if (mask
< other
.mask
)
123 if (!domain
&& !other
.domain
)
124 return domain
.Compare(other
.domain
);
126 if (address
> other
.address
)
128 if (address
< other
.address
)
135 void PIpAccessControlEntry::PrintOn(ostream
& strm
) const
143 if (domain
.IsEmpty())
145 else if (domain
[0] != '\xff')
152 if (mask
!= 0 && mask
!= 0xffffffff)
157 void PIpAccessControlEntry::ReadFrom(istream
& strm
)
160 strm
>> ws
>> buffer
;
165 BOOL
PIpAccessControlEntry::Parse(const PString
& description
)
170 if (description
.IsEmpty())
173 // Check for the allow/deny indication in first character of description
175 if (description
[0] == '-')
179 if (description
[0] != '+')
183 // Check for indication entry is from the hosts.allow/hosts.deny file
185 if (description
[offset
] == '@') {
190 if (description
.Mid(offset
) *= "all") {
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
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
210 else if (preSlash
[preSlash
.GetLength()-1] != '.') {
211 // Must be explicit IP number if doesn't end in dot
215 // Must be partial IP number, count the dots!
216 PINDEX dot
= preSlash
.Find('.', preSlash
.Find('.')+1);
217 if (dot
== P_MAX_INDEX
) {
222 else if ((dot
= preSlash
.Find('.', dot
+1)) == P_MAX_INDEX
) {
225 mask
= "255.255.0.0";
227 else if (preSlash
.Find('.', dot
+1) == P_MAX_INDEX
) {
230 mask
= "255.255.255.0";
233 // Has more than three dots!
241 if (slash
== P_MAX_INDEX
) {
242 // No slash so assume a full mask
247 PString postSlash
= description
.Mid(slash
+1);
248 if (strspn(postSlash
, "0123456789.") != (size_t)postSlash
.GetLength()) {
254 if (postSlash
.Find('.') != P_MAX_INDEX
)
257 DWORD bits
= postSlash
.AsUnsigned();
259 mask
= PSocket::Host2Net(bits
);
261 mask
= PSocket::Host2Net((DWORD
)(0xffffffff << (32 - bits
)));
267 address
= (DWORD
)address
& (DWORD
)mask
;
273 PString
PIpAccessControlEntry::AsString() const
281 BOOL
PIpAccessControlEntry::IsValid()
283 return address
!= 0 || !domain
;
287 BOOL
PIpAccessControlEntry::Match(PIPSocket::Address
& addr
)
290 case '\0' : // Must have address field set
293 case '.' : // Are a domain name
294 return PIPSocket::GetHostName(addr
).Right(domain
.GetLength()) *= domain
;
296 case '\xff' : // Match all
299 default : // All else must be a hostname
300 if (!PIPSocket::GetHostAddress(domain
, address
))
304 return (address
& mask
) == (addr
& mask
);
308 ///////////////////////////////////////////////////////////////////////////////
310 PIpAccessControlList::PIpAccessControlList(BOOL defAllow
)
311 : defaultAllowance(defAllow
)
316 static BOOL
ReadConfigFileLine(PTextFile
& file
, PString
& line
)
321 if (!file
.ReadLine(line
))
323 } while (line
.IsEmpty() || line
[0] == '#');
326 while (line
[lastCharPos
= line
.GetLength()-1] == '\\') {
328 if (!file
.ReadLine(str
))
330 line
[lastCharPos
] = ' ';
338 static void ParseConfigFileExcepts(const PString
& str
,
339 PStringList
& entries
,
340 PStringList
& exceptions
)
342 PStringArray terms
= str
.Tokenise(' ', FALSE
);
344 BOOL hadExcept
= FALSE
;
346 for (d
= 0; d
< terms
.GetSize(); d
++) {
347 if (terms
[d
] == "EXCEPT")
350 exceptions
.AppendString(terms
[d
]);
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
)
363 daemons
= line
.Left(colon
).Trim();
365 PINDEX other_colon
= line
.Find(':', ++colon
);
366 clients
= line(colon
, other_colon
-1).Trim();
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
) {
380 for (out
= 0; out
< daemonsOut
.GetSize(); out
++) {
381 if (daemonsOut
[out
] == daemon
)
384 if (out
>= daemonsOut
.GetSize())
393 static BOOL
ReadConfigFile(PTextFile
& file
,
394 const PString
& daemon
,
395 PStringList
& clientsIn
,
396 PStringList
& clientsOut
)
399 while (ReadConfigFileLine(file
, line
)) {
400 PString daemons
, clients
;
401 if (SplitConfigFileLine(line
, daemons
, clients
) &&
402 IsDaemonInConfigFileLine(daemon
, daemons
)) {
403 ParseConfigFileExcepts(clients
, clientsIn
, clientsOut
);
412 BOOL
PIpAccessControlList::InternalLoadHostsAccess(const PString
& daemonName
,
413 const char * filename
,
417 if (!file
.Open(PProcess::GetOSConfigDir() + filename
, PFile::ReadOnly
))
422 PStringList clientsIn
;
423 PStringList clientsOut
;
424 while (ReadConfigFile(file
, daemonName
, clientsIn
, clientsOut
)) {
426 for (i
= 0; i
< clientsOut
.GetSize(); i
++) {
427 if (!Add((allowance
? "-@" : "+@") + clientsOut
[i
]))
430 for (i
= 0; i
< clientsIn
.GetSize(); i
++) {
431 if (!Add((allowance
? "+@" : "-@") + clientsIn
[i
]))
440 BOOL
PIpAccessControlList::LoadHostsAccess(const char * daemonName
)
443 if (daemonName
!= NULL
)
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
)
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
))))
474 void PIpAccessControlList::Save(PConfig
& cfg
)
476 Save(cfg
, DefaultConfigName
);
480 void PIpAccessControlList::Save(PConfig
& cfg
, const PString
& baseName
)
484 for (PINDEX i
= 0; i
< GetSize(); i
++) {
485 PIpAccessControlEntry
& entry
= operator[](i
);
486 if (!entry
.IsHidden()) {
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()) {
503 PINDEX idx
= GetValuesIndex(*entry
);
504 if (idx
== P_MAX_INDEX
) {
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();
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())
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
)
563 PIpAccessControlEntry
* PIpAccessControlList::CreateControlEntry(const PString
& description
)
565 return new PIpAccessControlEntry(description
);
569 PIpAccessControlEntry
* PIpAccessControlList::Find(PIPSocket::Address address
) const
571 PINDEX size
= GetSize();
575 for (PINDEX i
= 0; i
< GetSize(); i
++) {
576 PIpAccessControlEntry
& entry
= operator[](i
);
577 if (entry
.Match(address
))
585 BOOL
PIpAccessControlList::IsAllowed(PTCPSocket
& socket
) const
588 return defaultAllowance
;
590 PIPSocket::Address address
;
591 if (socket
.GetPeerAddress(address
))
592 return IsAllowed(address
);
598 BOOL
PIpAccessControlList::IsAllowed(PIPSocket::Address address
) const
601 return defaultAllowance
;
603 PIpAccessControlEntry
* entry
= Find(address
);
607 return entry
->IsAllowed();
611 // End of File ///////////////////////////////////////////////////////////////