X-Git-Url: https://git.netwichtig.de/gitweb/?a=blobdiff_plain;f=src%2Fmodules%2Fm_httpd.cpp;h=f3ec3298bd8823b154b4c9fb5017159ba7bea800;hb=HEAD;hp=6ff80ad807ca15322fe3fe7fd8addbb3dcb9ebd0;hpb=bab14f0dd2345c9d7dcbc47c918563709e1ac094;p=user%2Fhenk%2Fcode%2Finspircd.git diff --git a/src/modules/m_httpd.cpp b/src/modules/m_httpd.cpp index 6ff80ad80..f3ec3298b 100644 --- a/src/modules/m_httpd.cpp +++ b/src/modules/m_httpd.cpp @@ -1 +1,489 @@ -/* +------------------------------------+ * | Inspire Internet Relay Chat Daemon | * +------------------------------------+ * * InspIRCd: (C) 2002-2007 InspIRCd Development Team * See: http://www.inspircd.org/wiki/index.php/Credits * * This program is free but copyrighted software; see * the file COPYING for details. * * --------------------------------------------------- */ #include "inspircd.h" #include #include "modules.h" #include "httpd.h" /* $ModDesc: Provides HTTP serving facilities to modules */ class ModuleHttpServer; static ModuleHttpServer* HttpModule; static bool claimed; /** HTTP socket states */ enum HttpState { HTTP_LISTEN = 0, HTTP_SERVE_WAIT_REQUEST = 1, HTTP_SERVE_RECV_POSTDATA = 2, HTTP_SERVE_SEND_DATA = 3 }; class HttpServerSocket; /** This class is used to handle HTTP socket timeouts */ class HttpServerTimeout : public InspTimer { private: /** HttpServerSocket we are attached to */ HttpServerSocket* s; /** Socketengine the file descriptor is in */ SocketEngine* SE; public: /** Attach timeout to HttpServerSocket */ HttpServerTimeout(HttpServerSocket* sock, SocketEngine* engine); /** Handle timer tick */ void Tick(time_t TIME); }; /** A socket used for HTTP transport */ class HttpServerSocket : public InspSocket { FileReader* index; HttpState InternalState; std::stringstream headers; std::string postdata; std::string request_type; std::string uri; std::string http_version; unsigned int postsize; HttpServerTimeout* Timeout; public: HttpServerSocket(InspIRCd* SI, std::string host, int port, bool listening, unsigned long maxtime, FileReader* index_page) : InspSocket(SI, host, port, listening, maxtime), index(index_page), postsize(0) { InternalState = HTTP_LISTEN; Timeout = NULL; } HttpServerSocket(InspIRCd* SI, int newfd, char* ip, FileReader* ind) : InspSocket(SI, newfd, ip), index(ind), postsize(0) { InternalState = HTTP_SERVE_WAIT_REQUEST; Timeout = new HttpServerTimeout(this, Instance->SE); Instance->Timers->AddTimer(Timeout); } FileReader* GetIndex() { return index; } ~HttpServerSocket() { if (Timeout) { if (Instance->Time() < Timeout->GetTimer()) Instance->Timers->DelTimer(Timeout); Timeout = NULL; } } virtual int OnIncomingConnection(int newsock, char* ip) { if (InternalState == HTTP_LISTEN) { HttpServerSocket* s = new HttpServerSocket(this->Instance, newsock, ip, index); s = s; /* Stop GCC whining */ } return true; } virtual void OnClose() { } std::string Response(int response) { switch (response) { 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 PERMENANTLY"; 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; } } void SendHeaders(unsigned long size, int response, const std::string &extraheaders) { time_t local = this->Instance->Time(); struct tm *timeinfo = gmtime(&local); this->Write("HTTP/1.1 "+ConvToStr(response)+" "+Response(response)+"\r\nDate: "); this->Write(asctime(timeinfo)); if (extraheaders.empty()) { this->Write("Content-Type: text/html\r\n"); } else { this->Write(extraheaders); } this->Write("Server: InspIRCd/m_httpd.so/1.1\r\nContent-Length: "+ConvToStr(size)+ "\r\nConnection: close\r\n\r\n"); } virtual bool OnDataReady() { char* data = this->Read(); /* Check that the data read is a valid pointer and it has some content */ if (data && *data) { headers << data; if (headers.str().find("\r\n\r\n") != std::string::npos) { if (request_type.empty()) { headers >> request_type; headers >> uri; headers >> http_version; 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 ((InternalState == HTTP_SERVE_WAIT_REQUEST) && (request_type == "POST")) { /* Do we need to fetch postdata? */ postdata.clear(); InternalState = HTTP_SERVE_RECV_POSTDATA; std::string header_item; while (headers >> header_item) { if (header_item == "Content-Length:") { headers >> header_item; postsize = atoi(header_item.c_str()); } } if (!postsize) { InternalState = HTTP_SERVE_SEND_DATA; SendHeaders(0, 400, ""); Timeout = new HttpServerTimeout(this, Instance->SE); Instance->Timers->AddTimer(Timeout); } else { std::string::size_type x = headers.str().find("\r\n\r\n"); postdata = headers.str().substr(x+4, headers.str().length()); /* Get content length and store */ if (postdata.length() >= postsize) ServeData(); } } else if (InternalState == HTTP_SERVE_RECV_POSTDATA) { /* Add postdata, once we have it all, send the event */ postdata.append(data); if (postdata.length() >= postsize) ServeData(); } else { ServeData(); } return true; } return true; } else { return false; } } void ServeData() { /* Headers are complete */ InternalState = HTTP_SERVE_SEND_DATA; Instance->Timers->DelTimer(Timeout); Timeout = NULL; if ((http_version != "HTTP/1.1") && (http_version != "HTTP/1.0")) { SendHeaders(0, 505, ""); } else { if ((request_type == "GET") && (uri == "/")) { SendHeaders(index->ContentSize(), 200, ""); this->Write(index->Contents()); } else { claimed = false; HTTPRequest httpr(request_type,uri,&headers,this,this->GetIP(),postdata); Event e((char*)&httpr, (Module*)HttpModule, "httpd_url"); e.Send(this->Instance); if (!claimed) { SendHeaders(0, 404, ""); } } } Timeout = new HttpServerTimeout(this, Instance->SE); Instance->Timers->AddTimer(Timeout); } void Page(std::stringstream* n, int response, std::string& extraheaders) { SendHeaders(n->str().length(), response, extraheaders); this->Write(n->str()); } }; HttpServerTimeout::HttpServerTimeout(HttpServerSocket* sock, SocketEngine* engine) : InspTimer(60, time(NULL)), s(sock), SE(engine) { } void HttpServerTimeout::Tick(time_t TIME) { SE->DelFd(s); s->Close(); } class ModuleHttpServer : public Module { std::vector httpsocks; public: void ReadConfig() { int port; std::string host; std::string bindip; std::string indexfile; FileReader* index; HttpServerSocket* http; ConfigReader c(ServerInstance); httpsocks.clear(); for (int i = 0; i < c.Enumerate("http"); i++) { host = c.ReadValue("http", "host", i); bindip = c.ReadValue("http", "ip", i); port = c.ReadInteger("http", "port", i, true); indexfile = c.ReadValue("http", "index", i); index = new FileReader(ServerInstance, indexfile); if (!index->Exists()) throw ModuleException("Can't read index file: "+indexfile); http = new HttpServerSocket(ServerInstance, bindip, port, true, 0, index); httpsocks.push_back(http); } } ModuleHttpServer(InspIRCd* Me) : Module(Me) { ReadConfig(); } void OnEvent(Event* event) { } char* OnRequest(Request* request) { claimed = true; HTTPDocument* doc = (HTTPDocument*)request->GetData(); HttpServerSocket* sock = (HttpServerSocket*)doc->sock; sock->Page(doc->GetDocument(), doc->GetResponseCode(), doc->GetExtraHeaders()); return NULL; } void Implements(char* List) { List[I_OnEvent] = List[I_OnRequest] = 1; } virtual ~ModuleHttpServer() { for (size_t i = 0; i < httpsocks.size(); i++) { ServerInstance->SE->DelFd(httpsocks[i]); delete httpsocks[i]->GetIndex(); delete httpsocks[i]; } } virtual Version GetVersion() { return Version(1,1,0,0,VF_VENDOR|VF_SERVICEPROVIDER,API_VERSION); } }; MODULE_INIT(ModuleHttpServer) \ No newline at end of file +/* + * 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) 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 + * License as published by the Free Software Foundation, version 2. + * + * This program 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 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 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, public Timer, public insp::intrusive_list_node +{ + private: + friend class ModuleHttpServer; + + http_parser parser; + http_parser_url url; + std::string ip; + std::string uri; + HTTPHeaders headers; + std::string body; + size_t total_buffers; + int status_code; + + /** True if this object is in the cull list + */ + bool waitingcull; + bool messagecomplete; + + bool Tick(time_t currtime) CXX11_OVERRIDE + { + if (!messagecomplete) + { + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "HTTP socket %d timed out", GetFd()); + Close(); + return false; + } + + return true; + } + + template + static int Callback(http_parser* p) + { + HttpServerSocket* sock = static_cast(p->data); + return (sock->*f)(); + } + + template + static int DataCallback(http_parser* p, const char* buf, size_t len) + { + 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; + } + + int OnHeaderValue(const char* buf, size_t len) + { + header_state = HEADER_VALUE; + if (!AcceptData(len)) + { + 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 Close() CXX11_OVERRIDE + { + if (waitingcull || !HasFd()) + return; + + waitingcull = true; + BufferedSocket::Close(); + ServerInstance->GlobalCulls.AddItem(this); + } + + 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(); + } + + 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); + } + + 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)); + + if (size) + rheaders.CreateHeader("Content-Type", "text/html"); + else + rheaders.RemoveHeader("Content-Type"); + + /* 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"); + + WriteData(rheaders.GetFormattedHeaders()); + WriteData("\r\n"); + } + + void OnDataReady() CXX11_OVERRIDE + { + 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)); + } + + 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) + { + SendHTTPError(404); + } + } + } + + void Page(const std::string& s, unsigned int response, HTTPHeaders* hheaders) + { + SendHeaders(s.length(), response, *hheaders); + WriteData(s); + BufferedSocket::Close(true); + } + + void Page(std::stringstream* n, unsigned int response, HTTPHeaders* hheaders) + { + Page(n->str(), response, hheaders); + } + + 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 (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 == ".") + { + // Stay at the current level. + continue; + } + + if (pathsegment == "..") + { + // Traverse up to the previous level. + if (!pathsegments.empty()) + pathsegments.pop_back(); + continue; + } + + pathsegments.push_back(pathsegment); + } + + out.path.reserve(url.field_data[UF_PATH].len); + out.path.append("/").append(stdalgo::string::join(pathsegments, '/')); + } + + 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 +{ + public: + HTTPdAPIImpl(Module* parent) + : HTTPdAPIBase(parent) + { + } + + void SendResponse(HTTPDocumentResponse& resp) CXX11_OVERRIDE + { + resp.src.sock->Page(resp.document, resp.responsecode, &resp.headers); + } +}; + +class ModuleHttpServer : public Module +{ + 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 + { + HttpModule = this; + } + + void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE + { + ConfigTag* tag = ServerInstance->Config->ConfValue("httpd"); + timeoutsec = tag->getDuration("timeout", 10, 1); + } + + ModResult OnAcceptConnection(int nfd, ListenSocket* from, irc::sockets::sockaddrs* client, irc::sockets::sockaddrs* server) CXX11_OVERRIDE + { + if (!stdalgo::string::equalsci(from->bind_tag->getString("type"), "httpd")) + return MOD_RES_PASSTHRU; + + sockets.push_front(new HttpServerSocket(nfd, client->addr(), from, client, server, timeoutsec)); + return MOD_RES_ALLOW; + } + + void OnUnloadModule(Module* mod) CXX11_OVERRIDE + { + for (insp::intrusive_list::const_iterator i = sockets.begin(); i != sockets.end(); ) + { + HttpServerSocket* sock = *i; + ++i; + if (sock->GetModHook(mod)) + { + sock->cull(); + delete sock; + } + } + } + + CullResult cull() CXX11_OVERRIDE + { + for (insp::intrusive_list::const_iterator i = sockets.begin(); i != sockets.end(); ++i) + { + HttpServerSocket* sock = *i; + sock->Close(); + } + return Module::cull(); + } + + Version GetVersion() CXX11_OVERRIDE + { + return Version("Allows the server administrator to serve various useful resources over HTTP.", VF_VENDOR); + } +}; + +MODULE_INIT(ModuleHttpServer)