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