mirror of
https://github.com/rjhansen/nsrlsvr.git
synced 2026-01-10 14:58:03 -05:00
Merge branch 'master' of github.com:rjhansen/nsrlsvr
This commit is contained in:
@@ -20,6 +20,7 @@ set(CPACK_RPM_PACKAGE_VERSION ${PACKAGE_VERSION})
|
||||
set(CPACK_PACKAGE_VERSION ${PACKAGE_VERSION})
|
||||
set(CPACK_PACKAGE_RELEASE "1")
|
||||
set(CPACK_RPM_PACKAGE_LICENSE "ISC")
|
||||
set(CPACK_RPM_PACKAGE_REQUIRES "libboost_program_options1_62_0 >= 1.62.0")
|
||||
set(CPACK_PACKAGE_DESCRIPTION_FILE "${CMAKE_CURRENT_SOURCE_DIR}/description.txt")
|
||||
set(CPACK_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}-${CPACK_PACKAGE_RELEASE}.${CMAKE_SYSTEM_PROCESSOR}")
|
||||
include(CPack)
|
||||
|
||||
@@ -1,43 +1,57 @@
|
||||
.Dd February 8, 2015
|
||||
.Dt NSRLSVR 1
|
||||
.Os
|
||||
.Sh NAME
|
||||
.Nm nsrlsvr
|
||||
.Nd server yielding hashes from NIST's NSRL RDS
|
||||
.Sh SYNOPSIS
|
||||
.Nm nsrlsvr
|
||||
.Op Fl b
|
||||
.Op Fl h
|
||||
.Op Fl v
|
||||
.Op Fl f Ar hash-file
|
||||
.Op Fl p Ar port
|
||||
.Sh DESCRIPTION
|
||||
nsrlsvr provides a daemon that services queries from clients requesting information
|
||||
about whether certain hash values are present in the NIST National Software Reference
|
||||
Laboratory Reference Data Set (NSRL RDS).
|
||||
.Sh OPTIONS
|
||||
.Bl -tag -width Ds
|
||||
.It Fl b
|
||||
show information on submitting bug reports, then exit
|
||||
.It Fl h
|
||||
show a help screen, then exit
|
||||
.It Fl v
|
||||
show version information, then exit
|
||||
.It Fl f Ar hash-file
|
||||
specify an alternate hash file in
|
||||
.Ar hash-file
|
||||
.It Fl p Ar port
|
||||
listen on port (default: 9120)
|
||||
.Ar port
|
||||
.El
|
||||
.Sh NOTES
|
||||
To support the full NSRL RDS requires a lot of memory. Although it will run on
|
||||
a 4Gb system, the results may be unsatisfactory. A 64-bit OS with at least 8Gb
|
||||
of RAM is recommended.
|
||||
.Pp
|
||||
.Sh BUGS
|
||||
.TH NSRLSVR 1 "October 27, 2016" "1.6.1"
|
||||
.SH NAME
|
||||
nsrlsvr - server yielding hashes from NIST's NSRL RDS
|
||||
.SH SYNOPSIS
|
||||
.B nsrlsvr
|
||||
[\fB\-h\fR,\fB\-\-help\fR]
|
||||
[\fB\-v\fR,\fB\-\-version\fR]
|
||||
[\fB\-\-bug\-report\fR]
|
||||
[\fB\-\-dry\-run\fR]
|
||||
[\fB\-f\fR,\fB\-\-file\fR \fIFILE\fR]
|
||||
[\fB\-p\fR,\fB\-\-port\fR \fIPORT\fr]
|
||||
.SH DESCRIPTION
|
||||
nsrlsvr provides a daemon that services queries from clients requesting
|
||||
information about whether certain hash values are present in the NIST
|
||||
National Software Reference Laboratory Reference Data Set (NSRL RDS).
|
||||
.SH OPTIONS
|
||||
.TP
|
||||
.BR \-h ", " \-\-help
|
||||
Help about running nsrlsvr
|
||||
.TP
|
||||
.BR \-v ", " \-\-version
|
||||
Show version information
|
||||
.TP
|
||||
.BR \-\-bug\-report
|
||||
Get the URL for nsrlsvr's bug tracker
|
||||
.TP
|
||||
.BR \-\-dry\-run
|
||||
Attempt to stand up the server, but stop before servicing any requests.
|
||||
This also redirects error messages, which would have gone to syslog, to
|
||||
standard error. This option is occasionally useful for debugging.
|
||||
.TP
|
||||
.BR \-f ", " \-\-file " " \fIFILE\fR
|
||||
Use \fIFILE\fR instead of the compiled-in default (which can be discovered
|
||||
by running \fB\-\-help\fR)
|
||||
.TP
|
||||
.BR \-p ", " \-\-port " " \fIPORT\fR
|
||||
Use \fIPORT\fR instead of the compiled-in default (normally 9120)
|
||||
.SH NOTES
|
||||
Installing this package \fBdoes not\fR install the associated database!
|
||||
.PP
|
||||
NIST releases updates to the NSRL RDS on a regular schedule. nsrlsvr is
|
||||
not updated on a regular schedule. You must download the latest minimal
|
||||
NSRL RDS from http://www.nsrl.nist.gov/Downloads.htm#reduced, uncompress
|
||||
it, and run the \fBnsrlupdate\fR script on the output. This will create
|
||||
the necessary database.
|
||||
.PP
|
||||
To support the full NSRL RDS requires a lot of memory. Although it will
|
||||
run on a 4Gb system, the results may be unsatisfactory. A 64-bit OS with
|
||||
at least 8Gb of RAM is recommended.
|
||||
.SH BUGS
|
||||
None known.
|
||||
.Sh SEE ALSO
|
||||
nsrllookup(1)
|
||||
.Sh AUTHOR
|
||||
Robert J. Hansen <rjh@secret-alchemy.com>
|
||||
.SH SEE ALSO
|
||||
.BR nsrllookup (1)
|
||||
.PP
|
||||
.BR nsrlupdate (1)
|
||||
.SH AUTHOR
|
||||
Robert J. Hansen <rob@hansen.engineering>
|
||||
|
||||
@@ -1,37 +1,30 @@
|
||||
.Dd February 9, 2015
|
||||
.Dt NSRLUPDATE 1
|
||||
.Os
|
||||
.Sh NAME
|
||||
.Nm nsrlupdate
|
||||
.Nd updates nsrlsvr's hash database
|
||||
.Sh SYNOPSIS
|
||||
.Nm nsrlupdate
|
||||
.Op Ar NSRLFile.txt
|
||||
.Sh DESCRIPTION
|
||||
.TH NSRLUPDATE 1 "October 27, 2016" "1.6.1"
|
||||
.SH NAME
|
||||
nsrlupdate - updates nsrlsvr's hash database
|
||||
.SH SYNOPSIS
|
||||
nsrlupdate \fI[NSRLFile.txt]\fR
|
||||
.SH DESCRIPTION
|
||||
nsrlupdate is used to replace an existing nsrlsvr hash database with the
|
||||
contents of a new NSRL RDS file. These files, which may be downloaded
|
||||
from http://www.nsrl.nist.gov, are released in ZIP format every few
|
||||
months.
|
||||
|
||||
from http://www.nsrl.nist.gov/Downloads.htm#reduced, are released in ZIP
|
||||
format every few months.
|
||||
.PP
|
||||
To keep your hash database up-to-date, periodically download the latest
|
||||
NSRL RDS minimal dataset (normally named rds_XYZ.zip) and extract the
|
||||
NSRL RDS minimal dataset (normally named rds_XYZm.zip) and extract the
|
||||
contents into a temporary directory. Somewhere in that temporary
|
||||
directory you'll find a file called "NSRLFile.txt"; make note of the path
|
||||
to it. Then call nsrlupdate, passing the path to NSRLFile.txt as an
|
||||
argument, and be prepared to wait for a while.
|
||||
|
||||
.PP
|
||||
nsrlupdate doesn't require much memory, but it may take a lot of time.
|
||||
As of RDS 2.47m, there are over 40 million hashes to extract. The final
|
||||
hashes.txt file will be around 1.3Gb in size. Expect this to only go up
|
||||
as subsequent RDSes are released.
|
||||
|
||||
As of RDS 2.53m, there are over 47 million hashes to extract.
|
||||
.PP
|
||||
nsrlupdate will wipe out the current contents of the hash database, so
|
||||
be careful if you've appended your own custom dataset.
|
||||
|
||||
.Sh BUGS
|
||||
.SH BUGS
|
||||
nsrlupdate is a Frankenstein's monster of Python 2 and Python 3 support.
|
||||
The good news is, it's a fairly small script.
|
||||
.Sh SEE ALSO
|
||||
.SH SEE ALSO
|
||||
nsrlsvr(1)
|
||||
.Sh AUTHOR
|
||||
Robert J. Hansen <rjh@secret-alchemy.com>
|
||||
.SH AUTHOR
|
||||
Robert J. Hansen <rob@hansen.engineering>
|
||||
|
||||
356
src/handler.cc
356
src/handler.cc
@@ -55,213 +55,203 @@ using std::fill;
|
||||
extern const vector<pair64>& hashes;
|
||||
|
||||
namespace {
|
||||
class NetworkTimeout : public std::exception
|
||||
{
|
||||
class NetworkTimeout : public std::exception {
|
||||
public:
|
||||
virtual const char* what() const noexcept { return "network timeout"; }
|
||||
virtual const char* what() const noexcept { return "network timeout"; }
|
||||
};
|
||||
|
||||
class NetworkError : public std::exception
|
||||
{
|
||||
class NetworkError : public std::exception {
|
||||
public:
|
||||
virtual const char* what() const noexcept { return "network error"; }
|
||||
virtual const char* what() const noexcept { return "network error"; }
|
||||
};
|
||||
|
||||
string
|
||||
read_line(const int32_t sockfd, int timeout = 15)
|
||||
{
|
||||
static vector<char> buffer;
|
||||
static array<char, 8192> rdbuf;
|
||||
struct pollfd pfd;
|
||||
struct timeval start;
|
||||
struct timeval now;
|
||||
time_t elapsed_time;
|
||||
ssize_t bytes_received;
|
||||
constexpr auto MEGABYTE = 1 << 20;
|
||||
static vector<char> buffer;
|
||||
static array<char, 8192> rdbuf;
|
||||
struct pollfd pfd;
|
||||
struct timeval start;
|
||||
struct timeval now;
|
||||
time_t elapsed_time;
|
||||
ssize_t bytes_received;
|
||||
constexpr auto MEGABYTE = 1 << 20;
|
||||
|
||||
if (buffer.capacity() < MEGABYTE)
|
||||
buffer.reserve(MEGABYTE);
|
||||
if (buffer.capacity() < MEGABYTE)
|
||||
buffer.reserve(MEGABYTE);
|
||||
|
||||
// Step zero: check to see if there's already a string in the
|
||||
// input queue awaiting a read.
|
||||
auto iter = find(buffer.begin(), buffer.end(), '\n');
|
||||
if (iter != buffer.end()) {
|
||||
vector<char> newbuf(buffer.begin(), iter);
|
||||
buffer.erase(buffer.begin(), iter + 1);
|
||||
newbuf.erase(remove(newbuf.begin(), newbuf.end(), '\r'), newbuf.end());
|
||||
return string(newbuf.begin(), newbuf.end());
|
||||
}
|
||||
|
||||
// Per POSIX, this can only err if we access invalid memory.
|
||||
// Since start is always valid, there's no problem here and
|
||||
// no need to check gettimeofday's return code.
|
||||
gettimeofday(&start, nullptr);
|
||||
now.tv_sec = start.tv_sec;
|
||||
now.tv_usec = start.tv_usec;
|
||||
elapsed_time = now.tv_sec - start.tv_sec;
|
||||
|
||||
while ((elapsed_time < timeout)) {
|
||||
pfd.fd = sockfd;
|
||||
pfd.events = POLLIN;
|
||||
pfd.revents = 0;
|
||||
fill(rdbuf.begin(), rdbuf.end(), 0);
|
||||
|
||||
if ((buffer.size() > MEGABYTE) || (-1 == poll(&pfd, 1, 1000)) ||
|
||||
(pfd.revents & POLLERR) || (pfd.revents & POLLHUP) ||
|
||||
(pfd.revents & POLLNVAL)) {
|
||||
log(LogLevel::ALERT, "network error: ");
|
||||
if (buffer.size() > MEGABYTE) {
|
||||
log(LogLevel::ALERT, "buffer too large");
|
||||
}
|
||||
if (pfd.revents & POLLERR) {
|
||||
log(LogLevel::ALERT, "POLLERR");
|
||||
}
|
||||
if (pfd.revents & POLLHUP) {
|
||||
log(LogLevel::ALERT, "POLLHUP");
|
||||
}
|
||||
if (pfd.revents & POLLNVAL) {
|
||||
log(LogLevel::ALERT, "POLLNVAL");
|
||||
}
|
||||
throw NetworkError();
|
||||
}
|
||||
if (pfd.revents & POLLIN) {
|
||||
bytes_received = recvfrom(sockfd, &rdbuf[0], rdbuf.size(), 0, NULL, 0);
|
||||
if (0 == bytes_received) {
|
||||
log(LogLevel::ALERT, "read_line read on closed socket");
|
||||
throw NetworkError();
|
||||
}
|
||||
copy(rdbuf.begin(), rdbuf.begin() + bytes_received,
|
||||
back_inserter(buffer));
|
||||
}
|
||||
|
||||
iter = find(buffer.begin(), buffer.end(), '\n');
|
||||
// Step zero: check to see if there's already a string in the
|
||||
// input queue awaiting a read.
|
||||
auto iter = find(buffer.begin(), buffer.end(), '\n');
|
||||
if (iter != buffer.end()) {
|
||||
string line(buffer.begin(), iter);
|
||||
if (line.at(line.size() - 1) == '\r') {
|
||||
line = string(line.begin(), line.end() - 1);
|
||||
}
|
||||
buffer.erase(buffer.begin(), iter + 1);
|
||||
return line;
|
||||
vector<char> newbuf(buffer.begin(), iter);
|
||||
buffer.erase(buffer.begin(), iter + 1);
|
||||
newbuf.erase(remove(newbuf.begin(), newbuf.end(), '\r'), newbuf.end());
|
||||
return string(newbuf.begin(), newbuf.end());
|
||||
}
|
||||
gettimeofday(&now, nullptr);
|
||||
|
||||
// Per POSIX, this can only err if we access invalid memory.
|
||||
// Since start is always valid, there's no problem here and
|
||||
// no need to check gettimeofday's return code.
|
||||
gettimeofday(&start, nullptr);
|
||||
now.tv_sec = start.tv_sec;
|
||||
now.tv_usec = start.tv_usec;
|
||||
elapsed_time = now.tv_sec - start.tv_sec;
|
||||
}
|
||||
throw NetworkTimeout();
|
||||
}
|
||||
|
||||
void
|
||||
write_line(const int32_t sockfd, string&& line)
|
||||
{
|
||||
string output = line + "\r\n";
|
||||
const char* msg = output.c_str();
|
||||
if (-1 == send(sockfd, reinterpret_cast<const void*>(msg), output.size(), 0))
|
||||
throw NetworkError();
|
||||
}
|
||||
while ((elapsed_time < timeout)) {
|
||||
pfd.fd = sockfd;
|
||||
pfd.events = POLLIN;
|
||||
pfd.revents = 0;
|
||||
fill(rdbuf.begin(), rdbuf.end(), 0);
|
||||
|
||||
auto
|
||||
tokenize(string&& line, char character = ' ')
|
||||
{
|
||||
vector<string> rv;
|
||||
transform(line.begin(), line.end(), line.begin(), toupper);
|
||||
if ((buffer.size() > MEGABYTE) || (-1 == poll(&pfd, 1, 1000)) || (pfd.revents & POLLERR) || (pfd.revents & POLLHUP) || (pfd.revents & POLLNVAL)) {
|
||||
log(LogLevel::ALERT, "network error: ");
|
||||
if (buffer.size() > MEGABYTE) {
|
||||
log(LogLevel::ALERT, "buffer too large");
|
||||
}
|
||||
if (pfd.revents & POLLERR) {
|
||||
log(LogLevel::ALERT, "POLLERR");
|
||||
}
|
||||
if (pfd.revents & POLLHUP) {
|
||||
log(LogLevel::ALERT, "POLLHUP");
|
||||
}
|
||||
if (pfd.revents & POLLNVAL) {
|
||||
log(LogLevel::ALERT, "POLLNVAL");
|
||||
}
|
||||
throw NetworkError();
|
||||
}
|
||||
if (pfd.revents & POLLIN) {
|
||||
bytes_received = recvfrom(sockfd, &rdbuf[0], rdbuf.size(), 0, NULL, 0);
|
||||
if (0 == bytes_received) {
|
||||
log(LogLevel::ALERT, "read_line read on closed socket");
|
||||
throw NetworkError();
|
||||
}
|
||||
copy(rdbuf.begin(), rdbuf.begin() + bytes_received,
|
||||
back_inserter(buffer));
|
||||
}
|
||||
|
||||
auto begin =
|
||||
find_if(line.cbegin(), line.cend(), [&](auto x) { return x != character; });
|
||||
auto end = (begin != line.cend()) ? find(begin + 1, line.cend(), character)
|
||||
: line.cend();
|
||||
|
||||
while (begin != line.cend()) {
|
||||
rv.emplace_back(string{ begin, end });
|
||||
if (end == line.cend()) {
|
||||
break;
|
||||
iter = find(buffer.begin(), buffer.end(), '\n');
|
||||
if (iter != buffer.end()) {
|
||||
string line(buffer.begin(), iter);
|
||||
if (line.at(line.size() - 1) == '\r') {
|
||||
line = string(line.begin(), line.end() - 1);
|
||||
}
|
||||
buffer.erase(buffer.begin(), iter + 1);
|
||||
return line;
|
||||
}
|
||||
gettimeofday(&now, nullptr);
|
||||
elapsed_time = now.tv_sec - start.tv_sec;
|
||||
}
|
||||
begin =
|
||||
find_if(end + 1, line.cend(), [&](auto x) { return x != character; });
|
||||
end = (begin != line.cend()) ? find(begin + 1, line.cend(), character)
|
||||
: line.cend();
|
||||
}
|
||||
return rv;
|
||||
throw NetworkTimeout();
|
||||
}
|
||||
|
||||
void write_line(const int32_t sockfd, string&& line)
|
||||
{
|
||||
string output = line + "\r\n";
|
||||
const char* msg = output.c_str();
|
||||
if (-1 == send(sockfd, reinterpret_cast<const void*>(msg), output.size(), 0))
|
||||
throw NetworkError();
|
||||
}
|
||||
|
||||
auto tokenize(string&& line, char character = ' ')
|
||||
{
|
||||
vector<string> rv;
|
||||
transform(line.begin(), line.end(), line.begin(), toupper);
|
||||
|
||||
auto begin = find_if(line.cbegin(), line.cend(), [&](auto x) { return x != character; });
|
||||
auto end = (begin != line.cend()) ? find(begin + 1, line.cend(), character)
|
||||
: line.cend();
|
||||
|
||||
while (begin != line.cend()) {
|
||||
rv.emplace_back(string{ begin, end });
|
||||
if (end == line.cend()) {
|
||||
break;
|
||||
}
|
||||
begin = find_if(end + 1, line.cend(), [&](auto x) { return x != character; });
|
||||
end = (begin != line.cend()) ? find(begin + 1, line.cend(), character)
|
||||
: line.cend();
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
string
|
||||
generate_response(vector<string>::const_iterator begin,
|
||||
vector<string>::const_iterator end)
|
||||
vector<string>::const_iterator end)
|
||||
{
|
||||
string rv = "OK ";
|
||||
string rv = "OK ";
|
||||
|
||||
for (auto i = begin; i != end; ++i) {
|
||||
bool present = binary_search(hashes.cbegin(), hashes.cend(), to_pair64(*i));
|
||||
for (auto i = begin; i != end; ++i) {
|
||||
bool present = binary_search(hashes.cbegin(), hashes.cend(), to_pair64(*i));
|
||||
|
||||
rv += present ? "1" : "0";
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
handle_client(const int32_t fd)
|
||||
{
|
||||
enum class Command
|
||||
{
|
||||
Version = 0,
|
||||
Bye = 1,
|
||||
Status = 2,
|
||||
Query = 3,
|
||||
Upshift = 4,
|
||||
Downshift = 5,
|
||||
Unknown = 6
|
||||
};
|
||||
|
||||
try {
|
||||
auto commands = tokenize(read_line(fd));
|
||||
while (true) {
|
||||
auto cmdstring = commands.at(0);
|
||||
Command cmd = Command::Unknown;
|
||||
|
||||
if (cmdstring == "VERSION:")
|
||||
cmd = Command::Version;
|
||||
else if (cmdstring == "BYE")
|
||||
cmd = Command::Bye;
|
||||
else if (cmdstring == "STATUS")
|
||||
cmd = Command::Status;
|
||||
else if (cmdstring == "QUERY")
|
||||
cmd = Command::Query;
|
||||
else if (cmdstring == "UPSHIFT")
|
||||
cmd = Command::Upshift;
|
||||
else if (cmdstring == "DOWNSHIFT")
|
||||
cmd = Command::Downshift;
|
||||
|
||||
switch (cmd) {
|
||||
case Command::Version:
|
||||
write_line(fd, "OK");
|
||||
break;
|
||||
|
||||
case Command::Bye:
|
||||
return;
|
||||
|
||||
case Command::Status:
|
||||
write_line(fd, "NOT SUPPORTED");
|
||||
break;
|
||||
|
||||
case Command::Query:
|
||||
write_line(fd,
|
||||
generate_response(commands.begin() + 1, commands.end()));
|
||||
break;
|
||||
|
||||
case Command::Upshift:
|
||||
write_line(fd, "NOT OK");
|
||||
break;
|
||||
|
||||
case Command::Downshift:
|
||||
write_line(fd, "NOT OK");
|
||||
break;
|
||||
|
||||
case Command::Unknown:
|
||||
write_line(fd, "NOT OK");
|
||||
return;
|
||||
}
|
||||
commands = tokenize(read_line(fd));
|
||||
rv += present ? "1" : "0";
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
|
||||
void handle_client(const int32_t fd)
|
||||
{
|
||||
enum class Command {
|
||||
Version = 0,
|
||||
Bye = 1,
|
||||
Status = 2,
|
||||
Query = 3,
|
||||
Upshift = 4,
|
||||
Downshift = 5,
|
||||
Unknown = 6
|
||||
};
|
||||
|
||||
try {
|
||||
auto commands = tokenize(read_line(fd));
|
||||
while (true) {
|
||||
auto cmdstring = commands.at(0);
|
||||
Command cmd = Command::Unknown;
|
||||
|
||||
if (cmdstring == "VERSION:")
|
||||
cmd = Command::Version;
|
||||
else if (cmdstring == "BYE")
|
||||
cmd = Command::Bye;
|
||||
else if (cmdstring == "STATUS")
|
||||
cmd = Command::Status;
|
||||
else if (cmdstring == "QUERY")
|
||||
cmd = Command::Query;
|
||||
else if (cmdstring == "UPSHIFT")
|
||||
cmd = Command::Upshift;
|
||||
else if (cmdstring == "DOWNSHIFT")
|
||||
cmd = Command::Downshift;
|
||||
|
||||
switch (cmd) {
|
||||
case Command::Version:
|
||||
write_line(fd, "OK");
|
||||
break;
|
||||
|
||||
case Command::Bye:
|
||||
return;
|
||||
|
||||
case Command::Status:
|
||||
write_line(fd, "NOT SUPPORTED");
|
||||
break;
|
||||
|
||||
case Command::Query:
|
||||
write_line(fd,
|
||||
generate_response(commands.begin() + 1, commands.end()));
|
||||
break;
|
||||
|
||||
case Command::Upshift:
|
||||
write_line(fd, "NOT OK");
|
||||
break;
|
||||
|
||||
case Command::Downshift:
|
||||
write_line(fd, "NOT OK");
|
||||
break;
|
||||
|
||||
case Command::Unknown:
|
||||
write_line(fd, "NOT OK");
|
||||
return;
|
||||
}
|
||||
commands = tokenize(read_line(fd));
|
||||
}
|
||||
} catch (std::exception&) {
|
||||
// Do nothing: just end the function, which will drop the connection.
|
||||
}
|
||||
} catch (std::exception&) {
|
||||
// Do nothing: just end the function, which will drop the connection.
|
||||
}
|
||||
}
|
||||
|
||||
630
src/main.cc
630
src/main.cc
@@ -60,261 +60,266 @@ using boost::program_options::value;
|
||||
|
||||
namespace {
|
||||
vector<pair64> hash_set;
|
||||
string hashes_location{PKGDATADIR "/hashes.txt"};
|
||||
uint16_t port{9120};
|
||||
bool dry_run{false};
|
||||
string hashes_location{ PKGDATADIR "/hashes.txt" };
|
||||
uint16_t port{ 9120 };
|
||||
bool dry_run{ false };
|
||||
|
||||
/** Attempts to load a set of MD5 hashes from disk.
|
||||
* Each line must be either blank or 32 hexadecimal digits. If the
|
||||
* file doesn't conform to this, nsrlsvr will abort and display an
|
||||
* error message to the log.
|
||||
*/
|
||||
void load_hashes() {
|
||||
const regex md5_re{"^[A-Fa-f0-9]{32}$"};
|
||||
uint32_t hash_count{0};
|
||||
ifstream infile{hashes_location.c_str()};
|
||||
|
||||
// As of this writing, the RDS had about 40 million entries.
|
||||
// When a vector needs to grow, it normally does so by doubling
|
||||
// the former allocation -- so after this, the next stop is a
|
||||
// 100 million allocation (@ 16 bytes per, or 1.6 GB). If you're
|
||||
// maintaining this code, try to keep the reserve a few million
|
||||
// larger than the RDS currently is, to give yourself room to
|
||||
// grow without a vector realloc.
|
||||
//
|
||||
// Failure to reserve this block of memory is non-recoverable.
|
||||
// Don't even try. Just log the error and bail out. Let the end
|
||||
// user worry about installing more RAM.
|
||||
try {
|
||||
hash_set.reserve(50000000);
|
||||
} catch (std::bad_alloc &) {
|
||||
log(LogLevel::ALERT, "couldn't reserve enough memory");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
if (not infile) {
|
||||
log(LogLevel::ALERT, "couldn't open hashes file " + hashes_location);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
while (infile) {
|
||||
string line;
|
||||
getline(infile, line);
|
||||
transform(line.begin(), line.end(), line.begin(), ::toupper);
|
||||
if (0 == line.size())
|
||||
continue;
|
||||
|
||||
if (!regex_match(line.cbegin(), line.cend(), md5_re)) {
|
||||
log(LogLevel::ALERT, "hash file appears corrupt! Loading no hashes.");
|
||||
log(LogLevel::ALERT, "offending line is: " + line);
|
||||
log(LogLevel::ALERT, "shutting down!");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
void load_hashes()
|
||||
{
|
||||
const regex md5_re{ "^[A-Fa-f0-9]{32}$" };
|
||||
uint32_t hash_count{ 0 };
|
||||
ifstream infile{ hashes_location.c_str() };
|
||||
|
||||
// As of this writing, the RDS had about 40 million entries.
|
||||
// When a vector needs to grow, it normally does so by doubling
|
||||
// the former allocation -- so after this, the next stop is a
|
||||
// 100 million allocation (@ 16 bytes per, or 1.6 GB). If you're
|
||||
// maintaining this code, try to keep the reserve a few million
|
||||
// larger than the RDS currently is, to give yourself room to
|
||||
// grow without a vector realloc.
|
||||
//
|
||||
// Failure to reserve this block of memory is non-recoverable.
|
||||
// Don't even try. Just log the error and bail out. Let the end
|
||||
// user worry about installing more RAM.
|
||||
try {
|
||||
// .emplace_back is the C++11 improvement over the old
|
||||
// vector.push_back. It has the benefit of not needing
|
||||
// to construct a temporary to hold the value; it can
|
||||
// just construct-in-place. For 40 million values, that
|
||||
// can be significant.
|
||||
//
|
||||
// Note that if the vector runs out of reserved room it
|
||||
// will attempt to make a new allocation double the size
|
||||
// of the last. That means the application will at least
|
||||
// briefly need *three times* the expected RAM -- one for
|
||||
// the data set and two for the newly-allocated chunk.
|
||||
// Given we're talking about multiple gigs of RAM, this
|
||||
// .emplace_back needs to consider the possibility of a
|
||||
// RAM allocation failure.
|
||||
hash_set.emplace_back(to_pair64(line));
|
||||
hash_count += 1;
|
||||
if (0 == hash_count % 1000000) {
|
||||
string howmany{to_string(hash_count / 1000000)};
|
||||
log(LogLevel::ALERT, "loaded " + howmany + " million hashes");
|
||||
}
|
||||
} catch (std::bad_alloc &) {
|
||||
log(LogLevel::ALERT, "couldn't allocate enough memory");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
string howmany{to_string(hash_count)};
|
||||
log(LogLevel::INFO, "read in " + howmany + " hashes");
|
||||
|
||||
infile.close();
|
||||
|
||||
sort(hash_set.begin(), hash_set.end());
|
||||
|
||||
if (hash_set.size() > 1) {
|
||||
log(LogLevel::INFO, "ensuring no duplicates");
|
||||
pair64 foo{hash_set.at(0)};
|
||||
for (auto iter = (hash_set.cbegin() + 1); iter != hash_set.cend(); ++iter) {
|
||||
if (foo == *iter) {
|
||||
log(LogLevel::ALERT, "hash file contains duplicates -- "
|
||||
"shutting down!");
|
||||
hash_set.reserve(50000000);
|
||||
} catch (std::bad_alloc&) {
|
||||
log(LogLevel::ALERT, "couldn't reserve enough memory");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
foo = *iter;
|
||||
}
|
||||
}
|
||||
|
||||
if (not infile) {
|
||||
log(LogLevel::ALERT, "couldn't open hashes file " + hashes_location);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
while (infile) {
|
||||
string line;
|
||||
getline(infile, line);
|
||||
transform(line.begin(), line.end(), line.begin(), ::toupper);
|
||||
if (0 == line.size())
|
||||
continue;
|
||||
|
||||
if (!regex_match(line.cbegin(), line.cend(), md5_re)) {
|
||||
log(LogLevel::ALERT, "hash file appears corrupt! Loading no hashes.");
|
||||
log(LogLevel::ALERT, "offending line is: " + line);
|
||||
log(LogLevel::ALERT, "shutting down!");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
try {
|
||||
// .emplace_back is the C++11 improvement over the old
|
||||
// vector.push_back. It has the benefit of not needing
|
||||
// to construct a temporary to hold the value; it can
|
||||
// just construct-in-place. For 40 million values, that
|
||||
// can be significant.
|
||||
//
|
||||
// Note that if the vector runs out of reserved room it
|
||||
// will attempt to make a new allocation double the size
|
||||
// of the last. That means the application will at least
|
||||
// briefly need *three times* the expected RAM -- one for
|
||||
// the data set and two for the newly-allocated chunk.
|
||||
// Given we're talking about multiple gigs of RAM, this
|
||||
// .emplace_back needs to consider the possibility of a
|
||||
// RAM allocation failure.
|
||||
hash_set.emplace_back(to_pair64(line));
|
||||
hash_count += 1;
|
||||
if (0 == hash_count % 1000000) {
|
||||
string howmany{ to_string(hash_count / 1000000) };
|
||||
log(LogLevel::ALERT, "loaded " + howmany + " million hashes");
|
||||
}
|
||||
} catch (std::bad_alloc&) {
|
||||
log(LogLevel::ALERT, "couldn't allocate enough memory");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
string howmany{ to_string(hash_count) };
|
||||
log(LogLevel::INFO, "read in " + howmany + " hashes");
|
||||
|
||||
infile.close();
|
||||
|
||||
sort(hash_set.begin(), hash_set.end());
|
||||
|
||||
if (hash_set.size() > 1) {
|
||||
log(LogLevel::INFO, "ensuring no duplicates");
|
||||
pair64 foo{ hash_set.at(0) };
|
||||
for (auto iter = (hash_set.cbegin() + 1); iter != hash_set.cend(); ++iter) {
|
||||
if (foo == *iter) {
|
||||
log(LogLevel::ALERT, "hash file contains duplicates -- "
|
||||
"shutting down!");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
foo = *iter;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Converts this process into a well-behaved UNIX daemon.*/
|
||||
void daemonize() {
|
||||
/* Nothing in here should be surprising. If it is, then please
|
||||
void daemonize()
|
||||
{
|
||||
/* Nothing in here should be surprising. If it is, then please
|
||||
check the standard literature to ensure you understand how a
|
||||
daemon is supposed to work. */
|
||||
const auto pid = fork();
|
||||
if (0 > pid) {
|
||||
log(LogLevel::WARN, "couldn't fork!");
|
||||
exit(EXIT_FAILURE);
|
||||
} else if (0 < pid) {
|
||||
exit(EXIT_SUCCESS);
|
||||
}
|
||||
log(LogLevel::INFO, "daemon started");
|
||||
const auto pid = fork();
|
||||
if (0 > pid) {
|
||||
log(LogLevel::WARN, "couldn't fork!");
|
||||
exit(EXIT_FAILURE);
|
||||
} else if (0 < pid) {
|
||||
exit(EXIT_SUCCESS);
|
||||
}
|
||||
log(LogLevel::INFO, "daemon started");
|
||||
|
||||
umask(0);
|
||||
umask(0);
|
||||
|
||||
if (0 > setsid()) {
|
||||
log(LogLevel::WARN, "couldn't set sid");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
if (0 > setsid()) {
|
||||
log(LogLevel::WARN, "couldn't set sid");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
if (0 > chdir("/")) {
|
||||
log(LogLevel::WARN, "couldn't chdir to root");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
if (0 > chdir("/")) {
|
||||
log(LogLevel::WARN, "couldn't chdir to root");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
close(STDIN_FILENO);
|
||||
close(STDOUT_FILENO);
|
||||
close(STDERR_FILENO);
|
||||
close(STDIN_FILENO);
|
||||
close(STDOUT_FILENO);
|
||||
close(STDERR_FILENO);
|
||||
}
|
||||
|
||||
/** Creates a server socket to listen for client connections. */
|
||||
auto make_socket() {
|
||||
/* If anything in here is surprising, please check the standard
|
||||
auto make_socket()
|
||||
{
|
||||
/* If anything in here is surprising, please check the standard
|
||||
literature to make sure you understand TCP/IP. */
|
||||
|
||||
sockaddr_in server;
|
||||
memset(static_cast<void *>(&server), 0, sizeof(server));
|
||||
server.sin_family = AF_INET;
|
||||
server.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||
server.sin_port = htons(port);
|
||||
sockaddr_in server;
|
||||
memset(static_cast<void*>(&server), 0, sizeof(server));
|
||||
server.sin_family = AF_INET;
|
||||
server.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||
server.sin_port = htons(port);
|
||||
|
||||
const auto sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
||||
if (sock < 0) {
|
||||
log(LogLevel::WARN, "couldn't create a server socket");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
if (0 > bind(sock, reinterpret_cast<sockaddr *>(&server), sizeof(server))) {
|
||||
log(LogLevel::WARN, "couldn't bind to port");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
if (0 > listen(sock, 20)) {
|
||||
log(LogLevel::WARN, "couldn't listen for clients");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
log(LogLevel::INFO, "ready for clients");
|
||||
const auto sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
||||
if (sock < 0) {
|
||||
log(LogLevel::WARN, "couldn't create a server socket");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
if (0 > bind(sock, reinterpret_cast<sockaddr*>(&server), sizeof(server))) {
|
||||
log(LogLevel::WARN, "couldn't bind to port");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
if (0 > listen(sock, 20)) {
|
||||
log(LogLevel::WARN, "couldn't listen for clients");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
log(LogLevel::INFO, "ready for clients");
|
||||
|
||||
return sock;
|
||||
return sock;
|
||||
}
|
||||
|
||||
/** Parse command-line options.
|
||||
@param argc argc from main()
|
||||
@param argv argv from main()
|
||||
*/
|
||||
void parse_options(int argc, char *argv[]) {
|
||||
std::array<char, PATH_MAX> filename_buffer;
|
||||
char* filepath {&filename_buffer[0]};
|
||||
fill(filename_buffer.begin(), filename_buffer.end(), 0);
|
||||
options_description options{"nsrlsvr options"};
|
||||
options.add_options()("help,h", "Help screen")("version,v",
|
||||
"Display package version")(
|
||||
"bug-report,b", "Display bug reporting information")(
|
||||
"file,f", value<string>()->default_value(PKGDATADIR "/hashes.txt"),
|
||||
"hash file")(
|
||||
"port,p", value<uint16_t>()->default_value(9120), "port")(
|
||||
"dry-run", "test configuration");
|
||||
variables_map vm;
|
||||
store(parse_command_line(argc, argv, options), vm);
|
||||
void parse_options(int argc, char* argv[])
|
||||
{
|
||||
std::array<char, PATH_MAX> filename_buffer;
|
||||
char* filepath{ &filename_buffer[0] };
|
||||
fill(filename_buffer.begin(), filename_buffer.end(), 0);
|
||||
options_description options{ "nsrlsvr options" };
|
||||
options.add_options()("help,h", "Help screen")("version,v",
|
||||
"Display package version")(
|
||||
"bug-report,b", "Display bug reporting information")(
|
||||
"file,f", value<string>()->default_value(PKGDATADIR "/hashes.txt"),
|
||||
"hash file")(
|
||||
"port,p", value<uint16_t>()->default_value(9120), "port")(
|
||||
"dry-run", "test configuration");
|
||||
variables_map vm;
|
||||
store(parse_command_line(argc, argv, options), vm);
|
||||
|
||||
dry_run = vm.count("dry-run") ? true : false;
|
||||
dry_run = vm.count("dry-run") ? true : false;
|
||||
|
||||
if (vm.count("help")) {
|
||||
cout << options << "\n";
|
||||
exit(EXIT_SUCCESS);
|
||||
}
|
||||
if (vm.count("version")) {
|
||||
cout << "nsrlsvr version " << PACKAGE_VERSION
|
||||
<< "\n\n"
|
||||
"This program is released under the ISC License.\n";
|
||||
exit(EXIT_SUCCESS);
|
||||
}
|
||||
if (vm.count("bug-report")) {
|
||||
cout << "To file a bug report, visit "
|
||||
"https://github.com/rjhansen/nsrlsvr/issues\n";
|
||||
exit(EXIT_SUCCESS);
|
||||
}
|
||||
port = vm["port"].as<uint16_t>();
|
||||
string relpath = vm["file"].as<string>();
|
||||
if (nullptr == (filepath = realpath(relpath.c_str(), filepath))) {
|
||||
switch (errno) {
|
||||
case EACCES:
|
||||
cerr << "Could not access file path " << relpath
|
||||
<< "\n(Do you have read privileges?)\n";
|
||||
break;
|
||||
case EINVAL:
|
||||
cerr << "Somehow, the system believes the file path passed to it\n"
|
||||
"is null. This is weird and probably a bug. Please report\n"
|
||||
"it!\n";
|
||||
break;
|
||||
case EIO:
|
||||
cerr << "An I/O error occurred while reading " << relpath << "\n";
|
||||
break;
|
||||
case ELOOP:
|
||||
cerr << "Too many symbolic links were found while translating "
|
||||
<< relpath << " into an absolute path.\n";
|
||||
break;
|
||||
case ENAMETOOLONG:
|
||||
cerr << "The file path " << relpath << " is too long.\n";
|
||||
break;
|
||||
case ENOENT:
|
||||
cerr << "The file " << relpath << " could not be found.\n";
|
||||
break;
|
||||
case ENOMEM:
|
||||
cerr << "Strangely, the system ran out of memory while processing\n"
|
||||
"your request. This is probably a bug in nsrlsvr.\n";
|
||||
break;
|
||||
case ENOTDIR:
|
||||
cerr << "A component of the file path " << relpath
|
||||
<< " is not a directory.";
|
||||
break;
|
||||
default:
|
||||
cerr << "... wtfbbq? This should never trip. It's an nsrlsvr bug.\n";
|
||||
break;
|
||||
if (vm.count("help")) {
|
||||
cout << options << "\n";
|
||||
exit(EXIT_SUCCESS);
|
||||
}
|
||||
if (vm.count("version")) {
|
||||
cout << "nsrlsvr version " << PACKAGE_VERSION
|
||||
<< "\n\n"
|
||||
"This program is released under the ISC License.\n";
|
||||
exit(EXIT_SUCCESS);
|
||||
}
|
||||
if (vm.count("bug-report")) {
|
||||
cout << "To file a bug report, visit "
|
||||
"https://github.com/rjhansen/nsrlsvr/issues\n";
|
||||
exit(EXIT_SUCCESS);
|
||||
}
|
||||
port = vm["port"].as<uint16_t>();
|
||||
string relpath = vm["file"].as<string>();
|
||||
if (nullptr == (filepath = realpath(relpath.c_str(), filepath))) {
|
||||
switch (errno) {
|
||||
case EACCES:
|
||||
cerr << "Could not access file path " << relpath
|
||||
<< "\n(Do you have read privileges?)\n";
|
||||
break;
|
||||
case EINVAL:
|
||||
cerr << "Somehow, the system believes the file path passed to it\n"
|
||||
"is null. This is weird and probably a bug. Please report\n"
|
||||
"it!\n";
|
||||
break;
|
||||
case EIO:
|
||||
cerr << "An I/O error occurred while reading " << relpath << "\n";
|
||||
break;
|
||||
case ELOOP:
|
||||
cerr << "Too many symbolic links were found while translating "
|
||||
<< relpath << " into an absolute path.\n";
|
||||
break;
|
||||
case ENAMETOOLONG:
|
||||
cerr << "The file path " << relpath << " is too long.\n";
|
||||
break;
|
||||
case ENOENT:
|
||||
cerr << "The file " << relpath << " could not be found.\n";
|
||||
break;
|
||||
case ENOMEM:
|
||||
cerr << "Strangely, the system ran out of memory while processing\n"
|
||||
"your request. This is probably a bug in nsrlsvr.\n";
|
||||
break;
|
||||
case ENOTDIR:
|
||||
cerr << "A component of the file path " << relpath
|
||||
<< " is not a directory.";
|
||||
break;
|
||||
default:
|
||||
cerr << "... wtfbbq? This should never trip. It's an nsrlsvr bug.\n";
|
||||
break;
|
||||
}
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
hashes_location = string(filepath);
|
||||
if (not ifstream(hashes_location.c_str())) {
|
||||
cerr << "Could not open " + hashes_location + " for reading.\n";
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
hashes_location = string(filepath);
|
||||
if (not ifstream(hashes_location.c_str())) {
|
||||
cerr << "Could not open " + hashes_location + " for reading.\n";
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** The set of all loaded hashes, represented as a const reference. */
|
||||
const vector<pair64>& hashes{hash_set};
|
||||
const vector<pair64>& hashes{ hash_set };
|
||||
|
||||
/** Writes to syslog with the given priority level.
|
||||
|
||||
@param level The priority of the message
|
||||
@param msg The message to write
|
||||
*/
|
||||
void log(const LogLevel level, const string &&msg) {
|
||||
void log(const LogLevel level, const string&& msg)
|
||||
{
|
||||
if (dry_run)
|
||||
cerr << msg << "\n";
|
||||
else
|
||||
syslog(LOG_MAKEPRI(LOG_USER, static_cast<int>(level)), "%s", msg.c_str());
|
||||
syslog(LOG_MAKEPRI(LOG_USER, static_cast<int>(level)), "%s", msg.c_str());
|
||||
}
|
||||
|
||||
/** Entry point for the application.
|
||||
@@ -322,115 +327,116 @@ void log(const LogLevel level, const string &&msg) {
|
||||
@param argc The number of command-line arguments
|
||||
@param argv Command-line arguments
|
||||
*/
|
||||
int main(int argc, char *argv[]) {
|
||||
parse_options(argc, argv);
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
parse_options(argc, argv);
|
||||
|
||||
int32_t client_sock{0};
|
||||
int32_t svr_sock{make_socket()};
|
||||
sockaddr_in client;
|
||||
sockaddr *client_addr = reinterpret_cast<sockaddr *>(&client);
|
||||
socklen_t client_length{sizeof(client)};
|
||||
int32_t client_sock{ 0 };
|
||||
int32_t svr_sock{ make_socket() };
|
||||
sockaddr_in client;
|
||||
sockaddr* client_addr = reinterpret_cast<sockaddr*>(&client);
|
||||
socklen_t client_length{ sizeof(client) };
|
||||
|
||||
if (! dry_run)
|
||||
daemonize();
|
||||
if (!dry_run)
|
||||
daemonize();
|
||||
|
||||
load_hashes();
|
||||
load_hashes();
|
||||
|
||||
// The following line helps avoid zombie processes. Normally parents
|
||||
// need to reap their children in order to prevent zombie processes;
|
||||
// if SIGCHLD is set to SIG_IGN, though, the processes can terminate
|
||||
// normally.
|
||||
signal(SIGCHLD, SIG_IGN);
|
||||
// The following line helps avoid zombie processes. Normally parents
|
||||
// need to reap their children in order to prevent zombie processes;
|
||||
// if SIGCHLD is set to SIG_IGN, though, the processes can terminate
|
||||
// normally.
|
||||
signal(SIGCHLD, SIG_IGN);
|
||||
|
||||
if (dry_run)
|
||||
if (dry_run)
|
||||
return EXIT_SUCCESS;
|
||||
|
||||
while (true) {
|
||||
if (0 > (client_sock = accept(svr_sock, client_addr, &client_length))) {
|
||||
log(LogLevel::WARN, "could not accept connection");
|
||||
switch (errno) {
|
||||
case EAGAIN:
|
||||
log(LogLevel::WARN, "-- EAGAIN");
|
||||
break;
|
||||
case ECONNABORTED:
|
||||
log(LogLevel::WARN, "-- ECONNABORTED");
|
||||
break;
|
||||
case EINTR:
|
||||
log(LogLevel::WARN, "-- EINTR");
|
||||
break;
|
||||
case EINVAL:
|
||||
log(LogLevel::WARN, "-- EINVAL");
|
||||
break;
|
||||
case EMFILE:
|
||||
log(LogLevel::WARN, "-- EMFILE");
|
||||
break;
|
||||
case ENFILE:
|
||||
log(LogLevel::WARN, "-- ENFILE");
|
||||
break;
|
||||
case ENOTSOCK:
|
||||
log(LogLevel::WARN, "-- ENOTSOCK");
|
||||
break;
|
||||
case EOPNOTSUPP:
|
||||
log(LogLevel::WARN, "-- EOPNOTSUPP");
|
||||
break;
|
||||
case ENOBUFS:
|
||||
log(LogLevel::WARN, "-- ENOBUFS");
|
||||
break;
|
||||
case ENOMEM:
|
||||
log(LogLevel::WARN, "-- ENOMEM");
|
||||
break;
|
||||
case EPROTO:
|
||||
log(LogLevel::WARN, "-- EPROTO");
|
||||
break;
|
||||
default:
|
||||
log(LogLevel::WARN, "-- EUNKNOWN");
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
string ipaddr{ inet_ntoa(client.sin_addr) };
|
||||
log(LogLevel::ALERT, string("accepted a client: ") + ipaddr);
|
||||
|
||||
if (0 == fork()) {
|
||||
log(LogLevel::ALERT, "calling handle_client");
|
||||
handle_client(client_sock);
|
||||
if (-1 == close(client_sock)) {
|
||||
log(LogLevel::WARN, string("Could not close client: ") + ipaddr);
|
||||
switch (errno) {
|
||||
case EBADF:
|
||||
log(LogLevel::WARN, "-- EBADF");
|
||||
break;
|
||||
case EINTR:
|
||||
log(LogLevel::WARN, "-- EINTR");
|
||||
break;
|
||||
case EIO:
|
||||
log(LogLevel::WARN, "-- EIO");
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
log(LogLevel::ALERT, string("closed client ") + ipaddr);
|
||||
}
|
||||
return 0;
|
||||
} else {
|
||||
if (-1 == close(client_sock)) {
|
||||
log(LogLevel::WARN, string("Parent could not close client: ") + ipaddr);
|
||||
switch (errno) {
|
||||
case EBADF:
|
||||
log(LogLevel::WARN, "-- EBADF");
|
||||
break;
|
||||
case EINTR:
|
||||
log(LogLevel::WARN, "-- EINTR");
|
||||
break;
|
||||
case EIO:
|
||||
log(LogLevel::WARN, "-- EIO");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Note that as is normal for daemons, the exit point is never
|
||||
// reached. This application does not normally terminate.
|
||||
return EXIT_SUCCESS;
|
||||
|
||||
while (true) {
|
||||
if (0 > (client_sock = accept(svr_sock, client_addr, &client_length))) {
|
||||
log(LogLevel::WARN, "could not accept connection");
|
||||
switch (errno) {
|
||||
case EAGAIN:
|
||||
log(LogLevel::WARN, "-- EAGAIN");
|
||||
break;
|
||||
case ECONNABORTED:
|
||||
log(LogLevel::WARN, "-- ECONNABORTED");
|
||||
break;
|
||||
case EINTR:
|
||||
log(LogLevel::WARN, "-- EINTR");
|
||||
break;
|
||||
case EINVAL:
|
||||
log(LogLevel::WARN, "-- EINVAL");
|
||||
break;
|
||||
case EMFILE:
|
||||
log(LogLevel::WARN, "-- EMFILE");
|
||||
break;
|
||||
case ENFILE:
|
||||
log(LogLevel::WARN, "-- ENFILE");
|
||||
break;
|
||||
case ENOTSOCK:
|
||||
log(LogLevel::WARN, "-- ENOTSOCK");
|
||||
break;
|
||||
case EOPNOTSUPP:
|
||||
log(LogLevel::WARN, "-- EOPNOTSUPP");
|
||||
break;
|
||||
case ENOBUFS:
|
||||
log(LogLevel::WARN, "-- ENOBUFS");
|
||||
break;
|
||||
case ENOMEM:
|
||||
log(LogLevel::WARN, "-- ENOMEM");
|
||||
break;
|
||||
case EPROTO:
|
||||
log(LogLevel::WARN, "-- EPROTO");
|
||||
break;
|
||||
default:
|
||||
log(LogLevel::WARN, "-- EUNKNOWN");
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
string ipaddr{inet_ntoa(client.sin_addr)};
|
||||
log(LogLevel::ALERT, string("accepted a client: ") + ipaddr);
|
||||
|
||||
if (0 == fork()) {
|
||||
log(LogLevel::ALERT, "calling handle_client");
|
||||
handle_client(client_sock);
|
||||
if (-1 == close(client_sock)) {
|
||||
log(LogLevel::WARN, string("Could not close client: ") + ipaddr);
|
||||
switch (errno) {
|
||||
case EBADF:
|
||||
log(LogLevel::WARN, "-- EBADF");
|
||||
break;
|
||||
case EINTR:
|
||||
log(LogLevel::WARN, "-- EINTR");
|
||||
break;
|
||||
case EIO:
|
||||
log(LogLevel::WARN, "-- EIO");
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
log(LogLevel::ALERT, string("closed client ") + ipaddr);
|
||||
}
|
||||
return 0;
|
||||
} else {
|
||||
if (-1 == close(client_sock)) {
|
||||
log(LogLevel::WARN, string("Parent could not close client: ") + ipaddr);
|
||||
switch (errno) {
|
||||
case EBADF:
|
||||
log(LogLevel::WARN, "-- EBADF");
|
||||
break;
|
||||
case EINTR:
|
||||
log(LogLevel::WARN, "-- EINTR");
|
||||
break;
|
||||
case EIO:
|
||||
log(LogLevel::WARN, "-- EIO");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Note that as is normal for daemons, the exit point is never
|
||||
// reached. This application does not normally terminate.
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -21,9 +21,9 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
#include <sys/types.h>
|
||||
#include <syslog.h>
|
||||
#include <utility>
|
||||
#include <cstdint>
|
||||
|
||||
using ulong64 = unsigned long long;
|
||||
using pair64 = std::pair<ulong64, ulong64>;
|
||||
using pair64 = std::pair<uint64_t, uint64_t>;
|
||||
|
||||
enum class LogLevel
|
||||
{
|
||||
|
||||
@@ -28,67 +28,64 @@ using std::make_pair;
|
||||
string
|
||||
from_pair64(pair64 input)
|
||||
{
|
||||
static string hexadecimal{ "0123456789ABCDEF" };
|
||||
static string hexadecimal{ "0123456789ABCDEF" };
|
||||
|
||||
string left = "", right = "";
|
||||
uint64_t first = input.first;
|
||||
uint64_t second = input.second;
|
||||
string left = "", right = "";
|
||||
uint64_t first = input.first;
|
||||
uint64_t second = input.second;
|
||||
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
left = hexadecimal[first & 0x0F] + left;
|
||||
right = hexadecimal[second & 0x0F] + right;
|
||||
first >>= 4;
|
||||
second >>= 4;
|
||||
}
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
left = hexadecimal[first & 0x0F] + left;
|
||||
right = hexadecimal[second & 0x0F] + right;
|
||||
first >>= 4;
|
||||
second >>= 4;
|
||||
}
|
||||
|
||||
return left + right;
|
||||
return left + right;
|
||||
}
|
||||
|
||||
pair64
|
||||
to_pair64(string input)
|
||||
{
|
||||
ulong64 left{ 0 };
|
||||
ulong64 right{ 0 };
|
||||
uint8_t val1{ 0 };
|
||||
uint8_t val2{ 0 };
|
||||
size_t index{ 0 };
|
||||
char ch1{ 0 };
|
||||
char ch2{ 0 };
|
||||
uint64_t left{ 0 };
|
||||
uint64_t right{ 0 };
|
||||
uint8_t val1{ 0 };
|
||||
uint8_t val2{ 0 };
|
||||
size_t index{ 0 };
|
||||
char ch1{ 0 };
|
||||
char ch2{ 0 };
|
||||
|
||||
transform(input.begin(), input.end(), input.begin(), ::tolower);
|
||||
transform(input.begin(), input.end(), input.begin(), ::tolower);
|
||||
|
||||
for (index = 0; index < 16; index += 1) {
|
||||
ch1 = input[index];
|
||||
ch2 = input[index + 16];
|
||||
for (index = 0; index < 16; index += 1) {
|
||||
ch1 = input[index];
|
||||
ch2 = input[index + 16];
|
||||
|
||||
val1 = (ch1 >= '0' and ch1 <= '9') ? static_cast<uint8_t>(ch1 - '0')
|
||||
: static_cast<uint8_t>(ch1 - 'a') + 10;
|
||||
val2 = (ch2 >= '0' and ch2 <= '9') ? static_cast<uint8_t>(ch2 - '0')
|
||||
: static_cast<uint8_t>(ch2 - 'a') + 10;
|
||||
val1 = (ch1 >= '0' and ch1 <= '9') ? static_cast<uint8_t>(ch1 - '0')
|
||||
: static_cast<uint8_t>(ch1 - 'a') + 10;
|
||||
val2 = (ch2 >= '0' and ch2 <= '9') ? static_cast<uint8_t>(ch2 - '0')
|
||||
: static_cast<uint8_t>(ch2 - 'a') + 10;
|
||||
|
||||
left = (left << 4) + val1;
|
||||
right = (right << 4) + val2;
|
||||
}
|
||||
return make_pair(left, right);
|
||||
left = (left << 4) + val1;
|
||||
right = (right << 4) + val2;
|
||||
}
|
||||
return make_pair(left, right);
|
||||
}
|
||||
|
||||
bool
|
||||
operator<(const pair64& lhs, const pair64& rhs)
|
||||
bool operator<(const pair64& lhs, const pair64& rhs)
|
||||
{
|
||||
return (lhs.first < rhs.first)
|
||||
? true
|
||||
: (lhs.first == rhs.first and lhs.second < rhs.second) ? true
|
||||
: false;
|
||||
return (lhs.first < rhs.first)
|
||||
? true
|
||||
: (lhs.first == rhs.first and lhs.second < rhs.second) ? true
|
||||
: false;
|
||||
}
|
||||
|
||||
bool
|
||||
operator==(const pair64& lhs, const pair64& rhs)
|
||||
bool operator==(const pair64& lhs, const pair64& rhs)
|
||||
{
|
||||
return (lhs.first == rhs.first) and (lhs.second == rhs.second);
|
||||
return (lhs.first == rhs.first) and (lhs.second == rhs.second);
|
||||
}
|
||||
|
||||
bool
|
||||
operator>(const pair64& lhs, const pair64& rhs)
|
||||
bool operator>(const pair64& lhs, const pair64& rhs)
|
||||
{
|
||||
return ((not(lhs < rhs)) and (not(lhs == rhs)));
|
||||
return ((not(lhs < rhs)) and (not(lhs == rhs)));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user