1183 lines
36 KiB
C++
1183 lines
36 KiB
C++
/*
|
|
* Copyright (c) 2017-2024 OARC, Inc.
|
|
* Copyright (c) 2011-2017, IIS - The Internet Foundation in Sweden
|
|
* All rights reserved.
|
|
*
|
|
* This file is part of PacketQ.
|
|
*
|
|
* PacketQ is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* PacketQ is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with PacketQ. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "packetq.h"
|
|
#include "pcap.h"
|
|
#include "reader.h"
|
|
|
|
#include <arpa/inet.h>
|
|
#include <dirent.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <getopt.h>
|
|
#include <list>
|
|
#include <map>
|
|
#include <netdb.h>
|
|
#include <netinet/in.h>
|
|
#include <poll.h>
|
|
#include <signal.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <string>
|
|
#include <sys/socket.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
#include <sys/wait.h>
|
|
#include <syslog.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
|
|
#define MAXHOSTNAME 256
|
|
|
|
namespace packetq {
|
|
namespace httpd {
|
|
class Socket;
|
|
class Server;
|
|
|
|
static char redirect[] = "HTTP/1.1 307 Temporary Redirect\r\n"
|
|
"Location: /\r\n"
|
|
"Connection: close\r\n"
|
|
"Content-Type: text/html\r\n"
|
|
"\r\n<html><head><title>moved</title></head><body><h1>moved</h1>this page has moved to /</body></html>";
|
|
|
|
static char header[] = "HTTP/1.1 200 OK\r\n"
|
|
"Server: PacketQ builtin\r\n"
|
|
"Connection: close\r\n"
|
|
"Access-Control-Allow-Origin: *\r\n"
|
|
"Content-Type: %s\r\n"
|
|
"\r\n";
|
|
|
|
static Server* g_server = 0;
|
|
|
|
class SocketPool {
|
|
public:
|
|
SocketPool()
|
|
{
|
|
m_free = 0;
|
|
for (int i = 0; i < FD_SETSIZE; i++) {
|
|
m_sockets[i] = 0;
|
|
}
|
|
m_socket_count = 0;
|
|
}
|
|
int add(Socket* s)
|
|
{
|
|
if (m_free < FD_SETSIZE)
|
|
m_sockets[m_free] = s;
|
|
else
|
|
return -1;
|
|
int idx = m_free;
|
|
while (m_free < FD_SETSIZE && m_sockets[m_free])
|
|
m_free++;
|
|
m_socket_count++;
|
|
return idx;
|
|
}
|
|
void remove(int s)
|
|
{
|
|
m_sockets[s] = 0;
|
|
if (s < m_free)
|
|
m_free = s;
|
|
m_socket_count--;
|
|
}
|
|
|
|
int get_sockets() { return m_socket_count; }
|
|
void select();
|
|
|
|
fd_set m_readset;
|
|
fd_set m_writeset;
|
|
int m_free;
|
|
int m_socket_count;
|
|
Socket* m_sockets[FD_SETSIZE];
|
|
};
|
|
|
|
SocketPool g_pool;
|
|
|
|
class Stream {
|
|
public:
|
|
class Buffer {
|
|
private:
|
|
Buffer& operator=(const Buffer& other);
|
|
Buffer const& operator=(Buffer&& other);
|
|
|
|
public:
|
|
Buffer(const unsigned char* buf, int len)
|
|
{
|
|
m_buf = new unsigned char[len];
|
|
memcpy(m_buf, buf, len);
|
|
m_len = len;
|
|
m_pos = 0;
|
|
}
|
|
Buffer(Buffer&& other) noexcept
|
|
{
|
|
m_buf = other.m_buf;
|
|
m_len = other.m_len;
|
|
m_pos = other.m_pos;
|
|
other.m_buf = 0;
|
|
other.m_len = 0;
|
|
other.m_pos = 0;
|
|
}
|
|
~Buffer()
|
|
{
|
|
m_len = 0;
|
|
delete[] m_buf;
|
|
}
|
|
unsigned char* m_buf;
|
|
int m_len;
|
|
int m_pos;
|
|
};
|
|
Stream()
|
|
{
|
|
m_len = 0;
|
|
}
|
|
void push_front(unsigned char* data, int len)
|
|
{
|
|
m_stream.push_front(Buffer(data, len));
|
|
m_len += len;
|
|
}
|
|
void write(unsigned char* data, int len)
|
|
{
|
|
m_stream.push_back(Buffer(data, len));
|
|
m_len += len;
|
|
}
|
|
int read(unsigned char* data, int maxlen)
|
|
{
|
|
int p = 0;
|
|
while (p < maxlen && m_len > 0) {
|
|
Buffer& buf = m_stream.front();
|
|
int l = maxlen - p;
|
|
if (l > buf.m_len - buf.m_pos)
|
|
l = buf.m_len - buf.m_pos;
|
|
for (int i = 0; i < l; i++) {
|
|
data[p++] = buf.m_buf[buf.m_pos++];
|
|
}
|
|
m_len -= l;
|
|
if (l == 0) {
|
|
m_stream.pop_front();
|
|
}
|
|
}
|
|
return p;
|
|
}
|
|
int len()
|
|
{
|
|
return m_len;
|
|
}
|
|
int get()
|
|
{
|
|
unsigned char c = '@';
|
|
int r = read(&c, 1);
|
|
if (r == 0)
|
|
return -1;
|
|
return c;
|
|
}
|
|
|
|
private:
|
|
int m_len;
|
|
std::list<Stream::Buffer> m_stream;
|
|
};
|
|
|
|
class Socket {
|
|
private:
|
|
Socket& operator=(const Socket& other);
|
|
Socket(Socket&& other) noexcept;
|
|
Socket const& operator=(Socket&& other);
|
|
|
|
public:
|
|
Socket(int s, bool serv)
|
|
: m_want_write(false)
|
|
{
|
|
int flags;
|
|
|
|
// Add non-blocking flag
|
|
if ((flags = fcntl(s, F_GETFL)) == -1) {
|
|
syslog(LOG_ERR | LOG_USER, "fcntl(%d, F_GETFL) failed: %d\n", s, errno);
|
|
exit(-1);
|
|
}
|
|
if (fcntl(s, F_SETFL, flags | O_NONBLOCK)) {
|
|
syslog(LOG_ERR | LOG_USER, "fcntl(%d, F_SETFL, 0x%x) failed: %d\n", s, errno, flags | O_NONBLOCK);
|
|
exit(-1);
|
|
}
|
|
|
|
m_bytes_written = 0;
|
|
m_bytes_read = 0;
|
|
m_serv = serv;
|
|
m_socket = s;
|
|
m_sidx = g_pool.add(this);
|
|
m_close_when_empty = false;
|
|
m_file = 0;
|
|
}
|
|
virtual ~Socket()
|
|
{
|
|
g_pool.remove(m_sidx);
|
|
close(m_socket);
|
|
}
|
|
void process(bool read)
|
|
{
|
|
// m_serv means this is a listening socket
|
|
if (m_serv)
|
|
return;
|
|
if (!read) {
|
|
on_write();
|
|
unsigned char ptr[4096];
|
|
int len;
|
|
|
|
while ((len = m_write.read(ptr, sizeof(ptr)))) {
|
|
int res = write(m_socket, ptr, len);
|
|
#if EAGAIN != EWOULDBLOCK
|
|
if (res == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) {
|
|
#else
|
|
if (res == -1 && (errno == EAGAIN)) {
|
|
#endif
|
|
m_write.push_front(ptr, len);
|
|
set_want_write();
|
|
return;
|
|
}
|
|
if (res < 0) {
|
|
delete this;
|
|
return;
|
|
}
|
|
m_bytes_written += res;
|
|
if (res < len) {
|
|
m_write.push_front(&ptr[res], len - res);
|
|
set_want_write();
|
|
return;
|
|
}
|
|
}
|
|
if (m_close_when_empty) {
|
|
shutdown(m_socket, SHUT_WR);
|
|
// fflush(m_socket);
|
|
delete this;
|
|
}
|
|
}
|
|
if (read) {
|
|
int avail = read_sock();
|
|
|
|
if (avail)
|
|
on_read();
|
|
}
|
|
}
|
|
virtual void file_write()
|
|
{
|
|
}
|
|
virtual void file_read()
|
|
{
|
|
}
|
|
void set_delete()
|
|
{
|
|
m_close_when_empty = true;
|
|
m_want_write = false;
|
|
}
|
|
virtual void on_write()
|
|
{
|
|
}
|
|
virtual void on_read()
|
|
{
|
|
}
|
|
int read_sock()
|
|
{
|
|
unsigned char buf[2000];
|
|
int len = 0;
|
|
if ((len = recv(m_socket, buf, sizeof(buf) - 1, 0)) > 0) {
|
|
m_bytes_read += len;
|
|
buf[len] = 0;
|
|
m_read.write(buf, len);
|
|
}
|
|
if (len < 0) {
|
|
delete this;
|
|
return 0;
|
|
}
|
|
return len;
|
|
}
|
|
bool failed()
|
|
{
|
|
return m_sidx == -1;
|
|
}
|
|
bool has_data()
|
|
{
|
|
return m_want_write || m_write.len() > 0;
|
|
}
|
|
void set_want_write(bool set = true)
|
|
{
|
|
m_want_write = set;
|
|
}
|
|
|
|
int m_bytes_written;
|
|
int m_bytes_read;
|
|
int m_socket;
|
|
int m_file;
|
|
int m_sidx;
|
|
bool m_serv;
|
|
bool m_want_write;
|
|
bool m_close_when_empty;
|
|
char m_buffer[4096];
|
|
|
|
Stream m_read, m_write;
|
|
};
|
|
|
|
void SocketPool::select()
|
|
{
|
|
FD_ZERO(&m_readset);
|
|
FD_ZERO(&m_writeset);
|
|
int max = 0;
|
|
for (int i = 0; i < FD_SETSIZE; i++) {
|
|
if (m_sockets[i]) {
|
|
if (max < m_sockets[i]->m_socket)
|
|
max = m_sockets[i]->m_socket;
|
|
FD_SET(m_sockets[i]->m_socket, &m_readset);
|
|
if (m_sockets[i]->has_data())
|
|
FD_SET(m_sockets[i]->m_socket, &m_writeset);
|
|
if (max < m_sockets[i]->m_file)
|
|
max = m_sockets[i]->m_file;
|
|
FD_SET(m_sockets[i]->m_file, &m_readset);
|
|
}
|
|
}
|
|
timeval timeout;
|
|
timeout.tv_sec = 30;
|
|
timeout.tv_usec = 0;
|
|
|
|
int sel = ::select(max + 1, &m_readset, &m_writeset, 0, &timeout);
|
|
if (sel < 0) {
|
|
syslog(LOG_ERR | LOG_USER, "sel -1 errno = %d %s max:%d", errno, strerror(errno), max);
|
|
exit(-1);
|
|
}
|
|
if (sel) {
|
|
for (int i = 0; i < FD_SETSIZE; i++) {
|
|
if (m_sockets[i] && m_sockets[i]->m_file) {
|
|
if (FD_ISSET(m_sockets[i]->m_file, &m_readset)) {
|
|
m_sockets[i]->file_read();
|
|
}
|
|
if (m_sockets[i] && FD_ISSET(m_sockets[i]->m_file, &m_writeset)) {
|
|
m_sockets[i]->file_write();
|
|
}
|
|
}
|
|
if (m_sockets[i] && FD_ISSET(m_sockets[i]->m_socket, &m_readset)) {
|
|
m_sockets[i]->process(true);
|
|
}
|
|
if (m_sockets[i] && FD_ISSET(m_sockets[i]->m_socket, &m_writeset)) {
|
|
m_sockets[i]->process(false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
class Server {
|
|
public:
|
|
Socket m_socket;
|
|
|
|
Server(int port, const std::string& pcaproot, const std::string& webroot)
|
|
: m_socket(establish(port), true)
|
|
, m_pcaproot(pcaproot)
|
|
, m_webroot(webroot)
|
|
{
|
|
if (m_socket.m_socket < 0) {
|
|
if (m_socket.m_socket == EADDRINUSE)
|
|
syslog(LOG_ERR | LOG_USER, "Fail EADDRINUSE (%d)\n", m_socket.m_socket);
|
|
else
|
|
syslog(LOG_ERR | LOG_USER, "Fail %d port:%d\n", m_socket.m_socket, port);
|
|
exit(-1);
|
|
}
|
|
}
|
|
|
|
~Server()
|
|
{
|
|
}
|
|
|
|
int get_connection()
|
|
{
|
|
int s = m_socket.m_socket;
|
|
int t; /* socket of connection */
|
|
if ((t = accept(s, NULL, NULL)) < 0) /* accept connection if there is one */
|
|
return (-1);
|
|
|
|
return (t);
|
|
}
|
|
|
|
int establish(unsigned short portnum)
|
|
{
|
|
int s, res;
|
|
sockaddr_in sa;
|
|
memset(&sa, 0, sizeof(struct sockaddr_in));
|
|
|
|
sa.sin_family = AF_INET;
|
|
sa.sin_addr.s_addr = htonl(INADDR_ANY);
|
|
|
|
sa.sin_port = htons(portnum);
|
|
|
|
if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0)
|
|
return (-2);
|
|
int on = 1;
|
|
|
|
res = setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
|
|
|
|
if ((res = bind(s, (const sockaddr*)&sa, sizeof(struct sockaddr_in))) < 0) {
|
|
close(s);
|
|
return (res); /* bind address to socket */
|
|
}
|
|
|
|
// Add non-blocking flag
|
|
int flags;
|
|
if ((flags = fcntl(s, F_GETFL)) == -1) {
|
|
syslog(LOG_ERR | LOG_USER, "fcntl(%d, F_GETFL) failed: %d\n", s, errno);
|
|
exit(-1);
|
|
}
|
|
if (fcntl(s, F_SETFL, flags | O_NONBLOCK)) {
|
|
syslog(LOG_ERR | LOG_USER, "fcntl(%d, F_SETFL, 0x%x) failed: %d\n", s, errno, flags | O_NONBLOCK);
|
|
exit(-1);
|
|
}
|
|
|
|
listen(s, 511); /* max # of queued connects */
|
|
return (s);
|
|
}
|
|
|
|
std::string m_pcaproot;
|
|
std::string m_webroot;
|
|
};
|
|
|
|
class Url {
|
|
public:
|
|
Url(const char* url)
|
|
: m_full(url)
|
|
{
|
|
int i = 0;
|
|
for (; i < m_full.length();) {
|
|
char c = m_full.c_str()[i];
|
|
if (c == '?') {
|
|
i++;
|
|
break;
|
|
}
|
|
m_path += c;
|
|
i++;
|
|
}
|
|
m_path = decode(m_path);
|
|
if (m_path == "")
|
|
m_path = "/";
|
|
decode_params(m_full.substr(i).c_str());
|
|
}
|
|
void decode_params(const char* params)
|
|
{
|
|
if (!params)
|
|
return;
|
|
std::string str = params;
|
|
for (int i = 0; i < str.length();) {
|
|
std::string param = "", value = "";
|
|
for (; i < str.length();) {
|
|
char c = str.c_str()[i];
|
|
if (c == '=' || c == '&') {
|
|
i++;
|
|
break;
|
|
}
|
|
param += c;
|
|
i++;
|
|
}
|
|
for (; i < str.length();) {
|
|
char c = str.c_str()[i];
|
|
if (c == '&') {
|
|
i++;
|
|
break;
|
|
}
|
|
value += c;
|
|
i++;
|
|
}
|
|
add_param(decode(param), decode(value));
|
|
}
|
|
}
|
|
const char* get_param(const char* param)
|
|
{
|
|
auto it = m_params.find(std::string(param));
|
|
if (it == m_params.end())
|
|
return 0;
|
|
return it->second.c_str();
|
|
}
|
|
void add_param(std::string key, std::string val)
|
|
{
|
|
auto it = m_params.find(key);
|
|
if (it == m_params.end()) {
|
|
m_params[key] = val;
|
|
m_counts[key] = 1;
|
|
return;
|
|
}
|
|
char cnt[100];
|
|
int n = m_counts[key];
|
|
m_counts[key] = n + 1;
|
|
|
|
snprintf(cnt, sizeof(cnt) - 1, "%d", n);
|
|
cnt[99] = 0;
|
|
std::string keyn = key;
|
|
keyn += cnt;
|
|
m_params[keyn] = val;
|
|
}
|
|
|
|
std::string decode(std::string str)
|
|
{
|
|
std::string dst;
|
|
int percent_state = 0;
|
|
int code = 0;
|
|
for (int i = 0; i < str.length(); i++) {
|
|
char c = str.c_str()[i];
|
|
if (percent_state) {
|
|
int n = 0;
|
|
if (c >= '0' && c <= '9')
|
|
n = c - '0';
|
|
if (c >= 'a' && c <= 'f')
|
|
n = c - 'a' + 10;
|
|
if (c >= 'A' && c <= 'F')
|
|
n = c - 'A' + 10;
|
|
code = (code << 4) | n;
|
|
percent_state--;
|
|
if (!percent_state) {
|
|
dst += char(code);
|
|
code = 0;
|
|
}
|
|
} else {
|
|
if (c == '%') {
|
|
percent_state = 2;
|
|
} else
|
|
dst += c;
|
|
}
|
|
}
|
|
return dst;
|
|
}
|
|
|
|
std::string get_full()
|
|
{
|
|
return m_full;
|
|
}
|
|
std::string get_path()
|
|
{
|
|
return m_path;
|
|
}
|
|
|
|
std::string m_full;
|
|
std::string m_path;
|
|
std::map<std::string, std::string> m_params;
|
|
std::map<std::string, int> m_counts;
|
|
};
|
|
|
|
class Page {
|
|
public:
|
|
Page(const char* url, const char* body)
|
|
: m_url(url)
|
|
{
|
|
m_url.decode_params(body);
|
|
}
|
|
|
|
void process()
|
|
{
|
|
|
|
if (m_url.get_path().compare("/query") == 0) {
|
|
if (!m_url.get_param("file")) {
|
|
printf(header, "text/plain");
|
|
printf("no file selected\n");
|
|
return;
|
|
}
|
|
|
|
printf(header, "text/plain");
|
|
if (m_url.get_param("sql"))
|
|
query(m_url.get_param("sql"));
|
|
else
|
|
printf("no query defined \n");
|
|
} else if (m_url.get_path().substr(0, 8).compare("/resolve") == 0) {
|
|
resolve();
|
|
} else if (m_url.get_path().substr(0, 5).compare("/list") == 0) {
|
|
serve_dir();
|
|
} else {
|
|
serve_static();
|
|
}
|
|
|
|
delete g_app;
|
|
}
|
|
static std::string join_path(const std::string& a, const std::string& b)
|
|
{
|
|
if (b.find("..") != std::string::npos)
|
|
return a;
|
|
if (a.length() == 0)
|
|
return b;
|
|
if (b.length() == 0)
|
|
return a;
|
|
if (a[a.length() - 1] != '/' && b[0] != '/')
|
|
return a + std::string("/") + b;
|
|
return a + b;
|
|
}
|
|
const char* get_mimetype(const std::string& file)
|
|
{
|
|
int p = file.find_last_of('.');
|
|
if (p == std::string::npos || p + 1 >= file.length())
|
|
return 0;
|
|
std::string suff = file.substr(p + 1);
|
|
if (suff.compare("js") == 0)
|
|
return "application/x-javascript";
|
|
if (suff.compare("jpg") == 0)
|
|
return "image/jpeg";
|
|
if (suff.compare("html") == 0)
|
|
return "text/html";
|
|
if (suff.compare("htm") == 0)
|
|
return "text/html";
|
|
if (suff.compare("txt") == 0)
|
|
return "text/plain";
|
|
if (suff.compare("png") == 0)
|
|
return "image/png";
|
|
if (suff.compare("gif") == 0)
|
|
return "image/gif";
|
|
if (suff.compare("ico") == 0)
|
|
return "image/x-icon";
|
|
if (suff.compare("json") == 0)
|
|
return "application/json";
|
|
if (suff.compare("css") == 0)
|
|
return "text/css";
|
|
|
|
return 0;
|
|
}
|
|
bool serve_file(const std::string& file)
|
|
{
|
|
const char* mimetype = get_mimetype(file);
|
|
|
|
if (mimetype) {
|
|
FILE* fp = fopen(file.c_str(), "rb");
|
|
if (fp) {
|
|
printf(header, mimetype);
|
|
char buffer[8192];
|
|
int len;
|
|
while ((len = fread(buffer, 1, 200, fp)) > 0) {
|
|
fwrite(buffer, 1, len, stdout);
|
|
}
|
|
fclose(fp);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
void serve_static()
|
|
{
|
|
if (g_server->m_webroot == "") {
|
|
printf(header, "text/html");
|
|
printf("<h2>This server is not configured to serve static pages</h2>");
|
|
printf("Start using the -w option to set a html directory");
|
|
return;
|
|
}
|
|
std::string file = join_path(g_server->m_webroot, m_url.get_path());
|
|
|
|
if (serve_file(file))
|
|
return;
|
|
if (serve_file(join_path(file, "index.html")))
|
|
return;
|
|
|
|
if (m_url.get_path().compare("/") != 0) {
|
|
printf("%s", redirect);
|
|
return;
|
|
}
|
|
|
|
printf(header, "text/html");
|
|
printf("<h2>It works !</h2><br>\n");
|
|
printf("%s", "<a href=\"/query?file=sample.pcap&sql=select%20qr,qname,protocol%20from%20dns%20limit%2018;\">Test query</a><br/>\n");
|
|
printf("%s", "<a href=\"/list\">list available files</a><br/>\n");
|
|
}
|
|
|
|
void resolve()
|
|
{
|
|
const char* ip = m_url.get_param("ip");
|
|
const char* name = m_url.get_param("name");
|
|
|
|
if (ip) {
|
|
|
|
printf(header, "application/json");
|
|
|
|
printf("[");
|
|
|
|
struct addrinfo* result;
|
|
struct addrinfo* res;
|
|
int error;
|
|
|
|
error = getaddrinfo(ip, NULL, NULL, &result);
|
|
if (error == 0) {
|
|
for (res = result; res != NULL; res = res->ai_next) {
|
|
char hostname[NI_MAXHOST] = "";
|
|
|
|
error = getnameinfo(res->ai_addr, res->ai_addrlen, hostname, NI_MAXHOST, NULL, 0, 0);
|
|
if (error != 0) {
|
|
continue;
|
|
}
|
|
if (*hostname != '\0') {
|
|
printf("\"%s\"", hostname);
|
|
break;
|
|
}
|
|
}
|
|
freeaddrinfo(result);
|
|
}
|
|
printf("]\n");
|
|
} else if (name) {
|
|
char tmp[100];
|
|
printf(header, "application/json");
|
|
|
|
printf("[");
|
|
|
|
struct addrinfo* result;
|
|
struct addrinfo* res;
|
|
int error;
|
|
|
|
error = getaddrinfo(name, NULL, NULL, &result);
|
|
char empty[] = "", line[] = ",\n";
|
|
char* sep = empty;
|
|
if (error == 0) {
|
|
for (res = result; res != NULL; res = res->ai_next) {
|
|
void* ptr = &((struct sockaddr_in*)res->ai_addr)->sin_addr;
|
|
if (res->ai_family == AF_INET6)
|
|
ptr = &((struct sockaddr_in6*)res->ai_addr)->sin6_addr;
|
|
tmp[0] = 0;
|
|
inet_ntop(res->ai_family, ptr, tmp, sizeof(tmp));
|
|
printf("%s\"%s\"", sep, tmp);
|
|
sep = line;
|
|
}
|
|
freeaddrinfo(result);
|
|
}
|
|
printf("]\n");
|
|
} else
|
|
printf("[]\n");
|
|
}
|
|
|
|
void serve_dir()
|
|
{
|
|
if (g_server->m_pcaproot == "") {
|
|
printf(header, "text/html");
|
|
printf("<h2>This server is not configured to list pcapfiles</h2>");
|
|
printf("Start using the -r option to set a pcap directory");
|
|
return;
|
|
}
|
|
std::string directory = join_path(g_server->m_pcaproot, m_url.get_path().substr(5));
|
|
|
|
DIR* dir = opendir(directory.c_str());
|
|
if (!dir) {
|
|
printf("%s", redirect);
|
|
return;
|
|
}
|
|
|
|
printf(header, "application/json");
|
|
|
|
printf("[\n");
|
|
struct dirent* d;
|
|
struct stat statbuf;
|
|
|
|
char comma = ' ';
|
|
|
|
while ((d = readdir(dir)) != 0) {
|
|
std::string subject = join_path(directory, d->d_name);
|
|
int fd = open(subject.c_str(), O_RDONLY);
|
|
|
|
if (fd < 0)
|
|
continue;
|
|
if (fstat(fd, &statbuf) == -1) {
|
|
close(fd);
|
|
continue;
|
|
}
|
|
if (S_ISDIR(statbuf.st_mode)) {
|
|
if ((strcmp(d->d_name, ".") != 0) && (strcmp(d->d_name, "..") != 0)) {
|
|
printf(" %c{\n \"data\" : \"%s\",\n \"attr\" : { \"id\": \"%s\" },\n \"children\" : [], \"state\" : \"closed\" }\n",
|
|
comma, d->d_name, join_path(m_url.get_path(), d->d_name).substr(5).c_str());
|
|
comma = ',';
|
|
}
|
|
} else {
|
|
bool found = false;
|
|
FILE* fp = fdopen(fd, "rb");
|
|
if (fp) {
|
|
Pcap_file pfile(fp);
|
|
if (pfile.get_header()) {
|
|
unsigned char* data = 0;
|
|
int s = 0, us, len;
|
|
data = pfile.get_packet(len, s, us);
|
|
if (data) {
|
|
printf(" %c{\n \"data\" : \"%s\",\n \"attr\" : { \"id\" : \"%s\", \"size\": %d, \"time\": %d,\"type\": \"pcap\" },\n \"children\" : [] }\n",
|
|
comma, d->d_name, join_path(m_url.get_path(), d->d_name).substr(5).c_str(), int(statbuf.st_size), s);
|
|
comma = ',';
|
|
found = true;
|
|
}
|
|
}
|
|
fclose(fp);
|
|
}
|
|
if (!found) {
|
|
std::string str = subject;
|
|
transform(str.begin(), str.end(), str.begin(), tolower);
|
|
if (str.rfind(".json") == str.length() - 5) {
|
|
printf(" %c{\n \"data\" : \"%s\",\n \"attr\" : { \"id\" : \"%s\", \"size\": %d, \"type\": \"json\" },\n \"children\" : [] }\n",
|
|
comma, d->d_name, join_path(m_url.get_path(), d->d_name).substr(5).c_str(), int(statbuf.st_size));
|
|
comma = ',';
|
|
}
|
|
}
|
|
}
|
|
close(fd);
|
|
}
|
|
|
|
printf("]\n");
|
|
|
|
closedir(dir);
|
|
}
|
|
|
|
void query(const char* sql)
|
|
{
|
|
Query query("result", sql);
|
|
|
|
query.parse();
|
|
|
|
std::vector<std::string> in_files;
|
|
|
|
int i = 0;
|
|
while (true) {
|
|
char param[50] = "file";
|
|
|
|
std::string par = "file";
|
|
if (i > 0) {
|
|
snprintf(param, sizeof(param) - 1, "file%d", i);
|
|
param[49] = 0;
|
|
}
|
|
i++;
|
|
const char* f = m_url.get_param(param);
|
|
if (!f)
|
|
break;
|
|
std::string file = join_path(g_server->m_pcaproot, f);
|
|
in_files.push_back(file);
|
|
}
|
|
|
|
Reader reader(in_files, g_app->get_limit());
|
|
|
|
query.execute(reader);
|
|
query.m_result->json(false);
|
|
}
|
|
Url m_url;
|
|
};
|
|
|
|
class Http_socket : public Socket {
|
|
private:
|
|
Http_socket& operator=(const Http_socket& other);
|
|
Http_socket(Http_socket&& other) noexcept;
|
|
Http_socket const& operator=(Http_socket&& other);
|
|
|
|
public:
|
|
enum State {
|
|
get_post,
|
|
header,
|
|
body,
|
|
error,
|
|
wait_child,
|
|
done
|
|
};
|
|
Http_socket(int socket)
|
|
: Socket(socket, false)
|
|
, m_http_version(0)
|
|
, m_emptyline(0)
|
|
{
|
|
m_state = get_post;
|
|
m_nextc = -1;
|
|
m_cr = false;
|
|
m_line = "";
|
|
m_url = "";
|
|
m_child_fd = 0;
|
|
m_child_pid = 0;
|
|
m_child_read = 0;
|
|
m_body_cnt = -1;
|
|
m_content_len = 0;
|
|
}
|
|
~Http_socket()
|
|
{
|
|
if (m_child_pid) {
|
|
kill(m_child_pid, SIGHUP);
|
|
int status;
|
|
waitpid(m_child_pid, &status, 0);
|
|
}
|
|
if (m_child_fd)
|
|
close(m_child_fd);
|
|
m_file = 0;
|
|
}
|
|
inline void print(const char* fmt, ...)
|
|
{
|
|
char string[4096];
|
|
va_list ap;
|
|
|
|
va_start(ap, fmt);
|
|
vsnprintf(string, sizeof(string), fmt, ap);
|
|
va_end(ap);
|
|
|
|
m_write.write((unsigned char*)string, strlen(string));
|
|
}
|
|
|
|
int peek()
|
|
{
|
|
if (m_nextc >= 0)
|
|
return m_nextc;
|
|
m_nextc = m_read.get();
|
|
return m_nextc;
|
|
}
|
|
int getc()
|
|
{
|
|
int c = peek();
|
|
m_nextc = -1;
|
|
return c;
|
|
}
|
|
|
|
void on_read()
|
|
{
|
|
while (true) {
|
|
int c = peek();
|
|
if (c == -1)
|
|
return;
|
|
if (m_body_cnt >= 0) {
|
|
c = getc();
|
|
if (!(m_body_cnt == 0 && c == 10)) {
|
|
m_line += char(c);
|
|
m_content_len--;
|
|
}
|
|
m_body_cnt++;
|
|
if (m_content_len == 0)
|
|
parseline();
|
|
continue;
|
|
}
|
|
c = getc();
|
|
if (c != 13 && c != 10) {
|
|
m_line += char(c);
|
|
m_cr = false;
|
|
} else {
|
|
bool cr = m_cr;
|
|
m_cr = false;
|
|
if (c == 10 && cr) {
|
|
continue;
|
|
}
|
|
if (c == 13)
|
|
m_cr = true;
|
|
parseline();
|
|
}
|
|
}
|
|
}
|
|
virtual void file_read()
|
|
{
|
|
set_want_write();
|
|
}
|
|
virtual void file_write()
|
|
{
|
|
}
|
|
void on_write()
|
|
{
|
|
set_want_write(false);
|
|
if (m_state == wait_child) {
|
|
unsigned char buffer[4096];
|
|
int status;
|
|
bool done = true;
|
|
if (0 == waitpid(m_child_pid, &status, WNOHANG)) {
|
|
done = false;
|
|
}
|
|
if (m_child_fd) {
|
|
// Add non-blocking flag
|
|
int flags;
|
|
if ((flags = fcntl(m_child_fd, F_GETFL)) == -1) {
|
|
syslog(LOG_ERR | LOG_USER, "fcntl(%d, F_GETFL) failed: %d\n", m_child_fd, errno);
|
|
exit(-1);
|
|
}
|
|
if (fcntl(m_child_fd, F_SETFL, flags | O_NONBLOCK)) {
|
|
syslog(LOG_ERR | LOG_USER, "fcntl(%d, F_SETFL, 0x%x) failed: %d\n", m_child_fd, errno, flags | O_NONBLOCK);
|
|
exit(-1);
|
|
}
|
|
|
|
size_t res;
|
|
pollfd pfd;
|
|
pfd.fd = m_child_fd;
|
|
pfd.events = POLLIN;
|
|
pfd.revents = 0;
|
|
if (1 == poll(&pfd, 1, 0) && (pfd.revents & POLLIN) != 0) {
|
|
if ((res = read(m_child_fd, buffer, (int)sizeof(buffer))) > 0) {
|
|
done = false;
|
|
m_child_read += res;
|
|
m_write.write(buffer, res);
|
|
}
|
|
}
|
|
}
|
|
if (done) {
|
|
m_child_pid = 0;
|
|
if (m_child_fd) {
|
|
close(m_child_fd);
|
|
m_child_fd = 0;
|
|
m_file = 0;
|
|
}
|
|
set_delete();
|
|
}
|
|
}
|
|
}
|
|
void parseline()
|
|
{
|
|
switch (m_state) {
|
|
case (get_post): {
|
|
m_state = error;
|
|
syslog(LOG_INFO | LOG_USER, "%s\n", m_line.c_str());
|
|
int p = 0;
|
|
if (m_line.find("GET ") != -1) {
|
|
if ((p = m_line.find(" HTTP/1.1")) != -1) {
|
|
m_http_version = 1;
|
|
} else if ((p = m_line.find(" HTTP/1.0")) != -1) {
|
|
m_http_version = 0;
|
|
} else {
|
|
return;
|
|
}
|
|
m_url = m_line.substr(4, p - 4);
|
|
m_state = header;
|
|
} else if (m_line.find("POST ") != -1) {
|
|
if ((p = m_line.find(" HTTP/1.1")) != -1) {
|
|
m_http_version = 1;
|
|
} else if ((p = m_line.find(" HTTP/1.0")) != -1) {
|
|
m_http_version = 0;
|
|
} else {
|
|
return;
|
|
}
|
|
m_url = m_line.substr(5, p - 5);
|
|
m_state = header;
|
|
}
|
|
} break;
|
|
case (header):
|
|
if (m_line.length() == 0) {
|
|
m_body_cnt = 0;
|
|
m_state = body;
|
|
} else {
|
|
int colon = m_line.find(": ");
|
|
std::string key = m_line.substr(0, colon);
|
|
std::string val = m_line.substr(colon + 2);
|
|
if (key == "Content-Length") {
|
|
if (val.length() > 0)
|
|
m_content_len = atoi(val.c_str());
|
|
}
|
|
}
|
|
break;
|
|
case (body):
|
|
m_body = m_line;
|
|
header_done();
|
|
break;
|
|
default:
|
|
printf("error line: %s !\n", m_line.c_str());
|
|
break;
|
|
}
|
|
m_line = "";
|
|
}
|
|
void header_done()
|
|
{
|
|
fflush(stdout); // required before fork or any unflushed output will go to the client
|
|
int fd[2];
|
|
if (pipe(fd) < 0)
|
|
return;
|
|
|
|
// Add non-blocking flag
|
|
int flags;
|
|
if ((flags = fcntl(fd[0], F_GETFL)) == -1) {
|
|
syslog(LOG_ERR | LOG_USER, "fcntl(%d, F_GETFL) failed: %d\n", fd[0], errno);
|
|
exit(-1);
|
|
}
|
|
if (fcntl(fd[0], F_SETFL, flags | O_NONBLOCK)) {
|
|
syslog(LOG_ERR | LOG_USER, "fcntl(%d, F_SETFL, 0x%x) failed: %d\n", fd[0], errno, flags | O_NONBLOCK);
|
|
exit(-1);
|
|
}
|
|
|
|
m_child_pid = fork();
|
|
if (m_child_pid < 0) {
|
|
print("Internal error");
|
|
set_delete();
|
|
return;
|
|
}
|
|
if (m_child_pid == 0) {
|
|
////////// child code /////////
|
|
dup2(fd[1], fileno(stdout));
|
|
dup2(fd[1], fileno(stderr));
|
|
close(fd[1]);
|
|
|
|
Page page(m_url.c_str(), m_body.c_str());
|
|
page.process();
|
|
fflush(stdout);
|
|
exit(0);
|
|
///////////// child exit() ///////////////
|
|
} else {
|
|
close(fd[1]);
|
|
m_child_fd = fd[0];
|
|
m_file = m_child_fd;
|
|
m_state = wait_child;
|
|
}
|
|
set_want_write();
|
|
}
|
|
State m_state;
|
|
bool m_cr;
|
|
int m_body_cnt;
|
|
int m_content_len;
|
|
int m_nextc;
|
|
int m_child_pid;
|
|
int m_child_fd;
|
|
|
|
int m_child_read;
|
|
|
|
int m_http_version; // 0 = HTTP/1.0 1 = HTTP/1.1
|
|
|
|
int m_emptyline;
|
|
std::string m_line;
|
|
std::string m_url;
|
|
std::string m_body;
|
|
};
|
|
|
|
} // namespace httpd
|
|
|
|
using namespace httpd;
|
|
|
|
void start_server(int port, bool fork_me, const std::string& pcaproot, const std::string& webroot, int max_conn)
|
|
{
|
|
pid_t pid, sid;
|
|
bool fg = !fork_me;
|
|
|
|
printf("listening on port %d\n", port);
|
|
|
|
if (!fg) {
|
|
pid = fork();
|
|
|
|
if (pid < 0) {
|
|
exit(EXIT_FAILURE);
|
|
} else if (pid > 0) {
|
|
exit(EXIT_SUCCESS);
|
|
}
|
|
|
|
sid = setsid();
|
|
|
|
if (sid < 0) {
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
}
|
|
openlog("packetq", LOG_PID, LOG_USER);
|
|
|
|
httpd::Server server(port, pcaproot, webroot);
|
|
g_server = &server;
|
|
|
|
while (true) {
|
|
httpd::g_pool.select();
|
|
int cnt = g_pool.get_sockets();
|
|
if (cnt < max_conn) {
|
|
int c = server.get_connection();
|
|
if (c > -1) {
|
|
Http_socket* s = new (std::nothrow) Http_socket(c);
|
|
if (s && s->failed()) {
|
|
syslog(LOG_ERR | LOG_USER, "failed to create socket");
|
|
delete s;
|
|
}
|
|
}
|
|
}
|
|
usleep(1000);
|
|
}
|
|
// loop will never break
|
|
// g_server = 0;
|
|
// syslog(LOG_INFO | LOG_USER, "exiting");
|
|
// exit(EXIT_SUCCESS);
|
|
}
|
|
|
|
} // namespace packetq
|