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