Merge branch 'master' of github.com:rjhansen/nsrlsvr

This commit is contained in:
Robert J. Hansen
2016-10-27 18:38:08 -04:00
7 changed files with 606 additions and 605 deletions

View File

@@ -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)

View File

@@ -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>

View File

@@ -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>

View File

@@ -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.
}
}

View File

@@ -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;
}

View File

@@ -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
{

View File

@@ -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)));
}