summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorlinuxdaemon <linuxdaemon@users.noreply.github.com>2019-02-06 04:33:06 -0600
committerPeter Powell <petpow@saberuk.com>2019-02-06 10:33:06 +0000
commitd4a1ea70451abb333e71f9cff09b624db59531a0 (patch)
treec9a07b0c6ee562f2d4d76e4c797e415f943f3936
parenta638de7715b55c9a09e51fd5d42d97f05d966da7 (diff)
Expand searching in m_httpd_stats, add global handling of GET parameters (#1566)
-rw-r--r--include/modules/httpd.h79
-rw-r--r--src/modules/m_httpd.cpp41
-rw-r--r--src/modules/m_httpd_acl.cpp8
-rw-r--r--src/modules/m_httpd_config.cpp2
-rw-r--r--src/modules/m_httpd_stats.cpp190
5 files changed, 277 insertions, 43 deletions
diff --git a/include/modules/httpd.h b/include/modules/httpd.h
index b4b88bed5..e1921cdb4 100644
--- a/include/modules/httpd.h
+++ b/include/modules/httpd.h
@@ -30,6 +30,60 @@
#include <sstream>
#include <map>
+class HTTPQueryParameters : public insp::flat_multimap<std::string, std::string>
+{
+ public:
+ bool get(const std::string& key, std::string& value) const
+ {
+ const_iterator it = find(key);
+ if (it == end())
+ return false;
+
+ value = it->second;
+ return true;
+ }
+
+ std::string getString(const std::string& key, const std::string& def = "") const
+ {
+ std::string value;
+ if (!get(key, value))
+ return def;
+
+ return value;
+ }
+
+ template <typename T>
+ T getNum(const std::string& key, T def = 0) const
+ {
+ std::string value;
+ if (!get(key, value))
+ return def;
+
+ return ConvToNum<T>(value);
+ }
+
+ unsigned long getDuration(const std::string& key, unsigned long def = 0) const
+ {
+ unsigned long value;
+ if (!InspIRCd::Duration(getString(key, "0"), value))
+ return def;
+
+ return value;
+ }
+
+ bool getBool(const std::string& key, bool def = false) const
+ {
+ return getNum<bool>(key, def);
+ }
+};
+
+struct HTTPRequestURI
+{
+ std::string path;
+ HTTPQueryParameters query_params;
+ std::string fragment;
+};
+
/** A modifyable list of HTTP header fields
*/
class HTTPHeaders
@@ -112,9 +166,9 @@ class HTTPRequest
{
protected:
std::string type;
- std::string document;
std::string ipaddr;
std::string postdata;
+ HTTPRequestURI parseduri;
public:
@@ -129,15 +183,19 @@ class HTTPRequest
/** Initialize HTTPRequest.
* This constructor is called by m_httpd.so to initialize the class.
* @param request_type The request type, e.g. GET, POST, HEAD
- * @param uri The URI, e.g. /page
* @param hdr The headers sent with the request
* @param opaque An opaque pointer used internally by m_httpd, which you must pass back to the module in your reply.
* @param ip The IP address making the web request.
* @param pdata The post data (content after headers) received with the request, up to Content-Length in size
*/
- HTTPRequest(const std::string& request_type, const std::string& uri,
+ HTTPRequest(const std::string& request_type, const HTTPRequestURI& Parseduri,
HTTPHeaders* hdr, HttpServerSocket* socket, const std::string &ip, const std::string &pdata)
- : type(request_type), document(uri), ipaddr(ip), postdata(pdata), headers(hdr), sock(socket)
+ : type(request_type)
+ , ipaddr(ip)
+ , postdata(pdata)
+ , parseduri(Parseduri)
+ , headers(hdr)
+ , sock(socket)
{
}
@@ -159,13 +217,14 @@ class HTTPRequest
return type;
}
- /** Get URI.
- * The URI string (URL minus hostname and scheme) will be provided by this function.
- * @return The URI being requested
- */
- std::string& GetURI()
+ HTTPRequestURI& GetParsedURI()
+ {
+ return parseduri;
+ }
+
+ std::string& GetPath()
{
- return document;
+ return GetParsedURI().path;
}
/** Get IP address of requester.
diff --git a/src/modules/m_httpd.cpp b/src/modules/m_httpd.cpp
index 3a0d4f861..168e09bb8 100644
--- a/src/modules/m_httpd.cpp
+++ b/src/modules/m_httpd.cpp
@@ -59,6 +59,7 @@ class HttpServerSocket : public BufferedSocket, public Timer, public insp::intru
friend ModuleHttpServer;
http_parser parser;
+ http_parser_url url;
std::string ip;
std::string uri;
HTTPHeaders headers;
@@ -280,11 +281,13 @@ class HttpServerSocket : public BufferedSocket, public Timer, public insp::intru
{
ModResult MOD_RESULT;
std::string method = http_method_str(static_cast<http_method>(parser.method));
- HTTPRequest acl(method, uri, &headers, this, ip, body);
+ HTTPRequestURI parsed;
+ ParseURI(uri, parsed);
+ HTTPRequest acl(method, parsed, &headers, this, ip, body);
FIRST_MOD_RESULT_CUSTOM(*aclevprov, HTTPACLEventListener, OnHTTPACLCheck, MOD_RESULT, (acl));
if (MOD_RESULT != MOD_RES_DENY)
{
- HTTPRequest url(method, uri, &headers, this, ip, body);
+ HTTPRequest url(method, parsed, &headers, this, ip, body);
FIRST_MOD_RESULT_CUSTOM(*reqevprov, HTTPRequestEventListener, OnHTTPRequest, MOD_RESULT, (url));
if (MOD_RESULT == MOD_RES_PASSTHRU)
{
@@ -309,6 +312,40 @@ class HttpServerSocket : public BufferedSocket, public Timer, public insp::intru
Close();
ServerInstance->GlobalCulls.AddItem(this);
}
+
+ bool ParseURI(const std::string& uri, HTTPRequestURI& out)
+ {
+ http_parser_url_init(&url);
+ if (http_parser_parse_url(uri.c_str(), uri.size(), 0, &url) != 0)
+ return false;
+
+ if (url.field_set & (1 << UF_PATH))
+ out.path = uri.substr(url.field_data[UF_PATH].off, url.field_data[UF_PATH].len);
+
+ if (url.field_set & (1 << UF_FRAGMENT))
+ out.fragment = uri.substr(url.field_data[UF_FRAGMENT].off, url.field_data[UF_FRAGMENT].len);
+
+ std::string param_str;
+ if (url.field_set & (1 << UF_QUERY))
+ param_str = uri.substr(url.field_data[UF_QUERY].off, url.field_data[UF_QUERY].len);
+
+ irc::sepstream param_stream(param_str, '&');
+ std::string token;
+ std::string::size_type eq_pos;
+ while (param_stream.GetToken(token))
+ {
+ eq_pos = token.find('=');
+ if (eq_pos == std::string::npos)
+ {
+ out.query_params.insert(std::make_pair(token, ""));
+ }
+ else
+ {
+ out.query_params.insert(std::make_pair(token.substr(0, eq_pos), token.substr(eq_pos + 1)));
+ }
+ }
+ return true;
+ }
};
class HTTPdAPIImpl : public HTTPdAPIBase
diff --git a/src/modules/m_httpd_acl.cpp b/src/modules/m_httpd_acl.cpp
index 49710c219..8052db4bc 100644
--- a/src/modules/m_httpd_acl.cpp
+++ b/src/modules/m_httpd_acl.cpp
@@ -113,7 +113,7 @@ class ModuleHTTPAccessList : public Module, public HTTPACLEventListener
for (std::vector<HTTPACL>::const_iterator this_acl = acl_list.begin(); this_acl != acl_list.end(); ++this_acl)
{
- if (InspIRCd::Match(http->GetURI(), this_acl->path, ascii_case_insensitive_map))
+ if (InspIRCd::Match(http->GetPath(), this_acl->path, ascii_case_insensitive_map))
{
if (!this_acl->blacklist.empty())
{
@@ -126,7 +126,7 @@ class ModuleHTTPAccessList : public Module, public HTTPACLEventListener
if (InspIRCd::Match(http->GetIP(), entry, ascii_case_insensitive_map))
{
ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Denying access to blacklisted resource %s (matched by pattern %s) from ip %s (matched by entry %s)",
- http->GetURI().c_str(), this_acl->path.c_str(), http->GetIP().c_str(), entry.c_str());
+ http->GetPath().c_str(), this_acl->path.c_str(), http->GetIP().c_str(), entry.c_str());
BlockAccess(http, 403);
return false;
}
@@ -148,7 +148,7 @@ class ModuleHTTPAccessList : public Module, public HTTPACLEventListener
if (!allow_access)
{
ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Denying access to whitelisted resource %s (matched by pattern %s) from ip %s (Not in whitelist)",
- http->GetURI().c_str(), this_acl->path.c_str(), http->GetIP().c_str());
+ http->GetPath().c_str(), this_acl->path.c_str(), http->GetIP().c_str());
BlockAccess(http, 403);
return false;
}
@@ -157,7 +157,7 @@ class ModuleHTTPAccessList : public Module, public HTTPACLEventListener
{
/* Password auth, first look to see if we have a basic authentication header */
ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Checking HTTP auth password for resource %s (matched by pattern %s) from ip %s, against username %s",
- http->GetURI().c_str(), this_acl->path.c_str(), http->GetIP().c_str(), this_acl->username.c_str());
+ http->GetPath().c_str(), this_acl->path.c_str(), http->GetIP().c_str(), this_acl->username.c_str());
if (http->headers->IsSet("Authorization"))
{
diff --git a/src/modules/m_httpd_config.cpp b/src/modules/m_httpd_config.cpp
index f729b2774..25d2f54bf 100644
--- a/src/modules/m_httpd_config.cpp
+++ b/src/modules/m_httpd_config.cpp
@@ -34,7 +34,7 @@ class ModuleHttpConfig : public Module, public HTTPRequestEventListener
ModResult OnHTTPRequest(HTTPRequest& request) CXX11_OVERRIDE
{
- if ((request.GetURI() != "/config") && (request.GetURI() != "/config/"))
+ if ((request.GetPath() != "/config") && (request.GetPath() != "/config/"))
return MOD_RES_PASSTHRU;
ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Handling request for the HTTP /config route");
diff --git a/src/modules/m_httpd_stats.cpp b/src/modules/m_httpd_stats.cpp
index 4b18eadc4..2b28c2628 100644
--- a/src/modules/m_httpd_stats.cpp
+++ b/src/modules/m_httpd_stats.cpp
@@ -99,7 +99,7 @@ namespace Stats
{
return data << "<server><name>" << ServerInstance->Config->ServerName << "</name><description>"
<< Sanitize(ServerInstance->Config->ServerDesc) << "</description><version>"
- << Sanitize(ServerInstance->GetVersionString()) << "</version></server>";
+ << Sanitize(ServerInstance->GetVersionString(true)) << "</version></server>";
}
std::ostream& ISupport(std::ostream& data)
@@ -124,6 +124,7 @@ namespace Stats
data << "<opercount>" << ServerInstance->Users->all_opers.size() << "</opercount>";
data << "<socketcount>" << (SocketEngine::GetUsedFds()) << "</socketcount><socketmax>" << SocketEngine::GetMaxFds() << "</socketmax>";
data << "<uptime><boot_time_t>" << ServerInstance->startup_time << "</boot_time_t></uptime>";
+ data << "<currenttime>" << ServerInstance->Time() << "</currenttime>";
data << ISupport;
return data << "</general>";
@@ -201,6 +202,37 @@ namespace Stats
return data << "</channellist>";
}
+ std::ostream& DumpUser(std::ostream& data, User* u)
+ {
+ data << "<user>";
+ data << "<nickname>" << u->nick << "</nickname><uuid>" << u->uuid << "</uuid><realhost>"
+ << u->GetRealHost() << "</realhost><displayhost>" << u->GetDisplayedHost() << "</displayhost><realname>"
+ << Sanitize(u->GetRealName()) << "</realname><server>" << u->server->GetName() << "</server><signon>"
+ << u->signon << "</signon><age>" << u->age << "</age>";
+
+ if (u->IsAway())
+ data << "<away>" << Sanitize(u->awaymsg) << "</away><awaytime>" << u->awaytime << "</awaytime>";
+
+ if (u->IsOper())
+ data << "<opertype>" << Sanitize(u->oper->name) << "</opertype>";
+
+ data << "<modes>" << u->GetModeLetters().substr(1) << "</modes><ident>" << Sanitize(u->ident) << "</ident>";
+
+ LocalUser* lu = IS_LOCAL(u);
+ if (lu)
+ data << "<local/><port>" << lu->GetServerPort() << "</port><servaddr>"
+ << lu->server_sa.str() << "</servaddr><connectclass>"
+ << lu->GetClass()->GetName() << "</connectclass><lastmsg>"
+ << lu->idle_lastmsg << "</lastmsg>";
+
+ data << "<ipaddress>" << u->GetIPString() << "</ipaddress>";
+
+ DumpMeta(data, u);
+
+ data << "</user>";
+ return data;
+ }
+
std::ostream& Users(std::ostream& data)
{
data << "<userlist>";
@@ -209,24 +241,10 @@ namespace Stats
{
User* u = i->second;
- data << "<user>";
- data << "<nickname>" << u->nick << "</nickname><uuid>" << u->uuid << "</uuid><realhost>"
- << u->GetRealHost() << "</realhost><displayhost>" << u->GetDisplayedHost() << "</displayhost><realname>"
- << Sanitize(u->GetRealName()) << "</realname><server>" << u->server->GetName() << "</server>";
- if (u->IsAway())
- data << "<away>" << Sanitize(u->awaymsg) << "</away><awaytime>" << u->awaytime << "</awaytime>";
- if (u->IsOper())
- data << "<opertype>" << Sanitize(u->oper->name) << "</opertype>";
- data << "<modes>" << u->GetModeLetters().substr(1) << "</modes><ident>" << Sanitize(u->ident) << "</ident>";
- LocalUser* lu = IS_LOCAL(u);
- if (lu)
- data << "<port>" << lu->GetServerPort() << "</port><servaddr>"
- << lu->server_sa.str() << "</servaddr>";
- data << "<ipaddress>" << u->GetIPString() << "</ipaddress>";
-
- DumpMeta(data, u);
+ if (u->registered != REG_ALL)
+ continue;
- data << "</user>";
+ DumpUser(data, u);
}
return data << "</userlist>";
}
@@ -265,28 +283,145 @@ namespace Stats
}
return data << "</commandlist>";
}
+
+ enum OrderBy
+ {
+ OB_NICK,
+ OB_LASTMSG,
+
+ OB_NONE
+ };
+
+ struct UserSorter
+ {
+ OrderBy order;
+ bool desc;
+
+ UserSorter(OrderBy Order, bool Desc = false) : order(Order), desc(Desc) {}
+
+ template <typename T>
+ inline bool Compare(const T& a, const T& b)
+ {
+ return desc ? a > b : a < b;
+ }
+
+ bool operator()(User* u1, User* u2)
+ {
+ switch (order) {
+ case OB_LASTMSG:
+ return Compare(IS_LOCAL(u1)->idle_lastmsg, IS_LOCAL(u2)->idle_lastmsg);
+ break;
+ case OB_NICK:
+ return Compare(u1->nick, u2->nick);
+ break;
+ default:
+ case OB_NONE:
+ return false;
+ break;
+ }
+ }
+ };
+
+ std::ostream& ListUsers(std::ostream& data, const HTTPQueryParameters& params)
+ {
+ if (params.empty())
+ return Users(data);
+
+ data << "<userlist>";
+
+ // Filters
+ size_t limit = params.getNum<size_t>("limit");
+ bool showunreg = params.getBool("showunreg");
+ bool localonly = params.getBool("localonly");
+
+ // Minimum time since a user's last message
+ unsigned long min_idle = params.getDuration("minidle");
+ time_t maxlastmsg = ServerInstance->Time() - min_idle;
+
+ if (min_idle)
+ // We can only check idle times on local users
+ localonly = true;
+
+ // Sorting
+ const std::string& sortmethod = params.getString("sortby");
+ bool desc = params.getBool("desc", false);
+
+ OrderBy orderby;
+ if (stdalgo::string::equalsci(sortmethod, "nick"))
+ orderby = OB_NICK;
+ else if (stdalgo::string::equalsci(sortmethod, "lastmsg"))
+ {
+ orderby = OB_LASTMSG;
+ // We can only check idle times on local users
+ localonly = true;
+ }
+ else
+ orderby = OB_NONE;
+
+ typedef std::list<User*> NewUserList;
+ NewUserList user_list;
+ user_hash users = ServerInstance->Users->GetUsers();
+ for (user_hash::iterator i = users.begin(); i != users.end(); ++i)
+ {
+ User* u = i->second;
+ if (!showunreg && u->registered != REG_ALL)
+ continue;
+
+ LocalUser* lu = IS_LOCAL(u);
+ if (localonly && !lu)
+ continue;
+
+ if (min_idle && lu->idle_lastmsg > maxlastmsg)
+ continue;
+
+ user_list.push_back(u);
+ }
+
+ UserSorter sorter(orderby, desc);
+ if (sorter.order != OB_NONE && !(!localonly && sorter.order == OB_LASTMSG))
+ user_list.sort(sorter);
+
+ size_t count = 0;
+ for (NewUserList::const_iterator i = user_list.begin(); i != user_list.end() && (!limit || count < limit); ++i, ++count)
+ DumpUser(data, *i);
+
+ data << "</userlist>";
+ return data;
+ }
}
class ModuleHttpStats : public Module, public HTTPRequestEventListener
{
HTTPdAPI API;
+ bool enableparams;
public:
ModuleHttpStats()
: HTTPRequestEventListener(this)
, API(this)
+ , enableparams(false)
{
}
+ void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE
+ {
+ ConfigTag* conf = ServerInstance->Config->ConfValue("httpstats");
+
+ // Parameterized queries may cause a performance issue
+ // Due to the sheer volume of data
+ // So default them to disabled
+ enableparams = conf->getBool("enableparams");
+ }
+
ModResult HandleRequest(HTTPRequest* http)
{
- std::string uri = http->GetURI();
+ std::string path = http->GetPath();
- if (uri != "/stats" && uri.substr(0, 7) != "/stats/")
+ if (path != "/stats" && path.substr(0, 7) != "/stats/")
return MOD_RES_PASSTHRU;
- if (uri[uri.size() - 1] == '/')
- uri.erase(uri.size() - 1, 1);
+ if (path[path.size() - 1] == '/')
+ path.erase(path.size() - 1, 1);
ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Handling httpd event");
@@ -294,20 +429,23 @@ class ModuleHttpStats : public Module, public HTTPRequestEventListener
std::stringstream data;
data << "<inspircdstats>";
- if (uri == "/stats")
+ if (path == "/stats")
{
data << Stats::ServerInfo << Stats::General
<< Stats::XLines << Stats::Modules
<< Stats::Channels << Stats::Users
<< Stats::Servers << Stats::Commands;
}
- else if (uri == "/stats/general")
+ else if (path == "/stats/general")
{
data << Stats::General;
}
- else if (uri == "/stats/users")
+ else if (path == "/stats/users")
{
- data << Stats::Users;
+ if (enableparams)
+ Stats::ListUsers(data, http->GetParsedURI().query_params);
+ else
+ data << Stats::Users;
}
else
{