]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - src/modules/m_httpd.cpp
Check for errors after calling IOHookProvider::OnAccept()
[user/henk/code/inspircd.git] / src / modules / m_httpd.cpp
1 /*
2  * InspIRCd -- Internet Relay Chat Daemon
3  *
4  *   Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org>
5  *   Copyright (C) 2007-2008 Robin Burchell <robin+git@viroteck.net>
6  *   Copyright (C) 2008 Pippijn van Steenhoven <pip88nl@gmail.com>
7  *   Copyright (C) 2006-2008 Craig Edwards <craigedwards@brainbox.cc>
8  *   Copyright (C) 2007 John Brooks <john.brooks@dereferenced.net>
9  *   Copyright (C) 2007 Dennis Friis <peavey@inspircd.org>
10  *
11  * This file is part of InspIRCd.  InspIRCd is free software: you can
12  * redistribute it and/or modify it under the terms of the GNU General Public
13  * License as published by the Free Software Foundation, version 2.
14  *
15  * This program is distributed in the hope that it will be useful, but WITHOUT
16  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
17  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
18  * details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
22  */
23
24
25 #include "inspircd.h"
26 #include "iohook.h"
27 #include "modules/httpd.h"
28
29 class ModuleHttpServer;
30
31 static ModuleHttpServer* HttpModule;
32 static insp::intrusive_list<HttpServerSocket> sockets;
33 static Events::ModuleEventProvider* aclevprov;
34 static Events::ModuleEventProvider* reqevprov;
35
36 /** HTTP socket states
37  */
38 enum HttpState
39 {
40         HTTP_SERVE_WAIT_REQUEST = 0, /* Waiting for a full request */
41         HTTP_SERVE_RECV_POSTDATA = 1, /* Waiting to finish recieving POST data */
42         HTTP_SERVE_SEND_DATA = 2 /* Sending response */
43 };
44
45 /** A socket used for HTTP transport
46  */
47 class HttpServerSocket : public BufferedSocket, public Timer, public insp::intrusive_list_node<HttpServerSocket>
48 {
49         HttpState InternalState;
50         std::string ip;
51
52         HTTPHeaders headers;
53         std::string reqbuffer;
54         std::string postdata;
55         unsigned int postsize;
56         std::string request_type;
57         std::string uri;
58         std::string http_version;
59
60         /** True if this object is in the cull list
61          */
62         bool waitingcull;
63
64         bool Tick(time_t currtime) CXX11_OVERRIDE
65         {
66                 AddToCull();
67                 return false;
68         }
69
70  public:
71         HttpServerSocket(int newfd, const std::string& IP, ListenSocket* via, irc::sockets::sockaddrs* client, irc::sockets::sockaddrs* server, unsigned int timeoutsec)
72                 : BufferedSocket(newfd)
73                 , Timer(timeoutsec)
74                 , InternalState(HTTP_SERVE_WAIT_REQUEST)
75                 , ip(IP)
76                 , postsize(0)
77                 , waitingcull(false)
78         {
79                 if ((!via->iohookprovs.empty()) && (via->iohookprovs.back()))
80                 {
81                         via->iohookprovs.back()->OnAccept(this, client, server);
82                         // IOHook may have errored
83                         if (!getError().empty())
84                         {
85                                 AddToCull();
86                                 return;
87                         }
88                 }
89
90                 ServerInstance->Timers.AddTimer(this);
91         }
92
93         ~HttpServerSocket()
94         {
95                 sockets.erase(this);
96         }
97
98         void OnError(BufferedSocketError) CXX11_OVERRIDE
99         {
100                 AddToCull();
101         }
102
103         std::string Response(int response)
104         {
105                 switch (response)
106                 {
107                         case 100:
108                                 return "CONTINUE";
109                         case 101:
110                                 return "SWITCHING PROTOCOLS";
111                         case 200:
112                                 return "OK";
113                         case 201:
114                                 return "CREATED";
115                         case 202:
116                                 return "ACCEPTED";
117                         case 203:
118                                 return "NON-AUTHORITATIVE INFORMATION";
119                         case 204:
120                                 return "NO CONTENT";
121                         case 205:
122                                 return "RESET CONTENT";
123                         case 206:
124                                 return "PARTIAL CONTENT";
125                         case 300:
126                                 return "MULTIPLE CHOICES";
127                         case 301:
128                                 return "MOVED PERMANENTLY";
129                         case 302:
130                                 return "FOUND";
131                         case 303:
132                                 return "SEE OTHER";
133                         case 304:
134                                 return "NOT MODIFIED";
135                         case 305:
136                                 return "USE PROXY";
137                         case 307:
138                                 return "TEMPORARY REDIRECT";
139                         case 400:
140                                 return "BAD REQUEST";
141                         case 401:
142                                 return "UNAUTHORIZED";
143                         case 402:
144                                 return "PAYMENT REQUIRED";
145                         case 403:
146                                 return "FORBIDDEN";
147                         case 404:
148                                 return "NOT FOUND";
149                         case 405:
150                                 return "METHOD NOT ALLOWED";
151                         case 406:
152                                 return "NOT ACCEPTABLE";
153                         case 407:
154                                 return "PROXY AUTHENTICATION REQUIRED";
155                         case 408:
156                                 return "REQUEST TIMEOUT";
157                         case 409:
158                                 return "CONFLICT";
159                         case 410:
160                                 return "GONE";
161                         case 411:
162                                 return "LENGTH REQUIRED";
163                         case 412:
164                                 return "PRECONDITION FAILED";
165                         case 413:
166                                 return "REQUEST ENTITY TOO LARGE";
167                         case 414:
168                                 return "REQUEST-URI TOO LONG";
169                         case 415:
170                                 return "UNSUPPORTED MEDIA TYPE";
171                         case 416:
172                                 return "REQUESTED RANGE NOT SATISFIABLE";
173                         case 417:
174                                 return "EXPECTATION FAILED";
175                         case 500:
176                                 return "INTERNAL SERVER ERROR";
177                         case 501:
178                                 return "NOT IMPLEMENTED";
179                         case 502:
180                                 return "BAD GATEWAY";
181                         case 503:
182                                 return "SERVICE UNAVAILABLE";
183                         case 504:
184                                 return "GATEWAY TIMEOUT";
185                         case 505:
186                                 return "HTTP VERSION NOT SUPPORTED";
187                         default:
188                                 return "WTF";
189                         break;
190
191                 }
192         }
193
194         void SendHTTPError(int response)
195         {
196                 HTTPHeaders empty;
197                 std::string data = "<html><head></head><body>Server error "+ConvToStr(response)+": "+Response(response)+"<br>"+
198                                    "<small>Powered by <a href='http://www.inspircd.org'>InspIRCd</a></small></body></html>";
199
200                 SendHeaders(data.length(), response, empty);
201                 WriteData(data);
202         }
203
204         void SendHeaders(unsigned long size, int response, HTTPHeaders &rheaders)
205         {
206
207                 WriteData(http_version + " "+ConvToStr(response)+" "+Response(response)+"\r\n");
208
209                 rheaders.CreateHeader("Date", InspIRCd::TimeString(ServerInstance->Time(), "%a, %d %b %Y %H:%M:%S GMT", true));
210                 rheaders.CreateHeader("Server", INSPIRCD_BRANCH);
211                 rheaders.SetHeader("Content-Length", ConvToStr(size));
212
213                 if (size)
214                         rheaders.CreateHeader("Content-Type", "text/html");
215                 else
216                         rheaders.RemoveHeader("Content-Type");
217
218                 /* Supporting Connection: keep-alive causes a whole world of hurt syncronizing timeouts,
219                  * so remove it, its not essential for what we need.
220                  */
221                 rheaders.SetHeader("Connection", "Close");
222
223                 WriteData(rheaders.GetFormattedHeaders());
224                 WriteData("\r\n");
225         }
226
227         void OnDataReady()
228         {
229                 if (InternalState == HTTP_SERVE_RECV_POSTDATA)
230                 {
231                         postdata.append(recvq);
232                         if (postdata.length() >= postsize)
233                                 ServeData();
234                 }
235                 else
236                 {
237                         reqbuffer.append(recvq);
238
239                         if (reqbuffer.length() >= 8192)
240                         {
241                                 ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "m_httpd dropped connection due to an oversized request buffer");
242                                 reqbuffer.clear();
243                                 SetError("Buffer");
244                         }
245
246                         if (InternalState == HTTP_SERVE_WAIT_REQUEST)
247                                 CheckRequestBuffer();
248                 }
249         }
250
251         void CheckRequestBuffer()
252         {
253                 std::string::size_type reqend = reqbuffer.find("\r\n\r\n");
254                 if (reqend == std::string::npos)
255                         return;
256
257                 // We have the headers; parse them all
258                 std::string::size_type hbegin = 0, hend;
259                 while ((hend = reqbuffer.find("\r\n", hbegin)) != std::string::npos)
260                 {
261                         if (hbegin == hend)
262                                 break;
263
264                         if (request_type.empty())
265                         {
266                                 std::istringstream cheader(std::string(reqbuffer, hbegin, hend - hbegin));
267                                 cheader >> request_type;
268                                 cheader >> uri;
269                                 cheader >> http_version;
270
271                                 if (request_type.empty() || uri.empty() || http_version.empty())
272                                 {
273                                         SendHTTPError(400);
274                                         return;
275                                 }
276
277                                 hbegin = hend + 2;
278                                 continue;
279                         }
280
281                         std::string cheader(reqbuffer, hbegin, hend - hbegin);
282
283                         std::string::size_type fieldsep = cheader.find(':');
284                         if ((fieldsep == std::string::npos) || (fieldsep == 0) || (fieldsep == cheader.length() - 1))
285                         {
286                                 SendHTTPError(400);
287                                 return;
288                         }
289
290                         headers.SetHeader(cheader.substr(0, fieldsep), cheader.substr(fieldsep + 2));
291
292                         hbegin = hend + 2;
293                 }
294
295                 reqbuffer.erase(0, reqend + 4);
296
297                 std::transform(request_type.begin(), request_type.end(), request_type.begin(), ::toupper);
298                 std::transform(http_version.begin(), http_version.end(), http_version.begin(), ::toupper);
299
300                 if ((http_version != "HTTP/1.1") && (http_version != "HTTP/1.0"))
301                 {
302                         SendHTTPError(505);
303                         return;
304                 }
305
306                 if (headers.IsSet("Content-Length") && (postsize = ConvToInt(headers.GetHeader("Content-Length"))) > 0)
307                 {
308                         InternalState = HTTP_SERVE_RECV_POSTDATA;
309
310                         if (reqbuffer.length() >= postsize)
311                         {
312                                 postdata.assign(reqbuffer, 0, postsize);
313                                 reqbuffer.erase(0, postsize);
314                         }
315                         else if (!reqbuffer.empty())
316                         {
317                                 postdata = reqbuffer;
318                                 reqbuffer.clear();
319                         }
320
321                         if (postdata.length() >= postsize)
322                                 ServeData();
323
324                         return;
325                 }
326
327                 ServeData();
328         }
329
330         void ServeData()
331         {
332                 InternalState = HTTP_SERVE_SEND_DATA;
333
334                 ModResult MOD_RESULT;
335                 HTTPRequest acl(request_type, uri, &headers, this, ip, postdata);
336                 FIRST_MOD_RESULT_CUSTOM(*aclevprov, HTTPACLEventListener, OnHTTPACLCheck, MOD_RESULT, (acl));
337                 if (MOD_RESULT != MOD_RES_DENY)
338                 {
339                         HTTPRequest url(request_type, uri, &headers, this, ip, postdata);
340                         FIRST_MOD_RESULT_CUSTOM(*reqevprov, HTTPRequestEventListener, OnHTTPRequest, MOD_RESULT, (url));
341                         if (MOD_RESULT == MOD_RES_PASSTHRU)
342                         {
343                                 SendHTTPError(404);
344                         }
345                 }
346         }
347
348         void Page(std::stringstream* n, int response, HTTPHeaders *hheaders)
349         {
350                 SendHeaders(n->str().length(), response, *hheaders);
351                 WriteData(n->str());
352         }
353
354         void AddToCull()
355         {
356                 if (waitingcull)
357                         return;
358
359                 waitingcull = true;
360                 Close();
361                 ServerInstance->GlobalCulls.AddItem(this);
362         }
363 };
364
365 class HTTPdAPIImpl : public HTTPdAPIBase
366 {
367  public:
368         HTTPdAPIImpl(Module* parent)
369                 : HTTPdAPIBase(parent)
370         {
371         }
372
373         void SendResponse(HTTPDocumentResponse& resp) CXX11_OVERRIDE
374         {
375                 resp.src.sock->Page(resp.document, resp.responsecode, &resp.headers);
376         }
377 };
378
379 class ModuleHttpServer : public Module
380 {
381         HTTPdAPIImpl APIImpl;
382         unsigned int timeoutsec;
383         Events::ModuleEventProvider acleventprov;
384         Events::ModuleEventProvider reqeventprov;
385
386  public:
387         ModuleHttpServer()
388                 : APIImpl(this)
389                 , acleventprov(this, "event/http-acl")
390                 , reqeventprov(this, "event/http-request")
391         {
392                 aclevprov = &acleventprov;
393                 reqevprov = &reqeventprov;
394         }
395
396         void init() CXX11_OVERRIDE
397         {
398                 HttpModule = this;
399         }
400
401         void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE
402         {
403                 ConfigTag* tag = ServerInstance->Config->ConfValue("httpd");
404                 timeoutsec = tag->getInt("timeout", 10, 1);
405         }
406
407         ModResult OnAcceptConnection(int nfd, ListenSocket* from, irc::sockets::sockaddrs* client, irc::sockets::sockaddrs* server) CXX11_OVERRIDE
408         {
409                 if (from->bind_tag->getString("type") != "httpd")
410                         return MOD_RES_PASSTHRU;
411                 int port;
412                 std::string incomingip;
413                 irc::sockets::satoap(*client, incomingip, port);
414                 sockets.push_front(new HttpServerSocket(nfd, incomingip, from, client, server, timeoutsec));
415                 return MOD_RES_ALLOW;
416         }
417
418         void OnUnloadModule(Module* mod)
419         {
420                 for (insp::intrusive_list<HttpServerSocket>::const_iterator i = sockets.begin(); i != sockets.end(); )
421                 {
422                         HttpServerSocket* sock = *i;
423                         ++i;
424                         if (sock->GetModHook(mod))
425                         {
426                                 sock->cull();
427                                 delete sock;
428                         }
429                 }
430         }
431
432         CullResult cull() CXX11_OVERRIDE
433         {
434                 for (insp::intrusive_list<HttpServerSocket>::const_iterator i = sockets.begin(); i != sockets.end(); ++i)
435                 {
436                         HttpServerSocket* sock = *i;
437                         sock->AddToCull();
438                 }
439                 return Module::cull();
440         }
441
442         Version GetVersion() CXX11_OVERRIDE
443         {
444                 return Version("Provides HTTP serving facilities to modules", VF_VENDOR);
445         }
446 };
447
448 MODULE_INIT(ModuleHttpServer)