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. */
33 #include <sys/types.h>
35 #include <sys/ioctl.h>
44 #include <ctype.h> /* For isdigit() */
46 typedef struct WINDOW WINDOW
;
47 #include "cons.saver.h"
52 /* Meaning of console_flag:
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:
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
;
73 static void dwrite (int fd
, char *buffer
)
75 write (fd
, buffer
, strlen (buffer
));
78 static void tty_getsize (void)
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
;
88 /* Never happens (I think) */
89 dwrite (2, "TIOCGWINSZ failed\n");
96 static inline void tty_cursormove(int y
, int x
)
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
)
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
);
118 if (fstat (fd
, &stat_buf
) == -1)
121 /* Must be character device */
122 if (!S_ISCHR (stat_buf
.st_mode
)){
128 "Device %s: major %d, minor %d\r\n",
130 ((int) stat_buf
.st_rdev
& 0xff00) >> 8,
131 ((int) stat_buf
.st_rdev
& 0xff));
135 /* Major number must be 4 */
136 if ((stat_buf
.st_rdev
& 0xff00) != 0x0400){
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){
146 /* Must be owned by the user */
147 if (stat_buf
.st_uid
!= getuid ()){
151 /* Everything seems to be okay */
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)
174 * Only allow /dev/ttyMINOR and /dev/vc/MINOR where MINOR is the minor
175 * device number of the console, set in check_file()
180 snprintf (console_name
, sizeof (console_name
), "/dev/vc/%d",
182 if (strncmp (console_name
, tty_name
, sizeof (console_name
)) != 0)
186 snprintf (console_name
, sizeof (console_name
), "/dev/tty%d",
188 if (strncmp (console_name
, tty_name
, sizeof (console_name
)) != 0)
195 snprintf (vcs_name
, sizeof (vcs_name
), "/dev/vcsa%d", console_minor
);
196 vcs_fd
= check_file (vcs_name
, 0);
200 snprintf (vcs_name
, sizeof (vcs_name
), "/dev/vcc/a%d", console_minor
);
201 vcs_fd
= check_file (vcs_name
, 0);
205 fprintf (stderr
, "vcs_fd = %d console_fd = %d\r\n", vcs_fd
, console_fd
);
215 static void save_console (void)
221 buffer
[1] = console_minor
;
222 if (console_flag
>= 2){
223 /* Linux >= 1.1.67 */
224 /* Get screen contents and cursor position */
226 if (console_flag
== 2){
227 if ((i
= ioctl (console_fd
, TIOCLINUX
, buffer
)) == -1){
228 /* Oops, this is not Linux 1.1.67 */
232 lseek (vcs_fd
, 0, 0);
233 read (vcs_fd
, buffer
, buffer_size
);
236 if (console_flag
== 1){
240 /* Get screen contents */
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 */
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
;
262 /*tty_cursormove(y + 1, 0);*/
266 static void restore_console (void)
270 if (console_flag
== 2){
271 /* Linux >= 1.1.67 */
272 /* Restore screen contents and cursor position */
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){
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;
296 unsigned char message
;
297 unsigned short bytes
;
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
;
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
)
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);
341 int main (int argc
, char **argv
)
343 unsigned char action
= 0;
347 * Make sure Stderr points to a valid place
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
);
358 while (dup2 (stderr_fd
, 2) == -1 && errno
== EINTR
)
362 /* Wrong number of arguments */
364 dwrite (2, "Usage: cons.saver <ttyname>\n");
366 write (cmd_output
, &console_flag
, 1);
370 /* Lose the control terminal */
373 /* Check that the argument is a legal console */
376 if (detect_console () == -1){
377 /* Not a console -> no need for privileges */
379 /* dwrite (2, error); */
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 */
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)
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)){
406 continue; /* Break while loop instead of switch clause */
410 case CONSOLE_RESTORE
:
413 case CONSOLE_CONTENTS
:
416 } /* switch (action) */
418 /* Inform the invoker that command is handled */
419 write (cmd_output
, &console_flag
, 1);
420 } /* while (read ...) */
429 #error "The Linux console screen saver works only on Linux"
431 #endif /* #ifdef linux */