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