s/paramter/parameter/
[midnight-commander.git] / src / cons.saver.c
blob06c0955dd17c1c357f71168ff526d322ceaa05db
1 #if defined(linux) || defined(__linux__)
3 /* General purpose Linux console screen save/restore server
4 Copyright (C) 1994 Janne Kukonlehto <jtklehto@stekt.oulu.fi>
5 Original idea from Unix Interactive Tools version 3.2b (tty.c)
6 This code requires root privileges.
8 This program is free software; you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation; either version 2 of the License, or
11 (at your option) any later version.
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
18 You should have received a copy of the GNU General Public License
19 along with this program; if not, write to the Free Software
20 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
22 /* cons.saver is run by MC to save and restore screen contents on Linux
23 virtual console. This is done by using file /dev/vcsaN or /dev/vcc/aN,
24 where N is the number of the virtual console.
25 In a properly set up system, /dev/vcsaN should become accessible
26 by the user when the user logs in on the corresponding console
27 /dev/ttyN or /dev/vc/N. In this case, cons.saver doesn't need to be
28 suid root. However, if /dev/vcsaN is not accessible by the user,
29 cons.saver can be made setuid root - this program is designed to be
30 safe even in this case. */
32 #include <config.h>
33 #include <sys/types.h>
34 #include <sys/stat.h>
35 #include <sys/ioctl.h>
36 #include <fcntl.h>
37 #include <errno.h>
38 #ifdef HAVE_UNISTD_H
39 # include <unistd.h>
40 #endif
41 #include <termios.h>
42 #include <stdlib.h>
43 #include <stdio.h>
44 #include <ctype.h> /* For isdigit() */
45 #include <string.h>
47 #define LINUX_CONS_SAVER_C
48 #include "cons.saver.h"
50 #define cmd_input 0
51 #define cmd_output 1
53 static int console_minor = 0;
54 static char *buffer = NULL;
55 static int buffer_size = 0;
56 static int columns, rows;
57 static int vcs_fd;
61 * Get window size for the given terminal.
62 * Return 0 for success, -1 otherwise.
64 static int tty_getsize (int console_fd)
66 struct winsize winsz;
68 winsz.ws_col = winsz.ws_row = 0;
69 ioctl (console_fd, TIOCGWINSZ, &winsz);
70 if (winsz.ws_col && winsz.ws_row) {
71 columns = winsz.ws_col;
72 rows = winsz.ws_row;
73 return 0;
76 return -1;
80 * Do various checks to make sure that the supplied filename is
81 * a suitable tty device. If check_console is set, check that we
82 * are dealing with a Linux virtual console.
83 * Return 0 for success, -1 otherwise.
85 static int check_file (char *filename, int check_console)
87 int fd;
88 struct stat stat_buf;
90 /* Avoiding race conditions: use of fstat makes sure that
91 both 'open' and 'stat' operate on the same file */
93 fd = open (filename, O_RDWR);
94 if (fd == -1)
95 return -1;
97 do {
98 if (fstat (fd, &stat_buf) == -1)
99 break;
101 /* Must be character device */
102 if (!S_ISCHR (stat_buf.st_mode)){
103 break;
106 if (check_console){
107 /* Major number must be 4 */
108 if ((stat_buf.st_rdev & 0xff00) != 0x0400){
109 break;
112 /* Minor number must be between 1 and 63 */
113 console_minor = (int) (stat_buf.st_rdev & 0x00ff);
114 if (console_minor < 1 || console_minor > 63){
115 break;
118 /* Must be owned by the user */
119 if (stat_buf.st_uid != getuid ()){
120 break;
123 /* Everything seems to be okay */
124 return fd;
125 } while (0);
127 close (fd);
128 return -1;
132 * Check if the supplied filename is a Linux virtual console.
133 * Return 0 if successful, -1 otherwise.
134 * Since the tty name is supplied by the user and cons.saver can be
135 * a setuid program, many checks have to be done to prevent possible
136 * security compromise.
138 static int detect_console (char *tty_name)
140 char console_name [16];
141 static char vcs_name [16];
142 int console_fd;
144 /* Must be console */
145 console_fd = check_file (tty_name, 1);
146 if (console_fd == -1)
147 return -1;
149 if (tty_getsize (console_fd) == -1) {
150 close (console_fd);
151 return -1;
154 close (console_fd);
157 * Only allow /dev/ttyMINOR and /dev/vc/MINOR where MINOR is the minor
158 * device number of the console, set in check_file()
160 switch (tty_name[5])
162 case 'v':
163 snprintf (console_name, sizeof (console_name), "/dev/vc/%d",
164 console_minor);
165 if (strncmp (console_name, tty_name, sizeof (console_name)) != 0)
166 return -1;
167 break;
168 case 't':
169 snprintf (console_name, sizeof (console_name), "/dev/tty%d",
170 console_minor);
171 if (strncmp (console_name, tty_name, sizeof (console_name)) != 0)
172 return -1;
173 break;
174 default:
175 return -1;
178 snprintf (vcs_name, sizeof (vcs_name), "/dev/vcsa%d", console_minor);
179 vcs_fd = check_file (vcs_name, 0);
181 /* Try devfs name */
182 if (vcs_fd == -1) {
183 snprintf (vcs_name, sizeof (vcs_name), "/dev/vcc/a%d", console_minor);
184 vcs_fd = check_file (vcs_name, 0);
187 if (vcs_fd == -1)
188 return -1;
190 return 0;
193 static void save_console (void)
195 lseek (vcs_fd, 0, SEEK_SET);
196 read (vcs_fd, buffer, buffer_size);
199 static void restore_console (void)
201 lseek (vcs_fd, 0, SEEK_SET);
202 write (vcs_fd, buffer, buffer_size);
205 static void send_contents (void)
207 unsigned char begin_line=0, end_line=0;
208 int index;
209 unsigned char message;
210 unsigned short bytes;
211 const int bytes_per_char = 2;
213 /* Inform the invoker that we can handle this command */
214 message = CONSOLE_CONTENTS;
215 write (cmd_output, &message, 1);
217 /* Read the range of lines wanted */
218 read (cmd_input, &begin_line, 1);
219 read (cmd_input, &end_line, 1);
220 if (begin_line > rows)
221 begin_line = rows;
222 if (end_line > rows)
223 end_line = rows;
225 /* Tell the invoker how many bytes it will be */
226 bytes = (end_line - begin_line) * columns;
227 write (cmd_output, &bytes, 2);
229 /* Send the contents */
230 for (index = (2 + begin_line * columns) * bytes_per_char;
231 index < (2 + end_line * columns) * bytes_per_char;
232 index += bytes_per_char)
233 write (cmd_output, buffer + index, 1);
235 /* All done */
238 int main (int argc, char **argv)
240 unsigned char action = 0;
241 int stderr_fd;
243 /* 0 - not a console, 3 - supported Linux console */
244 signed char console_flag = 0;
247 * Make sure stderr points to a valid place
249 close (2);
250 stderr_fd = open ("/dev/tty", O_RDWR);
252 /* This may well happen if program is running non-root */
253 if (stderr_fd == -1)
254 stderr_fd = open ("/dev/null", O_RDWR);
256 if (stderr_fd == -1)
257 exit (1);
259 if (stderr_fd != 2)
260 while (dup2 (stderr_fd, 2) == -1 && errno == EINTR)
263 if (argc != 2){
264 /* Wrong number of arguments */
266 write (cmd_output, &console_flag, 1);
267 return 3;
270 /* Lose the control terminal */
271 setsid ();
273 /* Check that the argument is a Linux console */
274 if (detect_console (argv [1]) == -1) {
275 /* Not a console -> no need for privileges */
276 setuid (getuid ());
277 } else {
278 console_flag = 3;
279 /* Allocate buffer for screen image */
280 buffer_size = 4 + 2 * columns * rows;
281 buffer = (char*) malloc (buffer_size);
284 /* Inform the invoker about the result of the tests */
285 write (cmd_output, &console_flag, 1);
287 /* Read commands from the invoker */
288 while (console_flag && read (cmd_input, &action, 1)){
289 /* Handle command */
290 switch (action){
291 case CONSOLE_DONE:
292 console_flag = 0;
293 continue; /* Break while loop instead of switch clause */
294 case CONSOLE_SAVE:
295 save_console ();
296 break;
297 case CONSOLE_RESTORE:
298 restore_console ();
299 break;
300 case CONSOLE_CONTENTS:
301 send_contents ();
302 break;
303 } /* switch (action) */
305 /* Inform the invoker that command has been handled */
306 write (cmd_output, &console_flag, 1);
307 } /* while (read ...) */
309 if (buffer)
310 free (buffer);
311 return 0;
314 #else
316 #error "The Linux console screen saver works only on Linux"
318 #endif /* #ifdef linux */