mc/src/cons.saver.c
2002-07-29 21:18:51 +00:00

319 lines
7.9 KiB
C

#if defined(linux) || defined(__linux__)
/* General purpose Linux console screen save/restore server
Copyright (C) 1994 Janne Kukonlehto <jtklehto@stekt.oulu.fi>
Original idea from Unix Interactive Tools version 3.2b (tty.c)
This code requires root privileges.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
/* cons.saver is run by MC to save and restore screen contents on Linux
virtual console. This is done by using file /dev/vcsaN or /dev/vcc/aN,
where N is the number of the virtual console.
In a properly set up system, /dev/vcsaN should become accessible
by the user when the user logs in on the corresponding console
/dev/ttyN or /dev/vc/N. In this case, cons.saver doesn't need to be
suid root. However, if /dev/vcsaN is not accessible by the user,
cons.saver can be made setuid root - this program is designed to be
safe even in this case. */
#include <config.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <errno.h>
#ifdef HAVE_UNISTD_H
# include <unistd.h>
#endif
#include <termios.h>
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h> /* For isdigit() */
#include <string.h>
#define LINUX_CONS_SAVER_C
#include "cons.saver.h"
#define cmd_input 0
#define cmd_output 1
static int console_minor = 0;
static char *buffer = NULL;
static int buffer_size = 0;
static int columns, rows;
static int vcs_fd;
/*
* Get window size for the given terminal.
* Return 0 for success, -1 otherwise.
*/
static int tty_getsize (int console_fd)
{
struct winsize winsz;
winsz.ws_col = winsz.ws_row = 0;
ioctl (console_fd, TIOCGWINSZ, &winsz);
if (winsz.ws_col && winsz.ws_row) {
columns = winsz.ws_col;
rows = winsz.ws_row;
return 0;
}
return -1;
}
/*
* Do various checks to make sure that the supplied filename is
* a suitable tty device. If check_console is set, check that we
* are dealing with a Linux virtual console.
* Return 0 for success, -1 otherwise.
*/
static int check_file (char *filename, int check_console)
{
int fd;
struct stat stat_buf;
/* Avoiding race conditions: use of fstat makes sure that
both 'open' and 'stat' operate on the same file */
fd = open (filename, O_RDWR);
if (fd == -1)
return -1;
do {
if (fstat (fd, &stat_buf) == -1)
break;
/* Must be character device */
if (!S_ISCHR (stat_buf.st_mode)){
break;
}
if (check_console){
/* Major number must be 4 */
if ((stat_buf.st_rdev & 0xff00) != 0x0400){
break;
}
/* Minor number must be between 1 and 63 */
console_minor = (int) (stat_buf.st_rdev & 0x00ff);
if (console_minor < 1 || console_minor > 63){
break;
}
/* Must be owned by the user */
if (stat_buf.st_uid != getuid ()){
break;
}
}
/* Everything seems to be okay */
return fd;
} while (0);
close (fd);
return -1;
}
/*
* Check if the supplied filename is a Linux virtual console.
* Return 0 if successful, -1 otherwise.
* Since the tty name is supplied by the user and cons.saver can be
* a setuid program, many checks have to be done to prevent possible
* security compromise.
*/
static int detect_console (char *tty_name)
{
char console_name [16];
static char vcs_name [16];
int console_fd;
/* Must be console */
console_fd = check_file (tty_name, 1);
if (console_fd == -1)
return -1;
if (tty_getsize (console_fd) == -1) {
close (console_fd);
return -1;
}
close (console_fd);
/*
* Only allow /dev/ttyMINOR and /dev/vc/MINOR where MINOR is the minor
* device number of the console, set in check_file()
*/
switch (tty_name[5])
{
case 'v':
snprintf (console_name, sizeof (console_name), "/dev/vc/%d",
console_minor);
if (strncmp (console_name, tty_name, sizeof (console_name)) != 0)
return -1;
break;
case 't':
snprintf (console_name, sizeof (console_name), "/dev/tty%d",
console_minor);
if (strncmp (console_name, tty_name, sizeof (console_name)) != 0)
return -1;
break;
default:
return -1;
}
snprintf (vcs_name, sizeof (vcs_name), "/dev/vcsa%d", console_minor);
vcs_fd = check_file (vcs_name, 0);
/* Try devfs name */
if (vcs_fd == -1) {
snprintf (vcs_name, sizeof (vcs_name), "/dev/vcc/a%d", console_minor);
vcs_fd = check_file (vcs_name, 0);
}
if (vcs_fd == -1)
return -1;
return 0;
}
static void save_console (void)
{
lseek (vcs_fd, 0, SEEK_SET);
read (vcs_fd, buffer, buffer_size);
}
static void restore_console (void)
{
lseek (vcs_fd, 0, SEEK_SET);
write (vcs_fd, buffer, buffer_size);
}
static void send_contents (void)
{
unsigned char begin_line=0, end_line=0;
int index;
unsigned char message;
unsigned short bytes;
const int bytes_per_char = 2;
/* Inform the invoker that we can handle this command */
message = CONSOLE_CONTENTS;
write (cmd_output, &message, 1);
/* Read the range of lines wanted */
read (cmd_input, &begin_line, 1);
read (cmd_input, &end_line, 1);
if (begin_line > rows)
begin_line = rows;
if (end_line > rows)
end_line = rows;
/* Tell the invoker how many bytes it will be */
bytes = (end_line - begin_line) * columns;
write (cmd_output, &bytes, 2);
/* Send the contents */
for (index = (2 + begin_line * columns) * bytes_per_char;
index < (2 + end_line * columns) * bytes_per_char;
index += bytes_per_char)
write (cmd_output, buffer + index, 1);
/* All done */
}
int main (int argc, char **argv)
{
unsigned char action = 0;
int stderr_fd;
/* 0 - not a console, 3 - supported Linux console */
signed char console_flag = 0;
/*
* Make sure stderr points to a valid place
*/
close (2);
stderr_fd = open ("/dev/tty", O_RDWR);
/* This may well happen if program is running non-root */
if (stderr_fd == -1)
stderr_fd = open ("/dev/null", O_RDWR);
if (stderr_fd == -1)
exit (1);
if (stderr_fd != 2)
while (dup2 (stderr_fd, 2) == -1 && errno == EINTR)
;
if (argc != 2){
/* Wrong number of arguments */
write (cmd_output, &console_flag, 1);
return 3;
}
/* Lose the control terminal */
setsid ();
/* Check that the argument is a Linux console */
if (detect_console (argv [1]) == -1) {
/* Not a console -> no need for privileges */
setuid (getuid ());
} else {
console_flag = 3;
/* Allocate buffer for screen image */
buffer_size = 4 + 2 * columns * rows;
buffer = (char*) malloc (buffer_size);
}
/* Inform the invoker about the result of the tests */
write (cmd_output, &console_flag, 1);
/* Read commands from the invoker */
while (console_flag && read (cmd_input, &action, 1)){
/* Handle command */
switch (action){
case CONSOLE_DONE:
console_flag = 0;
continue; /* Break while loop instead of switch clause */
case CONSOLE_SAVE:
save_console ();
break;
case CONSOLE_RESTORE:
restore_console ();
break;
case CONSOLE_CONTENTS:
send_contents ();
break;
} /* switch (action) */
/* Inform the invoker that command has been handled */
write (cmd_output, &console_flag, 1);
} /* while (read ...) */
if (buffer)
free (buffer);
return 0;
}
#else
#error "The Linux console screen saver works only on Linux"
#endif /* #ifdef linux */