1 /*
2  * SPDX-License-Identifier: ISC
3  *
4  * Copyright (c) 2011-2015, 2017-2020 Todd C. Miller <Todd.Miller@sudo.ws>
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18 
19 /*
20  * This is an open source non-commercial project. Dear PVS-Studio, please check it.
21  * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
22  */
23 
24 #include <config.h>
25 
26 #include <sys/ioctl.h>
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <errno.h>
31 #include <signal.h>
32 #include <termios.h>
33 #include <unistd.h>
34 
35 #include "sudo_compat.h"
36 #include "sudo_debug.h"
37 #include "sudo_util.h"
38 
39 /* TCSASOFT is a BSD extension that ignores control flags and speed. */
40 #ifndef TCSASOFT
41 # define TCSASOFT	0
42 #endif
43 
44 /* Non-standard termios input flags */
45 #ifndef IUCLC
46 # define IUCLC		0
47 #endif
48 #ifndef IMAXBEL
49 # define IMAXBEL	0
50 #endif
51 #ifndef IUTF8
52 # define IUTF8	0
53 #endif
54 
55 /* Non-standard termios output flags */
56 #ifndef OLCUC
57 # define OLCUC	0
58 #endif
59 #ifndef ONLCR
60 # define ONLCR	0
61 #endif
62 #ifndef OCRNL
63 # define OCRNL	0
64 #endif
65 #ifndef ONOCR
66 # define ONOCR	0
67 #endif
68 #ifndef ONLRET
69 # define ONLRET	0
70 #endif
71 
72 /* Non-standard termios local flags */
73 #ifndef XCASE
74 # define XCASE		0
75 #endif
76 #ifndef IEXTEN
77 # define IEXTEN		0
78 #endif
79 #ifndef ECHOCTL
80 # define ECHOCTL	0
81 #endif
82 #ifndef ECHOKE
83 # define ECHOKE		0
84 #endif
85 #ifndef PENDIN
86 # define PENDIN		0
87 #endif
88 
89 static struct termios oterm;
90 static int changed;
91 
92 /* tgetpass() needs to know the erase and kill chars for cbreak mode. */
93 sudo_dso_public int sudo_term_eof;
94 sudo_dso_public int sudo_term_erase;
95 sudo_dso_public int sudo_term_kill;
96 
97 static volatile sig_atomic_t got_sigttou;
98 
99 /*
100  * SIGTTOU signal handler for term_restore that just sets a flag.
101  */
102 static void
sigttou(int signo)103 sigttou(int signo)
104 {
105     got_sigttou = 1;
106 }
107 
108 /*
109  * Like tcsetattr() but restarts on EINTR _except_ for SIGTTOU.
110  * Returns 0 on success or -1 on failure, setting errno.
111  * Sets got_sigttou on failure if interrupted by SIGTTOU.
112  */
113 static int
tcsetattr_nobg(int fd,int flags,struct termios * tp)114 tcsetattr_nobg(int fd, int flags, struct termios *tp)
115 {
116     struct sigaction sa, osa;
117     int rc;
118 
119     /*
120      * If we receive SIGTTOU from tcsetattr() it means we are
121      * not in the foreground process group.
122      * This should be less racy than using tcgetpgrp().
123      */
124     memset(&sa, 0, sizeof(sa));
125     sigemptyset(&sa.sa_mask);
126     sa.sa_handler = sigttou;
127     got_sigttou = 0;
128     sigaction(SIGTTOU, &sa, &osa);
129     do {
130 	rc = tcsetattr(fd, flags, tp);
131     } while (rc != 0 && errno == EINTR && !got_sigttou);
132     sigaction(SIGTTOU, &osa, NULL);
133 
134     return rc;
135 }
136 
137 /*
138  * Restore saved terminal settings if we are in the foreground process group.
139  * Returns true on success or false on failure.
140  */
141 bool
sudo_term_restore_v1(int fd,bool flush)142 sudo_term_restore_v1(int fd, bool flush)
143 {
144     debug_decl(sudo_term_restore, SUDO_DEBUG_UTIL);
145 
146     if (changed) {
147 	const int flags = flush ? (TCSASOFT|TCSAFLUSH) : (TCSASOFT|TCSADRAIN);
148 	if (tcsetattr_nobg(fd, flags, &oterm) != 0)
149 	    debug_return_bool(false);
150 	changed = 0;
151     }
152     debug_return_bool(true);
153 }
154 
155 /*
156  * Disable terminal echo.
157  * Returns true on success or false on failure.
158  */
159 bool
sudo_term_noecho_v1(int fd)160 sudo_term_noecho_v1(int fd)
161 {
162     struct termios term;
163     debug_decl(sudo_term_noecho, SUDO_DEBUG_UTIL);
164 
165     if (!changed && tcgetattr(fd, &oterm) != 0)
166 	debug_return_bool(false);
167     (void) memcpy(&term, &oterm, sizeof(term));
168     CLR(term.c_lflag, ECHO|ECHONL);
169 #ifdef VSTATUS
170     term.c_cc[VSTATUS] = _POSIX_VDISABLE;
171 #endif
172     if (tcsetattr_nobg(fd, TCSASOFT|TCSADRAIN, &term) == 0) {
173 	changed = 1;
174 	debug_return_bool(true);
175     }
176     debug_return_bool(false);
177 }
178 
179 /*
180  * Set terminal to raw mode with optional terminal signals.
181  * Returns true on success or false on failure.
182  */
183 bool
sudo_term_raw_v1(int fd,int isig)184 sudo_term_raw_v1(int fd, int isig)
185 {
186     struct termios term;
187     debug_decl(sudo_term_raw, SUDO_DEBUG_UTIL);
188 
189     if (!changed && tcgetattr(fd, &oterm) != 0)
190 	debug_return_bool(false);
191     (void) memcpy(&term, &oterm, sizeof(term));
192     /* Set terminal to raw mode but optionally enable terminal signals. */
193     cfmakeraw(&term);
194     if (isig)
195 	SET(term.c_lflag, ISIG);
196     if (tcsetattr_nobg(fd, TCSASOFT|TCSADRAIN, &term) == 0) {
197 	changed = 1;
198     	debug_return_bool(true);
199     }
200     debug_return_bool(false);
201 }
202 
203 /*
204  * Set terminal to cbreak mode.
205  * Returns true on success or false on failure.
206  */
207 bool
sudo_term_cbreak_v1(int fd)208 sudo_term_cbreak_v1(int fd)
209 {
210     struct termios term;
211     debug_decl(sudo_term_cbreak, SUDO_DEBUG_UTIL);
212 
213     if (!changed && tcgetattr(fd, &oterm) != 0)
214 	debug_return_bool(false);
215     (void) memcpy(&term, &oterm, sizeof(term));
216     /* Set terminal to half-cooked mode */
217     term.c_cc[VMIN] = 1;
218     term.c_cc[VTIME] = 0;
219     /* cppcheck-suppress redundantAssignment */
220     CLR(term.c_lflag, ECHO | ECHONL | ICANON | IEXTEN);
221     /* cppcheck-suppress redundantAssignment */
222     SET(term.c_lflag, ISIG);
223 #ifdef VSTATUS
224     term.c_cc[VSTATUS] = _POSIX_VDISABLE;
225 #endif
226     if (tcsetattr_nobg(fd, TCSASOFT|TCSADRAIN, &term) == 0) {
227 	sudo_term_eof = term.c_cc[VEOF];
228 	sudo_term_erase = term.c_cc[VERASE];
229 	sudo_term_kill = term.c_cc[VKILL];
230 	changed = 1;
231 	debug_return_bool(true);
232     }
233     debug_return_bool(false);
234 }
235 
236 /* Termios flags to copy between terminals. */
237 #define INPUT_FLAGS (IGNPAR|PARMRK|INPCK|ISTRIP|INLCR|IGNCR|ICRNL|IUCLC|IXON|IXANY|IXOFF|IMAXBEL|IUTF8)
238 #define OUTPUT_FLAGS (OPOST|OLCUC|ONLCR|OCRNL|ONOCR|ONLRET)
239 #define CONTROL_FLAGS (CS7|CS8|PARENB|PARODD)
240 #define LOCAL_FLAGS (ISIG|ICANON|XCASE|ECHO|ECHOE|ECHOK|ECHONL|NOFLSH|TOSTOP|IEXTEN|ECHOCTL|ECHOKE|PENDIN)
241 
242 /*
243  * Copy terminal settings from one descriptor to another.
244  * We cannot simply copy the struct termios as src and dst may be
245  * different terminal types (pseudo-tty vs. console or glass tty).
246  * Returns true on success or false on failure.
247  */
248 bool
sudo_term_copy_v1(int src,int dst)249 sudo_term_copy_v1(int src, int dst)
250 {
251     struct termios tt_src, tt_dst;
252     struct winsize wsize;
253     speed_t speed;
254     int i;
255     debug_decl(sudo_term_copy, SUDO_DEBUG_UTIL);
256 
257     if (tcgetattr(src, &tt_src) != 0 || tcgetattr(dst, &tt_dst) != 0)
258 	debug_return_bool(false);
259 
260     /* Clear select input, output, control and local flags. */
261     CLR(tt_dst.c_iflag, INPUT_FLAGS);
262     CLR(tt_dst.c_oflag, OUTPUT_FLAGS);
263     CLR(tt_dst.c_cflag, CONTROL_FLAGS);
264     CLR(tt_dst.c_lflag, LOCAL_FLAGS);
265 
266     /* Copy select input, output, control and local flags. */
267     SET(tt_dst.c_iflag, (tt_src.c_iflag & INPUT_FLAGS));
268     SET(tt_dst.c_oflag, (tt_src.c_oflag & OUTPUT_FLAGS));
269     SET(tt_dst.c_cflag, (tt_src.c_cflag & CONTROL_FLAGS));
270     SET(tt_dst.c_lflag, (tt_src.c_lflag & LOCAL_FLAGS));
271 
272     /* Copy special chars from src verbatim. */
273     for (i = 0; i < NCCS; i++)
274 	tt_dst.c_cc[i] = tt_src.c_cc[i];
275 
276     /* Copy speed from src (zero output speed closes the connection). */
277     if ((speed = cfgetospeed(&tt_src)) == B0)
278 	speed = B38400;
279     cfsetospeed(&tt_dst, speed);
280     speed = cfgetispeed(&tt_src);
281     cfsetispeed(&tt_dst, speed);
282 
283     if (tcsetattr_nobg(dst, TCSASOFT|TCSAFLUSH, &tt_dst) == -1)
284 	debug_return_bool(false);
285 
286     if (ioctl(src, TIOCGWINSZ, &wsize) == 0)
287 	(void)ioctl(dst, TIOCSWINSZ, &wsize);
288 
289     debug_return_bool(true);
290 }
291