1 /*
2  * $LynxId: LYCgi.c,v 1.72 2018/03/18 18:56:05 tom Exp $
3  *                   Lynx CGI support                              LYCgi.c
4  *                   ================
5  *
6  * Authors
7  *          GL      George Lindholm <George.Lindholm@ubc.ca>
8  *
9  * History
10  *      15 Jun 95   Created as way to provide a lynx based service with
11  *                  dynamic pages without the need for a http daemon.  GL
12  *      27 Jun 95   Added <index> (command line) support.  Various cleanup
13  *                  and bug fixes. GL
14  *	04 Sep 97   Added support for PATH_INFO scripts.  JKT
15  *
16  * Bugs
17  *      If the called scripts aborts before sending the mime headers then
18  *      lynx hangs.
19  *
20  *      Should do something about SIGPIPE, (but then it should never happen)
21  *
22  *      No support for redirection.  Or mime-types.
23  *
24  *      Should try and parse for a HTTP 1.1 header in case we are "calling" a
25  *      nph- script.
26  */
27 
28 #include <HTUtils.h>
29 #include <HTTP.h>
30 #include <HTParse.h>
31 #include <HTTCP.h>
32 #include <HTFormat.h>
33 #include <HTFile.h>
34 #include <HTAlert.h>
35 #include <HTMIME.h>
36 #include <HTAABrow.h>
37 
38 #include <LYGlobalDefs.h>
39 #include <LYUtils.h>
40 #include <HTML.h>
41 #include <HTInit.h>
42 #include <LYGetFile.h>
43 #include <LYBookmark.h>
44 #include <GridText.h>
45 #include <LYCgi.h>
46 #include <LYStrings.h>
47 #include <LYLocal.h>
48 
49 #include <LYLeaks.h>
50 #include <www_wait.h>
51 
52 static char **env = NULL;	/* Environment variables */
53 static unsigned envc_size = 0;	/* Slots in environment array */
54 static unsigned envc = 0;	/* Slots used so far */
55 static HTList *alloced = NULL;
56 
57 #if defined(LYNXCGI_LINKS) && !defined(__MINGW32__)
58 static char *user_agent = NULL;
59 static char *server_software = NULL;
60 static char *accept_language = NULL;
61 static char *post_len = NULL;
62 #endif /* LYNXCGI_LINKS */
63 
64 static void add_environment_value(const char *env_value);
65 
66 #define PERROR(msg) CTRACE((tfp, "LYNXCGI: %s: %s\n", msg, LYStrerror(errno)))
67 
68 #define PUTS(buf)    (*target->isa->put_block)(target, buf, strlen(buf))
69 
70 #ifdef LY_FIND_LEAKS
free_alloced_lynxcgi(void)71 static void free_alloced_lynxcgi(void)
72 {
73     void *ptr;
74 
75     while ((ptr = HTList_removeLastObject(alloced)) != NULL) {
76 	FREE(ptr);
77     }
78     FREE(alloced);
79 #ifdef LYNXCGI_LINKS
80     FREE(user_agent);
81     FREE(server_software);
82 #endif
83 }
84 #endif /* LY_FIND_LEAKS */
85 
remember_alloced(void * ptr)86 static void remember_alloced(void *ptr)
87 {
88     if (!alloced) {
89 	alloced = HTList_new();
90 #ifdef LY_FIND_LEAKS
91 	atexit(free_alloced_lynxcgi);
92 #endif
93     }
94     HTList_addObject(alloced, ptr);
95 }
96 
97 /*
98  * Simple routine for expanding the environment array and adding a value to
99  * it
100  */
add_environment_value(const char * env_value)101 static void add_environment_value(const char *env_value)
102 {
103     if (envc == envc_size) {	/* Need some more slots */
104 	envc_size += 10;
105 	if (env) {
106 	    env = (char **) realloc(env,
107 				    sizeof(env[0]) * (envc_size + 2));
108 	    /* + terminator and base 0 */
109 	} else {
110 	    env = (char **) malloc(sizeof(env[0]) * (envc_size + 2));
111 	    /* + terminator and base 0 */
112 	    remember_alloced(env);
113 	}
114 	if (env == NULL) {
115 	    outofmem(__FILE__, "LYCgi");
116 	}
117     }
118 
119     env[envc++] = DeConst(env_value);
120     env[envc] = NULL;		/* Make sure it is always properly terminated */
121 }
122 
123 /*
124  * Add the value of an existing environment variable to those passed on to the
125  * lynxcgi script.
126  */
add_lynxcgi_environment(const char * variable_name)127 void add_lynxcgi_environment(const char *variable_name)
128 {
129     char *env_value;
130 
131     env_value = LYGetEnv(variable_name);
132     if (env_value != NULL) {
133 	char *add_value = NULL;
134 
135 	HTSprintf0(&add_value, "%s=%s", variable_name, env_value);
136 	add_environment_value(add_value);
137 	remember_alloced(add_value);
138     }
139 }
140 
141 #ifdef __MINGW32__
LYLoadCGI(const char * arg,HTParentAnchor * anAnchor,HTFormat format_out,HTStream * sink)142 static int LYLoadCGI(const char *arg,
143 		     HTParentAnchor *anAnchor,
144 		     HTFormat format_out,
145 		     HTStream *sink)
146 {
147     (void) arg;
148     (void) anAnchor;
149     (void) format_out;
150     (void) sink;
151     return -1;
152 }
153 #else
154 #ifdef LYNXCGI_LINKS
155 /*
156  * Wrapper for exec_ok(), confirming with user if the link text is not visible
157  * in the status line.
158  */
can_exec_cgi(const char * linktext,const char * linkargs)159 static BOOL can_exec_cgi(const char *linktext, const char *linkargs)
160 {
161     const char *format = gettext("Do you want to execute \"%s\"?");
162     char *message = NULL;
163     char *command = NULL;
164     char *p;
165     BOOL result = TRUE;
166 
167     if (!exec_ok(HTLoadedDocumentURL(), linktext, CGI_PATH)) {
168 	/* exec_ok gives out msg. */
169 	result = FALSE;
170     } else {
171 	StrAllocCopy(command, linktext);
172 	if (non_empty(linkargs)) {
173 	    HTSprintf(&command, " %s", linkargs);
174 	}
175 	HTUnEscape(command);
176 	for (p = command; *p; ++p)
177 	    if (*p == '+')
178 		*p = ' ';
179 	HTSprintf0(&message, format, command);
180 	result = HTConfirm(message);
181 	FREE(message);
182 	FREE(command);
183     }
184     return result;
185 }
186 #endif /* LYNXCGI_LINKS */
187 
LYLoadCGI(const char * arg,HTParentAnchor * anAnchor,HTFormat format_out,HTStream * sink)188 static int LYLoadCGI(const char *arg,
189 		     HTParentAnchor *anAnchor,
190 		     HTFormat format_out,
191 		     HTStream *sink)
192 {
193     int status = 0;
194 
195 #ifdef LYNXCGI_LINKS
196 #ifndef VMS
197     char *cp;
198     struct stat stat_buf;
199     char *pgm = NULL;		/* executable */
200     char *pgm_args = NULL;	/* and its argument(s) */
201     int statrv;
202     char *orig_pgm = NULL;	/* Path up to ? as given, URL-escaped */
203     char *document_root = NULL;	/* Corrected value of DOCUMENT_ROOT  */
204     char *path_info = NULL;	/* PATH_INFO extracted from pgm      */
205     char *pgm_buff = NULL;	/* PATH_INFO extraction buffer       */
206     char *path_translated;	/* From document_root/path_info      */
207 
208     if (isEmpty(arg) || strlen(arg) <= 8) {
209 	HTAlert(BAD_REQUEST);
210 	status = -2;
211 	return (status);
212 
213     } else {
214 	if (StrNCmp(arg, "lynxcgi://localhost", 19) == 0) {
215 	    StrAllocCopy(pgm, arg + 19);
216 	} else {
217 	    StrAllocCopy(pgm, arg + 8);
218 	}
219 	if ((cp = StrChr(pgm, '?')) != NULL) {	/* Need to terminate executable */
220 	    *cp++ = '\0';
221 	    pgm_args = cp;
222 	}
223     }
224 
225     StrAllocCopy(orig_pgm, pgm);
226     if (trimPoundSelector(pgm) != NULL) {
227 	/*
228 	 * Strip a #fragment from path.  In this case any pgm_args found above
229 	 * will also be bogus, since the '?' came after the '#' and is part of
230 	 * the fragment.  Note that we don't handle the case where a '#'
231 	 * appears after a '?' properly according to URL rules.  - kw
232 	 */
233 	pgm_args = NULL;
234     }
235     HTUnEscape(pgm);
236 
237     /* BEGIN WebSter Mods */
238     /* If pgm is not stat-able, see if PATH_INFO data is at the end of pgm */
239     if ((statrv = stat(pgm, &stat_buf)) < 0) {
240 	StrAllocCopy(pgm_buff, pgm);
241 	while (statrv < 0 || (statrv = stat(pgm_buff, &stat_buf)) < 0) {
242 	    if ((cp = strrchr(pgm_buff, '/')) != NULL) {
243 		*cp = '\0';
244 		statrv = 1;	/* force new stat()  - kw */
245 	    } else {
246 		PERROR("strrchr(pgm_buff, '/') returned NULL");
247 		break;
248 	    }
249 	}
250 
251 	if (statrv < 0) {
252 	    /* Did not find PATH_INFO data */
253 	    PERROR("stat() of pgm_buff failed");
254 	} else {
255 	    /* Found PATH_INFO data.  Strip it off of pgm and into path_info. */
256 	    StrAllocCopy(path_info, pgm + strlen(pgm_buff));
257 	    /* The following is safe since pgm_buff was derived from pgm
258 	       by stripping stuff off its end and by HTUnEscaping, so we
259 	       know we have enough memory allocated for pgm.  Note that
260 	       pgm_args may still point into that memory, so we cannot
261 	       reallocate pgm here. - kw */
262 	    strcpy(pgm, pgm_buff);
263 	    CTRACE((tfp,
264 		    "LYNXCGI: stat() of %s succeeded, path_info=\"%s\".\n",
265 		    pgm_buff, path_info));
266 	}
267 	FREE(pgm_buff);
268     }
269     /* END WebSter Mods */
270 
271     if (statrv != 0) {
272 	/*
273 	 * Neither the path as given nor any components examined by backing up
274 	 * were stat()able.  - kw
275 	 */
276 	HTAlert(gettext("Unable to access cgi script"));
277 	PERROR("stat() failed");
278 	status = -4;
279 
280     } else
281 #ifdef _WINDOWS			/* 1998/01/14 (Wed) 09:16:04 */
282 #define isExecutable(mode) (mode & (S_IXUSR))
283 #else
284 #define isExecutable(mode) (mode & (S_IXUSR|S_IXGRP|S_IXOTH))
285 #endif
286     if (!(S_ISREG(stat_buf.st_mode) && isExecutable(stat_buf.st_mode))) {
287 	/*
288 	 * Not a runnable file, See if we can load it using "file:" code.
289 	 */
290 	char *new_arg = NULL;
291 
292 	/*
293 	 * But try "file:" only if the file we are looking at is the path as
294 	 * given (no path_info was extracted), otherwise it will be to
295 	 * confusing to know just what file is loaded.  - kw
296 	 */
297 	if (path_info) {
298 	    CTRACE((tfp,
299 		    "%s is not a file and %s not an executable, giving up.\n",
300 		    orig_pgm, pgm));
301 	    FREE(path_info);
302 	    FREE(pgm);
303 	    FREE(orig_pgm);
304 	    status = -4;
305 	    return (status);
306 	}
307 
308 	LYLocalFileToURL(&new_arg, orig_pgm);
309 
310 	CTRACE((tfp, "%s is not an executable file, passing the buck.\n", arg));
311 	status = HTLoadFile(new_arg, anAnchor, format_out, sink);
312 	FREE(new_arg);
313 
314     } else if (path_info &&
315 	       anAnchor != HTMainAnchor &&
316 	       !(reloading && anAnchor->document) &&
317 	       strcmp(arg, HTLoadedDocumentURL()) &&
318 	       HText_AreDifferent(anAnchor, arg) &&
319 	       HTUnEscape(orig_pgm) &&
320 	       !can_exec_cgi(orig_pgm, "")) {
321 	/*
322 	 * If we have extra path info and are not just reloading the current,
323 	 * check the full file path (after unescaping) now to catch forbidden
324 	 * segments.  - kw
325 	 */
326 	status = HT_NOT_LOADED;
327 
328     } else if (no_lynxcgi) {
329 	HTUserMsg(CGI_DISABLED);
330 	status = HT_NOT_LOADED;
331 
332     } else if (no_bookmark_exec &&
333 	       anAnchor != HTMainAnchor &&
334 	       !(reloading && anAnchor->document) &&
335 	       strcmp(arg, HTLoadedDocumentURL()) &&
336 	       HText_AreDifferent(anAnchor, arg) &&
337 	       HTLoadedDocumentBookmark()) {
338 	/*
339 	 * If we are reloading a lynxcgi document that had already been loaded,
340 	 * the various checks above should allow it even if no_bookmark_exec is
341 	 * TRUE an we are not now coming from a bookmark page.  - kw
342 	 */
343 	HTUserMsg(BOOKMARK_EXEC_DISABLED);
344 	status = HT_NOT_LOADED;
345 
346     } else if (anAnchor != HTMainAnchor &&
347 	       !(reloading && anAnchor->document) &&
348 	       strcmp(arg, HTLoadedDocumentURL()) &&
349 	       HText_AreDifferent(anAnchor, arg) &&
350 	       !can_exec_cgi(pgm, pgm_args)) {
351 	/*
352 	 * If we are reloading a lynxcgi document that had already been loaded,
353 	 * the various checks above should allow it even if exec_ok() would
354 	 * reject it because we are not now coming from a document with a URL
355 	 * allowed by TRUSTED_LYNXCGI rules.  - kw
356 	 */
357 	status = HT_NOT_LOADED;
358 
359     } else {
360 	HTFormat format_in;
361 	HTStream *target = NULL;	/* Unconverted data */
362 	int fd1[2], fd2[2];
363 	char buf[MAX_LINE];
364 	int pid;
365 
366 #ifdef HAVE_TYPE_UNIONWAIT
367 	union wait wstatus;
368 
369 #else
370 	int wstatus;
371 #endif
372 
373 	fd1[0] = -1;
374 	fd1[1] = -1;
375 	fd2[0] = -1;
376 	fd2[1] = -1;
377 
378 	if (anAnchor->isHEAD || keep_mime_headers) {
379 
380 	    /* Show output as plain text */
381 	    format_in = WWW_PLAINTEXT;
382 	} else {
383 
384 	    /* Decode full HTTP response */
385 	    format_in = HTAtom_for("www/mime");
386 	}
387 
388 	target = HTStreamStack(format_in,
389 			       format_out,
390 			       sink, anAnchor);
391 
392 	if (target == NULL) {
393 	    char *tmp = 0;
394 
395 	    HTSprintf0(&tmp, CANNOT_CONVERT_I_TO_O,
396 		       HTAtom_name(format_in),
397 		       HTAtom_name(format_out));
398 	    HTAlert(tmp);
399 	    FREE(tmp);
400 	    status = HT_NOT_LOADED;
401 
402 	} else if (anAnchor->post_data && pipe(fd1) < 0) {
403 	    HTAlert(CONNECT_SET_FAILED);
404 	    PERROR("pipe() failed");
405 	    status = -3;
406 
407 	} else if (pipe(fd2) < 0) {
408 	    HTAlert(CONNECT_SET_FAILED);
409 	    PERROR("pipe() failed");
410 	    close(fd1[0]);
411 	    close(fd1[1]);
412 	    status = -3;
413 
414 	} else {
415 	    static BOOL first_time = TRUE;	/* One time setup flag */
416 
417 	    if (first_time) {	/* Set up static environment variables */
418 		first_time = FALSE;	/* Only once */
419 
420 		add_environment_value("REMOTE_HOST=localhost");
421 		add_environment_value("REMOTE_ADDR=127.0.0.1");
422 
423 		HTSprintf0(&user_agent, "HTTP_USER_AGENT=%s/%s libwww/%s",
424 			   LYNX_NAME, LYNX_VERSION, HTLibraryVersion);
425 		add_environment_value(user_agent);
426 
427 		HTSprintf0(&server_software, "SERVER_SOFTWARE=%s/%s",
428 			   LYNX_NAME, LYNX_VERSION);
429 		add_environment_value(server_software);
430 	    }
431 	    fflush(stdout);
432 	    fflush(stderr);
433 	    CTRACE_FLUSH(tfp);
434 
435 	    if ((pid = fork()) > 0) {	/* The good, */
436 		ssize_t chars;
437 		off_t total_chars;
438 
439 		close(fd2[1]);
440 
441 		if (anAnchor->post_data) {
442 		    ssize_t written;
443 		    int remaining, total_written = 0;
444 
445 		    close(fd1[0]);
446 
447 		    /* We have form data to push across the pipe */
448 		    if (TRACE) {
449 			CTRACE((tfp,
450 				"LYNXCGI: Doing post, content-type '%s'\n",
451 				anAnchor->post_content_type));
452 			CTRACE((tfp, "LYNXCGI: Writing:\n"));
453 			trace_bstring(anAnchor->post_data);
454 			CTRACE((tfp, "----------------------------------\n"));
455 		    }
456 		    remaining = BStrLen(anAnchor->post_data);
457 		    while ((written = write(fd1[1],
458 					    BStrData(anAnchor->post_data) + total_written,
459 					    (size_t) remaining)) != 0) {
460 			if (written < 0) {
461 #ifdef EINTR
462 			    if (errno == EINTR)
463 				continue;
464 #endif /* EINTR */
465 #ifdef ERESTARTSYS
466 			    if (errno == ERESTARTSYS)
467 				continue;
468 #endif /* ERESTARTSYS */
469 			    PERROR("write() of POST data failed");
470 			    break;
471 			}
472 			CTRACE((tfp, "LYNXCGI: Wrote %d bytes of POST data.\n",
473 				(int) written));
474 			total_written += (int) written;
475 			remaining -= (int) written;
476 			if (remaining == 0)
477 			    break;
478 		    }
479 		    if (remaining != 0) {
480 			CTRACE((tfp, "LYNXCGI: %d bytes remain unwritten!\n",
481 				remaining));
482 		    }
483 		    close(fd1[1]);
484 		}
485 
486 		HTReadProgress(total_chars = 0, (off_t) 0);
487 		while ((chars = read(fd2[0], buf, sizeof(buf))) != 0) {
488 		    if (chars < 0) {
489 #ifdef EINTR
490 			if (errno == EINTR)
491 			    continue;
492 #endif /* EINTR */
493 #ifdef ERESTARTSYS
494 			if (errno == ERESTARTSYS)
495 			    continue;
496 #endif /* ERESTARTSYS */
497 			PERROR("read() of CGI output failed");
498 			break;
499 		    }
500 		    total_chars += (int) chars;
501 		    HTReadProgress(total_chars, (off_t) 0);
502 		    CTRACE((tfp, "LYNXCGI: Rx: %.*s\n", (int) chars, buf));
503 		    (*target->isa->put_block) (target, buf, (int) chars);
504 		}
505 
506 		if (chars < 0 && total_chars == 0) {
507 		    status = HT_NOT_LOADED;
508 		    (*target->isa->_abort) (target, NULL);
509 		    target = NULL;
510 		} else if (chars != 0) {
511 		    status = HT_PARTIAL_CONTENT;
512 		} else {
513 		    status = HT_LOADED;
514 		}
515 
516 #ifndef HAVE_WAITPID
517 		while (wait(&wstatus) != pid) ;		/* do nothing */
518 #else
519 		while (-1 == waitpid(pid, &wstatus, 0)) {	/* wait for child */
520 #ifdef EINTR
521 		    if (errno == EINTR)
522 			continue;
523 #endif /* EINTR */
524 #ifdef ERESTARTSYS
525 		    if (errno == ERESTARTSYS)
526 			continue;
527 #endif /* ERESTARTSYS */
528 		    break;
529 		}
530 #endif /* !HAVE_WAITPID */
531 		close(fd2[0]);
532 
533 	    } else if (pid == 0) {	/* The Bad, */
534 		char **argv = NULL;
535 		int argv_cnt = 3;	/* name, one arg and terminator */
536 		char **cur_argv = NULL;
537 		int exec_errno;
538 
539 		/* Set up output pipe */
540 		close(fd2[0]);
541 		dup2(fd2[1], fileno(stdout));	/* Should check success code */
542 		dup2(fd2[1], fileno(stderr));
543 		close(fd2[1]);
544 
545 		if (non_empty(language)) {
546 		    HTSprintf0(&accept_language, "HTTP_ACCEPT_LANGUAGE=%s", language);
547 		    add_environment_value(accept_language);
548 		}
549 
550 		if (non_empty(pref_charset)) {
551 		    cp = NULL;
552 		    StrAllocCopy(cp, "HTTP_ACCEPT_CHARSET=");
553 		    StrAllocCat(cp, pref_charset);
554 		    add_environment_value(cp);
555 		}
556 
557 		if (anAnchor->post_data &&
558 		    anAnchor->post_content_type) {
559 		    cp = NULL;
560 		    StrAllocCopy(cp, "CONTENT_TYPE=");
561 		    StrAllocCat(cp, anAnchor->post_content_type);
562 		    add_environment_value(cp);
563 		}
564 
565 		if (anAnchor->post_data) {	/* post script, read stdin */
566 		    close(fd1[1]);
567 		    dup2(fd1[0], fileno(stdin));
568 		    close(fd1[0]);
569 
570 		    /* Build environment variables */
571 
572 		    add_environment_value("REQUEST_METHOD=POST");
573 
574 		    HTSprintf0(&post_len, "CONTENT_LENGTH=%d",
575 			       BStrLen(anAnchor->post_data));
576 		    add_environment_value(post_len);
577 		} else {
578 		    close(fileno(stdin));
579 
580 		    if (anAnchor->isHEAD) {
581 			add_environment_value("REQUEST_METHOD=HEAD");
582 		    }
583 		}
584 
585 		/*
586 		 * Set up argument line, mainly for <index> scripts
587 		 */
588 		if (pgm_args != NULL) {
589 		    for (cp = pgm_args; *cp != '\0'; cp++) {
590 			if (*cp == '+') {
591 			    argv_cnt++;
592 			}
593 		    }
594 		}
595 
596 		argv = (char **) malloc((unsigned) argv_cnt * sizeof(char *));
597 
598 		if (argv == NULL) {
599 		    outofmem(__FILE__, "LYCgi");
600 		}
601 
602 		cur_argv = argv + 1;	/* For argv[0] */
603 		if (pgm_args != NULL) {
604 		    char *cr;
605 
606 		    /* Data for a get/search form */
607 		    if (is_www_index) {
608 			add_environment_value("REQUEST_METHOD=SEARCH");
609 		    } else if (!anAnchor->isHEAD && !anAnchor->post_data) {
610 			add_environment_value("REQUEST_METHOD=GET");
611 		    }
612 
613 		    cp = NULL;
614 		    StrAllocCopy(cp, "QUERY_STRING=");
615 		    StrAllocCat(cp, pgm_args);
616 		    add_environment_value(cp);
617 
618 		    /*
619 		     * Split up arguments into argv array
620 		     */
621 		    cp = pgm_args;
622 		    cr = cp;
623 		    while (1) {
624 			if (*cp == '\0') {
625 			    *(cur_argv++) = HTUnEscape(cr);
626 			    break;
627 
628 			} else if (*cp == '+') {
629 			    *cp++ = '\0';
630 			    *(cur_argv++) = HTUnEscape(cr);
631 			    cr = cp;
632 			}
633 			cp++;
634 		    }
635 		} else if (!anAnchor->isHEAD && !anAnchor->post_data) {
636 		    add_environment_value("REQUEST_METHOD=GET");
637 		}
638 		*cur_argv = NULL;	/* Terminate argv */
639 		argv[0] = pgm;
640 
641 		/* Begin WebSter Mods  -jkt */
642 		if (non_empty(LYCgiDocumentRoot)) {
643 		    /* Add DOCUMENT_ROOT to env */
644 		    cp = NULL;
645 		    StrAllocCopy(cp, "DOCUMENT_ROOT=");
646 		    StrAllocCat(cp, LYCgiDocumentRoot);
647 		    add_environment_value(cp);
648 		}
649 		if (path_info != NULL) {
650 		    /* Add PATH_INFO to env */
651 		    cp = NULL;
652 		    StrAllocCopy(cp, "PATH_INFO=");
653 		    StrAllocCat(cp, path_info);
654 		    add_environment_value(cp);
655 		}
656 		if (non_empty(LYCgiDocumentRoot) && path_info != NULL) {
657 		    /* Construct and add PATH_TRANSLATED to env */
658 		    StrAllocCopy(document_root, LYCgiDocumentRoot);
659 		    LYTrimHtmlSep(document_root);
660 		    path_translated = document_root;
661 		    StrAllocCat(path_translated, path_info);
662 		    cp = NULL;
663 		    StrAllocCopy(cp, "PATH_TRANSLATED=");
664 		    StrAllocCat(cp, path_translated);
665 		    add_environment_value(cp);
666 		    FREE(path_translated);
667 		}
668 		/* End WebSter Mods  -jkt */
669 
670 		execve(argv[0], argv, env);
671 		exec_errno = errno;
672 		PERROR("execve failed");
673 		printf("Content-Type: " STR_PLAINTEXT "\r\n\r\n");
674 		if (!anAnchor->isHEAD) {
675 		    printf("exec of %s failed", pgm);
676 		    printf(": %s.\r\n", LYStrerror(exec_errno));
677 		}
678 		fflush(stdout);
679 		fflush(stderr);
680 		_exit(1);
681 
682 	    } else {		/* and the Ugly */
683 		HTAlert(CONNECT_FAILED);
684 		PERROR("fork() failed");
685 		close(fd1[0]);
686 		close(fd1[1]);
687 		close(fd2[0]);
688 		close(fd2[1]);
689 		status = -1;
690 	    }
691 
692 	}
693 	if (target != NULL) {
694 	    (*target->isa->_free) (target);
695 	}
696     }
697     FREE(path_info);
698     FREE(pgm);
699     FREE(orig_pgm);
700 #else /* VMS */
701     HTStream *target;
702     char *buf = 0;
703 
704     target = HTStreamStack(WWW_HTML,
705 			   format_out,
706 			   sink, anAnchor);
707 
708     HTSprintf0(&buf, "<html>\n<head>\n<title>%s</title>\n</head>\n<body>\n",
709 	       gettext("Good Advice"));
710     PUTS(buf);
711 
712     HTSprintf0(&buf, "<h1>%s</h1>\n", gettext("Good Advice"));
713     PUTS(buf);
714 
715     HTSprintf0(&buf, "%s <a\n",
716 	       gettext("An excellent http server for VMS is available via"));
717     PUTS(buf);
718 
719     HTSprintf0(&buf,
720 	       "href=\"http://www.ecr6.ohio-state.edu/www/doc/serverinfo.html\"\n");
721     PUTS(buf);
722 
723     HTSprintf0(&buf, ">%s</a>.\n", gettext("this link"));
724     PUTS(buf);
725 
726     HTSprintf0(&buf, "<p>%s\n",
727 	       gettext("It provides state of the art CGI script support.\n"));
728     PUTS(buf);
729 
730     HTSprintf0(&buf, "</body>\n</html>\n");
731     PUTS(buf);
732 
733     (*target->isa->_free) (target);
734     FREE(buf);
735     status = HT_LOADED;
736 #endif /* VMS */
737 #else /* LYNXCGI_LINKS */
738     HTUserMsg(CGI_NOT_COMPILED);
739     status = HT_NOT_LOADED;
740 #endif /* LYNXCGI_LINKS */
741 
742     (void) arg;
743     (void) anAnchor;
744     (void) format_out;
745     (void) sink;
746 
747     return (status);
748 }
749 #endif /* __MINGW32__ */
750 
751 #ifdef GLOBALDEF_IS_MACRO
752 #define _LYCGI_C_GLOBALDEF_1_INIT { "lynxcgi", LYLoadCGI, 0 }
753 GLOBALDEF(HTProtocol, LYLynxCGI, _LYCGI_C_GLOBALDEF_1_INIT);
754 #else
755 GLOBALDEF HTProtocol LYLynxCGI =
756 {"lynxcgi", LYLoadCGI, 0};
757 #endif /* GLOBALDEF_IS_MACRO */
758