*** empty log message ***
[midnight-commander.git] / src / cons.saver.c
blobe93c96efe544714e5ce1f1328fb76c843b9298f4
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.
7 You may want to make the cons.saver setuid root.
8 The code should be safe even if it is setuid but who knows?
10 This program is free software; you can redistribute it and/or modify
11 it under the terms of the GNU General Public License as published by
12 the Free Software Foundation; either version 2 of the License, or
13 (at your option) any later version.
15 This program is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 GNU General Public License for more details.
20 You should have received a copy of the GNU General Public License
21 along with this program; if not, write to the Free Software
22 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
24 /* This code does _not_ need to be setuid root. However, it needs
25 read/write access to /dev/vcsa* (which is priviledged
26 operation). You should create user vcsa, make cons.saver setuid
27 user vcsa, and make all vcsa's owned by user vcsa.
29 Seeing other peoples consoles is bad thing, but believe me, full
30 root is even worse. */
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>
46 typedef struct WINDOW WINDOW;
47 #include "cons.saver.h"
49 #define cmd_input 0
50 #define cmd_output 1
52 /* Meaning of console_flag:
53 -1 == to be detected,
54 0 == not a console
55 1 == is a console, Linux < 1.1.67 (black & white)
56 2 == is a console, Linux >= 1.1.67 (color)
57 3 == is a console, Linux >= 1.1.92 (color, use /dev/vcsa$num
59 static signed char console_flag = -1;
61 Meaning of console_fd:
62 -1 == not opened,
63 >=0 == opened
65 static int console_fd = -1;
66 static char *tty_name;
67 static int console_minor = 0;
68 static char *buffer = NULL;
69 static int buffer_size = 0;
70 static int columns, rows;
71 static int vcs_fd;
73 static void dwrite (int fd, char *buffer)
75 write (fd, buffer, strlen (buffer));
78 static void tty_getsize (void)
80 struct winsize winsz;
82 winsz.ws_col = winsz.ws_row = 0;
83 ioctl (console_fd, TIOCGWINSZ, &winsz);
84 if (winsz.ws_col && winsz.ws_row){
85 columns = winsz.ws_col;
86 rows = winsz.ws_row;
87 } else {
88 /* Never happens (I think) */
89 dwrite (2, "TIOCGWINSZ failed\n");
90 columns = 80;
91 rows = 25;
92 console_flag = 0;
96 static inline void tty_cursormove(int y, int x)
98 char buffer [128];
100 /* Standard ANSI escape sequence for cursor positioning */
101 snprintf (buffer, sizeof (buffer), "\33[%d;%dH", y + 1, x + 1);
102 dwrite (console_fd, buffer);
105 static int check_file (char *filename, int check_console)
107 int fd;
108 struct stat stat_buf;
110 /* Avoiding race conditions: use of fstat makes sure that
111 both 'open' and 'stat' operate on the same file */
113 fd = open (filename, O_RDWR);
114 if (fd == -1)
115 return -1;
117 do {
118 if (fstat (fd, &stat_buf) == -1)
119 break;
121 /* Must be character device */
122 if (!S_ISCHR (stat_buf.st_mode)){
123 break;
126 #ifdef DEBUG
127 fprintf (stderr,
128 "Device %s: major %d, minor %d\r\n",
129 filename,
130 ((int) stat_buf.st_rdev & 0xff00) >> 8,
131 ((int) stat_buf.st_rdev & 0xff));
132 #endif
134 if (check_console){
135 /* Major number must be 4 */
136 if ((stat_buf.st_rdev & 0xff00) != 0x0400){
137 break;
140 /* Minor number must be between 1 and 63 */
141 console_minor = (int) (stat_buf.st_rdev & 0x00ff);
142 if (console_minor < 1 || console_minor > 63){
143 break;
146 /* Must be owned by the user */
147 if (stat_buf.st_uid != getuid ()){
148 break;
151 /* Everything seems to be okay */
152 return fd;
153 } while (0);
155 close (fd);
156 return -1;
159 /* Detect console. Return 0 if successful, -1 otherwise. */
160 /* Because the name of the tty is supplied by the user and this
161 can be a setuid program a lot of checks has to done to avoid
162 creating a security hole */
163 static int detect_console (void)
165 char console_name [16];
166 static char vcs_name [16];
168 /* Must be console */
169 console_fd = check_file (tty_name, 1);
170 if (console_fd == -1)
171 return -1;
174 * Only allow /dev/ttyMINOR and /dev/vc/MINOR where MINOR is the minor
175 * device number of the console, set in check_file()
177 switch (tty_name[5])
179 case 'v':
180 snprintf (console_name, sizeof (console_name), "/dev/vc/%d",
181 console_minor);
182 if (strncmp (console_name, tty_name, sizeof (console_name)) != 0)
183 return -1;
184 break;
185 case 't':
186 snprintf (console_name, sizeof (console_name), "/dev/tty%d",
187 console_minor);
188 if (strncmp (console_name, tty_name, sizeof (console_name)) != 0)
189 return -1;
190 break;
191 default:
192 return -1;
195 snprintf (vcs_name, sizeof (vcs_name), "/dev/vcsa%d", console_minor);
196 vcs_fd = check_file (vcs_name, 0);
198 /* Try devfs name */
199 if (vcs_fd == -1) {
200 snprintf (vcs_name, sizeof (vcs_name), "/dev/vcc/a%d", console_minor);
201 vcs_fd = check_file (vcs_name, 0);
204 #ifdef DEBUG
205 fprintf (stderr, "vcs_fd = %d console_fd = %d\r\n", vcs_fd, console_fd);
206 #endif
208 if (vcs_fd != -1){
209 console_flag = 3;
212 return 0;
215 static void save_console (void)
217 int i;
219 if (!console_flag)
220 return;
221 buffer [1] = console_minor;
222 if (console_flag >= 2){
223 /* Linux >= 1.1.67 */
224 /* Get screen contents and cursor position */
225 buffer [0] = 8;
226 if (console_flag == 2){
227 if ((i = ioctl (console_fd, TIOCLINUX, buffer)) == -1){
228 /* Oops, this is not Linux 1.1.67 */
229 console_flag = 1;
231 } else {
232 lseek (vcs_fd, 0, 0);
233 read (vcs_fd, buffer, buffer_size);
236 if (console_flag == 1){
237 int index, x, y;
239 /* Linux < 1.1.67 */
240 /* Get screen contents */
241 buffer [0] = 0;
242 if (ioctl(console_fd, TIOCLINUX, buffer) == -1){
243 buffer[0] = buffer[1] = 0;
245 /* Linux bug: bad ioctl on console 8 */
246 if (ioctl(console_fd, TIOCLINUX, buffer) == -1){
247 /* Oops, this is not a console after all */
248 console_flag = 0;
249 return;
252 /* Select the beginning of the bottommost empty line
253 to be the cursor position */
254 index = 2 + rows * columns;
255 for (y = rows - 1; y >= 0; y--)
256 for (x = columns - 1; x >= 0; x--)
257 if (buffer[--index] != ' ')
258 goto non_space_found;
259 non_space_found:
260 buffer[0] = y + 1;
261 buffer[1] = 0;
262 /*tty_cursormove(y + 1, 0);*/
266 static void restore_console (void)
268 if (!console_flag)
269 return;
270 if (console_flag == 2){
271 /* Linux >= 1.1.67 */
272 /* Restore screen contents and cursor position */
273 buffer [0] = 9;
274 buffer [1] = console_minor;
275 ioctl (console_fd, TIOCLINUX, buffer);
277 if (console_flag == 3){
278 lseek (vcs_fd, 0, 0);
279 write (vcs_fd, buffer, buffer_size);
281 if (console_flag == 1){
282 /* Clear screen */
283 write(console_fd, "\033[H\033[2J", 7);
284 /* Output saved screen contents */
285 write(console_fd, buffer + 2, rows * columns);
286 /* Move the cursor to the previously selected position */
287 tty_cursormove(buffer[0], buffer[1]);
291 static void send_contents (void)
293 unsigned char begin_line=0, end_line=0;
294 int index, x, y;
295 int lastline;
296 unsigned char message;
297 unsigned short bytes;
298 int bytes_per_char;
300 bytes_per_char = console_flag == 1 ? 1 : 2;
302 /* Calculate the number of used lines */
303 if (console_flag == 2 || console_flag == 1 || console_flag == 3){
304 index = (2 + rows * columns) * bytes_per_char;
305 for (y = rows - 1; y >= 0; y--)
306 for (x = columns - 1; x >= 0; x--){
307 index -= bytes_per_char;
308 if (buffer[index] != ' ')
309 goto non_space_found;
311 non_space_found:
312 lastline = y + 1;
313 } else
314 return;
316 /* Inform the invoker that we can handle this command */
317 message = CONSOLE_CONTENTS;
318 write (cmd_output, &message, 1);
320 /* Read the range of lines wanted */
321 read (cmd_input, &begin_line, 1);
322 read (cmd_input, &end_line, 1);
323 if (begin_line > lastline)
324 begin_line = lastline;
325 if (end_line > lastline)
326 end_line = lastline;
328 /* Tell the invoker how many bytes it will be */
329 bytes = (end_line - begin_line) * columns;
330 write (cmd_output, &bytes, 2);
332 /* Send the contents */
333 for (index = (2 + begin_line * columns) * bytes_per_char;
334 index < (2 + end_line * columns) * bytes_per_char;
335 index += bytes_per_char)
336 write (cmd_output, buffer + index, 1);
338 /* All done */
341 int main (int argc, char **argv)
343 unsigned char action = 0;
344 int stderr_fd;
347 * Make sure Stderr points to a valid place
349 close (2);
350 stderr_fd = open ("/dev/tty", O_RDWR);
351 if (stderr_fd == -1) /* This may well happen if program is running non-root */
352 stderr_fd = open ("/dev/null", O_RDWR);
354 if (stderr_fd == -1)
355 exit (1);
357 if (stderr_fd != 2)
358 while (dup2 (stderr_fd, 2) == -1 && errno == EINTR)
361 if (argc != 2){
362 /* Wrong number of arguments */
364 dwrite (2, "Usage: cons.saver <ttyname>\n");
365 console_flag = 0;
366 write (cmd_output, &console_flag, 1);
367 return 3;
370 /* Lose the control terminal */
371 setsid ();
373 /* Check that the argument is a legal console */
374 tty_name = argv [1];
376 if (detect_console () == -1){
377 /* Not a console -> no need for privileges */
378 setuid (getuid ());
379 /* dwrite (2, error); */
380 console_flag = 0;
381 if (console_fd >= 0)
382 close (console_fd);
383 } else {
384 /* Console was detected */
385 if (console_flag != 3)
386 console_flag = 2; /* Default to Linux >= 1.1.67 */
387 /* Allocate buffer for screen image */
388 tty_getsize ();
389 buffer_size = 4 + 2 * columns * rows;
390 buffer = (char*) malloc (buffer_size);
393 /* If using /dev/vcs*, we don't need anymore the console fd */
394 if (console_flag == 3)
395 close (console_fd);
397 /* Inform the invoker about the result of the tests */
398 write (cmd_output, &console_flag, 1);
400 /* Read commands from the invoker */
401 while (console_flag && read (cmd_input, &action, 1)){
402 /* Handle command */
403 switch (action){
404 case CONSOLE_DONE:
405 console_flag = 0;
406 continue; /* Break while loop instead of switch clause */
407 case CONSOLE_SAVE:
408 save_console ();
409 break;
410 case CONSOLE_RESTORE:
411 restore_console ();
412 break;
413 case CONSOLE_CONTENTS:
414 send_contents ();
415 break;
416 } /* switch (action) */
418 /* Inform the invoker that command is handled */
419 write (cmd_output, &console_flag, 1);
420 } /* while (read ...) */
422 if (buffer)
423 free (buffer);
424 return 0;
427 #else
429 #error "The Linux console screen saver works only on Linux"
431 #endif /* #ifdef linux */