X-Git-Url: https://git.netwichtig.de/gitweb/?a=blobdiff_plain;f=src%2Fmodules%2Fm_httpd.cpp;h=f3ec3298bd8823b154b4c9fb5017159ba7bea800;hb=HEAD;hp=dda39afec1ce65b0087e20291b18a54aec5afe8e;hpb=e3bcf95ee996c058c73879c12ac5a487f8dcdf46;p=user%2Fhenk%2Fcode%2Finspircd.git diff --git a/src/modules/m_httpd.cpp b/src/modules/m_httpd.cpp index dda39afec..f3ec3298b 100644 --- a/src/modules/m_httpd.cpp +++ b/src/modules/m_httpd.cpp @@ -1,12 +1,17 @@ /* * InspIRCd -- Internet Relay Chat Daemon * + * Copyright (C) 2019 linuxdaemon + * Copyright (C) 2018 edef + * Copyright (C) 2013-2014, 2017-2020 Sadie Powell + * Copyright (C) 2012-2016 Attila Molnar + * Copyright (C) 2012 Robby + * Copyright (C) 2009 Uli Schlachter * Copyright (C) 2009 Daniel De Graaf - * Copyright (C) 2007-2008 Robin Burchell - * Copyright (C) 2008 Pippijn van Steenhoven - * Copyright (C) 2006-2008 Craig Edwards - * Copyright (C) 2007 John Brooks + * Copyright (C) 2008 Robin Burchell + * Copyright (C) 2007 John Brooks * Copyright (C) 2007 Dennis Friis + * Copyright (C) 2006, 2008, 2010 Craig Edwards * * This file is part of InspIRCd. InspIRCd is free software: you can * redistribute it and/or modify it under the terms of the GNU General Public @@ -21,176 +26,265 @@ * along with this program. If not, see . */ +/// $CompilerFlags: -Ivendor_directory("http_parser") + #include "inspircd.h" #include "iohook.h" #include "modules/httpd.h" +#ifdef __GNUC__ +# pragma GCC diagnostic push +#endif + +// Fix warnings about the use of commas at end of enumerator lists and long long +// on C++03. +#if defined __clang__ +# pragma clang diagnostic ignored "-Wc++11-extensions" +# pragma clang diagnostic ignored "-Wc++11-long-long" +#elif defined __GNUC__ +# pragma GCC diagnostic ignored "-Wlong-long" +# if (__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ >= 8)) +# pragma GCC diagnostic ignored "-Wpedantic" +# else +# pragma GCC diagnostic ignored "-pedantic" +# endif +#endif + +// Fix warnings about shadowing in http_parser. +#ifdef __GNUC__ +# pragma GCC diagnostic ignored "-Wshadow" +#endif + +#include + +#ifdef __GNUC__ +# pragma GCC diagnostic pop +#endif + class ModuleHttpServer; static ModuleHttpServer* HttpModule; -static bool claimed; -static std::set sockets; - -/** HTTP socket states - */ -enum HttpState -{ - HTTP_SERVE_WAIT_REQUEST = 0, /* Waiting for a full request */ - HTTP_SERVE_RECV_POSTDATA = 1, /* Waiting to finish recieving POST data */ - HTTP_SERVE_SEND_DATA = 2 /* Sending response */ -}; +static insp::intrusive_list sockets; +static Events::ModuleEventProvider* aclevprov; +static Events::ModuleEventProvider* reqevprov; +static http_parser_settings parser_settings; /** A socket used for HTTP transport */ -class HttpServerSocket : public BufferedSocket +class HttpServerSocket : public BufferedSocket, public Timer, public insp::intrusive_list_node { - HttpState InternalState; - std::string ip; + private: + friend class ModuleHttpServer; - HTTPHeaders headers; - std::string reqbuffer; - std::string postdata; - unsigned int postsize; - std::string request_type; + http_parser parser; + http_parser_url url; + std::string ip; std::string uri; - std::string http_version; + HTTPHeaders headers; + std::string body; + size_t total_buffers; + int status_code; - public: - const time_t createtime; + /** True if this object is in the cull list + */ + bool waitingcull; + bool messagecomplete; - HttpServerSocket(int newfd, const std::string& IP, ListenSocket* via, irc::sockets::sockaddrs* client, irc::sockets::sockaddrs* server) - : BufferedSocket(newfd), ip(IP), postsize(0) - , createtime(ServerInstance->Time()) + bool Tick(time_t currtime) CXX11_OVERRIDE { - InternalState = HTTP_SERVE_WAIT_REQUEST; + if (!messagecomplete) + { + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "HTTP socket %d timed out", GetFd()); + Close(); + return false; + } - if (via->iohookprov) - via->iohookprov->OnAccept(this, client, server); + return true; } - ~HttpServerSocket() + template + static int Callback(http_parser* p) { - sockets.erase(this); + HttpServerSocket* sock = static_cast(p->data); + return (sock->*f)(); } - void OnError(BufferedSocketError) CXX11_OVERRIDE + template + static int DataCallback(http_parser* p, const char* buf, size_t len) { - ServerInstance->GlobalCulls.AddItem(this); + HttpServerSocket* sock = static_cast(p->data); + return (sock->*f)(buf, len); + } + + static void ConfigureParser() + { + http_parser_settings_init(&parser_settings); + parser_settings.on_message_begin = Callback<&HttpServerSocket::OnMessageBegin>; + parser_settings.on_url = DataCallback<&HttpServerSocket::OnUrl>; + parser_settings.on_header_field = DataCallback<&HttpServerSocket::OnHeaderField>; + parser_settings.on_body = DataCallback<&HttpServerSocket::OnBody>; + parser_settings.on_message_complete = Callback<&HttpServerSocket::OnMessageComplete>; + } + + int OnMessageBegin() + { + uri.clear(); + header_state = HEADER_NONE; + body.clear(); + total_buffers = 0; + return 0; + } + + bool AcceptData(size_t len) + { + total_buffers += len; + return total_buffers < 8192; + } + + int OnUrl(const char* buf, size_t len) + { + if (!AcceptData(len)) + { + status_code = HTTP_STATUS_URI_TOO_LONG; + return -1; + } + uri.append(buf, len); + return 0; + } + + enum { HEADER_NONE, HEADER_FIELD, HEADER_VALUE } header_state; + std::string header_field; + std::string header_value; + + void OnHeaderComplete() + { + headers.SetHeader(header_field, header_value); + header_field.clear(); + header_value.clear(); + } + + int OnHeaderField(const char* buf, size_t len) + { + if (header_state == HEADER_VALUE) + OnHeaderComplete(); + header_state = HEADER_FIELD; + if (!AcceptData(len)) + { + status_code = HTTP_STATUS_REQUEST_HEADER_FIELDS_TOO_LARGE; + return -1; + } + header_field.append(buf, len); + return 0; } - std::string Response(int response) + int OnHeaderValue(const char* buf, size_t len) { - switch (response) + header_state = HEADER_VALUE; + if (!AcceptData(len)) { - case 100: - return "CONTINUE"; - case 101: - return "SWITCHING PROTOCOLS"; - case 200: - return "OK"; - case 201: - return "CREATED"; - case 202: - return "ACCEPTED"; - case 203: - return "NON-AUTHORITATIVE INFORMATION"; - case 204: - return "NO CONTENT"; - case 205: - return "RESET CONTENT"; - case 206: - return "PARTIAL CONTENT"; - case 300: - return "MULTIPLE CHOICES"; - case 301: - return "MOVED PERMANENTLY"; - case 302: - return "FOUND"; - case 303: - return "SEE OTHER"; - case 304: - return "NOT MODIFIED"; - case 305: - return "USE PROXY"; - case 307: - return "TEMPORARY REDIRECT"; - case 400: - return "BAD REQUEST"; - case 401: - return "UNAUTHORIZED"; - case 402: - return "PAYMENT REQUIRED"; - case 403: - return "FORBIDDEN"; - case 404: - return "NOT FOUND"; - case 405: - return "METHOD NOT ALLOWED"; - case 406: - return "NOT ACCEPTABLE"; - case 407: - return "PROXY AUTHENTICATION REQUIRED"; - case 408: - return "REQUEST TIMEOUT"; - case 409: - return "CONFLICT"; - case 410: - return "GONE"; - case 411: - return "LENGTH REQUIRED"; - case 412: - return "PRECONDITION FAILED"; - case 413: - return "REQUEST ENTITY TOO LARGE"; - case 414: - return "REQUEST-URI TOO LONG"; - case 415: - return "UNSUPPORTED MEDIA TYPE"; - case 416: - return "REQUESTED RANGE NOT SATISFIABLE"; - case 417: - return "EXPECTATION FAILED"; - case 500: - return "INTERNAL SERVER ERROR"; - case 501: - return "NOT IMPLEMENTED"; - case 502: - return "BAD GATEWAY"; - case 503: - return "SERVICE UNAVAILABLE"; - case 504: - return "GATEWAY TIMEOUT"; - case 505: - return "HTTP VERSION NOT SUPPORTED"; - default: - return "WTF"; - break; + status_code = HTTP_STATUS_REQUEST_HEADER_FIELDS_TOO_LARGE; + return -1; + } + header_value.append(buf, len); + return 0; + } + + int OnHeadersComplete() + { + if (header_state != HEADER_NONE) + OnHeaderComplete(); + return 0; + } + int OnBody(const char* buf, size_t len) + { + if (!AcceptData(len)) + { + status_code = HTTP_STATUS_PAYLOAD_TOO_LARGE; + return -1; } + body.append(buf, len); + return 0; + } + + int OnMessageComplete() + { + messagecomplete = true; + ServeData(); + return 0; + } + + public: + HttpServerSocket(int newfd, const std::string& IP, ListenSocket* via, irc::sockets::sockaddrs* client, irc::sockets::sockaddrs* server, unsigned int timeoutsec) + : BufferedSocket(newfd) + , Timer(timeoutsec) + , ip(IP) + , status_code(0) + , waitingcull(false) + , messagecomplete(false) + { + if ((!via->iohookprovs.empty()) && (via->iohookprovs.back())) + { + via->iohookprovs.back()->OnAccept(this, client, server); + // IOHook may have errored + if (!getError().empty()) + { + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "HTTP socket %d encountered a hook error: %s", + GetFd(), getError().c_str()); + Close(); + return; + } + } + + parser.data = this; + http_parser_init(&parser, HTTP_REQUEST); + ServerInstance->Timers.AddTimer(this); + } + + ~HttpServerSocket() + { + sockets.erase(this); } - void SendHTTPError(int response) + void Close() CXX11_OVERRIDE { - HTTPHeaders empty; - std::string data = "Server error "+ConvToStr(response)+": "+Response(response)+"
"+ - "Powered by InspIRCd"; + if (waitingcull || !HasFd()) + return; - SendHeaders(data.length(), response, empty); - WriteData(data); + waitingcull = true; + BufferedSocket::Close(); + ServerInstance->GlobalCulls.AddItem(this); } - void SendHeaders(unsigned long size, int response, HTTPHeaders &rheaders) + void OnError(BufferedSocketError err) CXX11_OVERRIDE { + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "HTTP socket %d encountered an error: %d - %s", + GetFd(), err, getError().c_str()); + Close(); + } - WriteData(http_version + " "+ConvToStr(response)+" "+Response(response)+"\r\n"); + void SendHTTPError(unsigned int response, const char* errstr = NULL) + { + if (!errstr) + errstr = http_status_str((http_status)response); + + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Sending HTTP error %u: %s", response, errstr); + static HTTPHeaders empty; + std::string data = InspIRCd::Format( + "" + "

Error %u

%s


" + "Powered by InspIRCd", + response, errstr); + + Page(data, response, &empty); + } - time_t local = ServerInstance->Time(); - struct tm *timeinfo = gmtime(&local); - char *date = asctime(timeinfo); - date[strlen(date) - 1] = '\0'; - rheaders.CreateHeader("Date", date); + void SendHeaders(unsigned long size, unsigned int response, HTTPHeaders &rheaders) + { + WriteData(InspIRCd::Format("HTTP/%u.%u %u %s\r\n", parser.http_major ? parser.http_major : 1, parser.http_major ? parser.http_minor : 1, response, http_status_str((http_status)response))); + rheaders.CreateHeader("Date", InspIRCd::TimeString(ServerInstance->Time(), "%a, %d %b %Y %H:%M:%S GMT", true)); rheaders.CreateHeader("Server", INSPIRCD_BRANCH); rheaders.SetHeader("Content-Length", ConvToStr(size)); @@ -199,7 +293,7 @@ class HttpServerSocket : public BufferedSocket else rheaders.RemoveHeader("Content-Type"); - /* Supporting Connection: keep-alive causes a whole world of hurt syncronizing timeouts, + /* Supporting Connection: keep-alive causes a whole world of hurt synchronizing timeouts, * so remove it, its not essential for what we need. */ rheaders.SetHeader("Connection", "Close"); @@ -208,131 +302,105 @@ class HttpServerSocket : public BufferedSocket WriteData("\r\n"); } - void OnDataReady() + void OnDataReady() CXX11_OVERRIDE { - if (InternalState == HTTP_SERVE_RECV_POSTDATA) - { - postdata.append(recvq); - if (postdata.length() >= postsize) - ServeData(); - } - else - { - reqbuffer.append(recvq); + if (parser.upgrade || HTTP_PARSER_ERRNO(&parser)) + return; + http_parser_execute(&parser, &parser_settings, recvq.data(), recvq.size()); + if (parser.upgrade) + SendHTTPError(status_code ? status_code : 400); + else if (HTTP_PARSER_ERRNO(&parser)) + SendHTTPError(status_code ? status_code : 400, http_errno_description((http_errno)parser.http_errno)); + } - if (reqbuffer.length() >= 8192) + void ServeData() + { + ModResult MOD_RESULT; + std::string method = http_method_str(static_cast(parser.method)); + 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 request(method, parsed, &headers, this, ip, body); + FIRST_MOD_RESULT_CUSTOM(*reqevprov, HTTPRequestEventListener, OnHTTPRequest, MOD_RESULT, (request)); + if (MOD_RESULT == MOD_RES_PASSTHRU) { - ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "m_httpd dropped connection due to an oversized request buffer"); - reqbuffer.clear(); - SetError("Buffer"); + SendHTTPError(404); } - - if (InternalState == HTTP_SERVE_WAIT_REQUEST) - CheckRequestBuffer(); } } - void CheckRequestBuffer() + void Page(const std::string& s, unsigned int response, HTTPHeaders* hheaders) { - std::string::size_type reqend = reqbuffer.find("\r\n\r\n"); - if (reqend == std::string::npos) - return; + SendHeaders(s.length(), response, *hheaders); + WriteData(s); + BufferedSocket::Close(true); + } - // We have the headers; parse them all - std::string::size_type hbegin = 0, hend; - while ((hend = reqbuffer.find("\r\n", hbegin)) != std::string::npos) - { - if (hbegin == hend) - break; + void Page(std::stringstream* n, unsigned int response, HTTPHeaders* hheaders) + { + Page(n->str(), response, hheaders); + } - if (request_type.empty()) - { - std::istringstream cheader(std::string(reqbuffer, hbegin, hend - hbegin)); - cheader >> request_type; - cheader >> uri; - cheader >> http_version; + bool ParseURI(const std::string& uristr, HTTPRequestURI& out) + { + http_parser_url_init(&url); + if (http_parser_parse_url(uristr.c_str(), uristr.size(), 0, &url) != 0) + return false; - if (request_type.empty() || uri.empty() || http_version.empty()) + if (url.field_set & (1 << UF_PATH)) + { + // Normalise the path. + std::vector pathsegments; + irc::sepstream pathstream(uri.substr(url.field_data[UF_PATH].off, url.field_data[UF_PATH].len), '/'); + for (std::string pathsegment; pathstream.GetToken(pathsegment); ) + { + if (pathsegment == ".") { - SendHTTPError(400); - return; + // Stay at the current level. + continue; } - hbegin = hend + 2; - continue; - } - - std::string cheader = reqbuffer.substr(hbegin, hend - hbegin); + if (pathsegment == "..") + { + // Traverse up to the previous level. + if (!pathsegments.empty()) + pathsegments.pop_back(); + continue; + } - std::string::size_type fieldsep = cheader.find(':'); - if ((fieldsep == std::string::npos) || (fieldsep == 0) || (fieldsep == cheader.length() - 1)) - { - SendHTTPError(400); - return; + pathsegments.push_back(pathsegment); } - headers.SetHeader(cheader.substr(0, fieldsep), cheader.substr(fieldsep + 2)); - - hbegin = hend + 2; + out.path.reserve(url.field_data[UF_PATH].len); + out.path.append("/").append(stdalgo::string::join(pathsegments, '/')); } - reqbuffer.erase(0, reqend + 4); - - std::transform(request_type.begin(), request_type.end(), request_type.begin(), ::toupper); - std::transform(http_version.begin(), http_version.end(), http_version.begin(), ::toupper); + if (url.field_set & (1 << UF_FRAGMENT)) + out.fragment = uri.substr(url.field_data[UF_FRAGMENT].off, url.field_data[UF_FRAGMENT].len); - if ((http_version != "HTTP/1.1") && (http_version != "HTTP/1.0")) - { - SendHTTPError(505); - return; - } + 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); - if (headers.IsSet("Content-Length") && (postsize = ConvToInt(headers.GetHeader("Content-Length"))) > 0) + irc::sepstream param_stream(param_str, '&'); + std::string token; + std::string::size_type eq_pos; + while (param_stream.GetToken(token)) { - InternalState = HTTP_SERVE_RECV_POSTDATA; - - if (reqbuffer.length() >= postsize) + eq_pos = token.find('='); + if (eq_pos == std::string::npos) { - postdata = reqbuffer.substr(0, postsize); - reqbuffer.erase(0, postsize); + out.query_params.insert(std::make_pair(token, "")); } - else if (!reqbuffer.empty()) + else { - postdata = reqbuffer; - reqbuffer.clear(); + out.query_params.insert(std::make_pair(token.substr(0, eq_pos), token.substr(eq_pos + 1))); } - - if (postdata.length() >= postsize) - ServeData(); - - return; } - - ServeData(); - } - - void ServeData() - { - InternalState = HTTP_SERVE_SEND_DATA; - - claimed = false; - HTTPRequest acl((Module*)HttpModule, "httpd_acl", request_type, uri, &headers, this, ip, postdata); - acl.Send(); - if (!claimed) - { - HTTPRequest url((Module*)HttpModule, "httpd_url", request_type, uri, &headers, this, ip, postdata); - url.Send(); - if (!claimed) - { - SendHTTPError(404); - } - } - } - - void Page(std::stringstream* n, int response, HTTPHeaders *hheaders) - { - SendHeaders(n->str().length(), response, *hheaders); - WriteData(n->str()); + return true; } }; @@ -346,21 +414,26 @@ class HTTPdAPIImpl : public HTTPdAPIBase void SendResponse(HTTPDocumentResponse& resp) CXX11_OVERRIDE { - claimed = true; resp.src.sock->Page(resp.document, resp.responsecode, &resp.headers); } }; class ModuleHttpServer : public Module { - std::vector httpsocks; HTTPdAPIImpl APIImpl; unsigned int timeoutsec; + Events::ModuleEventProvider acleventprov; + Events::ModuleEventProvider reqeventprov; public: ModuleHttpServer() : APIImpl(this) + , acleventprov(this, "event/http-acl") + , reqeventprov(this, "event/http-request") { + aclevprov = &acleventprov; + reqevprov = &reqeventprov; + HttpServerSocket::ConfigureParser(); } void init() CXX11_OVERRIDE @@ -371,31 +444,25 @@ class ModuleHttpServer : public Module void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE { ConfigTag* tag = ServerInstance->Config->ConfValue("httpd"); - timeoutsec = tag->getInt("timeout"); + timeoutsec = tag->getDuration("timeout", 10, 1); } ModResult OnAcceptConnection(int nfd, ListenSocket* from, irc::sockets::sockaddrs* client, irc::sockets::sockaddrs* server) CXX11_OVERRIDE { - if (from->bind_tag->getString("type") != "httpd") + if (!stdalgo::string::equalsci(from->bind_tag->getString("type"), "httpd")) return MOD_RES_PASSTHRU; - int port; - std::string incomingip; - irc::sockets::satoap(*client, incomingip, port); - sockets.insert(new HttpServerSocket(nfd, incomingip, from, client, server)); + + sockets.push_front(new HttpServerSocket(nfd, client->addr(), from, client, server, timeoutsec)); return MOD_RES_ALLOW; } - void OnBackgroundTimer(time_t curtime) CXX11_OVERRIDE + void OnUnloadModule(Module* mod) CXX11_OVERRIDE { - if (!timeoutsec) - return; - - time_t oldest_allowed = curtime - timeoutsec; - for (std::set::const_iterator i = sockets.begin(); i != sockets.end(); ) + for (insp::intrusive_list::const_iterator i = sockets.begin(); i != sockets.end(); ) { HttpServerSocket* sock = *i; ++i; - if (sock->createtime < oldest_allowed) + if (sock->GetModHook(mod)) { sock->cull(); delete sock; @@ -405,20 +472,17 @@ class ModuleHttpServer : public Module CullResult cull() CXX11_OVERRIDE { - std::set local; - local.swap(sockets); - for (std::set::const_iterator i = local.begin(); i != local.end(); ++i) + for (insp::intrusive_list::const_iterator i = sockets.begin(); i != sockets.end(); ++i) { HttpServerSocket* sock = *i; - sock->cull(); - delete sock; + sock->Close(); } return Module::cull(); } Version GetVersion() CXX11_OVERRIDE { - return Version("Provides HTTP serving facilities to modules", VF_VENDOR); + return Version("Allows the server administrator to serve various useful resources over HTTP.", VF_VENDOR); } };