1 /*
2  * Copyright (c) 1993-2016, 2018-2020 Paul Mattes.
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are met:
7  *     * Redistributions of source code must retain the above copyright
8  *       notice, this list of conditions and the following disclaimer.
9  *     * Redistributions in binary form must reproduce the above copyright
10  *       notice, this list of conditions and the following disclaimer in the
11  *       documentation and/or other materials provided with the distribution.
12  *     * Neither the names of Paul Mattes nor the names of his contributors
13  *       may be used to endorse or promote products derived from this software
14  *       without specific prior written permission.
15  *
16  * THIS SOFTWARE IS PROVIDED BY PAUL MATTES "AS IS" AND ANY EXPRESS OR IMPLIED
17  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
18  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
19  * EVENT SHALL PAUL MATTES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
22  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
23  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
24  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  */
27 
28 /*
29  *      childscript.c
30  *              The Script() action.
31  */
32 
33 #include "globals.h"
34 
35 #include <assert.h>
36 #include <fcntl.h>
37 #if !defined(_WIN32) /*[*/
38 # include <signal.h>
39 # include <sys/signal.h>
40 # include <sys/wait.h>
41 # include <sys/types.h>
42 # include <sys/socket.h>
43 # include <sys/un.h>
44 # include <netinet/in.h>
45 # include <arpa/inet.h>
46 #endif /*]*/
47 
48 #include "actions.h"
49 #include "appres.h"
50 #include "child.h"
51 #include "popups.h"
52 #include "child_popups.h"
53 #include "childscript.h"
54 #include "find_console.h"
55 #include "httpd-core.h"
56 #include "httpd-io.h"
57 #include "lazya.h"
58 #include "names.h"
59 #include "peerscript.h"
60 #include "s3270_proto.h"
61 #include "task.h"
62 #include "telnet_core.h"
63 #include "trace.h"
64 #include "utils.h"
65 #include "varbuf.h"
66 #include "w3misc.h"
67 
68 #define CHILD_BUF		1024
69 #define DELAYED_CLOSE_MS	3000
70 
71 static void child_data(task_cbh handle, const char *buf, size_t len,
72 	bool success);
73 static bool child_done(task_cbh handle, bool success, bool abort);
74 static bool child_run(task_cbh handle, bool *success);
75 static void child_closescript(task_cbh handle);
76 static void child_setflags(task_cbh handle, unsigned flags);
77 static unsigned child_getflags(task_cbh handle);
78 
79 static void child_setir(task_cbh handle, void *irhandle);
80 static void *child_getir(task_cbh handle);
81 static void child_setir_state(task_cbh handle, const char *name, void *state,
82 	ir_state_abort_cb abort_cb);
83 static void *child_getir_state(task_cbh handle, const char *name);
84 static const char *child_command(task_cbh handle);
85 static void child_reqinput(task_cbh handle, const char *buf, size_t len,
86 	bool echo);
87 
88 static irv_t child_irv = {
89     child_setir,
90     child_getir,
91     child_setir_state,
92     child_getir_state
93 };
94 
95 /* Callback block for parent script. */
96 static tcb_t script_cb = {
97     "child",
98     IA_SCRIPT,
99     0,
100     child_data,
101     child_done,
102     child_run,
103     child_closescript,
104     child_setflags,
105     child_getflags,
106     &child_irv,
107     child_command,
108     child_reqinput
109 };
110 
111 /* Asynchronous callback block for parent script. */
112 static tcb_t async_script_cb = {
113     "child",
114     IA_SCRIPT,
115     CB_NEW_TASKQ,
116     child_data,
117     child_done,
118     child_run,
119     child_closescript,
120     child_setflags,
121     child_getflags,
122     &child_irv,
123     child_command,
124     child_reqinput,
125 };
126 
127 #if !defined(_WIN32) /*[*/
128 /* Callback block for child (creates new taskq). */
129 static tcb_t child_cb = {
130     "child",
131     IA_SCRIPT,
132     CB_NEW_TASKQ,
133     child_data,
134     child_done,
135     child_run,
136     child_closescript,
137     child_setflags,
138     child_getflags,
139     &child_irv,
140     child_command,
141     child_reqinput
142 };
143 #endif /*]*/
144 
145 #if defined(_WIN32) /*[*/
146 /* Stdout read context. */
147 typedef struct {
148     HANDLE pipe_rd_handle;	/* read handle for pipe */
149     HANDLE pipe_wr_handle;	/* write handle for pipe */
150     HANDLE enable_event;	/* enable event (emulator to read thread) */
151     HANDLE done_event;		/* done event (read thread to emulator) */
152     ioid_t done_id;		/* I/O identifier for done event */
153     HANDLE read_thread;		/* read thread */
154     char buf[CHILD_BUF];	/* input  buffer */
155     DWORD nr;			/* length of data in I/O buffer */
156     int error;			/* error code for failed read */
157     bool dead;			/* read thread has exited, set by read thread */
158     bool collected_eof;		/* EOF collected by I/O function */
159 } cr_t;
160 #endif /*]*/
161 
162 typedef struct {
163     peer_listen_t peer;
164     hio_listener_t *httpd;
165 } listeners_t;
166 
167 /* Child script context. */
168 typedef struct {
169     llist_t llist;		/* linkage */
170     char *parent_name;		/* cb name */
171     char *command;		/* command text */
172     bool is_async;		/* true if script is async */
173     bool done;			/* true if script is complete */
174     bool success;		/* success or failure */
175     ioid_t exit_id;		/* I/O identifier for child exit */
176     int exit_status;		/* exit status */
177     bool enabled;		/* enabled */
178     char *output_buf;		/* output buffer */
179     size_t output_buflen;	/* size of output buffer */
180     listeners_t listeners;	/* listeners for child commands */
181     bool keyboard_lock;		/* lock/unlock keyboard while running */
182     unsigned capabilities;	/* self-reported capabilities */
183     void *irhandle;		/* input request handle */
184     task_cb_ir_state_t ir_state; /* named input request state */
185 #if defined(_WIN32) /*[*/
186     DWORD pid;			/* process ID */
187     HANDLE child_handle;	/* status collection handle */
188     cr_t cr;			/* stdout read context */
189 #else /*][*/
190     char *child_name;		/* cb name */
191     pid_t pid;			/* process ID */
192     int infd;			/* input (to emulator) file descriptor */
193     int outfd;			/* output (to script) file descriptor */
194     ioid_t id;			/* input I/O identifier */
195     char *buf;			/* pending command */
196     size_t buf_len;		/* length of pending command */
197     int stdoutpipe;		/* stdout pipe */
198     ioid_t stdout_id;		/* stdout I/O identifier */
199 #endif /*]*/
200 } child_t;
201 static llist_t child_scripts = LLIST_INIT(child_scripts);
202 
203 #if !defined(_WIN32) /*[*/
204 typedef struct {
205     llist_t llist;		/* linkage */
206     ioid_t id;			/* I/O identifier for timeout */
207     listeners_t listeners;	/* listeners */
208 } delayed_close_t;
209 static llist_t delayed_closes = LLIST_INIT(delayed_closes);
210 #endif /*]*/
211 
212 /**
213  * Free a child.
214  *
215  * @param[in,out] c	Child.
216  */
217 static void
free_child(child_t * c)218 free_child(child_t *c)
219 {
220     llist_unlink(&c->llist);
221     Replace(c->parent_name, NULL);
222     Replace(c->command, NULL);
223 #if !defined(_WIN32) /*[*/
224     Replace(c->child_name, NULL);
225 #endif /*]*/
226     Replace(c->output_buf, NULL);
227     Free(c);
228 }
229 
230 /**
231  * Close a set of listeners.
232  *
233  * @param[in] l		Listeners.
234  */
235 static void
close_listeners(listeners_t * l)236 close_listeners(listeners_t *l)
237 {
238     if (l->httpd != NULL) {
239 	hio_stop_x(l->httpd);
240 	l->httpd = NULL;
241     }
242     if (l->peer != NULL) {
243 	peer_shutdown(l->peer);
244 	l->peer = NULL;
245     }
246 }
247 
248 #if !defined(_WIN32) /*[*/
249 /**
250  * Run the next command in the child buffer.
251  *
252  * @param[in,out] c	Child
253  *
254  * @return true if command was run. Command is deleted from the buffer.
255  */
256 static bool
run_next(child_t * c)257 run_next(child_t *c)
258 {
259     size_t cmdlen;
260     char *name;
261 
262     /* Find a newline in the buffer. */
263     for (cmdlen = 0; cmdlen < c->buf_len; cmdlen++) {
264 	if (c->buf[cmdlen] == '\n') {
265 	    break;
266 	}
267     }
268     if (cmdlen >= c->buf_len) {
269 	return false;
270     }
271 
272     /*
273      * Run the first command.
274      * cmdlen is the number of characters in the command, not including the
275      * newline.
276      */
277     name = push_cb(c->buf, cmdlen, &child_cb, (task_cbh)c);
278     Replace(c->child_name, NewString(name));
279 
280     /* If there is more, shift it over. */
281     cmdlen++; /* count the newline */
282     if (c->buf_len > cmdlen) {
283 	memmove(c->buf, c->buf + cmdlen, c->buf_len - cmdlen);
284 	c->buf_len = c->buf_len - cmdlen;
285     } else {
286 	Replace(c->buf, NULL);
287 	c->buf_len = 0;
288     }
289     return true;
290 }
291 
292 /**
293  * Delayed close of a child script listener.
294  *
295  * @param[in] id	I/O identifier.
296  */
297 static void
delayed_close(ioid_t id)298 delayed_close(ioid_t id)
299 {
300     delayed_close_t *dc;
301 
302     FOREACH_LLIST(&delayed_closes, dc, delayed_close_t *) {
303 	if (dc->id == id) {
304 	    vtrace("Delayed shutdown of listeners\n");
305 	    close_listeners(&dc->listeners);
306 	    llist_unlink(&dc->llist);
307 	    Free(dc);
308 	    return;
309 	}
310     } FOREACH_LLIST_END(&delayed_closes, dc, delayed_close_t *);
311 
312     vtrace("Error: Delayed shutdown record not found\n");
313 }
314 
315 /**
316  * Tear down a child.
317  *
318  * @param[in,out] c	Child.
319  */
320 static void
close_child(child_t * c)321 close_child(child_t *c)
322 {
323     if (c->is_async && c->listeners.httpd != NULL) {
324 	delayed_close_t *dc = Calloc(sizeof(delayed_close_t), 1);
325 
326 	/*
327 	 * Delay the close. gnome-terminal, for example, forks and execs a
328 	 * new process for the console. We need to wait a while for it to
329 	 * connect.
330 	 */
331 	llist_init(&dc->llist);
332 	dc->listeners = c->listeners; /* struct copy */
333 	LLIST_APPEND(&dc->llist, delayed_closes);
334 	dc->id = AddTimeOut(DELAYED_CLOSE_MS, delayed_close);
335     } else {
336 	close_listeners(&c->listeners);
337     }
338     c->listeners.httpd = NULL;
339     c->listeners.peer = NULL;
340     if (c->infd != -1) {
341 	close(c->infd);
342 	c->infd = -1;
343     }
344     if (c->outfd != -1) {
345 	close(c->outfd);
346 	c->outfd = -1;
347     }
348     if (c->id != NULL_IOID) {
349 	RemoveInput(c->id);
350 	c->id = NULL_IOID;
351     }
352     Replace(c->buf, NULL);
353     if (c->stdout_id != NULL_IOID) {
354 	RemoveInput(c->stdout_id);
355 	c->stdout_id = NULL_IOID;
356     }
357     if (c->stdoutpipe != -1) {
358 	close(c->stdoutpipe);
359 	c->stdoutpipe = -1;
360     }
361     if (c->irhandle != NULL) {
362 	task_abort_input_request_irhandle(c->irhandle);
363 	c->irhandle = NULL;
364     }
365     task_cb_abort_ir_state(&c->ir_state);
366 
367     /* Abort any pending child. */
368     if (c->child_name != NULL) {
369 	abort_queue(c->child_name);
370     }
371 }
372 
373 /**
374  * Read the next command from a child pipe.
375  * @param[in] fd	File descriptor
376  * @param[in] id	I/O identifier
377  */
378 static void
child_input(iosrc_t fd _is_unused,ioid_t id)379 child_input(iosrc_t fd _is_unused, ioid_t id)
380 {
381     child_t *c;
382     bool found_child = false;
383     char buf[8192];
384     size_t n2r;
385     size_t nr;
386     size_t i;
387 
388     /* Find the child. */
389     FOREACH_LLIST(&child_scripts, c, child_t *) {
390 	if (c->id == id) {
391 	    found_child = true;
392 	    break;
393 	}
394     } FOREACH_LLIST_END(&child_scripts, c, child_t *);
395     assert(found_child);
396 
397     /* Read input. */
398     n2r = sizeof(buf);
399     nr = read(c->infd, buf, (int)n2r);
400     assert(nr >= 0);
401     vtrace("%s input complete, nr=%d\n", c->parent_name, (int)nr);
402     if (nr == 0) {
403 	vtrace("%s script EOF\n", c->parent_name);
404 	close_child(c);
405 	if (c->exit_id == NULL_IOID) {
406 	    c->done = true;
407 	    task_activate((task_cbh *)c);
408 	}
409 	RemoveInput(c->id);
410 	c->id = NULL_IOID;
411 	close(c->infd);
412 	c->infd = -1;
413 	return;
414     }
415 
416     /* Append, filtering out CRs. */
417     c->buf = Realloc(c->buf, c->buf_len + nr + 1);
418     for (i = 0; i < nr; i++) {
419 	char ch = buf[i];
420 
421 	if (ch != '\r') {
422 	    c->buf[c->buf_len++] = ch;
423 	}
424     }
425 
426     /* Disable further input. */
427     if (c->id != NULL_IOID) {
428 	RemoveInput(c->id);
429 	c->id = NULL_IOID;
430     }
431 
432     /* Run the next command, if we have it all. */
433     if (!run_next(c) && c->id == NULL_IOID) {
434 	/* Get more input. */
435 	c->id = AddInput(c->infd, child_input);
436     }
437 }
438 
439 /**
440  * Read output from a child script.
441  * @param[in] fd	File descriptor
442  * @param[in] id	I/O identifier
443  */
444 static void
child_stdout(iosrc_t fd _is_unused,ioid_t id)445 child_stdout(iosrc_t fd _is_unused, ioid_t id)
446 {
447     child_t *c;
448     bool found_child = false;
449     char buf[8192];
450     size_t n2r;
451     size_t nr;
452     size_t new_buflen;
453 
454     /* Find the child. */
455     FOREACH_LLIST(&child_scripts, c, child_t *) {
456 	if (c->stdout_id == id) {
457 	    found_child = true;
458 	    break;
459 	}
460     } FOREACH_LLIST_END(&child_scripts, c, child_t *);
461     assert(found_child);
462 
463     /* Read input. */
464     n2r = sizeof(buf);
465     nr = read(fd, buf, (int)n2r);
466     assert(nr >= 0);
467     vtrace("%s stdout read complete, nr=%d\n", c->parent_name, (int)nr);
468     if (nr == 0) {
469 	vtrace("%s script stdout EOF\n", c->parent_name);
470 	RemoveInput(c->stdout_id);
471 	c->stdout_id = NULL_IOID;
472 	close(c->stdoutpipe);
473 	c->stdoutpipe = -1;
474 	return;
475     }
476 
477     /* Save it. */
478     new_buflen = c->output_buflen + nr;
479     c->output_buf = Realloc(c->output_buf, new_buflen + 1);
480     memcpy(c->output_buf + c->output_buflen, buf, nr);
481     c->output_buflen = new_buflen;
482     c->output_buf[new_buflen] = '\0';
483 }
484 #endif /*]*/
485 
486 /**
487  * Callback for data returned to child script command via a pipe.
488  *
489  * @param[in] handle    Callback handle
490  * @param[in] buf       Buffer
491  * @param[in] len       Buffer length
492  * @param[in] success   True if data, false if error message
493  */
494 static void
child_data(task_cbh handle,const char * buf,size_t len,bool success)495 child_data(task_cbh handle, const char *buf, size_t len, bool success)
496 {
497 #if !defined(_WIN32) /*[*/
498     child_t *c = (child_t *)handle;
499     char *s = lazyaf(DATA_PREFIX "%.*s\n", (int)len, buf);
500     ssize_t nw;
501 
502     nw = write(c->outfd, s, strlen(s));
503     if (nw != (ssize_t)strlen(s)) {
504 	vtrace("child_data: short write\n");
505     }
506 #endif /*]*/
507 }
508 
509 /**
510  * Callback for input request
511  *
512  * @param[in] handle    Callback handle
513  * @param[in] buf       Buffer
514  * @param[in] len       Buffer length
515  * @param[in] echo      True to echo input
516  */
517 static void
child_reqinput(task_cbh handle,const char * buf,size_t len,bool echo)518 child_reqinput(task_cbh handle, const char *buf, size_t len, bool echo)
519 {
520 #if !defined(_WIN32) /*[*/
521     child_t *c = (child_t *)handle;
522     char *s = lazyaf("%s%.*s\n", echo? INPUT_PREFIX: PWINPUT_PREFIX,
523 	    (int)len, buf);
524     ssize_t nw;
525 
526     nw = write(c->outfd, s, strlen(s));
527     if (nw != (ssize_t)strlen(s)) {
528 	vtrace("child_reqinput: short write\n");
529     }
530 #endif /*]*/
531 }
532 
533 /**
534  * Callback for completion of one command executed from the child script in
535  * s3270 mode.
536  *
537  * @param[in] handle		Callback handle
538  * @param[in] success		True if child succeeded
539  * @param[in] abort		True if aborting
540  *
541  * @return True of script has terminated
542  */
543 static bool
child_done(task_cbh handle,bool success,bool abort)544 child_done(task_cbh handle, bool success, bool abort)
545 {
546     child_t *c = (child_t *)handle;
547 #if !defined(_WIN32) /*[*/
548     bool new_child;
549     char *prompt;
550     char *s;
551     ssize_t nw;
552 
553     if (abort || !c->enabled) {
554 	close_listeners(&c->listeners);
555 	vtrace("%s terminating script process\n", c->parent_name);
556 	kill(c->pid, SIGTERM);
557 	if (c->keyboard_lock) {
558 	    disable_keyboard(ENABLE, IMPLICIT, AnScript "() abort");
559 	}
560 	return true;
561     }
562 
563     /* Print the prompt. */
564     prompt = task_cb_prompt(handle);
565     s = lazyaf("%s\n%s\n", prompt, success? "ok": "error");
566     vtrace("Output for %s: %s/%s\n", c->child_name, prompt,
567 	success? "ok": "error");
568     nw = write(c->outfd, s, strlen(s));
569     if (nw != (ssize_t)strlen(s)) {
570 	vtrace("child_done: short write\n");
571     }
572 
573     /* Run any pending command that we already read in. */
574     new_child = run_next(c);
575     if (!new_child && c->id == NULL_IOID && c->infd != -1) {
576 	/* Allow more input. */
577 	c->id = AddInput(c->infd, child_input);
578     }
579 
580     /*
581      * If there was a new child, we're still active. Otherwise, let our CB
582      * be popped.
583      */
584     return !new_child;
585 
586 #else /*][*/
587 
588     if (abort) {
589 	close_listeners(&c->listeners);
590 	vtrace("%s terminating script process\n", c->parent_name);
591 	TerminateProcess(c->child_handle, 1);
592 	if (c->keyboard_lock) {
593 	    disable_keyboard(ENABLE, IMPLICIT, AnScript "() abort");
594 	}
595     }
596     return true;
597 
598 #endif /*]*/
599 }
600 
601 #if defined(_WIN32) /*[*/
602 static void
cr_teardown(cr_t * cr)603 cr_teardown(cr_t *cr)
604 {
605     if (cr->pipe_rd_handle != INVALID_HANDLE_VALUE) {
606 	CloseHandle(cr->pipe_rd_handle);
607 	cr->pipe_rd_handle = INVALID_HANDLE_VALUE;
608     }
609     if (cr->pipe_wr_handle != INVALID_HANDLE_VALUE) {
610 	CloseHandle(cr->pipe_wr_handle);
611 	cr->pipe_wr_handle = INVALID_HANDLE_VALUE;
612     }
613     if (cr->enable_event != INVALID_HANDLE_VALUE) {
614 	CloseHandle(cr->enable_event);
615 	cr->enable_event = INVALID_HANDLE_VALUE;
616     }
617     if (cr->done_event != INVALID_HANDLE_VALUE) {
618 	CloseHandle(cr->done_event);
619 	cr->done_event = INVALID_HANDLE_VALUE;
620     }
621     if (cr->done_id != NULL_IOID) {
622 	RemoveInput(cr->done_id);
623 	cr->done_id = NULL_IOID;
624     }
625     if (cr->read_thread != INVALID_HANDLE_VALUE) {
626 	CloseHandle(cr->read_thread);
627 	cr->read_thread = INVALID_HANDLE_VALUE;
628     }
629 }
630 
631 /**
632  * Tear down a child.
633  *
634  * @param[in,out] c	Child.
635  */
636 static void
close_child(child_t * c)637 close_child(child_t *c)
638 {
639     if (c->child_handle != INVALID_HANDLE_VALUE) {
640 	CloseHandle(c->child_handle);
641 	c->child_handle = INVALID_HANDLE_VALUE;
642     }
643     close_listeners(&c->listeners);
644     cr_teardown(&c->cr);
645     if (c->irhandle != NULL) {
646 	task_abort_input_request_irhandle(c->irhandle);
647 	c->irhandle = NULL;
648     }
649     task_cb_abort_ir_state(&c->ir_state);
650 }
651 
652 /*
653  * Collect output from the read thread.
654  * Returns true if more input may be available.
655  */
656 static bool
cr_collect(child_t * c)657 cr_collect(child_t *c)
658 {
659     cr_t *cr = &c->cr;
660     if (cr->nr != 0) {
661 	vtrace("Got %d bytes of script stdout/stderr\n", (int)cr->nr);
662 	if (cr->nr == 2 && !strncmp(cr->buf, "^C", 2)) {
663 	    /* Hack, hack, hack. */
664 	    vtrace("Suppressing '^C' output from child\n");
665 	} else {
666 	    c->output_buf = Realloc(c->output_buf,
667 		    c->output_buflen + cr->nr + 1);
668 	    memcpy(c->output_buf + c->output_buflen, cr->buf, cr->nr);
669 	    c->output_buflen += cr->nr;
670 	    c->output_buf[c->output_buflen] = '\0';
671 	}
672 
673 	/* Ready for more. */
674 	cr->nr = 0;
675     }
676     if (cr->dead) {
677 	if (cr->error != 0) {
678 	    vtrace("Script stdout/stderr read failed: %s\n",
679 		    win32_strerror(cr->error));
680 	}
681 	cr->collected_eof = true;
682 	return false;
683     }
684     SetEvent(cr->enable_event);
685     return true;
686 }
687 #endif /*]*/
688 
689 /**
690  * Run vector for child scripts.
691  *
692  * @param[in] handle	Context.
693  * @param[out] success	Returned true if script succeeded.
694  *
695  * @return True if script is complete.
696  */
697 static bool
child_run(task_cbh handle,bool * success)698 child_run(task_cbh handle, bool *success)
699 {
700     child_t *c = (child_t *)handle;
701 
702     if (c->done) {
703 #if defined(_WIN32) /*[*/
704 	/* Collect remaining output and let the read thread exit. */
705 	cr_t *cr = &c->cr;
706 
707 	if (!cr->collected_eof) {
708 	    do {
709 		vtrace("Waiting for child final stdout/stderr\n");
710 		WaitForSingleObject(cr->done_event, INFINITE);
711 	    } while (cr_collect(c));
712 	}
713 #endif /*]*/
714 	if (c->output_buflen) {
715 	    /* Strip out CRs. */
716 	    char *tmp = Malloc(strlen(c->output_buf) + 1);
717 	    char *s = c->output_buf;
718 	    char *t = tmp;
719 	    char c;
720 
721 	    while ((c = *s++) != '\0') {
722 		if (c != '\r') {
723 		    *t++ = c;
724 		}
725 	    }
726 	    *t = '\0';
727 	    action_output("%s", tmp);
728 	    Free(tmp);
729 	}
730 	close_child(c);
731 	if (!c->success) {
732 #if !defined(_WIN32) /*[*/
733 	    if (WIFEXITED(c->exit_status)) {
734 		popup_an_error("Script exited with status %d",
735 			WEXITSTATUS(c->exit_status));
736 	    } else if (WIFSIGNALED(c->exit_status)) {
737 		popup_an_error("Script killed by signal %d",
738 			WTERMSIG(c->exit_status));
739 	    } else {
740 		popup_an_error("Script stopped by unknown status %d",
741 			c->exit_status);
742 	    }
743 #else /*][*/
744 	    popup_an_error("Script exited with status %d",
745 		    c->exit_status);
746 #endif /*]*/
747 	}
748 	*success = c->success;
749 	if (c->keyboard_lock) {
750 	    disable_keyboard(ENABLE, IMPLICIT, "Script() completion");
751 	}
752 	free_child(c);
753 	return true;
754     }
755 
756     return false;
757 }
758 
759 /**
760  * Close a running child script.
761  *
762  * @param[in] handle	Child context
763  */
764 static void
child_closescript(task_cbh handle)765 child_closescript(task_cbh handle)
766 {
767     child_t *c = (child_t *)handle;
768 
769     c->enabled = false;
770 }
771 
772 /**
773  * Set capabilities flags.
774  *
775  * @param[in] handle	Child context
776  * @param[in] flags	Flags
777  */
778 static void
child_setflags(task_cbh handle,unsigned flags)779 child_setflags(task_cbh handle, unsigned flags)
780 {
781     child_t *c = (child_t *)handle;
782 
783     c->capabilities = flags;
784 }
785 
786 /**
787  * Get capabilities flags.
788  *
789  * @param[in] handle	Child context
790  * @returns flags
791  */
792 static unsigned
child_getflags(task_cbh handle)793 child_getflags(task_cbh handle)
794 {
795     child_t *c = (child_t *)handle;
796 
797     return c->capabilities;
798 }
799 
800 /**
801  * Set the pending input request.
802  *
803  * @param[in] handle	Child context
804  * @param[in] irhandle	Input request handle
805  */
806 static void
child_setir(task_cbh handle,void * irhandle)807 child_setir(task_cbh handle, void *irhandle)
808 {
809     child_t *c = (child_t *)handle;
810 
811     c->irhandle = irhandle;
812 }
813 
814 /**
815  * Get the pending input request.
816  *
817  * @param[in] handle	Child context
818  *
819  * @returns input request handle
820  */
821 static void *
child_getir(task_cbh handle)822 child_getir(task_cbh handle)
823 {
824     child_t *c = (child_t *)handle;
825 
826     return c->irhandle;
827 }
828 
829 /**
830  * Set input request state.
831  *
832  * @param[in] handle    CB handle
833  * @param[in] name      Input request type name
834  * @param[in] state     State to store
835  * @param[in] abort     Abort callback
836  */
837 static void
child_setir_state(task_cbh handle,const char * name,void * state,ir_state_abort_cb abort)838 child_setir_state(task_cbh handle, const char *name, void *state,
839 	ir_state_abort_cb abort)
840 {
841     child_t *c = (child_t *)handle;
842 
843     task_cb_set_ir_state(&c->ir_state, name, state, abort);
844 }
845 
846 /**
847  * Get input request state.
848  *
849  * @param[in] handle    CB handle
850  * @param[in] name      Input request type name
851  *
852  * @returns input request state
853  */
854 static void *
child_getir_state(task_cbh handle,const char * name)855 child_getir_state(task_cbh handle, const char *name)
856 {
857     child_t *c = (child_t *)handle;
858 
859     return task_cb_get_ir_state(&c->ir_state, name);
860 }
861 
862 /**
863  * Get the command text.
864  *
865  * @param[in] handle	CB handle
866  *
867  * @returns command text, or NULL
868  */
869 static const char *
child_command(task_cbh handle)870 child_command(task_cbh handle)
871 {
872     child_t *c = (child_t *)handle;
873 
874     return c->command;
875 }
876 
877 #if !defined(_WIN32) /*[*/
878 static void
child_exited(ioid_t id,int status)879 child_exited(ioid_t id, int status)
880 {
881     child_t *c;
882     bool found_child = false;
883 
884     FOREACH_LLIST(&child_scripts, c, child_t *) {
885 	if (c->exit_id == id) {
886 	    found_child = true;
887 	    break;
888 	}
889     } FOREACH_LLIST_END(&child_scripts, c, child_t *);
890 
891     if (!found_child) {
892 	vtrace("child_exited: no match\n");
893 	return;
894     }
895 
896     vtrace("%s script %d exited with status %d\n",
897 	    (c->child_name != NULL) ? c->child_name : "socket",
898 	    (int)c->pid, status);
899 
900     c->exit_status = status;
901     if (status != 0) {
902 	c->success = false;
903     }
904     c->exit_id = NULL_IOID;
905     if (c->id == NULL_IOID) {
906 	/* This task should be run. */
907 	c->done = true;
908 	task_activate((task_cbh *)c);
909     }
910 }
911 #endif /*]*/
912 
913 /* Let the system pick a TCP port to bind to. */
914 static unsigned short
pick_port(socket_t * sp)915 pick_port(socket_t *sp)
916 {
917     socket_t s;
918     struct sockaddr_in sin;
919     socklen_t len;
920     int on = 1;
921 
922     s = socket(PF_INET, SOCK_STREAM, 0);
923     if (s == INVALID_SOCKET) {
924 	popup_a_sockerr("socket");
925 	return 0;
926     }
927     memset(&sin, '\0', sizeof(sin));
928     sin.sin_family = AF_INET;
929     sin.sin_addr.s_addr = htonl(INADDR_ANY);
930     if (bind(s, (struct sockaddr *)&sin, sizeof(sin)) < 0) {
931 	popup_a_sockerr("bind");
932 	SOCK_CLOSE(s);
933 	return 0;
934     }
935     if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof(on)) < 0) {
936 	popup_a_sockerr("setsockopt");
937 	SOCK_CLOSE(s);
938 	return 0;
939     }
940     len = sizeof(sin);
941     if (getsockname(s, (struct sockaddr *)&sin, &len) < 0) {
942 	popup_a_sockerr("getsockaddr");
943 	SOCK_CLOSE(s);
944 	return 0;
945     }
946     *sp = s;
947     return ntohs(sin.sin_port);
948 }
949 
950 #if defined(_WIN32) /*[*/
951 /* Process an event on a child script handle (a process exit). */
952 static void
child_exited(iosrc_t fd _is_unused,ioid_t id _is_unused)953 child_exited(iosrc_t fd _is_unused, ioid_t id _is_unused)
954 {
955     child_t *c;
956     bool found_child = false;
957     DWORD status;
958 
959     FOREACH_LLIST(&child_scripts, c, child_t *) {
960 	if (c->exit_id == id) {
961 	    found_child = true;
962 	    break;
963 	}
964     } FOREACH_LLIST_END(&child_scripts, c, child_t *);
965 
966     if (!found_child) {
967 	vtrace("child_exited: no match\n");
968 	return;
969     }
970 
971     status = 0;
972     if (GetExitCodeProcess(c->child_handle, &status) == 0) {
973 	popup_an_error("GetExitCodeProcess failed: %s",
974 	win32_strerror(GetLastError()));
975     } else if (status != STILL_ACTIVE) {
976 	vtrace("%s script exited with status %d\n", c->parent_name,
977 		(unsigned)status);
978 	c->exit_status = status;
979 	if (status != 0) {
980 	    c->success = false;
981 	}
982 	CloseHandle(c->child_handle);
983 	c->child_handle = INVALID_HANDLE_VALUE;
984 	RemoveInput(c->exit_id);
985 	c->exit_id = NULL_IOID;
986 
987 	/* This task should be run. */
988 	c->done = true;
989 	task_activate((task_cbh *)c);
990     }
991 }
992 
993 /* Read from the child's stdout or stderr. */
994 static DWORD WINAPI
child_read_thread(LPVOID parameter)995 child_read_thread(LPVOID parameter)
996 {
997     child_t *child = (child_t *)parameter;
998     cr_t *cr = &child->cr;
999     DWORD success;
1000     bool done = false;
1001 
1002     while (!done) {
1003 	switch (WaitForSingleObject(cr->enable_event, INFINITE)) {
1004 	case WAIT_OBJECT_0:
1005 	    success = ReadFile(cr->pipe_rd_handle, cr->buf, CHILD_BUF, &cr->nr,
1006 		    NULL);
1007 	    if (!success) {
1008 		/* Canceled or pipe broken. */
1009 		cr->error = GetLastError();
1010 		done = true;
1011 		break;
1012 	    }
1013 	    SetEvent(cr->done_event);
1014 	    break;
1015 	default:
1016 	    cr->error = GetLastError();
1017 	    done = true;
1018 	    break;
1019 	}
1020     }
1021 
1022     /* All done, I hope. */
1023     cr->nr = 0;
1024     cr->dead = true;
1025     SetEvent(cr->done_event);
1026     return 0;
1027 }
1028 
1029 /* The child stdout/stderr thread produced output. */
1030 static void
cr_output(iosrc_t fd,ioid_t id)1031 cr_output(iosrc_t fd, ioid_t id)
1032 {
1033     child_t *c;
1034     bool found_child = false;
1035 
1036     /* Find the descriptor. */
1037     FOREACH_LLIST(&child_scripts, c, child_t *) {
1038 	if (c->cr.done_id == id) {
1039 	    found_child = true;
1040 	    break;
1041 	}
1042     } FOREACH_LLIST_END(&child_scripts, c, child_t *);
1043     assert(found_child);
1044 
1045     /* Collect the output. */
1046     cr_collect(c);
1047 }
1048 
1049 /* Set up the stdout reader context. */
1050 static bool
setup_cr(child_t * c)1051 setup_cr(child_t *c)
1052 {
1053     cr_t *cr = &c->cr;
1054     SECURITY_ATTRIBUTES sa;
1055     DWORD mode;
1056 
1057     /* Create the pipe. */
1058     memset(&sa, 0, sizeof(sa));
1059     sa.nLength = sizeof(SECURITY_ATTRIBUTES);
1060     sa.bInheritHandle = TRUE;
1061     sa.lpSecurityDescriptor = NULL;
1062     if (!CreatePipe(&cr->pipe_rd_handle, &cr->pipe_wr_handle, &sa, 0)) {
1063 	popup_an_error("CreatePipe() failed: %s",
1064 		win32_strerror(GetLastError()));
1065 	return false;
1066     }
1067     if (!SetHandleInformation(cr->pipe_rd_handle, HANDLE_FLAG_INHERIT, 0)) {
1068 	popup_an_error("SetHandleInformation() failed: %s",
1069 		win32_strerror(GetLastError()));
1070 	CloseHandle(cr->pipe_rd_handle);
1071 	CloseHandle(cr->pipe_wr_handle);
1072 	return false;
1073     }
1074     mode = PIPE_READMODE_BYTE;
1075     if (!SetNamedPipeHandleState(cr->pipe_rd_handle, &mode, NULL, NULL)) {
1076 	popup_an_error("SetNamedPipeHandleState(stdout) failed: %s",
1077 		win32_strerror(GetLastError()));
1078 	CloseHandle(cr->pipe_rd_handle);
1079 	CloseHandle(cr->pipe_wr_handle);
1080 	return false;
1081     }
1082 
1083     /* Express interest in their output. */
1084     cr->enable_event = CreateEvent(NULL, FALSE, FALSE, NULL);
1085     cr->done_event = CreateEvent(NULL, FALSE, FALSE, NULL);
1086     cr->read_thread = CreateThread(NULL, 0, child_read_thread, c, 0, NULL);
1087     cr->done_id = AddInput(cr->done_event, cr_output);
1088 
1089     return true;
1090 }
1091 #endif /*]*/
1092 
1093 
1094 /* "Script" action, runs a script as a child process. */
1095 bool
Script_action(ia_t ia,unsigned argc,const char ** argv)1096 Script_action(ia_t ia, unsigned argc, const char **argv)
1097 {
1098     child_t *c;
1099     char *name;
1100     bool async = false;
1101     bool keyboard_lock = true;
1102     bool stdout_redirect = true;
1103     peer_listen_mode mode = PLM_MULTI;
1104     listeners_t listeners;
1105     varbuf_t r;
1106     unsigned i;
1107     socket_t s;
1108     unsigned short httpd_port;
1109     unsigned short script_port;
1110     struct sockaddr_in *sin;
1111 #if !defined(_WIN32) /*[*/
1112     pid_t pid;
1113     int inpipe[2] = { -1, -1 };
1114     int outpipe[2] = { -1, -1 };
1115     int stdoutpipe[2];
1116 #else /*][*/
1117     bool share_console = false;
1118     STARTUPINFO startupinfo;
1119     PROCESS_INFORMATION process_information;
1120     char *args;
1121     cr_t *cr;
1122 #endif /*]*/
1123 
1124     action_debug(AnScript, ia, argc, argv);
1125 
1126     for (;;) {
1127 	if (argc < 1) {
1128 	    popup_an_error(AnScript "() requires at least one argument");
1129 	    return false;
1130 	}
1131 	if (!strcasecmp(argv[0], KwDashAsync)) {
1132 	    async = true;
1133 	    keyboard_lock = false;
1134 	    argc--;
1135 	    argv++;
1136 	} else if (!strcasecmp(argv[0], KwDashNoLock)) {
1137 	    keyboard_lock = false;
1138 	    argc--;
1139 	    argv++;
1140 	} else if (!strcasecmp(argv[0], KwDashSingle)) {
1141 	    mode = PLM_SINGLE;
1142 	    argc--;
1143 	    argv++;
1144 	} else if (!strcasecmp(argv[0], KwDashNoStdoutRedirect)) {
1145 	    stdout_redirect = false;
1146 	    argc--;
1147 	    argv++;
1148 #if defined(_WIN32) /*[*/
1149 	} else if (!strcasecmp(argv[0], KwDashShareConsole)) {
1150 	    share_console = true;
1151 	    argc--;
1152 	    argv++;
1153 #endif /*]*/
1154 	} else {
1155 	    break;
1156 	}
1157     }
1158 
1159     listeners.peer = NULL;
1160     listeners.httpd = NULL;
1161 
1162     /* Set up X3270PORT or X3270URL for the child process. */
1163     httpd_port = pick_port(&s);
1164     if (httpd_port == 0) {
1165 	return false;
1166     }
1167     sin = (struct sockaddr_in *)Calloc(1, sizeof(struct sockaddr_in));
1168     sin->sin_family = AF_INET;
1169     sin->sin_addr.s_addr = htonl(INADDR_LOOPBACK);
1170     sin->sin_port = htons(httpd_port);
1171     listeners.httpd = hio_init_x((struct sockaddr *)sin, sizeof(*sin));
1172     SOCK_CLOSE(s);
1173     if (listeners.httpd == NULL) {
1174 	return false;
1175     }
1176 
1177     script_port = pick_port(&s);
1178     if (script_port == 0) {
1179 	hio_stop_x(listeners.httpd);
1180 	return false;
1181     }
1182     sin = (struct sockaddr_in *)Calloc(1, sizeof(struct sockaddr_in));
1183     sin->sin_family = AF_INET;
1184     sin->sin_addr.s_addr = htonl(INADDR_LOOPBACK);
1185     sin->sin_port = htons(script_port);
1186     listeners.peer = peer_init((struct sockaddr *)sin, sizeof(*sin), mode);
1187     SOCK_CLOSE(s);
1188     if (listeners.peer == NULL) {
1189 	hio_stop_x(listeners.httpd);
1190 	return false;
1191     }
1192 
1193     putenv(lazyaf(URL_ENV "=http://127.0.0.1:%u/3270/rest/", httpd_port));
1194     putenv(lazyaf(PORT_ENV "=%d", script_port));
1195 
1196 #if !defined(_WIN32) /*[*/
1197     /*
1198      * Create pipes and stdout stream for the script process.
1199      *  inpipe[] is read by x3270, written by the script
1200      *  outpipe[] is written by x3270, read by the script
1201      */
1202     if (pipe(inpipe) < 0) {
1203 	popup_an_error("pipe() failed");
1204 	close_listeners(&listeners);
1205 	return false;
1206     }
1207     if (pipe(outpipe) < 0) {
1208 	popup_an_error("pipe() failed");
1209 	close(inpipe[0]);
1210 	close(inpipe[1]);
1211 	close_listeners(&listeners);
1212 	return false;
1213     }
1214 
1215     /* Create a pipe to capture child stdout. */
1216     if (pipe(stdoutpipe) < 0) {
1217 	popup_an_error("pipe() failed");
1218 	close(outpipe[0]);
1219 	close(outpipe[1]);
1220 	close(inpipe[0]);
1221 	close(inpipe[1]);
1222 	close_listeners(&listeners);
1223     }
1224 
1225     /* Fork and exec the script process. */
1226     if ((pid = fork()) < 0) {
1227 	popup_an_error("fork() failed");
1228 	close(inpipe[0]);
1229 	close(inpipe[1]);
1230 	close(outpipe[0]);
1231 	close(outpipe[1]);
1232 	close(stdoutpipe[0]);
1233 	close(stdoutpipe[1]);
1234 	close_listeners(&listeners);
1235 	return false;
1236     }
1237 
1238     /* Child processing. */
1239     if (pid == 0) {
1240 	char **child_argv;
1241 	unsigned i;
1242 
1243 	/* Become a process group. */
1244 	setsid();
1245 
1246 	/* Clean up the pipes. */
1247 	close(outpipe[1]);
1248 	close(inpipe[0]);
1249 	close(stdoutpipe[0]);
1250 
1251 	/* Redirect output. */
1252 	if (stdout_redirect) {
1253 	    dup2(stdoutpipe[1], 1);
1254 	} else {
1255 	    dup2(open("/dev/null", O_WRONLY), 1);
1256 	}
1257 	dup2(stdoutpipe[1], 2);
1258 
1259 	/* Export the names of the pipes into the environment. */
1260 	putenv(xs_buffer(OUTPUT_ENV "=%d", outpipe[0]));
1261 	putenv(xs_buffer(INPUT_ENV "=%d", inpipe[1]));
1262 
1263 	/* Set up arguments. */
1264 	child_argv = (char **)Malloc((argc + 1) * sizeof(char *));
1265 	for (i = 0; i < argc; i++) {
1266 	    child_argv[i] = (char *)argv[i];
1267 	}
1268 	child_argv[i] = NULL;
1269 
1270 	/* Exec. */
1271 	execvp(argv[0], child_argv);
1272 	fprintf(stderr, "exec(%s) failed\n", argv[0]);
1273 	_exit(1);
1274     }
1275 
1276     c = (child_t *)Calloc(1, sizeof(child_t));
1277     llist_init(&c->llist);
1278     LLIST_APPEND(&c->llist, child_scripts);
1279     c->success = true;
1280     c->done = false;
1281     c->buf = NULL;
1282     c->buf_len = 0;
1283     c->pid = pid;
1284     c->exit_id = AddChild(pid, child_exited);
1285     c->enabled = true;
1286     c->stdoutpipe = stdoutpipe[0];
1287     task_cb_init_ir_state(&c->ir_state);
1288 
1289     /* Clean up our ends of the pipes. */
1290     c->infd = inpipe[0];
1291     close(inpipe[1]);
1292     c->outfd = outpipe[1];
1293     close(outpipe[0]);
1294     close(stdoutpipe[1]);
1295 
1296     /* Link the listeners. */
1297     c->listeners = listeners; /* struct copy */
1298 
1299     /* Allow child pipe input. */
1300     c->id = AddInput(c->infd, child_input);
1301 
1302     /* Capture child output. */
1303     c->stdout_id = AddInput(c->stdoutpipe, child_stdout);
1304 
1305 #else /*]*/
1306 
1307     /* Set up the stdout/stderr output pipes. */
1308     c = (child_t *)Calloc(1, sizeof(child_t));
1309     if (!setup_cr(c)) {
1310 	Free(c);
1311 	return false;
1312     }
1313     task_cb_init_ir_state(&c->ir_state);
1314     cr = &c->cr;
1315 
1316     /* Start the child process. */
1317     memset(&startupinfo, '\0', sizeof(STARTUPINFO));
1318     startupinfo.cb = sizeof(STARTUPINFO);
1319     if (stdout_redirect) {
1320 	startupinfo.hStdOutput = cr->pipe_wr_handle;
1321     }
1322     startupinfo.hStdError = cr->pipe_wr_handle;
1323     startupinfo.dwFlags |= STARTF_USESTDHANDLES;
1324     memset(&process_information, '\0', sizeof(PROCESS_INFORMATION));
1325     args = NewString(argv[0]);
1326     for (i = 1; i < argc; i++) {
1327 	char *t;
1328 
1329 	if (strchr(argv[i], ' ') != NULL &&
1330 	    argv[i][0] != '"' &&
1331 	    argv[i][strlen(argv[i]) - 1] != '"') {
1332 	    t = xs_buffer("%s \"%s\"", args, argv[i]);
1333 	} else {
1334 	    t = xs_buffer("%s %s", args, argv[i]);
1335 	}
1336 	Free(args);
1337 	args = t;
1338     }
1339     if (CreateProcess(NULL, args, NULL, NULL, TRUE,
1340 		(stdout_redirect && !share_console)? DETACHED_PROCESS: 0,
1341 		NULL, NULL, &startupinfo, &process_information) == 0) {
1342 	popup_an_error("CreateProcess(%s) failed: %s", argv[0],
1343 		win32_strerror(GetLastError()));
1344 	close_listeners(&listeners);
1345 
1346 	/* Let the read thread complete. */
1347 	CloseHandle(cr->pipe_wr_handle);
1348 	cr->pipe_wr_handle = INVALID_HANDLE_VALUE;
1349 	SetEvent(cr->enable_event);
1350 	WaitForSingleObject(cr->done_event, INFINITE);
1351 
1352 	cr_teardown(cr);
1353 	Free(c);
1354 	Free(args);
1355 	return false;
1356     }
1357 
1358     Free(args);
1359     CloseHandle(process_information.hThread);
1360     CloseHandle(cr->pipe_wr_handle);
1361     cr->pipe_wr_handle = INVALID_HANDLE_VALUE;
1362     SetEvent(cr->enable_event);
1363 
1364     /* Create a new script description. */
1365     llist_init(&c->llist);
1366     LLIST_APPEND(&c->llist, child_scripts);
1367     c->success = true;
1368     c->done = false;
1369     c->child_handle = process_information.hProcess;
1370     c->pid = (int)process_information.dwProcessId;
1371     c->listeners = listeners; /* struct copy */
1372     c->enabled = true;
1373 
1374     /*
1375      * Wait for the child process to exit.
1376      * Note that this is an asynchronous event -- exits for multiple
1377      * children can happen in any order.
1378      */
1379     c->exit_id = AddInput(process_information.hProcess, child_exited);
1380 
1381 #endif /*]*/
1382 
1383     /* Save the arguments. */
1384     vb_init(&r);
1385     for (i = 0; i < argc; i++) {
1386 	if (i > 0) {
1387 	    vb_appends(&r, ",");
1388 	}
1389 	vb_appends(&r, argv[i]);
1390     }
1391     c->command = vb_consume(&r);
1392 
1393     /* Create the context. It will be idle. */
1394     c->is_async = async;
1395     c->keyboard_lock = keyboard_lock;
1396     name = push_cb(NULL, 0, async? &async_script_cb: &script_cb, (task_cbh)c);
1397     Replace(c->parent_name, NewString(name));
1398     vtrace("%s script process is %d\n", c->parent_name, (int)c->pid);
1399 
1400     if (keyboard_lock) {
1401 	disable_keyboard(DISABLE, IMPLICIT, AnScript "() start");
1402     }
1403 
1404     return true;
1405 }
1406 
1407 /*
1408  * Start an x3270if-based interactive console, optionally overriding the app
1409  * name used as the prompt.
1410  *
1411  * Prompt([prompt[,help-action][,i18n-file]])
1412  */
1413 bool
Prompt_action(ia_t ia,unsigned argc,const char ** argv)1414 Prompt_action(ia_t ia, unsigned argc, const char **argv)
1415 {
1416     const char *params[3] = { programname, NULL, NULL };
1417     unsigned i;
1418     const char **nargv = NULL;
1419     int nargc = 0;
1420 #if !defined(_WIN32) /*[*/
1421     console_desc_t *t;
1422     const char *errmsg;
1423 #endif /*]*/
1424 
1425     action_debug(AnPrompt, ia, argc, argv);
1426     if (check_argc(AnPrompt, argc, 0, 3) < 0) {
1427 	return false;
1428     }
1429 
1430 #if !defined(_WIN32) /*[*/
1431     /* Find a console emulator to run the prompt in. */
1432     t = find_console(&errmsg);
1433     if (t == NULL)  {
1434 	popup_an_error(AnPrompt "(): console program:\n%s", errmsg);
1435 	return false;
1436     }
1437 
1438     /* Make sure x3270if is available. */
1439     if (!find_in_path("x3270if")) {
1440 	popup_an_error(AnPrompt "(): can't find x3270if");
1441 	return false;
1442     }
1443 #endif /*]*/
1444 
1445     if (appres.alias != NULL) {
1446 	params[0] = appres.alias;
1447     }
1448 #if defined(_WIN32) /*[*/
1449     else {
1450 	size_t sl = strlen(params[0]);
1451 
1452 	if (sl > 4 && !strcasecmp(params[0] + sl - 4, ".exe")) {
1453 	    params[0] = lazyaf("%.*s", (int)(sl - 4), params[0]);
1454 	}
1455     }
1456 #endif /*]*/
1457 
1458     for (i = 0; i < argc; i++) {
1459 	const char *in = argv[i];
1460 	char *new_param = lazya(NewString(argv[i]));
1461 	char *out = new_param;
1462 	char c;
1463 
1464 	while ((c = *in++)) {
1465 	    if (c != '\'' && c != '"' && (i == 2 || !isspace((int)c))) {
1466 		*out++ = c;
1467 	    }
1468 	}
1469 	*out = '\0';
1470 	if (strlen(new_param) > 0) {
1471 	    params[i] = new_param;
1472 	}
1473     }
1474 
1475     array_add(&nargv, nargc++, KwDashAsync);
1476     array_add(&nargv, nargc++, KwDashSingle);
1477 #if !defined(_WIN32) /*[*/
1478     nargc = console_args(t, lazyaf("%s>", params[0]), &nargv, nargc);
1479     array_add(&nargv, nargc++, "/bin/sh");
1480     array_add(&nargv, nargc++, "-c");
1481     array_add(&nargv, nargc++,
1482 	    lazyaf("x3270if -I '%s'%s%s || (echo 'Press <Enter>'; read x)",
1483 		params[0],
1484 		(params[1] != NULL)? lazyaf(" -H '%s'", params[1]): "",
1485 		(params[2] != NULL)? lazyaf(" -L '%s'", params[2]): ""));
1486 #else /*][*/
1487     array_add(&nargv, nargc++, KwDashSingle);
1488     array_add(&nargv, nargc++, "cmd.exe");
1489     array_add(&nargv, nargc++, "/c");
1490     array_add(&nargv, nargc++, "start");
1491     array_add(&nargv, nargc++, lazyaf("\"%s\"", params[0]));
1492     array_add(&nargv, nargc++, "/wait");
1493     array_add(&nargv, nargc++, "x3270if.exe");
1494     array_add(&nargv, nargc++, "-I");
1495     array_add(&nargv, nargc++, params[0]);
1496     if (params[1] != NULL) {
1497 	array_add(&nargv, nargc++, "-H");
1498 	array_add(&nargv, nargc++, params[1]);
1499     }
1500     if (params[2] != NULL) {
1501 	array_add(&nargv, nargc++, "-L");
1502 	array_add(&nargv, nargc++, lazyaf("\"%s\"", params[2]));
1503     }
1504 #endif /*]*/
1505     array_add(&nargv, nargc++, NULL);
1506     lazya((void *)nargv);
1507 
1508     return Script_action(ia, nargc - 1, nargv);
1509 }
1510