1999-09-09 Federico Mena Quintero <federico@redhat.com>
[midnight-commander.git] / src / cons.saver.c
blob276de581fef311ed2a68a4ba63d85dd0693c67f9
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. */
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 typedef struct WINDOW WINDOW;
46 #include "cons.saver.h"
48 #define cmd_input 0
49 #define cmd_output 1
51 /* Meaning of console_flag:
52 -1 == to be detected,
53 0 == not a console
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:
61 -1 == not opened,
62 >=0 == opened
64 static int console_fd = -1;
65 static char *tty_name;
66 static int len;
67 static char *buffer = NULL;
68 static int buffer_size = 0;
69 static int columns, rows;
70 static char vcs_name [128];
71 static int vcs_fd;
73 static void dwrite (int fd, char *buffer)
75 write (fd, buffer, strlen (buffer));
78 static void tty_getsize ()
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 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 int check_file (char *filename, int check_console, char **msg)
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 *msg = 0;
115 fd = open (filename, O_RDWR);
116 if (fd == -1)
117 return -1;
119 if (fstat (fd, &stat_buf) == -1)
120 return -1;
122 /* Must be character device */
123 if (!S_ISCHR (stat_buf.st_mode)){
124 *msg = "Not a character device";
125 return -1;
128 #ifdef DEBUG
129 fprintf (stderr, "Device: %x\n", stat_buf.st_rdev);
130 #endif
131 if (check_console){
132 /* Second time: must be console */
133 if ((stat_buf.st_rdev & 0xff00) != 0x0400){
134 *msg = "Not a console";
135 return -1;
138 if ((stat_buf.st_rdev & 0x00ff) > 63){
139 *msg = "Minor device number too big";
140 return -1;
143 /* Must be owned by the user */
144 if (stat_buf.st_uid != getuid ()){
145 *msg = "Not a owner";
146 return -1;
150 /* Everything seems to be okay */
151 return fd;
154 /* Detect console */
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)
160 char *msg;
161 int xlen;
163 /* Must be console */
164 /* Handle the case for /dev/tty?? */
165 if (tty_name[len-5] == 't')
166 xlen = len - 1;
167 else
168 xlen = len;
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);
183 #ifdef DEBUG
184 fprintf (stderr, "vcs_fd = %d console_fd = %d\n", vcs_fd, console_fd);
185 #endif
187 if (vcs_fd != -1){
188 console_flag = 3;
191 if (console_fd == -1)
192 return msg;
194 return NULL;
197 void save_console (void)
199 int i;
201 if (!console_flag)
202 return;
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 */
207 buffer [0] = 8;
208 if (console_flag == 2){
209 if ((i = ioctl (console_fd, TIOCLINUX, buffer)) == -1){
210 /* Oops, this is not Linux 1.1.67 */
211 console_flag = 1;
213 } else {
214 lseek (vcs_fd, 0, 0);
215 read (vcs_fd, buffer, buffer_size);
218 if (console_flag == 1){
219 int index, x, y;
221 /* Linux < 1.1.67 */
222 /* Get screen contents */
223 buffer [0] = 0;
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 */
230 console_flag = 0;
231 return;
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;
241 non_space_found:
242 buffer[0] = y + 1;
243 buffer[1] = 0;
244 /*tty_cursormove(y + 1, 0);*/
248 void restore_console (void)
250 if (!console_flag)
251 return;
252 if (console_flag == 2){
253 /* Linux >= 1.1.67 */
254 /* Restore screen contents and cursor position */
255 buffer [0] = 9;
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){
264 /* Clear screen */
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;
276 int index, x, y;
277 int lastline;
278 unsigned char message;
279 unsigned short bytes;
280 int bytes_per_char;
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;
293 non_space_found:
294 lastline = y + 1;
295 } else
296 return;
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)
308 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);
320 /* All done */
323 int main (int argc, char **argv)
325 char *error;
326 unsigned char action = 0;
327 int stderr_fd;
330 * Make sure Stderr points to a valid place
332 close (2);
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);
337 if (stderr_fd == -1)
338 exit (1);
340 if (stderr_fd != 2)
341 while (dup2 (stderr_fd, 2) == -1 && errno == EINTR)
344 if (argc != 2){
345 /* Wrong number of arguments */
347 dwrite (2, "Usage: cons.saver <ttyname>\n");
348 console_flag = 0;
349 write (cmd_output, &console_flag, 1);
350 return 3;
353 /* Lose the control terminal */
354 setsid ();
356 /* Check that the argument is a legal console */
357 tty_name = argv [1];
358 len = strlen(tty_name);
359 error = detect_console ();
361 if (error){
362 /* Not a console -> no need for privileges */
363 setuid (getuid ());
364 /* dwrite (2, error); */
365 console_flag = 0;
366 if (console_fd >= 0)
367 close (console_fd);
368 } else {
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 */
373 tty_getsize ();
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)
380 close (console_fd);
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)){
387 /* Handle command */
388 switch (action){
389 case CONSOLE_DONE:
390 console_flag = 0;
391 continue; /* Break while loop instead of switch clause */
392 case CONSOLE_SAVE:
393 save_console ();
394 break;
395 case CONSOLE_RESTORE:
396 restore_console ();
397 break;
398 case CONSOLE_CONTENTS:
399 send_contents ();
400 break;
401 } /* switch (action) */
403 /* Inform the invoker that command is handled */
404 write (cmd_output, &console_flag, 1);
405 } /* while (read ...) */
407 if (buffer)
408 free (buffer);
409 return 0;
412 #else
414 #error The Linux console screen saver works only on Linux.
416 #endif /* #ifdef linux */