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., 675 Mass Ave, Cambridge, MA 02139, 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() */
45 typedef struct WINDOW WINDOW
;
46 #include "cons.saver.h"
51 /* Meaning of console_flag:
54 1 == is a console, Linux < 1.1.67 (black & white)
55 2 == is a console, Linux >= 1.1.67 (color)
56 3 == is a console, Linux >= 1.1.92 (color, use /dev/vcsa$num
58 static signed char console_flag
= -1;
60 Meaning of console_fd:
64 static int console_fd
= -1;
65 static char *tty_name
;
67 static char *buffer
= NULL
;
68 static int buffer_size
= 0;
69 static int columns
, rows
;
70 static char vcs_name
[128];
73 static void dwrite (int fd
, char *buffer
)
75 write (fd
, buffer
, strlen (buffer
));
78 static void tty_getsize ()
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 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 int check_file (char *filename
, int check_console
, char **msg
)
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 */
115 fd
= open (filename
, O_RDWR
);
119 if (fstat (fd
, &stat_buf
) == -1)
122 /* Must be character device */
123 if (!S_ISCHR (stat_buf
.st_mode
)){
124 *msg
= "Not a character device";
129 fprintf (stderr
, "Device: %x\n", stat_buf
.st_rdev
);
132 /* Second time: must be console */
133 if ((stat_buf
.st_rdev
& 0xff00) != 0x0400){
134 *msg
= "Not a console";
138 if ((stat_buf
.st_rdev
& 0x00ff) > 63){
139 *msg
= "Minor device number too big";
143 /* Must be owned by the user */
144 if (stat_buf
.st_uid
!= getuid ()){
145 *msg
= "Not a owner";
150 /* Everything seems to be okay */
155 /* Because the name of the tty is supplied by the user and this
156 can be a setuid program a lot of checks has to done to avoid
157 creating a security hole */
158 char *detect_console (void)
163 /* Must be console */
164 /* Handle the case for /dev/tty?? */
165 if (tty_name
[len
-5] == 't')
170 /* General: /dev/ttyn */
171 if (tty_name
[xlen
- 5] != '/' ||
172 tty_name
[xlen
- 4] != 't' ||
173 tty_name
[xlen
- 3] != 't' ||
174 tty_name
[xlen
- 2] != 'y' ||
175 !isdigit(tty_name
[xlen
- 1]) ||
176 !isdigit(tty_name
[len
- 1]))
177 return "Doesn't look like console";
179 snprintf (vcs_name
, sizeof (vcs_name
), "/dev/vcsa%s", tty_name
+ xlen
- 1);
180 vcs_fd
= check_file (vcs_name
, 0, &msg
);
181 console_fd
= check_file (tty_name
, 1, &msg
);
184 fprintf (stderr
, "vcs_fd = %d console_fd = %d\n", vcs_fd
, console_fd
);
191 if (console_fd
== -1)
197 void save_console (void)
203 buffer
[1] = tty_name
[len
-1] - '0';
204 if (console_flag
>= 2){
205 /* Linux >= 1.1.67 */
206 /* Get screen contents and cursor position */
208 if (console_flag
== 2){
209 if ((i
= ioctl (console_fd
, TIOCLINUX
, buffer
)) == -1){
210 /* Oops, this is not Linux 1.1.67 */
214 lseek (vcs_fd
, 0, 0);
215 read (vcs_fd
, buffer
, buffer_size
);
218 if (console_flag
== 1){
222 /* Get screen contents */
224 if (ioctl(console_fd
, TIOCLINUX
, buffer
) == -1){
225 buffer
[0] = buffer
[1] = 0;
227 /* Linux bug: bad ioctl on console 8 */
228 if (ioctl(console_fd
, TIOCLINUX
, buffer
) == -1){
229 /* Oops, this is not a console after all */
234 /* Select the beginning of the bottommost empty line
235 to be the cursor position */
236 index
= 2 + rows
* columns
;
237 for (y
= rows
- 1; y
>= 0; y
--)
238 for (x
= columns
- 1; x
>= 0; x
--)
239 if (buffer
[--index
] != ' ')
240 goto non_space_found
;
244 /*tty_cursormove(y + 1, 0);*/
248 void restore_console (void)
252 if (console_flag
== 2){
253 /* Linux >= 1.1.67 */
254 /* Restore screen contents and cursor position */
256 buffer
[1] = tty_name
[len
-1] - '0';
257 ioctl (console_fd
, TIOCLINUX
, buffer
);
259 if (console_flag
== 3){
260 lseek (vcs_fd
, 0, 0);
261 write (vcs_fd
, buffer
, buffer_size
);
263 if (console_flag
== 1){
265 write(console_fd
, "\033[H\033[2J", 7);
266 /* Output saved screen contents */
267 write(console_fd
, buffer
+ 2, rows
* columns
);
268 /* Move the cursor to the previously selected position */
269 tty_cursormove(buffer
[0], buffer
[1]);
273 void send_contents ()
275 unsigned char begin_line
=0, end_line
=0;
278 unsigned char message
;
279 unsigned short bytes
;
282 bytes_per_char
= console_flag
== 1 ? 1 : 2;
284 /* Calculate the number of used lines */
285 if (console_flag
== 2 || console_flag
== 1 || console_flag
== 3){
286 index
= (2 + rows
* columns
) * bytes_per_char
;
287 for (y
= rows
- 1; y
>= 0; y
--)
288 for (x
= columns
- 1; x
>= 0; x
--){
289 index
-= bytes_per_char
;
290 if (buffer
[index
] != ' ')
291 goto non_space_found
;
298 /* Inform the invoker that we can handle this command */
299 message
= CONSOLE_CONTENTS
;
300 write (cmd_output
, &message
, 1);
302 /* Read the range of lines wanted */
303 read (cmd_input
, &begin_line
, 1);
304 read (cmd_input
, &end_line
, 1);
305 if (begin_line
> lastline
)
306 begin_line
= lastline
;
307 if (end_line
> lastline
)
310 /* Tell the invoker how many bytes it will be */
311 bytes
= (end_line
- begin_line
) * columns
;
312 write (cmd_output
, &bytes
, 2);
314 /* Send the contents */
315 for (index
= (2 + begin_line
* columns
) * bytes_per_char
;
316 index
< (2 + end_line
* columns
) * bytes_per_char
;
317 index
+= bytes_per_char
)
318 write (cmd_output
, buffer
+ index
, 1);
323 int main (int argc
, char **argv
)
326 unsigned char action
= 0;
330 * Make sure Stderr points to a valid place
333 stderr_fd
= open ("/dev/tty", O_RDWR
);
334 if (stderr_fd
== -1) /* This may well happen if program is running non-root */
335 stderr_fd
= open ("/dev/null", O_RDWR
);
341 while (dup2 (stderr_fd
, 2) == -1 && errno
== EINTR
)
345 /* Wrong number of arguments */
347 dwrite (2, "Usage: cons.saver <ttyname>\n");
349 write (cmd_output
, &console_flag
, 1);
353 /* Lose the control terminal */
356 /* Check that the argument is a legal console */
358 len
= strlen(tty_name
);
359 error
= detect_console ();
362 /* Not a console -> no need for privileges */
364 /* dwrite (2, error); */
369 /* Console was detected */
370 if (console_flag
!= 3)
371 console_flag
= 2; /* Default to Linux >= 1.1.67 */
372 /* Allocate buffer for screen image */
374 buffer_size
= 4 + 2 * columns
* rows
;
375 buffer
= (char*) malloc (buffer_size
);
378 /* If using /dev/vcs*, we don't need anymore the console fd */
379 if (console_flag
== 3)
382 /* Inform the invoker about the result of the tests */
383 write (cmd_output
, &console_flag
, 1);
385 /* Read commands from the invoker */
386 while (console_flag
&& read (cmd_input
, &action
, 1)){
391 continue; /* Break while loop instead of switch clause */
395 case CONSOLE_RESTORE
:
398 case CONSOLE_CONTENTS
:
401 } /* switch (action) */
403 /* Inform the invoker that command is handled */
404 write (cmd_output
, &console_flag
, 1);
405 } /* while (read ...) */
414 #error The Linux console screen saver works only on Linux.
416 #endif /* #ifdef linux */