LibVNCServer/LibVNCClient
 All Data Structures Files Functions Variables Typedefs Enumerations Enumerator Macros Groups Pages
httpd.c
Go to the documentation of this file.
1 /*
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 
77 static void httpProcessInput(rfbScreenInfoPtr screen);
78 static rfbBool compareAndSkip(char **ptr, const char *str);
79 static rfbBool parseParams(const char *request, char *result, int max_bytes);
80 static rfbBool validateString(char *str);
81 
82 #define BUF_SIZE 32768
83 
84 static char buf[BUF_SIZE];
85 static size_t buf_filled=0;
86 
87 /*
88  * httpInitSockets sets up the TCP socket to listen for HTTP connections.
89  */
90 
91 void
92 rfbHttpInitSockets(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 
129 void 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 
154 void
155 rfbHttpCheckFds(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 
245 static void
246 httpCloseSock(rfbScreenInfoPtr rfbScreen)
247 {
248  close(rfbScreen->httpSock);
249  rfbScreen->httpSock = -1;
250  buf_filled = 0;
251 }
252 
253 static rfbClientRec cl;
254 
255 /*
256  * httpProcessInput is called when input is received on the HTTP socket.
257  */
258 
259 static void
260 httpProcessInput(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");
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");
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");
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 
541 static rfbBool
542 compareAndSkip(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 
557 static rfbBool
558 parseParams(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 
628 static rfbBool
629 validateString(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