• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 #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
rfbHttpInitSockets(rfbScreenInfoPtr rfbScreen)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 
rfbHttpShutdownSockets(rfbScreenInfoPtr rfbScreen)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
rfbHttpCheckFds(rfbScreenInfoPtr rfbScreen)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
httpCloseSock(rfbScreenInfoPtr rfbScreen)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
httpProcessInput(rfbScreenInfoPtr rfbScreen)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
compareAndSkip(char ** ptr,const char * str)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
parseParams(const char * request,char * result,int max_bytes)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(&param_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
validateString(char * str)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