unrealircd- supernets unrealircd source & configuration |
git clone git://git.acid.vegas/unrealircd.git |
Log | Files | Refs | Archive | README | LICENSE |
webserver.c (17087B)
1 /* 2 * Webserver 3 * (C)Copyright 2016 Bram Matthys and the UnrealIRCd team 4 * License: GPLv2 or later 5 */ 6 7 #include "unrealircd.h" 8 #include "dns.h" 9 10 ModuleHeader MOD_HEADER 11 = { 12 "webserver", 13 "1.0.0", 14 "Webserver", 15 "UnrealIRCd Team", 16 "unrealircd-6", 17 }; 18 19 #if CHAR_MIN < 0 20 #error "In UnrealIRCd char should always be unsigned. Check your compiler" 21 #endif 22 23 /* How many seconds to wait with closing after sending the response */ 24 #define WEB_CLOSE_TIME 1 25 26 /* The "Server: xyz" in the response */ 27 #define WEB_SOFTWARE "UnrealIRCd" 28 29 /* Macros */ 30 #define WEB(client) ((WebRequest *)moddata_client(client, webserver_md).ptr) 31 #define WEBSERVER(client) ((client->local && client->local->listener) ? client->local->listener->webserver : NULL) 32 #define reset_handshake_timeout(client, delta) do { client->local->creationtime = TStime() - iConf.handshake_timeout + delta; } while(0) 33 34 /* Forward declarations */ 35 int webserver_packet_out(Client *from, Client *to, Client *intended_to, char **msg, int *length); 36 int webserver_packet_in(Client *client, const char *readbuf, int *length); 37 void webserver_mdata_free(ModData *m); 38 int webserver_handle_packet(Client *client, const char *readbuf, int length); 39 int webserver_handle_handshake(Client *client, const char *readbuf, int *length); 40 int webserver_handle_request_header(Client *client, const char *readbuf, int *length); 41 void _webserver_send_response(Client *client, int status, char *msg); 42 void _webserver_close_client(Client *client); 43 int _webserver_handle_body(Client *client, WebRequest *web, const char *readbuf, int length); 44 45 /* Global variables */ 46 ModDataInfo *webserver_md; 47 48 MOD_TEST() 49 { 50 MARK_AS_OFFICIAL_MODULE(modinfo); 51 EfunctionAddVoid(modinfo->handle, EFUNC_WEBSERVER_SEND_RESPONSE, _webserver_send_response); 52 EfunctionAddVoid(modinfo->handle, EFUNC_WEBSERVER_CLOSE_CLIENT, _webserver_close_client); 53 EfunctionAdd(modinfo->handle, EFUNC_WEBSERVER_HANDLE_BODY, _webserver_handle_body); 54 return MOD_SUCCESS; 55 } 56 57 MOD_INIT() 58 { 59 ModDataInfo mreq; 60 61 MARK_AS_OFFICIAL_MODULE(modinfo); 62 63 //HookAdd(modinfo->handle, HOOKTYPE_PACKET, INT_MAX, webserver_packet_out); 64 HookAdd(modinfo->handle, HOOKTYPE_RAWPACKET_IN, INT_MIN, webserver_packet_in); 65 66 memset(&mreq, 0, sizeof(mreq)); 67 mreq.name = "web"; 68 mreq.serialize = NULL; 69 mreq.unserialize = NULL; 70 mreq.free = webserver_mdata_free; 71 mreq.sync = 0; 72 mreq.type = MODDATATYPE_CLIENT; 73 webserver_md = ModDataAdd(modinfo->handle, mreq); 74 75 return MOD_SUCCESS; 76 } 77 78 MOD_LOAD() 79 { 80 return MOD_SUCCESS; 81 } 82 83 MOD_UNLOAD() 84 { 85 return MOD_SUCCESS; 86 } 87 88 /** UnrealIRCd internals: free WebRequest object. */ 89 void webserver_mdata_free(ModData *m) 90 { 91 WebRequest *wsu = (WebRequest *)m->ptr; 92 if (wsu) 93 { 94 safe_free(wsu->uri); 95 free_nvplist(wsu->headers); 96 safe_free(wsu->lefttoparse); 97 safe_free(wsu->request_buffer); 98 safe_free(m->ptr); 99 } 100 } 101 102 /** Outgoing packet hook. 103 * Do we need this? 104 */ 105 int webserver_packet_out(Client *from, Client *to, Client *intended_to, char **msg, int *length) 106 { 107 static char utf8buf[510]; 108 109 if (MyConnect(to) && WEB(to)) 110 { 111 // TODO: Inhibit all? 112 // Websocket can override though? 113 return 0; 114 } 115 return 0; 116 } 117 118 HttpMethod webserver_get_method(const char *buf) 119 { 120 if (!strncmp(buf, "HEAD ", 5)) 121 return HTTP_METHOD_HEAD; 122 if (!strncmp(buf, "GET ", 4)) 123 return HTTP_METHOD_GET; 124 if (!strncmp(buf, "PUT ", 4)) 125 return HTTP_METHOD_PUT; 126 if (!strncmp(buf, "POST ", 5)) 127 return HTTP_METHOD_POST; 128 return HTTP_METHOD_NONE; /* invalid */ 129 } 130 131 void webserver_possible_request(Client *client, const char *buf, int len) 132 { 133 HttpMethod method; 134 135 if (len < 8) 136 return; 137 138 /* Probably redundant, but just to be sure, if already tagged, then don't change it! */ 139 if (WEB(client)) 140 return; 141 142 method = webserver_get_method(buf); 143 if (method == HTTP_METHOD_NONE) 144 return; /* invalid */ 145 146 moddata_client(client, webserver_md).ptr = safe_alloc(sizeof(WebRequest)); 147 WEB(client)->method = method; 148 149 /* Set some default values: */ 150 WEB(client)->content_length = -1; 151 WEB(client)->config_max_request_buffer_size = 4096; /* 4k */ 152 } 153 154 /** Incoming packet hook. This processes web requests. 155 * NOTE The different return values: 156 * -1 means: don't touch this client anymore, it has or might have been killed! 157 * 0 means: don't process this data, but you can read another packet if you want 158 * >0 means: process this data (regular IRC data, non-web stuff) 159 */ 160 int webserver_packet_in(Client *client, const char *readbuf, int *length) 161 { 162 if ((client->local->traffic.messages_received == 0) && WEBSERVER(client)) 163 webserver_possible_request(client, readbuf, *length); 164 165 if (!WEB(client)) 166 return 1; /* "normal" IRC client */ 167 168 if (WEB(client)->request_header_parsed) 169 return WEBSERVER(client)->handle_body(client, WEB(client), readbuf, *length); 170 171 /* else.. */ 172 return webserver_handle_request_header(client, readbuf, length); 173 } 174 175 /** Helper function to parse the HTTP header consisting of multiple 'Key: value' pairs */ 176 int webserver_handshake_helper(char *buffer, int len, char **key, char **value, char **lastloc, int *end_of_request) 177 { 178 static char buf[4096], *nextptr; 179 char *p; 180 char *k = NULL, *v = NULL; 181 int foundlf = 0; 182 183 if (buffer) 184 { 185 /* Initialize */ 186 if (len > sizeof(buf) - 1) 187 len = sizeof(buf) - 1; 188 189 memcpy(buf, buffer, len); 190 buf[len] = '\0'; 191 nextptr = buf; 192 } 193 194 *end_of_request = 0; 195 196 p = nextptr; 197 198 if (!p) 199 { 200 *key = *value = NULL; 201 return 0; /* done processing data */ 202 } 203 204 if (!strncmp(p, "\n", 1) || !strncmp(p, "\r\n", 2)) 205 { 206 *key = *value = NULL; 207 *end_of_request = 1; 208 return 0; 209 } 210 211 /* Note: p *could* point to the NUL byte ('\0') */ 212 213 /* Special handling for GET line itself. */ 214 if (webserver_get_method(p) != HTTP_METHOD_NONE) 215 { 216 k = "REQUEST"; 217 p = strchr(p, ' ') + 1; /* space (0x20) is guaranteed to be there, see strncmp above */ 218 v = p; /* SET VALUE */ 219 nextptr = NULL; /* set to "we are done" in case next for loop fails */ 220 for (; *p; p++) 221 { 222 if (*p == ' ') 223 { 224 *p = '\0'; /* terminate before "HTTP/1.X" part */ 225 } 226 else if (*p == '\r') 227 { 228 *p = '\0'; /* eat silently, but don't consider EOL */ 229 } 230 else if (*p == '\n') 231 { 232 *p = '\0'; 233 nextptr = p+1; /* safe, there is data or at least a \0 there */ 234 break; 235 } 236 } 237 *key = k; 238 *value = v; 239 return 1; 240 } 241 242 /* Header parsing starts here. 243 * Example line "Host: www.unrealircd.org" 244 */ 245 k = p; /* SET KEY */ 246 247 /* First check if the line contains a terminating \n. If not, don't process it 248 * as it may have been a cut header. 249 */ 250 for (; *p; p++) 251 { 252 if (*p == '\n') 253 { 254 foundlf = 1; 255 break; 256 } 257 } 258 259 if (!foundlf) 260 { 261 *key = *value = NULL; 262 *lastloc = k; 263 return 0; 264 } 265 266 p = k; 267 268 for (; *p; p++) 269 { 270 if ((*p == '\n') || (*p == '\r')) 271 { 272 /* Reached EOL but 'value' not found */ 273 *p = '\0'; 274 break; 275 } 276 if (*p == ':') 277 { 278 *p++ = '\0'; 279 if (*p++ != ' ') 280 break; /* missing mandatory space after ':' */ 281 282 v = p; /* SET VALUE */ 283 nextptr = NULL; /* set to "we are done" in case next for loop fails */ 284 for (; *p; p++) 285 { 286 if (*p == '\r') 287 { 288 *p = '\0'; /* eat silently, but don't consider EOL */ 289 } 290 else if (*p == '\n') 291 { 292 *p = '\0'; 293 nextptr = p+1; /* safe, there is data or at least a \0 there */ 294 break; 295 } 296 } 297 /* A key-value pair was succesfully parsed, return it */ 298 *key = k; 299 *value = v; 300 return 1; 301 } 302 } 303 304 /* Fatal parse error */ 305 *key = *value = NULL; 306 return 0; 307 } 308 309 /** Check if there is any data at the end of the request */ 310 char *find_end_of_request(char *header, int totalsize, int *remaining_bytes) 311 { 312 char *nextframe1; 313 char *nextframe2; 314 char *nextframe = NULL; 315 316 // find first occurance, yeah this is just stupid, but it works. 317 nextframe1 = strstr(header, "\r\n\r\n"); // = +4 318 nextframe2 = strstr(header, "\n\n"); // = +2 319 if (nextframe1 && nextframe2) 320 { 321 if (nextframe1 < nextframe2) 322 { 323 nextframe = nextframe1 + 4; 324 } else { 325 nextframe = nextframe2 + 2; 326 } 327 } else 328 if (nextframe1) 329 { 330 nextframe = nextframe1 + 4; 331 } else 332 if (nextframe2) 333 { 334 nextframe = nextframe2 + 2; 335 } 336 if (nextframe) 337 { 338 *remaining_bytes = totalsize - (nextframe - header); 339 if (*remaining_bytes > 0) 340 return nextframe; 341 } 342 return NULL; 343 } 344 345 /** Handle HTTP request 346 * Yes, I'm going to assume that the header fits in one packet and one packet only. 347 */ 348 int webserver_handle_request_header(Client *client, const char *readbuf, int *length) 349 { 350 char *key, *value; 351 int r, end_of_request; 352 static char netbuf[16384]; 353 static char netbuf2[16384]; 354 char *lastloc = NULL; 355 int n, maxcopy, nprefix=0; 356 int totalsize; 357 358 /* Totally paranoid: */ 359 memset(netbuf, 0, sizeof(netbuf)); 360 memset(netbuf2, 0, sizeof(netbuf2)); 361 362 /** Frame re-assembling starts here **/ 363 if (WEB(client)->lefttoparse) 364 { 365 strlcpy(netbuf, WEB(client)->lefttoparse, sizeof(netbuf)); 366 nprefix = strlen(netbuf); 367 } 368 maxcopy = sizeof(netbuf) - nprefix - 1; 369 /* (Need to do some manual checking here as strlen() can't be safely used 370 * on readbuf. Same is true for strlncat since it uses strlen().) 371 */ 372 n = *length; 373 if (n > maxcopy) 374 n = maxcopy; 375 if (n <= 0) 376 { 377 webserver_close_client(client); // Oversized line 378 return -1; 379 } 380 memcpy(netbuf+nprefix, readbuf, n); /* SAFE: see checking above */ 381 totalsize = n + nprefix; 382 netbuf[totalsize] = '\0'; 383 memcpy(netbuf2, netbuf, totalsize+1); // copy, including the "always present \0 at the end just in case we use strstr etc". 384 safe_free(WEB(client)->lefttoparse); 385 386 /** Now step through the lines.. **/ 387 for (r = webserver_handshake_helper(netbuf, strlen(netbuf), &key, &value, &lastloc, &end_of_request); 388 r; 389 r = webserver_handshake_helper(NULL, 0, &key, &value, &lastloc, &end_of_request)) 390 { 391 if (BadPtr(value)) 392 continue; /* skip empty values */ 393 394 if (!strcasecmp(key, "REQUEST")) 395 { 396 safe_strdup(WEB(client)->uri, value); 397 } else 398 { 399 if (!strcasecmp(key, "Content-Length")) 400 { 401 WEB(client)->content_length = atoll(value); 402 } else 403 if (!strcasecmp(key, "Transfer-Encoding")) 404 { 405 if (!strcasecmp(value, "chunked")) 406 WEB(client)->transfer_encoding = TRANSFER_ENCODING_CHUNKED; 407 } 408 add_nvplist(&WEB(client)->headers, WEB(client)->num_headers, key, value); 409 } 410 } 411 412 if (end_of_request) 413 { 414 int n; 415 int remaining_bytes = 0; 416 char *nextframe; 417 418 /* Some sanity checks */ 419 if (!WEB(client)->uri) 420 { 421 webserver_send_response(client, 400, "Malformed HTTP request"); 422 return -1; 423 } 424 425 WEB(client)->request_header_parsed = 1; 426 n = WEBSERVER(client)->handle_request(client, WEB(client)); 427 if ((n <= 0) || IsDead(client)) 428 return n; /* byebye */ 429 430 /* There could be data directly after the request header (eg for 431 * a POST or PUT), check for it here so it isn't lost. 432 */ 433 nextframe = find_end_of_request(netbuf2, totalsize, &remaining_bytes); 434 if (nextframe) 435 return WEBSERVER(client)->handle_body(client, WEB(client), nextframe, remaining_bytes); 436 return 0; 437 } 438 439 if (lastloc) 440 { 441 /* Last line was cut somewhere, save it for next round. */ 442 safe_strdup(WEB(client)->lefttoparse, lastloc); 443 } 444 return 0; /* don't let UnrealIRCd process this */ 445 } 446 447 /** Send a HTTP(S) response. 448 * @param client Client to send to 449 * @param status HTTP status code 450 * @param msg The message body. 451 * @note if 'msgs' is NULL then don't close the connection. 452 */ 453 void _webserver_send_response(Client *client, int status, char *msg) 454 { 455 char buf[512]; 456 char *statusmsg = "???"; 457 458 if (status == 200) 459 statusmsg = "OK"; 460 else if (status == 201) 461 statusmsg = "Created"; 462 else if (status == 500) 463 statusmsg = "Internal Server Error"; 464 else if (status == 400) 465 statusmsg = "Bad Request"; 466 else if (status == 401) 467 statusmsg = "Unauthorized"; 468 else if (status == 403) 469 statusmsg = "Forbidden"; 470 else if (status == 404) 471 statusmsg = "Not Found"; 472 else if (status == 416) 473 statusmsg = "Range Not Satisfiable"; 474 475 snprintf(buf, sizeof(buf), 476 "HTTP/1.1 %d %s\r\nServer: %s\r\nConnection: close\r\n\r\n", 477 status, statusmsg, WEB_SOFTWARE); 478 if (msg) 479 { 480 strlcat(buf, msg, sizeof(buf)); 481 strlcat(buf, "\n", sizeof(buf)); 482 } 483 484 dbuf_put(&client->local->sendQ, buf, strlen(buf)); 485 if (msg) 486 webserver_close_client(client); 487 } 488 489 /** Close a web client softly, after data has been sent. */ 490 void _webserver_close_client(Client *client) 491 { 492 send_queued(client); 493 if (DBufLength(&client->local->sendQ) == 0) 494 { 495 exit_client(client, NULL, "End of request"); 496 //dead_socket(client, ""); 497 } else { 498 send_queued(client); 499 reset_handshake_timeout(client, WEB_CLOSE_TIME); 500 } 501 } 502 503 int webserver_handle_body_append_buffer(Client *client, const char *buf, int len) 504 { 505 /* Guard.. */ 506 if (len <= 0) 507 { 508 dead_socket(client, "HTTP request error"); 509 return 0; 510 } 511 512 if (WEB(client)->request_buffer) 513 { 514 long long newsize = WEB(client)->request_buffer_size + len + 1; 515 if (newsize > WEB(client)->config_max_request_buffer_size) 516 { 517 /* We would overflow */ 518 unreal_log(ULOG_WARNING, "webserver", "HTTP_BODY_TOO_LARGE", client, 519 "[webserver] Client $client: request body too large ($length)", 520 log_data_integer("length", newsize)); 521 dead_socket(client, ""); 522 return 0; 523 } 524 WEB(client)->request_buffer = realloc(WEB(client)->request_buffer, newsize); 525 } else 526 { 527 if (len + 1 > WEB(client)->config_max_request_buffer_size) 528 { 529 /* We would overflow */ 530 unreal_log(ULOG_WARNING, "webserver", "HTTP_BODY_TOO_LARGE", client, 531 "[webserver] Client $client: request body too large ($length)", 532 log_data_integer("length", len+1)); 533 dead_socket(client, ""); 534 return 0; 535 } 536 WEB(client)->request_buffer = malloc(len+1); 537 } 538 memcpy(WEB(client)->request_buffer + WEB(client)->request_buffer_size, buf, len); 539 WEB(client)->request_buffer_size += len; 540 WEB(client)->request_buffer[WEB(client)->request_buffer_size] = '\0'; 541 return 1; 542 } 543 544 /** Handle HTTP body parsing, eg for a PUT request, concatting it all together. 545 * @param client The client 546 * @param web The WEB(client) 547 * @param readbuf Packet in the read buffer 548 * @param pktsize Packet size of the read buffer 549 * @return 1 to continue processing, 0 if client is killed. 550 */ 551 int _webserver_handle_body(Client *client, WebRequest *web, const char *readbuf, int pktsize) 552 { 553 char *buf; 554 long long n; 555 char *free_this_buffer = NULL; 556 557 if (WEB(client)->transfer_encoding == TRANSFER_ENCODING_NONE) 558 { 559 if (!webserver_handle_body_append_buffer(client, readbuf, pktsize)) 560 return 0; 561 562 if ((WEB(client)->content_length >= 0) && 563 (WEB(client)->request_buffer_size >= WEB(client)->content_length)) 564 { 565 WEB(client)->request_body_complete = 1; 566 } 567 return 1; 568 } 569 570 /* Fill 'buf' nd set 'buflen' with what we had + what we have now. 571 * Makes things easy. 572 */ 573 if (WEB(client)->lefttoparse) 574 { 575 n = WEB(client)->lefttoparselen + pktsize; 576 free_this_buffer = buf = safe_alloc(n); 577 memcpy(buf, WEB(client)->lefttoparse, WEB(client)->lefttoparselen); 578 memcpy(buf+WEB(client)->lefttoparselen, readbuf, pktsize); 579 safe_free(WEB(client)->lefttoparse); 580 WEB(client)->lefttoparselen = 0; 581 } else { 582 n = pktsize; 583 free_this_buffer = buf = safe_alloc(n); 584 memcpy(buf, readbuf, n); 585 } 586 587 /* Chunked transfers.. yayyyy.. */ 588 while (n > 0) 589 { 590 if (WEB(client)->chunk_remaining > 0) 591 { 592 /* Eat it */ 593 int eat = MIN(WEB(client)->chunk_remaining, n); 594 if (!webserver_handle_body_append_buffer(client, buf, eat)) 595 { 596 /* fatal error such as size exceeded */ 597 safe_free(free_this_buffer); 598 return 0; 599 } 600 n -= eat; 601 buf += eat; 602 WEB(client)->chunk_remaining -= eat; 603 } else 604 { 605 int gotlf = 0; 606 int i; 607 608 /* First check if it is a (trailing) empty line, 609 * eg from a previous chunk. Skip over. 610 */ 611 if ((n >= 2) && !strncmp(buf, "\r\n", 2)) 612 { 613 buf += 2; 614 n -= 2; 615 } else 616 if ((n >= 1) && !strncmp(buf, "\n", 1)) 617 { 618 buf++; 619 n--; 620 } 621 622 /* Now we are (possibly) at the chunk size line, 623 * this is or example '7f' + newline. 624 * So first, check if we have a newline at all. 625 */ 626 for (i=0; i < n; i++) 627 { 628 if (buf[i] == '\n') 629 { 630 gotlf = 1; 631 break; 632 } 633 } 634 if (!gotlf) 635 { 636 /* The line telling us the chunk size is incomplete, 637 * as it does not contain an \n. Wait for more data 638 * from the network socket. 639 */ 640 if (n > 0) 641 { 642 /* Store what we have first.. */ 643 WEB(client)->lefttoparselen = n; 644 WEB(client)->lefttoparse = safe_alloc(n); 645 memcpy(WEB(client)->lefttoparse, buf, n); 646 } 647 safe_free(free_this_buffer); 648 return 1; /* WE WANT MORE! */ 649 } 650 buf[i] = '\0'; /* cut at LF */ 651 i++; /* point to next data */ 652 WEB(client)->chunk_remaining = strtol(buf, NULL, 16); 653 if (WEB(client)->chunk_remaining < 0) 654 { 655 unreal_log(ULOG_WARNING, "webserver", "WEB_NEGATIVE_CHUNK", client, 656 "Webrequest from $client: Negative chunk encountered"); 657 safe_free(free_this_buffer); 658 dead_socket(client, ""); 659 return 0; 660 } 661 if (WEB(client)->chunk_remaining == 0) 662 { 663 /* DONE! */ 664 WEB(client)->request_body_complete = 1; 665 safe_free(free_this_buffer); 666 return 1; 667 } 668 buf += i; 669 n -= i; 670 } 671 } 672 673 safe_free(free_this_buffer); 674 return 1; 675 }