]> granicus.if.org Git - sudo/blob - lib/util/term.c
Add SPDX-License-Identifier to files.
[sudo] / lib / util / term.c
1 /*
2  * SPDX-License-Identifier: ISC
3  *
4  * Copyright (c) 2011-2015, 2017 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/types.h>
27 #include <sys/ioctl.h>
28 #include <stdio.h>
29 #include <stdlib.h>
30 #ifdef HAVE_STRING_H
31 # include <string.h>
32 #endif /* HAVE_STRING_H */
33 #ifdef HAVE_STRINGS_H
34 # include <strings.h>
35 #endif /* HAVE_STRINGS_H */
36 #include <errno.h>
37 #include <signal.h>
38 #include <termios.h>
39 #include <unistd.h>
40
41 #include "sudo_compat.h"
42 #include "sudo_debug.h"
43 #include "sudo_util.h"
44
45 /* TCSASOFT is a BSD extension that ignores control flags and speed. */
46 #ifndef TCSASOFT
47 # define TCSASOFT       0
48 #endif
49
50 /* Non-standard termios input flags */
51 #ifndef IUCLC
52 # define IUCLC          0
53 #endif
54 #ifndef IMAXBEL
55 # define IMAXBEL        0
56 #endif
57 #ifndef IUTF8
58 # define IUTF8  0
59 #endif
60
61 /* Non-standard termios output flags */
62 #ifndef OLCUC
63 # define OLCUC  0
64 #endif
65 #ifndef ONLCR
66 # define ONLCR  0
67 #endif
68 #ifndef OCRNL
69 # define OCRNL  0
70 #endif
71 #ifndef ONOCR
72 # define ONOCR  0
73 #endif
74 #ifndef ONLRET
75 # define ONLRET 0
76 #endif
77
78 /* Non-standard termios local flags */
79 #ifndef XCASE
80 # define XCASE          0
81 #endif
82 #ifndef IEXTEN
83 # define IEXTEN         0
84 #endif
85 #ifndef ECHOCTL
86 # define ECHOCTL        0
87 #endif
88 #ifndef ECHOKE
89 # define ECHOKE         0
90 #endif
91 #ifndef PENDIN
92 # define PENDIN         0
93 #endif
94
95 #ifndef _POSIX_VDISABLE
96 # ifdef VDISABLE
97 #  define _POSIX_VDISABLE       VDISABLE
98 # else
99 #  define _POSIX_VDISABLE       0
100 # endif
101 #endif
102
103 static struct termios term, oterm;
104 static int changed;
105
106 /* tgetpass() needs to know the erase and kill chars for cbreak mode. */
107 __dso_public int sudo_term_eof;
108 __dso_public int sudo_term_erase;
109 __dso_public int sudo_term_kill;
110
111 static volatile sig_atomic_t got_sigttou;
112
113 /*
114  * SIGTTOU signal handler for term_restore that just sets a flag.
115  */
116 static void
117 sigttou(int signo)
118 {
119     got_sigttou = 1;
120 }
121
122 /*
123  * Like tcsetattr() but restarts on EINTR _except_ for SIGTTOU.
124  * Returns 0 on success or -1 on failure, setting errno.
125  * Sets got_sigttou on failure if interrupted by SIGTTOU.
126  */
127 static int
128 tcsetattr_nobg(int fd, int flags, struct termios *tp)
129 {
130     struct sigaction sa, osa;
131     int rc;
132
133     /*
134      * If we receive SIGTTOU from tcsetattr() it means we are
135      * not in the foreground process group.
136      * This should be less racy than using tcgetpgrp().
137      */
138     memset(&sa, 0, sizeof(sa));
139     sigemptyset(&sa.sa_mask);
140     sa.sa_handler = sigttou;
141     got_sigttou = 0;
142     sigaction(SIGTTOU, &sa, &osa);
143     do {
144         rc = tcsetattr(fd, flags, tp);
145     } while (rc != 0 && errno == EINTR && !got_sigttou);
146     sigaction(SIGTTOU, &osa, NULL);
147
148     return rc;
149 }
150
151 /*
152  * Restore saved terminal settings if we are in the foreground process group.
153  * Returns true on success or false on failure.
154  */
155 bool
156 sudo_term_restore_v1(int fd, bool flush)
157 {
158     debug_decl(sudo_term_restore, SUDO_DEBUG_UTIL)
159
160     if (changed) {
161         const int flags = flush ? (TCSASOFT|TCSAFLUSH) : (TCSASOFT|TCSADRAIN);
162         if (tcsetattr_nobg(fd, flags, &oterm) != 0)
163             debug_return_bool(false);
164         changed = 0;
165     }
166     debug_return_bool(true);
167 }
168
169 /*
170  * Disable terminal echo.
171  * Returns true on success or false on failure.
172  */
173 bool
174 sudo_term_noecho_v1(int fd)
175 {
176     debug_decl(sudo_term_noecho, SUDO_DEBUG_UTIL)
177
178     if (!changed && tcgetattr(fd, &oterm) != 0)
179         debug_return_bool(false);
180     (void) memcpy(&term, &oterm, sizeof(term));
181     CLR(term.c_lflag, ECHO|ECHONL);
182 #ifdef VSTATUS
183     term.c_cc[VSTATUS] = _POSIX_VDISABLE;
184 #endif
185     if (tcsetattr_nobg(fd, TCSASOFT|TCSADRAIN, &term) == 0) {
186         changed = 1;
187         debug_return_bool(true);
188     }
189     debug_return_bool(false);
190 }
191
192 /*
193  * Set terminal to raw mode.
194  * Returns true on success or false on failure.
195  */
196 bool
197 sudo_term_raw_v1(int fd, int isig)
198 {
199     struct termios term;
200     debug_decl(sudo_term_raw, SUDO_DEBUG_UTIL)
201
202     if (!changed && tcgetattr(fd, &oterm) != 0)
203         debug_return_bool(false);
204     (void) memcpy(&term, &oterm, sizeof(term));
205     /* Set terminal to raw mode */
206     term.c_cc[VMIN] = 1;
207     term.c_cc[VTIME] = 0;
208     CLR(term.c_iflag, ICRNL | IGNCR | INLCR | IUCLC | IXON);
209     CLR(term.c_oflag, OPOST);
210     CLR(term.c_lflag, ECHO | ICANON | ISIG | IEXTEN);
211     if (isig)
212         SET(term.c_lflag, ISIG);
213     if (tcsetattr_nobg(fd, TCSASOFT|TCSADRAIN, &term) == 0) {
214         changed = 1;
215         debug_return_bool(true);
216     }
217     debug_return_bool(false);
218 }
219
220 /*
221  * Set terminal to cbreak mode.
222  * Returns true on success or false on failure.
223  */
224 bool
225 sudo_term_cbreak_v1(int fd)
226 {
227     debug_decl(sudo_term_cbreak, SUDO_DEBUG_UTIL)
228
229     if (!changed && tcgetattr(fd, &oterm) != 0)
230         debug_return_bool(false);
231     (void) memcpy(&term, &oterm, sizeof(term));
232     /* Set terminal to half-cooked mode */
233     term.c_cc[VMIN] = 1;
234     term.c_cc[VTIME] = 0;
235     /* cppcheck-suppress redundantAssignment */
236     CLR(term.c_lflag, ECHO | ECHONL | ICANON | IEXTEN);
237     /* cppcheck-suppress redundantAssignment */
238     SET(term.c_lflag, ISIG);
239 #ifdef VSTATUS
240     term.c_cc[VSTATUS] = _POSIX_VDISABLE;
241 #endif
242     if (tcsetattr_nobg(fd, TCSASOFT|TCSADRAIN, &term) == 0) {
243         sudo_term_eof = term.c_cc[VEOF];
244         sudo_term_erase = term.c_cc[VERASE];
245         sudo_term_kill = term.c_cc[VKILL];
246         changed = 1;
247         debug_return_bool(true);
248     }
249     debug_return_bool(false);
250 }
251
252 /* Termios flags to copy between terminals. */
253 #define INPUT_FLAGS (IGNPAR|PARMRK|INPCK|ISTRIP|INLCR|IGNCR|ICRNL|IUCLC|IXON|IXANY|IXOFF|IMAXBEL|IUTF8)
254 #define OUTPUT_FLAGS (OPOST|OLCUC|ONLCR|OCRNL|ONOCR|ONLRET)
255 #define CONTROL_FLAGS (CS7|CS8|PARENB|PARODD)
256 #define LOCAL_FLAGS (ISIG|ICANON|XCASE|ECHO|ECHOE|ECHOK|ECHONL|NOFLSH|TOSTOP|IEXTEN|ECHOCTL|ECHOKE|PENDIN)
257
258 /*
259  * Copy terminal settings from one descriptor to another.
260  * We cannot simply copy the struct termios as src and dst may be
261  * different terminal types (pseudo-tty vs. console or glass tty).
262  * Returns true on success or false on failure.
263  */
264 bool
265 sudo_term_copy_v1(int src, int dst)
266 {
267     struct termios tt_src, tt_dst;
268     struct winsize wsize;
269     speed_t speed;
270     int i;
271     debug_decl(sudo_term_copy, SUDO_DEBUG_UTIL)
272
273     if (tcgetattr(src, &tt_src) != 0 || tcgetattr(dst, &tt_dst) != 0)
274         debug_return_bool(false);
275
276     /* Clear select input, output, control and local flags. */
277     CLR(tt_dst.c_iflag, INPUT_FLAGS);
278     CLR(tt_dst.c_oflag, OUTPUT_FLAGS);
279     CLR(tt_dst.c_cflag, CONTROL_FLAGS);
280     CLR(tt_dst.c_lflag, LOCAL_FLAGS);
281
282     /* Copy select input, output, control and local flags. */
283     SET(tt_dst.c_iflag, (tt_src.c_iflag & INPUT_FLAGS));
284     SET(tt_dst.c_oflag, (tt_src.c_oflag & OUTPUT_FLAGS));
285     SET(tt_dst.c_cflag, (tt_src.c_cflag & CONTROL_FLAGS));
286     SET(tt_dst.c_lflag, (tt_src.c_lflag & LOCAL_FLAGS));
287
288     /* Copy special chars from src verbatim. */
289     for (i = 0; i < NCCS; i++)
290         tt_dst.c_cc[i] = tt_src.c_cc[i];
291
292     /* Copy speed from src (zero output speed closes the connection). */
293     if ((speed = cfgetospeed(&tt_src)) == B0)
294         speed = B38400;
295     cfsetospeed(&tt_dst, speed);
296     speed = cfgetispeed(&tt_src);
297     cfsetispeed(&tt_dst, speed);
298
299     if (tcsetattr_nobg(dst, TCSASOFT|TCSAFLUSH, &tt_dst) == -1)
300         debug_return_bool(false);
301
302     if (ioctl(src, TIOCGWINSZ, &wsize) == 0)
303         (void)ioctl(dst, TIOCSWINSZ, &wsize);
304
305     debug_return_bool(true);
306 }