1/*
2Copyright (c) 2001 by Juliusz Chroboczek
3
4Permission is hereby granted, free of charge, to any person obtaining a copy
5of this software and associated documentation files (the "Software"), to deal
6in the Software without restriction, including without limitation the rights
7to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8copies of the Software, and to permit persons to whom the Software is
9furnished to do so, subject to the following conditions:
10
11The above copyright notice and this permission notice shall be included in
12all copies or substantial portions of the Software.
13
14THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
17AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20THE SOFTWARE.
21*/
22
23#ifdef HAVE_CONFIG_H
24# include "config.h"
25#endif
26
27#include <stdio.h>
28#include <stdlib.h>
29#include <string.h>
30#include <locale.h>
31#include <sys/types.h>
32#include <fcntl.h>
33#include <unistd.h>
34#include <errno.h>
35#include <assert.h>
36#include <stdarg.h>
37#include <sys/ioctl.h>
38#include <signal.h>
39
40#include "luit.h"
41#include "sys.h"
42#include "other.h"
43#include "parser.h"
44#include "iso2022.h"
45
46static int pipe_option = 0;
47static int p2c_waitpipe[2];
48static int c2p_waitpipe[2];
49
50static Iso2022Ptr inputState = NULL, outputState = NULL;
51
52static char *child_argv0 = NULL;
53static const char *locale_name = NULL;
54static int exitOnChild = 0;
55static int converter = 0;
56
57const char *locale_alias = LOCALE_ALIAS_FILE;
58
59int ilog = -1;
60int olog = -1;
61int verbose = 0;
62
63static volatile int sigwinch_queued = 0;
64static volatile int sigchld_queued = 0;
65
66static int convert(int, int);
67static int condom(int, char **);
68
69void
70ErrorF(const char *f,...)
71{
72    va_list args;
73    va_start(args, f);
74    vfprintf(stderr, f, args);
75    va_end(args);
76}
77
78void
79FatalError(const char *f,...)
80{
81    va_list args;
82    va_start(args, f);
83    vfprintf(stderr, f, args);
84    va_end(args);
85    ExitProgram(1);
86}
87
88static void
89help(void)
90{
91    fprintf(stderr,
92	    "luit\n"
93	    "  [ -V ] [ -h ] [ -list ] [ -v ] [ -argv0 name ]\n"
94	    "  [ -gl gn ] [-gr gk] "
95	    "[ -g0 set ] [ -g1 set ] "
96	    "[ -g2 set ] [ -g3 set ]\n"
97	    "  [ -encoding encoding ] "
98	    "[ +oss ] [ +ols ] [ +osl ] [ +ot ]\n"
99	    "  [ -kgl gn ] [-kgr gk] "
100	    "[ -kg0 set ] [ -kg1 set ] "
101	    "[ -kg2 set ] [ -kg3 set ]\n"
102	    "  [ -k7 ] [ +kss ] [ +kssgr ] [ -kls ]\n"
103	    "  [ -c ] "
104	    "[ -p ] "
105	    "[ -x ] "
106	    "[ -ilog filename ] "
107	    "[ -olog filename ] "
108	    "[ -alias filename ] "
109	    "[ -- ]\n"
110	    "  [ program [ args ] ]\n");
111}
112
113static int
114parseOptions(int argc, char **argv)
115{
116    int i = 1;
117    while (i < argc) {
118	if (argv[i][0] != '-' && argv[i][0] != '+') {
119	    break;
120	} else if (!strcmp(argv[i], "--")) {
121	    i++;
122	    break;
123	} else if (!strcmp(argv[i], "-v")) {
124	    verbose++;
125	    i++;
126	} else if (!strcmp(argv[i], "-V")) {
127	    printf("%s - %s\n", argv[0], VERSION);
128	    ExitProgram(0);
129	} else if (!strcmp(argv[i], "-h")) {
130	    help();
131	    ExitProgram(0);
132	} else if (!strcmp(argv[i], "-list")) {
133	    reportCharsets();
134	    ExitProgram(0);
135	} else if (!strcmp(argv[i], "+oss")) {
136	    outputState->outputFlags &= ~OF_SS;
137	    i++;
138	} else if (!strcmp(argv[i], "+ols")) {
139	    outputState->outputFlags &= ~OF_LS;
140	    i++;
141	} else if (!strcmp(argv[i], "+osl")) {
142	    outputState->outputFlags &= ~OF_SELECT;
143	    i++;
144	} else if (!strcmp(argv[i], "+ot")) {
145	    outputState->outputFlags = OF_PASSTHRU;
146	    i++;
147	} else if (!strcmp(argv[i], "-k7")) {
148	    inputState->inputFlags &= ~IF_EIGHTBIT;
149	    i++;
150	} else if (!strcmp(argv[i], "+kss")) {
151	    inputState->inputFlags &= ~IF_SS;
152	    i++;
153	} else if (!strcmp(argv[1], "+kssgr")) {
154	    inputState->inputFlags &= ~IF_SSGR;
155	    i++;
156	} else if (!strcmp(argv[i], "-kls")) {
157	    inputState->inputFlags |= IF_LS;
158	    i++;
159	} else if (!strcmp(argv[i], "-g0")) {
160	    if (i + 1 >= argc)
161		FatalError("-g0 requires an argument\n");
162	    G0(outputState) = getCharsetByName(argv[i + 1]);
163	    i += 2;
164	} else if (!strcmp(argv[i], "-g1")) {
165	    if (i + 1 >= argc)
166		FatalError("-g1 requires an argument\n");
167	    G1(outputState) = getCharsetByName(argv[i + 1]);
168	    i += 2;
169	} else if (!strcmp(argv[i], "-g2")) {
170	    if (i + 1 >= argc)
171		FatalError("-g2 requires an argument\n");
172	    G2(outputState) = getCharsetByName(argv[i + 1]);
173	    i += 2;
174	} else if (!strcmp(argv[i], "-g3")) {
175	    if (i + 1 >= argc)
176		FatalError("-g3 requires an argument\n");
177	    G3(outputState) = getCharsetByName(argv[i + 1]);
178
179	    i += 2;
180	} else if (!strcmp(argv[i], "-gl")) {
181	    int j;
182	    if (i + 1 >= argc)
183		FatalError("-gl requires an argument\n");
184	    if (strlen(argv[i + 1]) != 2 ||
185		argv[i + 1][0] != 'g')
186		j = -1;
187	    else
188		j = argv[i + 1][1] - '0';
189	    if (j < 0 || j > 3)
190		FatalError("The argument of -gl "
191			   "should be one of g0 through g3,\n"
192			   "not %s\n", argv[i + 1]);
193	    else
194		outputState->glp = &outputState->g[j];
195	    i += 2;
196	} else if (!strcmp(argv[i], "-gr")) {
197	    int j;
198	    if (i + 1 >= argc)
199		FatalError("-gr requires an argument\n");
200	    if (strlen(argv[i + 1]) != 2 ||
201		argv[i + 1][0] != 'g')
202		j = -1;
203	    else
204		j = argv[i + 1][1] - '0';
205	    if (j < 0 || j > 3)
206		FatalError("The argument of -gl "
207			   "should be one of g0 through g3,\n"
208			   "not %s\n", argv[i + 1]);
209	    else
210		outputState->grp = &outputState->g[j];
211	    i += 2;
212	} else if (!strcmp(argv[i], "-kg0")) {
213	    if (i + 1 >= argc)
214		FatalError("-kg0 requires an argument\n");
215	    G0(inputState) = getCharsetByName(argv[i + 1]);
216	    i += 2;
217	} else if (!strcmp(argv[i], "-kg1")) {
218	    if (i + 1 >= argc)
219		FatalError("-kg1 requires an argument\n");
220	    G1(inputState) = getCharsetByName(argv[i + 1]);
221	    i += 2;
222	} else if (!strcmp(argv[i], "-kg2")) {
223	    if (i + 1 >= argc)
224		FatalError("-kg2 requires an argument\n");
225	    G2(inputState) = getCharsetByName(argv[i + 1]);
226	    i += 2;
227	} else if (!strcmp(argv[i], "-kg3")) {
228	    if (i + 1 >= argc)
229		FatalError("-kg3 requires an argument\n");
230	    G3(inputState) = getCharsetByName(argv[i + 1]);
231
232	    i += 2;
233	} else if (!strcmp(argv[i], "-kgl")) {
234	    int j;
235	    if (i + 1 >= argc)
236		FatalError("-kgl requires an argument\n");
237	    if (strlen(argv[i + 1]) != 2 ||
238		argv[i + 1][0] != 'g')
239		j = -1;
240	    else
241		j = argv[i + 1][1] - '0';
242	    if (j < 0 || j > 3)
243		FatalError("The argument of -kgl "
244			   "should be one of g0 through g3,\n"
245			   "not %s\n", argv[i + 1]);
246	    else
247		inputState->glp = &inputState->g[j];
248	    i += 2;
249	} else if (!strcmp(argv[i], "-kgr")) {
250	    int j;
251	    if (i + 1 >= argc)
252		FatalError("-kgl requires an argument\n");
253	    if (strlen(argv[i + 1]) != 2 ||
254		argv[i + 1][0] != 'g')
255		j = -1;
256	    else
257		j = argv[i + 1][1] - '0';
258	    if (j < 0 || j > 3)
259		FatalError("The argument of -kgl "
260			   "should be one of g0 through g3,\n"
261			   "not %s\n", argv[i + 1]);
262	    else
263		inputState->grp = &inputState->g[j];
264	    i += 2;
265	} else if (!strcmp(argv[i], "-argv0")) {
266	    if (i + 1 >= argc)
267		FatalError("-argv0 requires an argument\n");
268	    child_argv0 = argv[i + 1];
269	    i += 2;
270	} else if (!strcmp(argv[i], "-x")) {
271	    exitOnChild = 1;
272	    i++;
273	} else if (!strcmp(argv[i], "-c")) {
274	    converter = 1;
275	    i++;
276	} else if (!strcmp(argv[i], "-ilog")) {
277	    if (i + 1 >= argc)
278		FatalError("-ilog requires an argument\n");
279	    ilog = open(argv[i + 1], O_WRONLY | O_CREAT | O_TRUNC, 0777);
280	    if (ilog < 0) {
281		perror("Couldn't open input log");
282		ExitProgram(1);
283	    }
284	    i += 2;
285	} else if (!strcmp(argv[i], "-olog")) {
286	    if (i + 1 >= argc)
287		FatalError("-olog requires an argument\n");
288	    olog = open(argv[i + 1], O_WRONLY | O_CREAT | O_TRUNC, 0777);
289	    if (olog < 0) {
290		perror("Couldn't open output log");
291		ExitProgram(1);
292	    }
293	    i += 2;
294	} else if (!strcmp(argv[i], "-alias")) {
295	    if (i + 1 >= argc)
296		FatalError("-alias requires an argument\n");
297	    locale_alias = argv[i + 1];
298	    i += 2;
299	} else if (!strcmp(argv[i], "-encoding")) {
300	    if (i + 1 >= argc)
301		FatalError("-encoding requires an argument\n");
302	    locale_name = argv[i + 1];
303	    i += 2;
304	} else if (!strcmp(argv[i], "-p")) {
305	    pipe_option = 1;
306	    i += 1;
307	} else {
308	    FatalError("Unknown option %s\n", argv[i]);
309	}
310    }
311    return i;
312}
313
314static int
315parseArgs(int argc, char **argv,
316	  char *argv0,
317	  char **path_return,
318	  char ***argv_return)
319{
320    char *path = NULL;
321    char **child_argv = NULL;
322
323    if (argc <= 0) {
324	char *shell;
325	shell = getenv("SHELL");
326	if (shell) {
327	    path = strmalloc(shell);
328	    if (!path)
329		goto bail;
330	} else {
331	    path = strmalloc("/bin/sh");
332	    if (!path)
333		goto bail;
334	}
335	child_argv = malloc(2 * sizeof(char *));
336	if (!child_argv)
337	    goto bail;
338	if (argv0)
339	    child_argv[0] = argv0;
340	else
341	    child_argv[0] = my_basename(path);
342	child_argv[1] = NULL;
343    } else {
344	path = strmalloc(argv[0]);
345	if (!path)
346	    goto bail;
347	child_argv = malloc((unsigned) (argc + 1) * sizeof(char *));
348	if (!child_argv) {
349	    goto bail;
350	}
351	if (child_argv0)
352	    child_argv[0] = argv0;
353	else
354	    child_argv[0] = my_basename(argv[0]);
355	memcpy(child_argv + 1, argv + 1, (unsigned) (argc - 1) * sizeof(char *));
356	child_argv[argc] = NULL;
357    }
358
359    *path_return = path;
360    *argv_return = child_argv;
361    return 0;
362
363  bail:
364    if (path)
365	free(path);
366    if (child_argv)
367	free(child_argv);
368    return -1;
369}
370
371int
372main(int argc, char **argv)
373{
374    int rc;
375    int i;
376    char *l;
377
378#ifdef HAVE_PUTENV
379    if ((l = strmalloc("NCURSES_NO_UTF8_ACS=1")) != 0)
380	putenv(l);
381#endif
382
383    l = setlocale(LC_ALL, "");
384    if (!l)
385	ErrorF("Warning: couldn't set locale.\n");
386
387    inputState = allocIso2022();
388    if (!inputState)
389	FatalError("Couldn't create input state\n");
390
391    outputState = allocIso2022();
392    if (!outputState)
393	FatalError("Couldn't create output state\n");
394
395    if (l) {
396	locale_name = setlocale(LC_CTYPE, NULL);
397    } else {
398	locale_name = getenv("LC_ALL");
399	if (locale_name == NULL) {
400	    locale_name = getenv("LC_CTYPE");
401	    if (locale_name == NULL) {
402		locale_name = getenv("LANG");
403	    }
404	}
405    }
406
407    if (locale_name == NULL) {
408	ErrorF("Couldn't get locale name -- using C\n");
409	locale_name = "C";
410    }
411
412    i = parseOptions(argc, argv);
413    if (i < 0)
414	FatalError("Couldn't parse options\n");
415
416    rc = initIso2022(locale_name, NULL, outputState);
417    if (rc < 0)
418	FatalError("Couldn't init output state\n");
419
420    rc = mergeIso2022(inputState, outputState);
421    if (rc < 0)
422	FatalError("Couldn't init input state\n");
423
424    if (converter)
425	rc = convert(0, 1);
426    else
427	rc = condom(argc - i, argv + i);
428
429#ifdef NO_LEAKS
430    ExitProgram(rc);
431#endif
432    return rc;
433}
434
435static int
436convert(int ifd, int ofd)
437{
438    int rc, i;
439    unsigned char buf[BUFFER_SIZE];
440
441    rc = droppriv();
442    if (rc < 0) {
443	perror("Couldn't drop privileges");
444	ExitProgram(1);
445    }
446
447    while (1) {
448	i = (int) read(ifd, buf, (size_t) BUFFER_SIZE);
449	if (i <= 0) {
450	    if (i < 0) {
451		perror("Read error");
452		ExitProgram(1);
453	    }
454	    break;
455	}
456	copyOut(outputState, ofd, buf, (unsigned) i);
457    }
458    return 0;
459}
460
461#ifdef SIGWINCH
462static void
463sigwinchHandler(int sig GCC_UNUSED)
464{
465    sigwinch_queued = 1;
466}
467#endif
468
469static void
470sigchldHandler(int sig GCC_UNUSED)
471{
472    sigchld_queued = 1;
473}
474
475static int
476setup_io(int pty)
477{
478    int rc;
479    int val;
480
481#ifdef SIGWINCH
482    installHandler(SIGWINCH, sigwinchHandler);
483#endif
484    installHandler(SIGCHLD, sigchldHandler);
485
486    rc = copyTermios(0, pty);
487    if (rc < 0)
488	FatalError("Couldn't copy terminal settings\n");
489
490    rc = setRawTermios();
491    if (rc < 0)
492	FatalError("Couldn't set terminal to raw\n");
493
494    val = fcntl(0, F_GETFL, 0);
495    if (val >= 0) {
496	fcntl(0, F_SETFL, val | O_NONBLOCK);
497    }
498    val = fcntl(pty, F_GETFL, 0);
499    if (val >= 0) {
500	fcntl(pty, F_SETFL, val | O_NONBLOCK);
501    }
502
503    setWindowSize(0, pty);
504
505    return rc;
506}
507
508static void
509cleanup_io(int pty)
510{
511    int val;
512
513#ifdef SIGWINCH
514    installHandler(SIGWINCH, SIG_DFL);
515#endif
516    installHandler(SIGCHLD, SIG_DFL);
517
518    val = fcntl(0, F_GETFL, 0);
519    if (val >= 0) {
520	fcntl(0, F_SETFL, val & ~O_NONBLOCK);
521    }
522    val = fcntl(pty, F_GETFL, 0);
523    if (val >= 0) {
524	fcntl(pty, F_SETFL, val & ~O_NONBLOCK);
525    }
526}
527
528static void
529close_waitpipe(int which)
530{
531    close(p2c_waitpipe[which]);
532    close(c2p_waitpipe[!which]);
533}
534
535static void
536write_waitpipe(int fds[2])
537{
538    IGNORE_RC(write(fds[1], "1", (size_t) 1));
539}
540
541static void
542read_waitpipe(int fds[2])
543{
544    char tmp[10];
545    IGNORE_RC(read(fds[0], tmp, (size_t) 1));
546}
547
548static int
549condom(int argc, char **argv)
550{
551    int pty;
552    int pid;
553    char *line;
554    char *path = 0;
555    char **child_argv = 0;
556    int rc;
557
558    rc = parseArgs(argc, argv, child_argv0,
559		   &path, &child_argv);
560    if (rc < 0)
561	FatalError("Couldn't parse arguments\n");
562
563    rc = allocatePty(&pty, &line);
564    if (rc < 0) {
565	perror("Couldn't allocate pty");
566	ExitProgram(1);
567    }
568
569    rc = droppriv();
570    if (rc < 0) {
571	perror("Couldn't drop privileges");
572	ExitProgram(1);
573    }
574
575    if (pipe_option) {
576	IGNORE_RC(pipe(p2c_waitpipe));
577	IGNORE_RC(pipe(c2p_waitpipe));
578    }
579
580    pid = fork();
581    if (pid < 0) {
582	perror("Couldn't fork");
583	ExitProgram(1);
584    }
585
586    if (pid == 0) {
587	close(pty);
588	if (pipe_option) {
589	    close_waitpipe(1);
590	}
591	child(line, path, child_argv);
592    } else {
593	if (pipe_option) {
594	    close_waitpipe(0);
595	}
596	free(child_argv);
597	free(path);
598	free(line);
599	parent(pid, pty);
600    }
601
602    return 0;
603}
604
605void
606child(char *line, char *path, char *const argv[])
607{
608    int tty;
609    int pgrp;
610
611    close(0);
612    close(1);
613    close(2);
614
615    pgrp = setsid();
616    if (pgrp < 0) {
617	kill(getppid(), SIGABRT);
618	ExitProgram(1);
619    }
620
621    tty = openTty(line);
622    if (tty < 0) {
623	kill(getppid(), SIGABRT);
624	ExitProgram(1);
625    }
626    if (pipe_option) {
627	write_waitpipe(c2p_waitpipe);
628    }
629
630    if (tty != 0)
631	dup2(tty, 0);
632    if (tty != 1)
633	dup2(tty, 1);
634    if (tty != 2)
635	dup2(tty, 2);
636
637    if (tty > 2)
638	close(tty);
639
640    if (pipe_option) {
641	read_waitpipe(p2c_waitpipe);
642	close_waitpipe(0);
643    }
644
645    execvp(path, argv);
646    perror("Couldn't exec");
647    ExitProgram(1);
648}
649
650void
651parent(int pid GCC_UNUSED, int pty)
652{
653    unsigned char buf[BUFFER_SIZE];
654    int i;
655    int rc;
656
657    if (pipe_option) {
658	read_waitpipe(c2p_waitpipe);
659    }
660
661    if (verbose) {
662	reportIso2022(outputState);
663    }
664    setup_io(pty);
665
666    if (pipe_option) {
667	write_waitpipe(p2c_waitpipe);
668	close_waitpipe(1);
669    }
670
671    for (;;) {
672	rc = waitForInput(0, pty);
673
674	if (sigwinch_queued) {
675	    sigwinch_queued = 0;
676	    setWindowSize(0, pty);
677	}
678
679	if (sigchld_queued && exitOnChild)
680	    break;
681
682	if (rc > 0) {
683	    if (rc & 2) {
684		i = (int) read(pty, buf, (size_t) BUFFER_SIZE);
685		if ((i == 0) || ((i < 0) && (errno != EAGAIN)))
686		    break;
687		if (i > 0)
688		    copyOut(outputState, 0, buf, (unsigned) i);
689	    }
690	    if (rc & 1) {
691		i = (int) read(0, buf, (size_t) BUFFER_SIZE);
692		if ((i == 0) || ((i < 0) && (errno != EAGAIN)))
693		    break;
694		if (i > 0)
695		    copyIn(inputState, pty, buf, i);
696	    }
697	}
698    }
699
700    restoreTermios();
701    cleanup_io(pty);
702}
703
704#ifdef NO_LEAKS
705void
706luit_leaks(void)
707{
708    destroyIso2022(inputState);
709    destroyIso2022(outputState);
710}
711#endif
712