]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - src/modules/m_httpd.cpp
m_flashpolicyd, m_httpd Store sockets in a intrusive list
[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 bool claimed;
33 static insp::intrusive_list<HttpServerSocket> sockets;
34
35 /** HTTP socket states
36  */
37 enum HttpState
38 {
39         HTTP_SERVE_WAIT_REQUEST = 0, /* Waiting for a full request */
40         HTTP_SERVE_RECV_POSTDATA = 1, /* Waiting to finish recieving POST data */
41         HTTP_SERVE_SEND_DATA = 2 /* Sending response */
42 };
43
44 /** A socket used for HTTP transport
45  */
46 class HttpServerSocket : public BufferedSocket, public Timer, public insp::intrusive_list_node<HttpServerSocket>
47 {
48         HttpState InternalState;
49         std::string ip;
50
51         HTTPHeaders headers;
52         std::string reqbuffer;
53         std::string postdata;
54         unsigned int postsize;
55         std::string request_type;
56         std::string uri;
57         std::string http_version;
58
59         /** True if this object is in the cull list
60          */
61         bool waitingcull;
62
63         bool Tick(time_t currtime) CXX11_OVERRIDE
64         {
65                 AddToCull();
66                 return false;
67         }
68
69  public:
70         HttpServerSocket(int newfd, const std::string& IP, ListenSocket* via, irc::sockets::sockaddrs* client, irc::sockets::sockaddrs* server, unsigned int timeoutsec)
71                 : BufferedSocket(newfd)
72                 , Timer(timeoutsec)
73                 , InternalState(HTTP_SERVE_WAIT_REQUEST)
74                 , ip(IP)
75                 , postsize(0)
76                 , waitingcull(false)
77         {
78                 ServerInstance->Timers.AddTimer(this);
79
80                 if (via->iohookprov)
81                         via->iohookprov->OnAccept(this, client, server);
82         }
83
84         ~HttpServerSocket()
85         {
86                 sockets.erase(this);
87         }
88
89         void OnError(BufferedSocketError) CXX11_OVERRIDE
90         {
91                 AddToCull();
92         }
93
94         std::string Response(int response)
95         {
96                 switch (response)
97                 {
98                         case 100:
99                                 return "CONTINUE";
100                         case 101:
101                                 return "SWITCHING PROTOCOLS";
102                         case 200:
103                                 return "OK";
104                         case 201:
105                                 return "CREATED";
106                         case 202:
107                                 return "ACCEPTED";
108                         case 203:
109                                 return "NON-AUTHORITATIVE INFORMATION";
110                         case 204:
111                                 return "NO CONTENT";
112                         case 205:
113                                 return "RESET CONTENT";
114                         case 206:
115                                 return "PARTIAL CONTENT";
116                         case 300:
117                                 return "MULTIPLE CHOICES";
118                         case 301:
119                                 return "MOVED PERMANENTLY";
120                         case 302:
121                                 return "FOUND";
122                         case 303:
123                                 return "SEE OTHER";
124                         case 304:
125                                 return "NOT MODIFIED";
126                         case 305:
127                                 return "USE PROXY";
128                         case 307:
129                                 return "TEMPORARY REDIRECT";
130                         case 400:
131                                 return "BAD REQUEST";
132                         case 401:
133                                 return "UNAUTHORIZED";
134                         case 402:
135                                 return "PAYMENT REQUIRED";
136                         case 403:
137                                 return "FORBIDDEN";
138                         case 404:
139                                 return "NOT FOUND";
140                         case 405:
141                                 return "METHOD NOT ALLOWED";
142                         case 406:
143                                 return "NOT ACCEPTABLE";
144                         case 407:
145                                 return "PROXY AUTHENTICATION REQUIRED";
146                         case 408:
147                                 return "REQUEST TIMEOUT";
148                         case 409:
149                                 return "CONFLICT";
150                         case 410:
151                                 return "GONE";
152                         case 411:
153                                 return "LENGTH REQUIRED";
154                         case 412:
155                                 return "PRECONDITION FAILED";
156                         case 413:
157                                 return "REQUEST ENTITY TOO LARGE";
158                         case 414:
159                                 return "REQUEST-URI TOO LONG";
160                         case 415:
161                                 return "UNSUPPORTED MEDIA TYPE";
162                         case 416:
163                                 return "REQUESTED RANGE NOT SATISFIABLE";
164                         case 417:
165                                 return "EXPECTATION FAILED";
166                         case 500:
167                                 return "INTERNAL SERVER ERROR";
168                         case 501:
169                                 return "NOT IMPLEMENTED";
170                         case 502:
171                                 return "BAD GATEWAY";
172                         case 503:
173                                 return "SERVICE UNAVAILABLE";
174                         case 504:
175                                 return "GATEWAY TIMEOUT";
176                         case 505:
177                                 return "HTTP VERSION NOT SUPPORTED";
178                         default:
179                                 return "WTF";
180                         break;
181
182                 }
183         }
184
185         void SendHTTPError(int response)
186         {
187                 HTTPHeaders empty;
188                 std::string data = "<html><head></head><body>Server error "+ConvToStr(response)+": "+Response(response)+"<br>"+
189                                    "<small>Powered by <a href='http://www.inspircd.org'>InspIRCd</a></small></body></html>";
190
191                 SendHeaders(data.length(), response, empty);
192                 WriteData(data);
193         }
194
195         void SendHeaders(unsigned long size, int response, HTTPHeaders &rheaders)
196         {
197
198                 WriteData(http_version + " "+ConvToStr(response)+" "+Response(response)+"\r\n");
199
200                 rheaders.CreateHeader("Date", InspIRCd::TimeString(ServerInstance->Time(), "%a, %d %b %Y %H:%M:%S GMT", true));
201                 rheaders.CreateHeader("Server", INSPIRCD_BRANCH);
202                 rheaders.SetHeader("Content-Length", ConvToStr(size));
203
204                 if (size)
205                         rheaders.CreateHeader("Content-Type", "text/html");
206                 else
207                         rheaders.RemoveHeader("Content-Type");
208
209                 /* Supporting Connection: keep-alive causes a whole world of hurt syncronizing timeouts,
210                  * so remove it, its not essential for what we need.
211                  */
212                 rheaders.SetHeader("Connection", "Close");
213
214                 WriteData(rheaders.GetFormattedHeaders());
215                 WriteData("\r\n");
216         }
217
218         void OnDataReady()
219         {
220                 if (InternalState == HTTP_SERVE_RECV_POSTDATA)
221                 {
222                         postdata.append(recvq);
223                         if (postdata.length() >= postsize)
224                                 ServeData();
225                 }
226                 else
227                 {
228                         reqbuffer.append(recvq);
229
230                         if (reqbuffer.length() >= 8192)
231                         {
232                                 ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "m_httpd dropped connection due to an oversized request buffer");
233                                 reqbuffer.clear();
234                                 SetError("Buffer");
235                         }
236
237                         if (InternalState == HTTP_SERVE_WAIT_REQUEST)
238                                 CheckRequestBuffer();
239                 }
240         }
241
242         void CheckRequestBuffer()
243         {
244                 std::string::size_type reqend = reqbuffer.find("\r\n\r\n");
245                 if (reqend == std::string::npos)
246                         return;
247
248                 // We have the headers; parse them all
249                 std::string::size_type hbegin = 0, hend;
250                 while ((hend = reqbuffer.find("\r\n", hbegin)) != std::string::npos)
251                 {
252                         if (hbegin == hend)
253                                 break;
254
255                         if (request_type.empty())
256                         {
257                                 std::istringstream cheader(std::string(reqbuffer, hbegin, hend - hbegin));
258                                 cheader >> request_type;
259                                 cheader >> uri;
260                                 cheader >> http_version;
261
262                                 if (request_type.empty() || uri.empty() || http_version.empty())
263                                 {
264                                         SendHTTPError(400);
265                                         return;
266                                 }
267
268                                 hbegin = hend + 2;
269                                 continue;
270                         }
271
272                         std::string cheader = reqbuffer.substr(hbegin, hend - hbegin);
273
274                         std::string::size_type fieldsep = cheader.find(':');
275                         if ((fieldsep == std::string::npos) || (fieldsep == 0) || (fieldsep == cheader.length() - 1))
276                         {
277                                 SendHTTPError(400);
278                                 return;
279                         }
280
281                         headers.SetHeader(cheader.substr(0, fieldsep), cheader.substr(fieldsep + 2));
282
283                         hbegin = hend + 2;
284                 }
285
286                 reqbuffer.erase(0, reqend + 4);
287
288                 std::transform(request_type.begin(), request_type.end(), request_type.begin(), ::toupper);
289                 std::transform(http_version.begin(), http_version.end(), http_version.begin(), ::toupper);
290
291                 if ((http_version != "HTTP/1.1") && (http_version != "HTTP/1.0"))
292                 {
293                         SendHTTPError(505);
294                         return;
295                 }
296
297                 if (headers.IsSet("Content-Length") && (postsize = ConvToInt(headers.GetHeader("Content-Length"))) > 0)
298                 {
299                         InternalState = HTTP_SERVE_RECV_POSTDATA;
300
301                         if (reqbuffer.length() >= postsize)
302                         {
303                                 postdata = reqbuffer.substr(0, postsize);
304                                 reqbuffer.erase(0, postsize);
305                         }
306                         else if (!reqbuffer.empty())
307                         {
308                                 postdata = reqbuffer;
309                                 reqbuffer.clear();
310                         }
311
312                         if (postdata.length() >= postsize)
313                                 ServeData();
314
315                         return;
316                 }
317
318                 ServeData();
319         }
320
321         void ServeData()
322         {
323                 InternalState = HTTP_SERVE_SEND_DATA;
324
325                 claimed = false;
326                 HTTPRequest acl((Module*)HttpModule, "httpd_acl", request_type, uri, &headers, this, ip, postdata);
327                 acl.Send();
328                 if (!claimed)
329                 {
330                         HTTPRequest url((Module*)HttpModule, "httpd_url", request_type, uri, &headers, this, ip, postdata);
331                         url.Send();
332                         if (!claimed)
333                         {
334                                 SendHTTPError(404);
335                         }
336                 }
337         }
338
339         void Page(std::stringstream* n, int response, HTTPHeaders *hheaders)
340         {
341                 SendHeaders(n->str().length(), response, *hheaders);
342                 WriteData(n->str());
343         }
344
345         void AddToCull()
346         {
347                 if (waitingcull)
348                         return;
349
350                 waitingcull = true;
351                 Close();
352                 ServerInstance->GlobalCulls.AddItem(this);
353         }
354 };
355
356 class HTTPdAPIImpl : public HTTPdAPIBase
357 {
358  public:
359         HTTPdAPIImpl(Module* parent)
360                 : HTTPdAPIBase(parent)
361         {
362         }
363
364         void SendResponse(HTTPDocumentResponse& resp) CXX11_OVERRIDE
365         {
366                 claimed = true;
367                 resp.src.sock->Page(resp.document, resp.responsecode, &resp.headers);
368         }
369 };
370
371 class ModuleHttpServer : public Module
372 {
373         HTTPdAPIImpl APIImpl;
374         unsigned int timeoutsec;
375
376  public:
377         ModuleHttpServer()
378                 : APIImpl(this)
379         {
380         }
381
382         void init() CXX11_OVERRIDE
383         {
384                 HttpModule = this;
385         }
386
387         void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE
388         {
389                 ConfigTag* tag = ServerInstance->Config->ConfValue("httpd");
390                 timeoutsec = tag->getInt("timeout", 10, 1);
391         }
392
393         ModResult OnAcceptConnection(int nfd, ListenSocket* from, irc::sockets::sockaddrs* client, irc::sockets::sockaddrs* server) CXX11_OVERRIDE
394         {
395                 if (from->bind_tag->getString("type") != "httpd")
396                         return MOD_RES_PASSTHRU;
397                 int port;
398                 std::string incomingip;
399                 irc::sockets::satoap(*client, incomingip, port);
400                 sockets.push_front(new HttpServerSocket(nfd, incomingip, from, client, server, timeoutsec));
401                 return MOD_RES_ALLOW;
402         }
403
404         CullResult cull() CXX11_OVERRIDE
405         {
406                 for (insp::intrusive_list<HttpServerSocket>::const_iterator i = sockets.begin(); i != sockets.end(); ++i)
407                 {
408                         HttpServerSocket* sock = *i;
409                         sock->AddToCull();
410                 }
411                 return Module::cull();
412         }
413
414         Version GetVersion() CXX11_OVERRIDE
415         {
416                 return Version("Provides HTTP serving facilities to modules", VF_VENDOR);
417         }
418 };
419
420 MODULE_INIT(ModuleHttpServer)