1 /* 2 * httpd.c - a simple HTTP server 3 */ 4 5 /* 6 * Copyright (C) 2011-2012 Christian Beier <dontmind (at) freeshell.org> 7 * Copyright (C) 2002 RealVNC Ltd. 8 * Copyright (C) 1999 AT&T Laboratories Cambridge. All Rights Reserved. 9 * 10 * This is free software; you can redistribute it and/or modify 11 * it under the terms of the GNU General Public License as published by 12 * the Free Software Foundation; either version 2 of the License, or 13 * (at your option) any later version. 14 * 15 * This software is distributed in the hope that it will be useful, 16 * but WITHOUT ANY WARRANTY; without even the implied warranty of 17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 * GNU General Public License for more details. 19 * 20 * You should have received a copy of the GNU General Public License 21 * along with this software; if not, write to the Free Software 22 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, 23 * USA. 24 */ 25 26 #ifdef __STRICT_ANSI__ 27 #define _BSD_SOURCE 28 #define _POSIX_SOURCE 29 #endif 30 31 #include <rfb/rfb.h> 32 33 #include <ctype.h> 34 #ifdef LIBVNCSERVER_HAVE_UNISTD_H 35 #include <unistd.h> 36 #endif 37 #ifdef LIBVNCSERVER_HAVE_SYS_TYPES_H 38 #include <sys/types.h> 39 #endif 40 #ifdef LIBVNCSERVER_HAVE_FCNTL_H 41 #include <fcntl.h> 42 #endif 43 #include <errno.h> 44 45 #ifdef WIN32 46 #include <io.h> 47 #include <winsock2.h> 48 #include <ws2tcpip.h> 49 #define close closesocket 50 #if defined(_MSC_VER) 51 #include <BaseTsd.h> /* For the missing ssize_t */ 52 #define ssize_t SSIZE_T 53 #define read _read /* Prevent POSIX deprecation warnings */ 54 #endif 55 #else 56 #ifdef LIBVNCSERVER_HAVE_SYS_TIME_H 57 #include <sys/time.h> 58 #endif 59 #ifdef LIBVNCSERVER_HAVE_SYS_SOCKET_H 60 #include <sys/socket.h> 61 #endif 62 #ifdef LIBVNCSERVER_HAVE_NETINET_IN_H 63 #include <netinet/in.h> 64 #include <netinet/tcp.h> 65 #include <netdb.h> 66 #include <arpa/inet.h> 67 #endif 68 #include <pwd.h> 69 #endif 70 71 #ifdef USE_LIBWRAP 72 #include <tcpd.h> 73 #endif 74 75 76 #define NOT_FOUND_STR "HTTP/1.0 404 Not found\r\nConnection: close\r\n\r\n" \ 77 "<HEAD><TITLE>File Not Found</TITLE></HEAD>\n" \ 78 "<BODY><H1>File Not Found</H1></BODY>\n" 79 80 #define INVALID_REQUEST_STR "HTTP/1.0 400 Invalid Request\r\nConnection: close\r\n\r\n" \ 81 "<HEAD><TITLE>Invalid Request</TITLE></HEAD>\n" \ 82 "<BODY><H1>Invalid request</H1></BODY>\n" 83 84 #define OK_STR "HTTP/1.0 200 OK\r\nConnection: close\r\n\r\n" 85 #define OK_STR_HTML "HTTP/1.0 200 OK\r\nConnection: close\r\nContent-Type: text/html\r\n\r\n" 86 87 88 89 static void httpProcessInput(rfbScreenInfoPtr screen); 90 static rfbBool compareAndSkip(char **ptr, const char *str); 91 static rfbBool parseParams(const char *request, char *result, int max_bytes); 92 static rfbBool validateString(char *str); 93 94 #define BUF_SIZE 32768 95 96 static char buf[BUF_SIZE]; 97 static size_t buf_filled=0; 98 99 /* 100 * httpInitSockets sets up the TCP socket to listen for HTTP connections. 101 */ 102 103 void 104 rfbHttpInitSockets(rfbScreenInfoPtr rfbScreen) 105 { 106 if (rfbScreen->httpInitDone) 107 return; 108 109 rfbScreen->httpInitDone = TRUE; 110 111 if (!rfbScreen->httpDir) 112 return; 113 114 if (rfbScreen->httpPort == 0) { 115 rfbScreen->httpPort = rfbScreen->port-100; 116 } 117 118 if ((rfbScreen->httpListenSock = 119 rfbListenOnTCPPort(rfbScreen->httpPort, rfbScreen->listenInterface)) < 0) { 120 rfbLogPerror("ListenOnTCPPort"); 121 return; 122 } 123 rfbLog("Listening for HTTP connections on TCP port %d\n", rfbScreen->httpPort); 124 rfbLog(" URL http://%s:%d\n",rfbScreen->thisHost,rfbScreen->httpPort); 125 126 #ifdef LIBVNCSERVER_IPv6 127 if (rfbScreen->http6Port == 0) { 128 rfbScreen->http6Port = rfbScreen->ipv6port-100; 129 } 130 131 if ((rfbScreen->httpListen6Sock 132 = rfbListenOnTCP6Port(rfbScreen->http6Port, rfbScreen->listen6Interface)) < 0) { 133 /* ListenOnTCP6Port has its own detailed error printout */ 134 return; 135 } 136 rfbLog("Listening for HTTP connections on TCP6 port %d\n", rfbScreen->http6Port); 137 rfbLog(" URL http://%s:%d\n",rfbScreen->thisHost,rfbScreen->http6Port); 138 #endif 139 } 140 141 void rfbHttpShutdownSockets(rfbScreenInfoPtr rfbScreen) { 142 if(rfbScreen->httpSock>-1) { 143 close(rfbScreen->httpSock); 144 FD_CLR(rfbScreen->httpSock,&rfbScreen->allFds); 145 rfbScreen->httpSock=-1; 146 } 147 148 if(rfbScreen->httpListenSock>-1) { 149 close(rfbScreen->httpListenSock); 150 FD_CLR(rfbScreen->httpListenSock,&rfbScreen->allFds); 151 rfbScreen->httpListenSock=-1; 152 } 153 154 if(rfbScreen->httpListen6Sock>-1) { 155 close(rfbScreen->httpListen6Sock); 156 FD_CLR(rfbScreen->httpListen6Sock,&rfbScreen->allFds); 157 rfbScreen->httpListen6Sock=-1; 158 } 159 } 160 161 /* 162 * httpCheckFds is called from ProcessInputEvents to check for input on the 163 * HTTP socket(s). If there is input to process, httpProcessInput is called. 164 */ 165 166 void 167 rfbHttpCheckFds(rfbScreenInfoPtr rfbScreen) 168 { 169 int nfds; 170 fd_set fds; 171 struct timeval tv; 172 #ifdef LIBVNCSERVER_IPv6 173 struct sockaddr_storage addr; 174 #else 175 struct sockaddr_in addr; 176 #endif 177 socklen_t addrlen = sizeof(addr); 178 179 if (!rfbScreen->httpDir) 180 return; 181 182 if (rfbScreen->httpListenSock < 0) 183 return; 184 185 FD_ZERO(&fds); 186 FD_SET(rfbScreen->httpListenSock, &fds); 187 if (rfbScreen->httpListen6Sock >= 0) { 188 FD_SET(rfbScreen->httpListen6Sock, &fds); 189 } 190 if (rfbScreen->httpSock >= 0) { 191 FD_SET(rfbScreen->httpSock, &fds); 192 } 193 tv.tv_sec = 0; 194 tv.tv_usec = 0; 195 nfds = select(max(rfbScreen->httpListen6Sock, max(rfbScreen->httpSock,rfbScreen->httpListenSock)) + 1, &fds, NULL, NULL, &tv); 196 if (nfds == 0) { 197 return; 198 } 199 if (nfds < 0) { 200 #ifdef WIN32 201 errno = WSAGetLastError(); 202 #endif 203 if (errno != EINTR) 204 rfbLogPerror("httpCheckFds: select"); 205 return; 206 } 207 208 if ((rfbScreen->httpSock >= 0) && FD_ISSET(rfbScreen->httpSock, &fds)) { 209 httpProcessInput(rfbScreen); 210 } 211 212 if (FD_ISSET(rfbScreen->httpListenSock, &fds) || FD_ISSET(rfbScreen->httpListen6Sock, &fds)) { 213 if (rfbScreen->httpSock >= 0) close(rfbScreen->httpSock); 214 215 if(FD_ISSET(rfbScreen->httpListenSock, &fds)) { 216 if ((rfbScreen->httpSock = accept(rfbScreen->httpListenSock, (struct sockaddr *)&addr, &addrlen)) < 0) { 217 rfbLogPerror("httpCheckFds: accept"); 218 return; 219 } 220 } 221 else if(FD_ISSET(rfbScreen->httpListen6Sock, &fds)) { 222 if ((rfbScreen->httpSock = accept(rfbScreen->httpListen6Sock, (struct sockaddr *)&addr, &addrlen)) < 0) { 223 rfbLogPerror("httpCheckFds: accept"); 224 return; 225 } 226 } 227 228 #ifdef USE_LIBWRAP 229 char host[1024]; 230 #ifdef LIBVNCSERVER_IPv6 231 if(getnameinfo((struct sockaddr*)&addr, addrlen, host, sizeof(host), NULL, 0, NI_NUMERICHOST) != 0) { 232 rfbLogPerror("httpCheckFds: error in getnameinfo"); 233 host[0] = '\0'; 234 } 235 #else 236 memcpy(host, inet_ntoa(addr.sin_addr), sizeof(host)); 237 #endif 238 if(!hosts_ctl("vnc",STRING_UNKNOWN, host, 239 STRING_UNKNOWN)) { 240 rfbLog("Rejected HTTP connection from client %s\n", 241 host); 242 close(rfbScreen->httpSock); 243 rfbScreen->httpSock=-1; 244 return; 245 } 246 #endif 247 if(!rfbSetNonBlocking(rfbScreen->httpSock)) { 248 close(rfbScreen->httpSock); 249 rfbScreen->httpSock=-1; 250 return; 251 } 252 /*AddEnabledDevice(httpSock);*/ 253 } 254 } 255 256 257 static void 258 httpCloseSock(rfbScreenInfoPtr rfbScreen) 259 { 260 close(rfbScreen->httpSock); 261 rfbScreen->httpSock = -1; 262 buf_filled = 0; 263 } 264 265 static rfbClientRec cl; 266 267 /* 268 * httpProcessInput is called when input is received on the HTTP socket. 269 */ 270 271 static void 272 httpProcessInput(rfbScreenInfoPtr rfbScreen) 273 { 274 #ifdef LIBVNCSERVER_IPv6 275 struct sockaddr_storage addr; 276 #else 277 struct sockaddr_in addr; 278 #endif 279 socklen_t addrlen = sizeof(addr); 280 char fullFname[512]; 281 char params[1024]; 282 char *ptr; 283 char *fname; 284 unsigned int maxFnameLen; 285 FILE* fd; 286 rfbBool performSubstitutions = FALSE; 287 char str[256+32]; 288 #ifndef WIN32 289 char* user=getenv("USER"); 290 #endif 291 292 cl.sock=rfbScreen->httpSock; 293 294 if (strlen(rfbScreen->httpDir) > 255) { 295 rfbErr("-httpd directory too long\n"); 296 httpCloseSock(rfbScreen); 297 return; 298 } 299 strcpy(fullFname, rfbScreen->httpDir); 300 fname = &fullFname[strlen(fullFname)]; 301 maxFnameLen = 511 - strlen(fullFname); 302 303 buf_filled=0; 304 305 /* Read data from the HTTP client until we get a complete request. */ 306 while (1) { 307 ssize_t got; 308 309 if (buf_filled > sizeof (buf)) { 310 rfbErr("httpProcessInput: HTTP request is too long\n"); 311 httpCloseSock(rfbScreen); 312 return; 313 } 314 315 got = read (rfbScreen->httpSock, buf + buf_filled, 316 sizeof (buf) - buf_filled - 1); 317 318 if (got <= 0) { 319 if (got == 0) { 320 rfbErr("httpd: premature connection close\n"); 321 } else { 322 #ifdef WIN32 323 errno=WSAGetLastError(); 324 #endif 325 if (errno == EAGAIN) { 326 return; 327 } 328 rfbLogPerror("httpProcessInput: read"); 329 } 330 httpCloseSock(rfbScreen); 331 return; 332 } 333 334 buf_filled += got; 335 buf[buf_filled] = '\0'; 336 337 /* Is it complete yet (is there a blank line)? */ 338 if (strstr (buf, "\r\r") || strstr (buf, "\n\n") || 339 strstr (buf, "\r\n\r\n") || strstr (buf, "\n\r\n\r")) 340 break; 341 } 342 343 344 /* Process the request. */ 345 if(rfbScreen->httpEnableProxyConnect) { 346 const static char* PROXY_OK_STR = "HTTP/1.0 200 OK\r\nContent-Type: octet-stream\r\nPragma: no-cache\r\n\r\n"; 347 if(!strncmp(buf, "CONNECT ", 8)) { 348 if(atoi(strchr(buf, ':')+1)!=rfbScreen->port) { 349 rfbErr("httpd: CONNECT format invalid.\n"); 350 rfbWriteExact(&cl,INVALID_REQUEST_STR, strlen(INVALID_REQUEST_STR)); 351 httpCloseSock(rfbScreen); 352 return; 353 } 354 /* proxy connection */ 355 rfbLog("httpd: client asked for CONNECT\n"); 356 rfbWriteExact(&cl,PROXY_OK_STR,strlen(PROXY_OK_STR)); 357 rfbNewClientConnection(rfbScreen,rfbScreen->httpSock); 358 rfbScreen->httpSock = -1; 359 return; 360 } 361 if (!strncmp(buf, "GET ",4) && !strncmp(strchr(buf,'/'),"/proxied.connection HTTP/1.", 27)) { 362 /* proxy connection */ 363 rfbLog("httpd: client asked for /proxied.connection\n"); 364 rfbWriteExact(&cl,PROXY_OK_STR,strlen(PROXY_OK_STR)); 365 rfbNewClientConnection(rfbScreen,rfbScreen->httpSock); 366 rfbScreen->httpSock = -1; 367 return; 368 } 369 } 370 371 if (strncmp(buf, "GET ", 4)) { 372 rfbErr("httpd: no GET line\n"); 373 httpCloseSock(rfbScreen); 374 return; 375 } else { 376 /* Only use the first line. */ 377 buf[strcspn(buf, "\n\r")] = '\0'; 378 } 379 380 if (strlen(buf) > maxFnameLen) { 381 rfbErr("httpd: GET line too long\n"); 382 httpCloseSock(rfbScreen); 383 return; 384 } 385 386 if (sscanf(buf, "GET %s HTTP/1.", fname) != 1) { 387 rfbErr("httpd: couldn't parse GET line\n"); 388 httpCloseSock(rfbScreen); 389 return; 390 } 391 392 if (fname[0] != '/') { 393 rfbErr("httpd: filename didn't begin with '/'\n"); 394 rfbWriteExact(&cl, NOT_FOUND_STR, strlen(NOT_FOUND_STR)); 395 httpCloseSock(rfbScreen); 396 return; 397 } 398 399 400 getpeername(rfbScreen->httpSock, (struct sockaddr *)&addr, &addrlen); 401 #ifdef LIBVNCSERVER_IPv6 402 { 403 char host[1024]; 404 if(getnameinfo((struct sockaddr*)&addr, addrlen, host, sizeof(host), NULL, 0, NI_NUMERICHOST) != 0) { 405 rfbLogPerror("httpProcessInput: error in getnameinfo"); 406 } 407 rfbLog("httpd: get '%s' for %s\n", fname+1, host); 408 } 409 #else 410 rfbLog("httpd: get '%s' for %s\n", fname+1, 411 inet_ntoa(addr.sin_addr)); 412 #endif 413 414 /* Extract parameters from the URL string if necessary */ 415 416 params[0] = '\0'; 417 ptr = strchr(fname, '?'); 418 if (ptr != NULL) { 419 *ptr = '\0'; 420 if (!parseParams(&ptr[1], params, 1024)) { 421 params[0] = '\0'; 422 rfbErr("httpd: bad parameters in the URL\n"); 423 } 424 } 425 426 427 /* If we were asked for '/', actually read the file index.vnc */ 428 429 if (strcmp(fname, "/") == 0) { 430 strcpy(fname, "/index.vnc"); 431 rfbLog("httpd: defaulting to '%s'\n", fname+1); 432 } 433 434 /* Substitutions are performed on files ending .vnc */ 435 436 if (strlen(fname) >= 4 && strcmp(&fname[strlen(fname)-4], ".vnc") == 0) { 437 performSubstitutions = TRUE; 438 } 439 440 /* Open the file */ 441 442 if ((fd = fopen(fullFname, "r")) == 0) { 443 rfbLogPerror("httpProcessInput: open"); 444 rfbWriteExact(&cl, NOT_FOUND_STR, strlen(NOT_FOUND_STR)); 445 httpCloseSock(rfbScreen); 446 return; 447 } 448 449 if(performSubstitutions) /* is the 'index.vnc' file */ 450 rfbWriteExact(&cl, OK_STR_HTML, strlen(OK_STR_HTML)); 451 else 452 rfbWriteExact(&cl, OK_STR, strlen(OK_STR)); 453 454 while (1) { 455 int n = fread(buf, 1, BUF_SIZE-1, fd); 456 if (n < 0) { 457 rfbLogPerror("httpProcessInput: read"); 458 fclose(fd); 459 httpCloseSock(rfbScreen); 460 return; 461 } 462 463 if (n == 0) 464 break; 465 466 if (performSubstitutions) { 467 468 /* Substitute $WIDTH, $HEIGHT, etc with the appropriate values. 469 This won't quite work properly if the .vnc file is longer than 470 BUF_SIZE, but it's reasonable to assume that .vnc files will 471 always be short. */ 472 473 char *ptr = buf; 474 char *dollar; 475 buf[n] = 0; /* make sure it's null-terminated */ 476 477 while ((dollar = strchr(ptr, '$'))!=NULL) { 478 rfbWriteExact(&cl, ptr, (dollar - ptr)); 479 480 ptr = dollar; 481 482 if (compareAndSkip(&ptr, "$WIDTH")) { 483 484 sprintf(str, "%d", rfbScreen->width); 485 rfbWriteExact(&cl, str, strlen(str)); 486 487 } else if (compareAndSkip(&ptr, "$HEIGHT")) { 488 489 sprintf(str, "%d", rfbScreen->height); 490 rfbWriteExact(&cl, str, strlen(str)); 491 492 } else if (compareAndSkip(&ptr, "$APPLETWIDTH")) { 493 494 sprintf(str, "%d", rfbScreen->width); 495 rfbWriteExact(&cl, str, strlen(str)); 496 497 } else if (compareAndSkip(&ptr, "$APPLETHEIGHT")) { 498 499 sprintf(str, "%d", rfbScreen->height + 32); 500 rfbWriteExact(&cl, str, strlen(str)); 501 502 } else if (compareAndSkip(&ptr, "$PORT")) { 503 504 sprintf(str, "%d", rfbScreen->port); 505 rfbWriteExact(&cl, str, strlen(str)); 506 507 } else if (compareAndSkip(&ptr, "$DESKTOP")) { 508 509 rfbWriteExact(&cl, rfbScreen->desktopName, strlen(rfbScreen->desktopName)); 510 511 } else if (compareAndSkip(&ptr, "$DISPLAY")) { 512 513 sprintf(str, "%s:%d", rfbScreen->thisHost, rfbScreen->port-5900); 514 rfbWriteExact(&cl, str, strlen(str)); 515 516 } else if (compareAndSkip(&ptr, "$USER")) { 517 #ifndef WIN32 518 if (user) { 519 rfbWriteExact(&cl, user, 520 strlen(user)); 521 } else 522 #endif 523 rfbWriteExact(&cl, "?", 1); 524 } else if (compareAndSkip(&ptr, "$PARAMS")) { 525 if (params[0] != '\0') 526 rfbWriteExact(&cl, params, strlen(params)); 527 } else { 528 if (!compareAndSkip(&ptr, "$$")) 529 ptr++; 530 531 if (rfbWriteExact(&cl, "$", 1) < 0) { 532 fclose(fd); 533 httpCloseSock(rfbScreen); 534 return; 535 } 536 } 537 } 538 if (rfbWriteExact(&cl, ptr, (&buf[n] - ptr)) < 0) 539 break; 540 541 } else { 542 543 /* For files not ending .vnc, just write out the buffer */ 544 545 if (rfbWriteExact(&cl, buf, n) < 0) 546 break; 547 } 548 } 549 550 fclose(fd); 551 httpCloseSock(rfbScreen); 552 } 553 554 555 static rfbBool 556 compareAndSkip(char **ptr, const char *str) 557 { 558 if (strncmp(*ptr, str, strlen(str)) == 0) { 559 *ptr += strlen(str); 560 return TRUE; 561 } 562 563 return FALSE; 564 } 565 566 /* 567 * Parse the request tail after the '?' character, and format a sequence 568 * of <param> tags for inclusion into an HTML page with embedded applet. 569 */ 570 571 static rfbBool 572 parseParams(const char *request, char *result, int max_bytes) 573 { 574 char param_request[128]; 575 char param_formatted[196]; 576 const char *tail; 577 char *delim_ptr; 578 char *value_str; 579 int cur_bytes, len; 580 581 result[0] = '\0'; 582 cur_bytes = 0; 583 584 tail = request; 585 for (;;) { 586 /* Copy individual "name=value" string into a buffer */ 587 delim_ptr = strchr((char *)tail, '&'); 588 if (delim_ptr == NULL) { 589 if (strlen(tail) >= sizeof(param_request)) { 590 return FALSE; 591 } 592 strcpy(param_request, tail); 593 } else { 594 len = delim_ptr - tail; 595 if (len >= sizeof(param_request)) { 596 return FALSE; 597 } 598 memcpy(param_request, tail, len); 599 param_request[len] = '\0'; 600 } 601 602 /* Split the request into parameter name and value */ 603 value_str = strchr(¶m_request[1], '='); 604 if (value_str == NULL) { 605 return FALSE; 606 } 607 *value_str++ = '\0'; 608 if (strlen(value_str) == 0) { 609 return FALSE; 610 } 611 612 /* Validate both parameter name and value */ 613 if (!validateString(param_request) || !validateString(value_str)) { 614 return FALSE; 615 } 616 617 /* Prepare HTML-formatted representation of the name=value pair */ 618 len = sprintf(param_formatted, 619 "<PARAM NAME=\"%s\" VALUE=\"%s\">\n", 620 param_request, value_str); 621 if (cur_bytes + len + 1 > max_bytes) { 622 return FALSE; 623 } 624 strcat(result, param_formatted); 625 cur_bytes += len; 626 627 /* Go to the next parameter */ 628 if (delim_ptr == NULL) { 629 break; 630 } 631 tail = delim_ptr + 1; 632 } 633 return TRUE; 634 } 635 636 /* 637 * Check if the string consists only of alphanumeric characters, '+' 638 * signs, underscores, dots, colons and square brackets. 639 * Replace all '+' signs with spaces. 640 */ 641 642 static rfbBool 643 validateString(char *str) 644 { 645 char *ptr; 646 647 for (ptr = str; *ptr != '\0'; ptr++) { 648 if (!isalnum(*ptr) && *ptr != '_' && *ptr != '.' 649 && *ptr != ':' && *ptr != '[' && *ptr != ']' ) { 650 if (*ptr == '+') { 651 *ptr = ' '; 652 } else { 653 return FALSE; 654 } 655 } 656 } 657 return TRUE; 658 } 659 660