inspircd

A modular C++ IRC daemon (ircd). https://www.inspircd.org/
Log | Files | Refs | README

commit d4a1ea70451abb333e71f9cff09b624db59531a0
parent a638de7715b55c9a09e51fd5d42d97f05d966da7
Author: linuxdaemon <linuxdaemon@users.noreply.github.com>
Date:   Wed,  6 Feb 2019 04:33:06 -0600

Expand searching in m_httpd_stats, add global handling of GET parameters (#1566)


Diffstat:
Minclude/modules/httpd.h | 79+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------
Msrc/modules/m_httpd.cpp | 41+++++++++++++++++++++++++++++++++++++++--
Msrc/modules/m_httpd_acl.cpp | 8++++----
Msrc/modules/m_httpd_config.cpp | 2+-
Msrc/modules/m_httpd_stats.cpp | 190++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------
5 files changed, 277 insertions(+), 43 deletions(-)

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