unrealircd- supernets unrealircd source & configuration |
git clone git://git.acid.vegas/unrealircd.git |
Log | Files | Refs | Archive | README | LICENSE |
url_unreal.c (27767B)
1 /* 2 * Unreal Internet Relay Chat Daemon, src/url.c 3 * (C) 2021 Bram Matthys and the UnrealIRCd team 4 * 5 * This program is free software; you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License as published by 7 * the Free Software Foundation; either version 1, or (at your option) 8 * any later version. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program; if not, write to the Free Software 17 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 18 */ 19 20 #include "unrealircd.h" 21 #include "dns.h" 22 23 /* Structs */ 24 25 /* Stores information about the async transfer. 26 * Used to maintain information about the transfer 27 * to trigger the callback upon completion. 28 */ 29 typedef struct Download Download; 30 31 struct Download 32 { 33 Download *prev, *next; 34 vFP callback; 35 void *callback_data; 36 FILE *file_fd; /**< File open for writing (otherwise NULL) */ 37 char filename[PATH_MAX]; 38 char *url; /*< must be free()d by url_do_transfers_async() */ 39 char errorbuf[512]; 40 time_t cachetime; 41 char *hostname; /**< Parsed hostname (from 'url') */ 42 int port; /**< Parsed port (from 'url') */ 43 char *username; 44 char *password; 45 char *document; /**< Parsed document (from 'url') */ 46 char *ip; /**< Resolved IP */ 47 int ipv6; 48 SSL *ssl; 49 int fd; /**< Socket */ 50 int connected; 51 int got_response; 52 int http_status_code; 53 char *lefttoparse; 54 long long lefttoparselen; /* size of data in lefttoparse (note: not used for first header parsing) */ 55 time_t last_modified; 56 time_t download_started; 57 int dns_refcnt; 58 TransferEncoding transfer_encoding; 59 long chunk_remaining; 60 /* for redirects: */ 61 int redirects_remaining; 62 char *redirect_new_location; 63 char *redirect_original_url; 64 }; 65 66 /* Variables */ 67 Download *downloads = NULL; 68 SSL_CTX *https_ctx = NULL; 69 70 /* Forward declarations */ 71 void url_resolve_cb(void *arg, int status, int timeouts, struct hostent *he); 72 void unreal_https_initiate_connect(Download *handle); 73 int url_parse(const char *url, char **host, int *port, char **username, char **password, char **document); 74 SSL_CTX *https_new_ctx(void); 75 void unreal_https_connect_handshake(int fd, int revents, void *data); 76 int https_connect(Download *handle); 77 int https_fatal_tls_error(int ssl_error, int my_errno, Download *handle); 78 void https_connect_send_header(Download *handle); 79 void https_receive_response(int fd, int revents, void *data); 80 int https_handle_response_header(Download *handle, char *readbuf, int n); 81 int https_handle_response_file(Download *handle, char *readbuf, int n); 82 void https_done(Download *handle); 83 void https_done_cached(Download *handle); 84 void https_redirect(Download *handle); 85 int https_parse_header(char *buffer, int len, char **key, char **value, char **lastloc, int *end_of_request); 86 char *url_find_end_of_request(char *header, int totalsize, int *remaining_bytes); 87 void https_cancel(Download *handle, FORMAT_STRING(const char *pattern), ...) __attribute__((format(printf,2,3))); 88 89 void url_free_handle(Download *handle) 90 { 91 DelListItem(handle, downloads); 92 if (handle->fd > 0) 93 { 94 fd_close(handle->fd); 95 fd_unnotify(handle->fd); 96 } 97 if (handle->file_fd) 98 fclose(handle->file_fd); 99 safe_free(handle->url); 100 safe_free(handle->hostname); 101 safe_free(handle->username); 102 safe_free(handle->password); 103 safe_free(handle->document); 104 safe_free(handle->ip); 105 if (handle->ssl) 106 SSL_free(handle->ssl); 107 safe_free(handle->lefttoparse); 108 safe_free(handle->redirect_new_location); 109 safe_free(handle->redirect_original_url); 110 safe_free(handle); 111 } 112 113 void url_cancel_handle_by_callback_data(void *ptr) 114 { 115 Download *d, *d_next; 116 117 for (d = downloads; d; d = d_next) 118 { 119 d_next = d->next; 120 if (d->callback_data == ptr) 121 { 122 d->callback = NULL; 123 d->callback_data = NULL; 124 } 125 } 126 } 127 128 void https_cancel(Download *handle, FORMAT_STRING(const char *pattern), ...) 129 { 130 va_list vl; 131 va_start(vl, pattern); 132 vsnprintf(handle->errorbuf, sizeof(handle->errorbuf), pattern, vl); 133 va_end(vl); 134 if (handle->callback) 135 handle->callback(handle->url, NULL, handle->errorbuf, 0, handle->callback_data); 136 url_free_handle(handle); 137 } 138 139 void download_file_async(const char *url, time_t cachetime, vFP callback, void *callback_data, char *original_url, int maxredirects) 140 { 141 char *file; 142 const char *filename; 143 char *tmp; 144 Download *handle = NULL; 145 int ipv6 = 0; 146 char *host; 147 int port; 148 char *username; 149 char *password; 150 char *document; 151 152 handle = safe_alloc(sizeof(Download)); 153 handle->download_started = TStime(); 154 handle->callback = callback; 155 handle->callback_data = callback_data; 156 handle->cachetime = cachetime; 157 safe_strdup(handle->url, url); 158 safe_strdup(handle->redirect_original_url, original_url); 159 handle->redirects_remaining = maxredirects; 160 AddListItem(handle, downloads); 161 162 if (strncmp(url, "https://", 8)) 163 { 164 https_cancel(handle, "Only https:// is supported (either rebuild UnrealIRCd with curl support or use https)"); 165 return; 166 } 167 if (!url_parse(url, &host, &port, &username, &password, &document)) 168 { 169 https_cancel(handle, "Failed to parse HTTP url"); 170 return; 171 } 172 173 safe_strdup(handle->hostname, host); 174 handle->port = port; 175 safe_strdup(handle->username, username); 176 safe_strdup(handle->password, password); 177 safe_strdup(handle->document, document); 178 179 file = url_getfilename(url); 180 filename = unreal_getfilename(file); 181 tmp = unreal_mktemp(TMPDIR, filename ? filename : "download.conf"); 182 183 handle->file_fd = fopen(tmp, "wb"); 184 if (!handle->file_fd) 185 { 186 https_cancel(handle, "Cannot create '%s': %s", tmp, strerror(ERRNO)); 187 safe_free(file); 188 return; 189 } 190 191 strlcpy(handle->filename, tmp, sizeof(handle->filename)); 192 safe_free(file); 193 194 195 // todo: allocate handle, select en weetikt allemaal 196 // add to some global struct linkedlist, for timeouts 197 // register in i/o 198 199 if (is_valid_ip(handle->hostname)) 200 { 201 /* Nothing to resolve, eg https://127.0.0.1/ */ 202 safe_strdup(handle->ip, handle->hostname); 203 unreal_https_initiate_connect(handle); 204 } else { 205 /* Hostname, so start resolving... */ 206 handle->dns_refcnt++; 207 ares_gethostbyname(resolver_channel, handle->hostname, AF_INET, url_resolve_cb, handle); 208 // TODO: check return value? 209 } 210 } 211 212 void url_resolve_cb(void *arg, int status, int timeouts, struct hostent *he) 213 { 214 Download *handle = (Download *)arg; 215 int n; 216 struct hostent *he2; 217 char ipbuf[HOSTLEN+1]; 218 const char *ip = NULL; 219 220 handle->dns_refcnt--; 221 222 if ((status != 0) || !he->h_addr_list || !he->h_addr_list[0]) 223 { 224 https_cancel(handle, "Unable to resolve hostname '%s'", handle->hostname); 225 return; 226 } 227 228 if (!he->h_addr_list[0] || (he->h_length != (handle->ipv6 ? 16 : 4)) || 229 !(ip = inetntop(handle->ipv6 ? AF_INET6 : AF_INET, he->h_addr_list[0], ipbuf, sizeof(ipbuf)))) 230 { 231 /* Illegal response -- fatal */ 232 https_cancel(handle, "Unable to resolve hostname '%s'", handle->hostname); 233 return; 234 } 235 236 /* Ok, since we got here, it seems things were actually succesfull */ 237 238 safe_strdup(handle->ip, ip); 239 240 unreal_https_initiate_connect(handle); 241 } 242 243 void unreal_https_initiate_connect(Download *handle) 244 { 245 // todo: allocate handle, select en weetikt allemaal 246 // add to some global struct linkedlist, for timeouts 247 // register in i/o 248 249 if (!handle->ip) 250 { 251 https_cancel(handle, "No IP address found to connect to"); 252 return; 253 } 254 255 handle->fd = fd_socket(handle->ipv6 ? AF_INET6 : AF_INET, SOCK_STREAM, 0, "HTTPS"); 256 if (handle->fd < 0) 257 { 258 https_cancel(handle, "Could not create socket: %s", strerror(ERRNO)); 259 return; 260 } 261 set_sock_opts(handle->fd, NULL, handle->ipv6); 262 if (!unreal_connect(handle->fd, handle->ip, handle->port, handle->ipv6)) 263 { 264 https_cancel(handle, "Could not connect: %s", strerror(ERRNO)); 265 return; 266 } 267 268 fd_setselect(handle->fd, FD_SELECT_WRITE, unreal_https_connect_handshake, handle); 269 } 270 271 // based on unreal_tls_client_handshake() 272 void unreal_https_connect_handshake(int fd, int revents, void *data) 273 { 274 Download *handle = data; 275 handle->ssl = SSL_new(https_ctx); 276 if (!handle->ssl) 277 { 278 https_cancel(handle, "Failed to setup SSL"); 279 return; 280 } 281 SSL_set_fd(handle->ssl, handle->fd); 282 SSL_set_connect_state(handle->ssl); 283 SSL_set_nonblocking(handle->ssl); 284 SSL_set_tlsext_host_name(handle->ssl, handle->hostname); 285 286 if (https_connect(handle) < 0) 287 { 288 /* Some fatal error already */ 289 https_cancel(handle, "TLS_connect() failed early"); 290 return; 291 } 292 293 /* Is now connecting... */ 294 } 295 296 SSL_CTX *https_new_ctx(void) 297 { 298 SSL_CTX *ctx_client; 299 char buf1[512], buf2[512]; 300 char *curl_ca_bundle = buf1; 301 302 SSL_load_error_strings(); 303 SSLeay_add_ssl_algorithms(); 304 305 ctx_client = SSL_CTX_new(SSLv23_client_method()); 306 if (!ctx_client) 307 return NULL; 308 #ifdef HAS_SSL_CTX_SET_MIN_PROTO_VERSION 309 SSL_CTX_set_min_proto_version(ctx_client, TLS1_2_VERSION); 310 #endif 311 SSL_CTX_set_options(ctx_client, SSL_OP_NO_SSLv2|SSL_OP_NO_SSLv3|SSL_OP_NO_TLSv1|SSL_OP_NO_TLSv1_1); 312 313 /* Verify peer certificate */ 314 snprintf(buf1, sizeof(buf1), "%s/tls/curl-ca-bundle.crt", CONFDIR); 315 if (!file_exists(buf1)) 316 { 317 snprintf(buf2, sizeof(buf2), "%s/doc/conf/tls/curl-ca-bundle.crt", BUILDDIR); 318 if (!file_exists(buf2)) 319 { 320 unreal_log(ULOG_ERROR, "url", "CA_BUNDLE_NOT_FOUND", NULL, 321 "Neither $filename1 nor $filename2 exist.\n" 322 "Cannot use built-in https client without curl-ca-bundle.crt\n", 323 log_data_string("filename1", buf1), 324 log_data_string("filename2", buf2)); 325 exit(-1); 326 } 327 curl_ca_bundle = buf2; 328 } 329 SSL_CTX_load_verify_locations(ctx_client, curl_ca_bundle, NULL); 330 SSL_CTX_set_verify(ctx_client, SSL_VERIFY_PEER, NULL); 331 332 /* Limit ciphers as well */ 333 SSL_CTX_set_cipher_list(ctx_client, UNREALIRCD_DEFAULT_CIPHERS); 334 335 return ctx_client; 336 } 337 338 // Based on unreal_tls_connect_retry 339 void https_connect_retry(int fd, int revents, void *data) 340 { 341 Download *handle = data; 342 https_connect(handle); 343 } 344 345 // Based on unreal_tls_connect() 346 int https_connect(Download *handle) 347 { 348 int ssl_err; 349 char *errstr; 350 351 if ((ssl_err = SSL_connect(handle->ssl)) <= 0) 352 { 353 ssl_err = SSL_get_error(handle->ssl, ssl_err); 354 switch(ssl_err) 355 { 356 case SSL_ERROR_SYSCALL: 357 if (ERRNO == P_EINTR || ERRNO == P_EWOULDBLOCK || ERRNO == P_EAGAIN) 358 { 359 /* Hmmm. This implementation is different than in unreal_tls_accept(). 360 * One of them must be wrong -- better check! (TODO) 361 */ 362 fd_setselect(handle->fd, FD_SELECT_READ|FD_SELECT_WRITE, https_connect_retry, handle); 363 return 0; 364 } 365 return https_fatal_tls_error(ssl_err, ERRNO, handle); 366 case SSL_ERROR_WANT_READ: 367 fd_setselect(handle->fd, FD_SELECT_READ, https_connect_retry, handle); 368 fd_setselect(handle->fd, FD_SELECT_WRITE, NULL, handle); 369 return 0; 370 case SSL_ERROR_WANT_WRITE: 371 fd_setselect(handle->fd, FD_SELECT_READ, NULL, handle); 372 fd_setselect(handle->fd, FD_SELECT_WRITE, https_connect_retry, handle); 373 return 0; 374 default: 375 return https_fatal_tls_error(ssl_err, ERRNO, handle); 376 } 377 /* NOTREACHED */ 378 return -1; 379 } 380 381 /* We are connected now. */ 382 383 if (!verify_certificate(handle->ssl, handle->hostname, &errstr)) 384 { 385 https_cancel(handle, "TLS Certificate error for server: %s", errstr); 386 return -1; 387 } 388 https_connect_send_header(handle); 389 return 1; 390 } 391 392 /** 393 * Report a fatal TLS error and terminate the download. 394 * 395 * @param ssl_error The error as from OpenSSL. 396 * @param where The location, one of the SAFE_SSL_* defines. 397 * @param my_errno A preserved value of errno to pass to ssl_error_str(). 398 * @param client The client the error is associated with. 399 */ 400 int https_fatal_tls_error(int ssl_error, int my_errno, Download *handle) 401 { 402 const char *ssl_errstr; 403 unsigned long additional_errno = ERR_get_error(); 404 char additional_info[256]; 405 const char *one, *two; 406 407 #if OPENSSL_VERSION_NUMBER >= 0x30000000L 408 /* Fetch additional error information from OpenSSL 3.0.0+ */ 409 two = ERR_reason_error_string(additional_errno); 410 if (two && *two) 411 { 412 snprintf(additional_info, sizeof(additional_info), ": %s", two); 413 } else { 414 *additional_info = '\0'; 415 } 416 #else 417 /* Fetch additional error information from OpenSSL. This is new as of Nov 2017 (4.0.16+) */ 418 one = ERR_func_error_string(additional_errno); 419 two = ERR_reason_error_string(additional_errno); 420 if (one && *one && two && *two) 421 { 422 snprintf(additional_info, sizeof(additional_info), ": %s: %s", one, two); 423 } else { 424 *additional_info = '\0'; 425 } 426 #endif 427 428 ssl_errstr = ssl_error_str(ssl_error, my_errno); 429 430 https_cancel(handle, "%s [%s]", ssl_errstr, additional_info); 431 return -1; 432 } 433 434 // copied 100% from modulemanager parse_url() 435 int url_parse(const char *url, char **hostname, int *port, char **username, char **password, char **document) 436 { 437 char *p, *p2; 438 static char hostbuf[256]; 439 static char documentbuf[512]; 440 441 *hostname = *username = *password = *document = NULL; 442 *port = 443; 443 444 if (strncmp(url, "https://", 8)) 445 return 0; 446 url += 8; /* skip over https:// part */ 447 448 p = strchr(url, '/'); 449 if (!p) 450 return 0; 451 452 strlncpy(hostbuf, url, sizeof(hostbuf), p - url); 453 454 strlcpy(documentbuf, p, sizeof(documentbuf)); 455 456 *hostname = hostbuf; 457 *document = documentbuf; 458 459 /* Actually we may still need to extract the port */ 460 p = strchr(hostbuf, '@'); 461 if (p) 462 { 463 *p++ = '\0'; 464 465 *username = hostbuf; 466 p2 = strchr(hostbuf, ':'); 467 if (p2) 468 { 469 *p2++ = '\0'; 470 *password = p2; 471 } 472 *hostname = p; 473 } 474 p = strchr(*hostname, ':'); 475 if (p) 476 { 477 *p++ = '\0'; 478 *port = atoi(p); 479 } 480 481 return 1; 482 } 483 484 void https_connect_send_header(Download *handle) 485 { 486 char buf[1024]; 487 char hostandport[512]; 488 int ssl_err; 489 char *host; 490 int port; 491 char *document; 492 493 handle->connected = 1; 494 snprintf(hostandport, sizeof(hostandport), "%s:%d", handle->hostname, handle->port); 495 496 /* Prepare the header */ 497 snprintf(buf, sizeof(buf), "GET %s HTTP/1.1\r\n" 498 "User-Agent: UnrealIRCd %s\r\n" 499 "Host: %s\r\n" 500 "Connection: close\r\n", 501 handle->document, 502 VERSIONONLY, 503 hostandport); 504 if (handle->username && handle->password) 505 { 506 char wbuf[128]; 507 char obuf[256]; 508 char header[512]; 509 510 snprintf(wbuf, sizeof(wbuf), "%s:%s", handle->username, handle->password); 511 if (b64_encode(wbuf, strlen(wbuf), obuf, sizeof(obuf)-1) > 0) 512 { 513 snprintf(header, sizeof(header), "Authorization: Basic %s\r\n", obuf); 514 strlcat(buf, header, sizeof(buf)); 515 } 516 } 517 if (handle->cachetime > 0) 518 { 519 const char *datestr = rfc2616_time(handle->cachetime); 520 if (datestr) 521 { 522 // snprintf_append... 523 snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), 524 "If-Modified-Since: %s\r\n", datestr); 525 } 526 } 527 strlcat(buf, "\r\n", sizeof(buf)); 528 529 ssl_err = SSL_write(handle->ssl, buf, strlen(buf)); 530 if (ssl_err < 0) 531 { 532 https_fatal_tls_error(ssl_err, ERRNO, handle); 533 return; 534 } 535 fd_setselect(handle->fd, FD_SELECT_WRITE, NULL, handle); 536 fd_setselect(handle->fd, FD_SELECT_READ, https_receive_response, handle); 537 } 538 539 void https_receive_response(int fd, int revents, void *data) 540 { 541 Download *handle = data; 542 int n; 543 char readbuf[2048]; 544 545 n = SSL_read(handle->ssl, readbuf, sizeof(readbuf)-1); 546 if (n == 0) 547 { 548 /* Graceful close */ 549 https_done(handle); 550 return; 551 } 552 if (n < 0) 553 { 554 int ssl_err = SSL_get_error(handle->ssl, n); 555 switch (ssl_err) 556 { 557 case SSL_ERROR_WANT_WRITE: 558 fd_setselect(fd, FD_SELECT_READ, NULL, handle); 559 fd_setselect(fd, FD_SELECT_WRITE, https_receive_response, handle); 560 return; 561 case SSL_ERROR_WANT_READ: 562 /* Wants to read more data; let it call us next time again */ 563 return; 564 case SSL_ERROR_SYSCALL: 565 case SSL_ERROR_SSL: 566 default: 567 https_fatal_tls_error(ssl_err, ERRNO, handle); 568 return; 569 } 570 return; 571 } 572 readbuf[n] = '\0'; 573 574 //fprintf(stderr, "Got: '%s'\n", readbuf); 575 576 if (!handle->got_response) 577 { 578 https_handle_response_header(handle, readbuf, n); 579 return; 580 } else 581 if (handle->got_response) 582 { 583 if (!https_handle_response_file(handle, readbuf, n)) 584 return; /* handle is already freed! */ 585 } 586 } 587 588 // Based on websocket_handle_handshake() 589 int https_handle_response_header(Download *handle, char *readbuf, int n) 590 { 591 char *key, *value; 592 int r, end_of_request; 593 char netbuf[4096], netbuf2[4096]; 594 char *lastloc = NULL; 595 int maxcopy, nprefix=0; 596 int totalsize; 597 598 /* Yeah, totally paranoid: */ 599 memset(netbuf, 0, sizeof(netbuf)); 600 memset(netbuf2, 0, sizeof(netbuf2)); 601 602 /** Frame re-assembling starts here **/ 603 *netbuf = '\0'; 604 if (handle->lefttoparse) 605 { 606 strlcpy(netbuf, handle->lefttoparse, sizeof(netbuf)); 607 nprefix = strlen(netbuf); 608 } 609 maxcopy = sizeof(netbuf) - nprefix - 1; 610 /* (Need to some manual checking here as strlen() can't be safely used 611 * on readbuf. Same is true for strlncat since it uses strlen().) 612 */ 613 if (n > maxcopy) 614 n = maxcopy; 615 if (n <= 0) 616 { 617 https_cancel(handle, "Oversized line in HTTP response"); 618 return 0; 619 } 620 memcpy(netbuf+nprefix, readbuf, n); /* SAFE: see checking above */ 621 totalsize = n + nprefix; 622 netbuf[totalsize] = '\0'; 623 memcpy(netbuf2, netbuf, totalsize+1); // copy, including the "always present \0 at the end just in case we use strstr etc". 624 safe_free(handle->lefttoparse); 625 626 /** Now step through the lines.. **/ 627 for (r = https_parse_header(netbuf, strlen(netbuf), &key, &value, &lastloc, &end_of_request); 628 r; 629 r = https_parse_header(NULL, 0, &key, &value, &lastloc, &end_of_request)) 630 { 631 // do something actually with the header here ;) 632 if (!strcasecmp(key, "RESPONSE")) 633 { 634 handle->http_status_code = atoi(value); 635 if (handle->http_status_code == 304) 636 { 637 /* 304 Not Modified: cache hit */ 638 https_done_cached(handle); 639 return 0; 640 } 641 else if ((handle->http_status_code >= 301) && (handle->http_status_code <= 308)) 642 { 643 /* Redirect */ 644 if (handle->redirects_remaining == 0) 645 { 646 https_cancel(handle, "Too many HTTP redirects (%d)", DOWNLOAD_MAX_REDIRECTS); 647 return 0; 648 } 649 /* Let it continue.. we handle it later, as we need to 650 * receive the "Location" header as well. 651 */ 652 } 653 else if (handle->http_status_code != 200) 654 { 655 /* HTTP Failure code */ 656 https_cancel(handle, "HTTP Error: %s", value); 657 return 0; 658 } 659 } else 660 if (!strcasecmp(key, "Last-Modified") && value) 661 { 662 handle->last_modified = rfc2616_time_to_unix_time(value); 663 } else 664 if (!strcasecmp(key, "Location") && value) 665 { 666 safe_strdup(handle->redirect_new_location, value); 667 } else 668 if (!strcasecmp(key, "Transfer-Encoding") && value) 669 { 670 if (value && !strcasecmp(value, "chunked")) 671 handle->transfer_encoding = TRANSFER_ENCODING_CHUNKED; 672 } 673 //fprintf(stderr, "\nHEADER '%s'\n\n", key); 674 } 675 676 if (end_of_request) 677 { 678 int remaining_bytes = 0; 679 char *nextframe; 680 681 safe_free(handle->lefttoparse); 682 handle->got_response = 1; 683 684 if (handle->http_status_code == 0) 685 { 686 https_cancel(handle, "Invalid HTTP response"); 687 return 0; 688 } 689 if (handle->http_status_code != 200) 690 { 691 if (handle->redirect_new_location) 692 { 693 https_redirect(handle); 694 return 0; /* this old request dies */ 695 } else { 696 https_cancel(handle, "HTTP Redirect encountered but no URL specified!?"); 697 return 0; 698 } 699 } 700 701 nextframe = url_find_end_of_request(netbuf2, totalsize, &remaining_bytes); 702 if (nextframe) 703 { 704 if (!https_handle_response_file(handle, nextframe, remaining_bytes)) 705 return 0; 706 } 707 } 708 709 if (lastloc) 710 { 711 /* Last line was cut somewhere, save it for next round. */ 712 safe_strdup(handle->lefttoparse, lastloc); 713 } 714 715 return 1; 716 } 717 718 int https_handle_response_file(Download *handle, char *readbuf, int pktsize) 719 { 720 char *buf; 721 long long n; 722 char *free_this_buffer = NULL; 723 724 // TODO we fail to check for write errors ;) 725 // TODO: Makes sense to track if we got everything? :D 726 727 if (handle->transfer_encoding == TRANSFER_ENCODING_NONE) 728 { 729 /* Ohh.. so easy! */ 730 fwrite(readbuf, 1, pktsize, handle->file_fd); 731 return 1; 732 } 733 734 /* Fill 'buf' nd set 'buflen' with what we had + what we have now. 735 * Makes things easy. 736 */ 737 if (handle->lefttoparse) 738 { 739 n = handle->lefttoparselen + pktsize; 740 free_this_buffer = buf = safe_alloc(n); 741 memcpy(buf, handle->lefttoparse, handle->lefttoparselen); 742 memcpy(buf+handle->lefttoparselen, readbuf, pktsize); 743 safe_free(handle->lefttoparse); 744 handle->lefttoparselen = 0; 745 } else { 746 n = pktsize; 747 buf = readbuf; 748 } 749 750 /* Chunked transfers.. yayyyy.. */ 751 while (n > 0) 752 { 753 if (handle->chunk_remaining > 0) 754 { 755 /* Eat it */ 756 int eat = MIN(handle->chunk_remaining, n); 757 fwrite(buf, 1, eat, handle->file_fd); 758 n -= eat; 759 buf += eat; 760 handle->chunk_remaining -= eat; 761 } else 762 { 763 int gotlf = 0; 764 int i; 765 766 /* First check if it is a (trailing) empty line, 767 * eg from a previous chunk. Skip over. 768 */ 769 if ((n >= 2) && !strncmp(buf, "\r\n", 2)) 770 { 771 buf += 2; 772 n -= 2; 773 } else 774 if ((n >= 1) && !strncmp(buf, "\n", 1)) 775 { 776 buf++; 777 n--; 778 } 779 780 /* Now we are (possibly) at the chunk size line, 781 * this is or example '7f' + newline. 782 * So first, check if we have a newline at all. 783 */ 784 for (i=0; i < n; i++) 785 { 786 if (buf[i] == '\n') 787 { 788 gotlf = 1; 789 break; 790 } 791 } 792 if (!gotlf) 793 { 794 /* The line telling us the chunk size is incomplete, 795 * as it does not contain an \n. Wait for more data 796 * from the network socket. 797 */ 798 if (n > 0) 799 { 800 /* Store what we have first.. */ 801 handle->lefttoparselen = n; 802 handle->lefttoparse = safe_alloc(n); 803 memcpy(handle->lefttoparse, buf, n); 804 } 805 safe_free(free_this_buffer); 806 return 1; /* WE WANT MORE! */ 807 } 808 buf[i] = '\0'; /* cut at LF */ 809 i++; /* point to next data */ 810 handle->chunk_remaining = strtol(buf, NULL, 16); 811 if (handle->chunk_remaining < 0) 812 { 813 https_cancel(handle, "Negative chunk encountered (%ld)", handle->chunk_remaining); 814 safe_free(free_this_buffer); 815 return 0; 816 } 817 if (handle->chunk_remaining == 0) 818 { 819 https_done(handle); 820 safe_free(free_this_buffer); 821 return 0; 822 } 823 buf += i; 824 n -= i; 825 } 826 } 827 828 safe_free(free_this_buffer); 829 return 1; 830 } 831 832 void https_done(Download *handle) 833 { 834 char *url = handle->redirect_original_url ? handle->redirect_original_url : handle->url; 835 836 fclose(handle->file_fd); 837 handle->file_fd = NULL; 838 839 if (!handle->callback) 840 ; /* No special action, request was cancelled */ 841 else if (!handle->got_response) 842 handle->callback(url, NULL, "HTTPS response not received", 0, handle->callback_data); 843 else 844 { 845 if (handle->last_modified > 0) 846 unreal_setfilemodtime(handle->filename, handle->last_modified); 847 handle->callback(url, handle->filename, NULL, 0, handle->callback_data); 848 } 849 url_free_handle(handle); 850 return; 851 } 852 853 void https_done_cached(Download *handle) 854 { 855 char *url = handle->redirect_original_url ? handle->redirect_original_url : handle->url; 856 857 fclose(handle->file_fd); 858 handle->file_fd = NULL; 859 if (handle->callback) 860 handle->callback(url, NULL, NULL, 1, handle->callback_data); 861 url_free_handle(handle); 862 } 863 864 void https_redirect(Download *handle) 865 { 866 if (handle->redirects_remaining == 0) 867 { 868 https_cancel(handle, "Too many HTTP redirects (%d)", DOWNLOAD_MAX_REDIRECTS); 869 return; 870 } 871 handle->redirects_remaining--; 872 873 if (handle->callback) 874 { 875 /* If still an outstanding request (not cancelled), follow the redirect.. */ 876 download_file_async(handle->redirect_new_location, handle->cachetime, handle->callback, handle->callback_data, 877 handle->url, handle->redirects_remaining); 878 } 879 /* Don't call the hook, just free this, the new redirect from above will call the hook later */ 880 url_free_handle(handle); 881 } 882 883 /** Helper function to parse the HTTP header consisting of multiple 'Key: value' pairs */ 884 int https_parse_header(char *buffer, int len, char **key, char **value, char **lastloc, int *end_of_request) 885 { 886 static char buf[4096], *nextptr; 887 char *p; 888 char *k = NULL, *v = NULL; 889 int foundlf = 0; 890 891 if (buffer) 892 { 893 /* Initialize */ 894 if (len > sizeof(buf) - 1) 895 len = sizeof(buf) - 1; 896 897 memcpy(buf, buffer, len); 898 buf[len] = '\0'; 899 nextptr = buf; 900 } 901 902 *end_of_request = 0; 903 904 p = nextptr; 905 906 if (!p) 907 { 908 *key = *value = NULL; 909 return 0; /* done processing data */ 910 } 911 912 if (!strncmp(p, "\n", 1) || !strncmp(p, "\r\n", 2)) 913 { 914 *key = *value = NULL; 915 *end_of_request = 1; 916 // new compared to websocket handling: 917 if (*p == '\n') 918 *lastloc = p + 1; 919 else 920 *lastloc = p + 2; 921 return 0; 922 } 923 924 /* Note: p *could* point to the NUL byte ('\0') */ 925 926 /* Special handling for response line itself. */ 927 if (!strncmp(p, "HTTP/1", 6) && (strlen(p)>=13)) 928 { 929 k = "RESPONSE"; 930 p += 9; 931 v = p; /* SET VALUE */ 932 nextptr = NULL; /* set to "we are done" in case next for loop fails */ 933 for (; *p; p++) 934 { 935 if (*p == '\r') 936 { 937 *p = '\0'; /* eat silently, but don't consider EOL */ 938 } 939 else if (*p == '\n') 940 { 941 *p = '\0'; 942 nextptr = p+1; /* safe, there is data or at least a \0 there */ 943 break; 944 } 945 } 946 *key = k; 947 *value = v; 948 return 1; 949 } 950 951 /* Header parsing starts here. 952 * Example line "Host: www.unrealircd.org" 953 */ 954 k = p; /* SET KEY */ 955 956 /* First check if the line contains a terminating \n. If not, don't process it 957 * as it may have been a cut header. 958 */ 959 for (; *p; p++) 960 { 961 if (*p == '\n') 962 { 963 foundlf = 1; 964 break; 965 } 966 } 967 968 if (!foundlf) 969 { 970 *key = *value = NULL; 971 *lastloc = k; 972 return 0; 973 } 974 975 p = k; 976 977 for (; *p; p++) 978 { 979 if ((*p == '\n') || (*p == '\r')) 980 { 981 /* Reached EOL but 'value' not found */ 982 *p = '\0'; 983 break; 984 } 985 if (*p == ':') 986 { 987 *p++ = '\0'; 988 if (*p++ != ' ') 989 break; /* missing mandatory space after ':' */ 990 991 v = p; /* SET VALUE */ 992 nextptr = NULL; /* set to "we are done" in case next for loop fails */ 993 for (; *p; p++) 994 { 995 if (*p == '\r') 996 { 997 *p = '\0'; /* eat silently, but don't consider EOL */ 998 } 999 else if (*p == '\n') 1000 { 1001 *p = '\0'; 1002 nextptr = p+1; /* safe, there is data or at least a \0 there */ 1003 break; 1004 } 1005 } 1006 /* A key-value pair was succesfully parsed, return it */ 1007 *key = k; 1008 *value = v; 1009 return 1; 1010 } 1011 } 1012 1013 /* Fatal parse error */ 1014 *key = *value = NULL; 1015 return 0; 1016 } 1017 1018 /** Check if there is any data at the end of the request */ 1019 char *url_find_end_of_request(char *header, int totalsize, int *remaining_bytes) 1020 { 1021 char *nextframe1; 1022 char *nextframe2; 1023 char *nextframe = NULL; 1024 1025 // find first occurance, yeah this is just stupid, but it works. 1026 nextframe1 = strstr(header, "\r\n\r\n"); // = +4 1027 nextframe2 = strstr(header, "\n\n"); // = +2 1028 if (nextframe1 && nextframe2) 1029 { 1030 if (nextframe1 < nextframe2) 1031 { 1032 nextframe = nextframe1 + 4; 1033 } else { 1034 nextframe = nextframe2 + 2; 1035 } 1036 } else 1037 if (nextframe1) 1038 { 1039 nextframe = nextframe1 + 4; 1040 } else 1041 if (nextframe2) 1042 { 1043 nextframe = nextframe2 + 2; 1044 } 1045 if (nextframe) 1046 { 1047 *remaining_bytes = totalsize - (nextframe - header); 1048 if (*remaining_bytes > 0) 1049 return nextframe; 1050 } 1051 return NULL; 1052 } 1053 1054 /* Handle timeouts. */ 1055 EVENT(url_socket_timeout) 1056 { 1057 Download *d, *d_next; 1058 for (d = downloads; d; d = d_next) 1059 { 1060 d_next = d->next; 1061 if (d->dns_refcnt) 1062 continue; /* can't touch this... */ 1063 if (!d->connected && (TStime() - d->download_started > DOWNLOAD_CONNECT_TIMEOUT)) 1064 { 1065 https_cancel(d, "Connect or DNS timeout after %ld seconds", (long)DOWNLOAD_CONNECT_TIMEOUT); 1066 continue; 1067 } 1068 if (d->connected && (TStime() - d->download_started > DOWNLOAD_TRANSFER_TIMEOUT)) 1069 { 1070 https_cancel(d, "Download timeout after %ld seconds", (long)DOWNLOAD_TRANSFER_TIMEOUT); 1071 continue; 1072 } 1073 } 1074 } 1075 1076 void url_init(void) 1077 { 1078 https_ctx = https_new_ctx(); 1079 if (!https_ctx) 1080 { 1081 unreal_log(ULOG_ERROR, "url", "HTTPS_NEW_CTX_FAILED", NULL, 1082 "Unable to initialize SSL context"); 1083 exit(-1); 1084 } 1085 EventAdd(NULL, "url_socket_timeout", url_socket_timeout, NULL, 500, 0); 1086 }