Skip to content

Commit

Permalink
Add access rule for local networks per-interface
Browse files Browse the repository at this point in the history
  • Loading branch information
SimulPiscator committed Oct 10, 2023
1 parent b3fc1e9 commit f7a43f8
Show file tree
Hide file tree
Showing 6 changed files with 168 additions and 89 deletions.
20 changes: 12 additions & 8 deletions etc/access.conf
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,17 @@
# if it is a Deny entry.

# If no match occurs, a "Deny" will be the result. As an exception to
# this rule, an empty list will always result in an "Allow".
# this, an empty list will always result in an "Allow".

allow 127.0.0.0/8
allow 10.0.0.0/8
allow 172.16.0.0/12
allow 192.168.0.0/16
# This rule will match all local addresses on all interfaces:
allow local on *

allow ::1
allow fe80::/10
allow fec0::/10
# This rule will match all local addresses on interface enp3s0:
# allow local on enp3s0

# This rule will match a single client address:
# allow 192.168.1.50

# These rules will match a range of client addresses:
# allow 192.168.0.0/16
# allow fe80::/10
19 changes: 10 additions & 9 deletions server/server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include <cstdint>
#include <regex>
#include <sstream>
#include <iomanip>
#include <unistd.h>

#include "mainpage.h"
Expand Down Expand Up @@ -227,10 +228,17 @@ Server::run()

bool ok = false, done = false;
do {
AccessFile accessfile(mAccessfile);
if (!accessfile.errors().empty()) {
std::clog << "errors in accessfile:\n" << accessfile.errors() << " terminating" << std::endl;
return false;
}
HttpServer::applyAccessFile(accessfile);

struct timespec t = { 0 };
::clock_gettime(CLOCK_MONOTONIC, &t);
float t0 = 1.0 * t.tv_sec + 1e-9 * t.tv_nsec;
std::clog << "start time is " << t0 << std::endl;
std::clog << "start time is " << std::fixed << std::setprecision(2) << t0 << std::endl;

OptionsFile optionsfile(mOptionsfile);
std::clog << "enumerating " << (mLocalonly ? "local " : " ") << "devices..."
Expand Down Expand Up @@ -290,21 +298,14 @@ Server::run()
mScanners.push_back(ScannerEntry({ pScanner, pService }));
}
}
ok = true;
AccessFile accessfile(mAccessfile);
if (!accessfile.errors().empty()) {
std::clog << "errors in accessfile:\n" << accessfile.errors() << " terminating" << std::endl;
ok = false;
}
HttpServer::applyAccessFile(accessfile);

::clock_gettime(CLOCK_MONOTONIC, &t);
float t1 = 1.0 * t.tv_sec + 1e-9 * t.tv_nsec;
std::clog << "end time is " << t1 << std::endl;
mStartupTimeSeconds = t1 - t0;
std::clog << "startup took " << mStartupTimeSeconds << " secconds" << std::endl;

ok = ok && HttpServer::run();
ok = HttpServer::run();
mScanners.clear();
if (ok && terminationStatus() == SIGHUP) {
std::clog << "received SIGHUP, reloading" << std::endl;
Expand Down
124 changes: 96 additions & 28 deletions web/accessfile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

#include "accessfile.h"

#include <ifaddrs.h>
#include <fstream>
#include <sstream>
#include <cstring>
Expand Down Expand Up @@ -86,6 +88,7 @@ AccessFile::AccessFile(const std::string& path)
std::ifstream file(path);
if (!file.is_open())
return;
std::clog << "reading access rules from file " << path << std::endl;
std::string line;
while (std::getline(file, line)) {
while (!line.empty() && std::iswspace(line.front()))
Expand All @@ -99,7 +102,7 @@ AccessFile::AccessFile(const std::string& path)
std::istringstream iss(line);
Entry entry;
if (!entry.parse(iss))
mErrors += "Illegal entry: " + line + "\n";
mErrors += "illegal entry in access file: " + line + "\n";
else
mEntries.push_back(entry);
}
Expand All @@ -112,58 +115,123 @@ const std::string& AccessFile::errors() const

bool AccessFile::isAllowed(const HttpServer::Sockaddr& addr) const
{
if (mEntries.empty())
if (mEntries.empty()) {
std::clog << "allowing " << HttpServer::ipString(addr) << ": access file is empty" << std::endl;
return true;
}
for (const auto& entry : mEntries) {
int result = entry.match(addr);
if (result == Entry::Allow)
return true;
if (result == Entry::Deny)
return false;
}
std::clog << "denying " << HttpServer::ipString(addr) << ": no rules matched" << std::endl;
return false;
}

std::istream& AccessFile::Entry::parse(std::istream& is)
{
mNetworks.clear();

std::string kind, address;
is >> kind;
if (!::strcasecmp(kind.c_str(), "allow"))
if (!::strcasecmp(kind.c_str(), "allow")) {
mKind = Allow;
else if (!::strcasecmp(kind.c_str(), "deny"))
}
else if (!::strcasecmp(kind.c_str(), "deny")) {
mKind = Deny;
else
is.setstate(std::ios::failbit);
std::getline(is >> std::ws, address);
int bits = -1;
size_t pos = address.find_last_of("/");
if (pos != std::string::npos) {
bits = ::atoi(address.substr(pos + 1).c_str());
address = address.substr(0, pos);
}
if (::inet_pton(AF_INET, address.c_str(), &mAddress.in.sin_addr)) {
if (bits == -1)
bits = 32;
mAddress.sa.sa_family = AF_INET;
mMask = mAddress;
SetMaskBits(mMask, bits);
else {
std::cerr << "expected \"allow\" or \"deny\", got \"" << kind << "\"" << std::endl;
is.setstate(std::ios::failbit);
return is;
}
else if (::inet_pton(AF_INET6, address.c_str(), &mAddress.in6.sin6_addr)) {
if (bits == -1)
bits = 128;
mAddress.sa.sa_family = AF_INET6;
mMask = mAddress;
SetMaskBits(mMask, bits);

std::getline(is >> std::ws, mRule);
const std::string ifkeywords = "local on ";
if (mRule.find(ifkeywords) == 0) {
std::string ifname = mRule.substr(ifkeywords.length());
if (ifname.empty()) {
std::cerr << "expected an interface name, or *" << std::endl;
is.setstate(std::ios::failbit);
return is;
}
bool matchAll = (ifname == "*");
struct ifaddrs* pAddr;
if (::getifaddrs(&pAddr)) {
std::cerr << ::strerror(errno) << std::endl;
is.setstate(std::ios::failbit);
return is;
}
for (const ifaddrs* p = pAddr; p != nullptr; p = p->ifa_next) {
if (p->ifa_addr && (matchAll || ifname == p->ifa_name)) {
Network network;
switch (p->ifa_addr->sa_family) {
case AF_INET:
::memcpy(&network.address.sa, p->ifa_addr, sizeof(sockaddr_in));
::memcpy(&network.mask.sa, p->ifa_netmask, sizeof(sockaddr_in));
break;
case AF_INET6:
::memcpy(&network.address.sa, p->ifa_addr, sizeof(sockaddr_in6));
::memcpy(&network.mask.sa, p->ifa_netmask, sizeof(sockaddr_in6));
break;
}
mNetworks.push_back(network);
}
}
::freeifaddrs(pAddr);
if (mNetworks.empty()) {
std::cerr << "\"" << ifname << "\" does not match any network interfaces" << std::endl;
is.setstate(std::ios::failbit);
return is;
}
}
else {
is.setstate(std::ios::failbit);
Network network;
int bits = -1;
size_t pos = mRule.find_last_of("/");
std::string address;
if (pos != std::string::npos) {
bits = ::atoi(mRule.substr(pos + 1).c_str());
address = mRule.substr(0, pos);
}
else {
address = mRule;
}
if (::inet_pton(AF_INET, address.c_str(), &network.address.in.sin_addr)) {
if (bits == -1)
bits = 32;
network.address.sa.sa_family = AF_INET;
network.mask = network.address;
SetMaskBits(network.mask, bits);
}
else if (::inet_pton(AF_INET6, address.c_str(), &network.address.in6.sin6_addr)) {
if (bits == -1)
bits = 128;
network.address.sa.sa_family = AF_INET6;
network.mask = network.address;
SetMaskBits(network.mask, bits);
}
else {
std::cerr << "not an IP address: " << address << std::endl;
is.setstate(std::ios::failbit);
return is;
}
mNetworks.push_back(network);
}
return is;
}

int AccessFile::Entry::match(const HttpServer::Sockaddr &addr) const
{
if (!MatchAddresses(addr, mAddress, mMask))
return NoMatch;
return mKind;
for (const auto& network : mNetworks) {
if (MatchAddresses(addr, network.address, network.mask)) {
std::clog << ((mKind == Allow) ? "allowing " : "denying ")
<< HttpServer::ipString(addr) << ", matching rule: "
<< mRule << std::endl;
return mKind;
}
}
return NoMatch;
}
7 changes: 6 additions & 1 deletion web/accessfile.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,12 @@ class AccessFile
std::istream& parse(std::istream&);
private:
int mKind;
HttpServer::Sockaddr mAddress, mMask;
std::string mRule;
struct Network
{
HttpServer::Sockaddr address, mask;
};
std::vector<Network> mNetworks;
};
std::vector<Entry> mEntries;
std::string mErrors;
Expand Down
69 changes: 34 additions & 35 deletions web/httpserver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -112,46 +112,14 @@ urldecode(const std::string& s)
return r;
}

std::string
ipString(const HttpServer::Sockaddr& address)
{
char buf[128] = "n/a";
switch (address.sa.sa_family) {
case AF_INET:
::inet_ntop(AF_INET, &address.in.sin_addr, buf, sizeof(buf));
break;
case AF_INET6:
::strcpy(buf, "[");
::inet_ntop(AF_INET6, &address.in6.sin6_addr, buf + 1, sizeof(buf) - 2);
::strcat(buf, "]");
break;
case AF_UNIX:
::strcpy(buf, "unix");
break;
}
return buf;
}

uint16_t
portNumber(const HttpServer::Sockaddr& address)
{
switch (address.sa.sa_family) {
case AF_INET:
return ntohs(address.in.sin_port);
case AF_INET6:
return ntohs(address.in6.sin6_port);
}
return 0;
}

std::string
describeAddress(const HttpServer::Sockaddr& address)
{
std::ostringstream oss;
switch (address.sa.sa_family) {
case AF_INET:
case AF_INET6:
oss << ipString(address) << ":" << portNumber(address);
oss << HttpServer::ipString(address) << ":" << HttpServer::portNumber(address);
break;
case AF_UNIX:
oss << address.un.sun_path;
Expand Down Expand Up @@ -190,6 +158,38 @@ interfaceAddresses(const char* if_name)

} // namespace

std::string
HttpServer::ipString(const HttpServer::Sockaddr& address)
{
char buf[128] = "n/a";
switch (address.sa.sa_family) {
case AF_INET:
::inet_ntop(AF_INET, &address.in.sin_addr, buf, sizeof(buf));
break;
case AF_INET6:
::strncpy(buf, "[", sizeof(buf));
::inet_ntop(AF_INET6, &address.in6.sin6_addr, buf + 1, sizeof(buf) - 2);
::strncat(buf, "]", sizeof(buf) - strlen(buf) - 1);
break;
case AF_UNIX:
::strncpy(buf, "unix", sizeof(buf));
break;
}
return buf;
}

uint16_t
HttpServer::portNumber(const HttpServer::Sockaddr& address)
{
switch (address.sa.sa_family) {
case AF_INET:
return ntohs(address.in.sin_port);
case AF_INET6:
return ntohs(address.in6.sin6_port);
}
return 0;
}

struct HttpServer::Private
{

Expand Down Expand Up @@ -224,7 +224,7 @@ struct HttpServer::Private
if (mUnixSocket.length() >= sizeof(addr.un.sun_path))
err = ENAMETOOLONG;
else {
::strcpy(addr.un.sun_path, mUnixSocket.c_str());
::strncpy(addr.un.sun_path, mUnixSocket.c_str(), sizeof(addr.un.sun_path));
addresses.push_back(addr);
}
}
Expand Down Expand Up @@ -375,7 +375,6 @@ struct HttpServer::Private
void handleRequest(int fd, Sockaddr address)
{
if (!mAccessFile.isAllowed(address)) {
std::clog << "Blocked access from " + ipString(address) << std::endl;
::close(fd);
return;
}
Expand Down
Loading

0 comments on commit f7a43f8

Please sign in to comment.