Debido a la gran importancia de llevar un registro de todos los cambios en archivos y carpetas de nuestro sistema o servidor hemos desarrollado FileTrack, una herramienta para Linux que permite monitorizar una carpeta, incluyendo subcarpetas, para detectar cambios en archivos o directorios.

En esta primera versión 1.0, puede detectar los siguientes cambios:

  • Creación de un archivo o carpeta.
  • Modificación de un archivo.
  • Eliminación de un archivo o carpeta.
  • Cambio de nombre de un archivo o carpeta.
  • Cambio de la ubicación de un archivo o carpeta.
  • Apertura o acceso a un archivo o carpeta.

Esta programado en el lenguaje de programación C y usa un servicio del Kernel de Linux llamado inotify para detectar los cambios en el sistema de archivos. Inotify esta disponible a partir de la versión 2.6.13 del Kernel de Linux.


Código Fuente

filetrack.h

#ifndef _SN_FILE_TRACK_H
#define _SN_FILE_TRACK_H

	/* INCLUDES */
	#include <stdio.h>
	#include <stdlib.h>
	#include <string.h>
	#include <errno.h>
	#include <sys/types.h>
	#include <sys/inotify.h>
	#include <dirent.h>
	#include <unistd.h>
	#include <signal.h>
	#include <time.h>
	
	/* CONSTANTS */
	#define APP_MAME "FileTrack"
	#define APP_USAGE "USAGE: ./filetrack <monitorize dir> [log path]"
	#define AUTHOR "Security Null"
	#define VERSION_MAJOR 1
	#define VERSION_MINOR 0
	#define VERSION_RELEASE 0
	#define WEBSITE "www.SecurityNull.net"
	#define LAST_UPDATE "22/01/2014"
	
	#ifndef PATH_MAX
		#define PATH_MAX 1024 // Max file path length
	#endif
	#define MAX_EVENTS 1024 // Max number of events to process at one go
	#define LEN_NAME 16 // Assuming that the length of the filename won't exceed 16 bytes
	#define EVENT_SIZE  (sizeof (struct inotify_event)) // Size of one event
	#define BUF_LEN     (MAX_EVENTS * (EVENT_SIZE + LEN_NAME)) // Buffer to store the data of events

	/* STRUCTS */
	typedef struct {
		int wd;
		char *full_path;
	} NOTIFY;
	
	/* PROTOTYPES */
	unsigned long addNewWatch(int, char *);
	int searchIDFromWD(int);
	void sigalCallback(int);
	
	/* VARS */
	int notify_events[] = {IN_CREATE, IN_DELETE, IN_MODIFY, IN_MOVED_FROM, IN_MOVED_TO, IN_ACCESS, IN_OPEN, IN_CLOSE/*, IN_CLOSE_WRITE, IN_CLOSE_NOWRITE, IN_ATTRIB*/};
	char *notify_events_name[] = {"CREATE", "DELETE", "MODIFY", "MOVE FROM", "MOVE TO", "ACCESS", "OPEN", "OPEN & CLOSE"/*, "WRITE", "NO WRITE", "CHANGE ATTRIB"*/};
	char *months_names[] = {"JANUARY", "FEBRUARY", "MARCH", "APRIL", "MAY", "JUNE", "JULY", "AUGUST", "SEPTEMBER", "OCTOBER", "NOVEMBER", "DECEMBER"};
	FILE *fp_log;
	NOTIFY *paths;
	unsigned int paths_count = 0;
	char basedir[PATH_MAX];
	char last_name[PATH_MAX];
	char file_log_path[PATH_MAX];
	int save_log = 0;

#endif

filetrack.c

/* INCLUDES */
#include "filetrack.h"

/* ENTRY POINT */
int main(int argc, char **argv) {
	// APP info
	printf("%s %i.%i.%i [%s] (%s) - %s\n\n", APP_MAME, VERSION_MAJOR, VERSION_MINOR, VERSION_RELEASE, AUTHOR, LAST_UPDATE, WEBSITE);
	
	char current_path[PATH_MAX];
	
	// Check APP params
	if (argc <= 1) {
		printf("%s\n", APP_USAGE);
		exit(0);
	} else {
		// Monitorize custom path
		strcpy(basedir, argv[1]);
		
		if (basedir[0] == '.') {
			// Use current path
			if (getcwd(current_path, sizeof(current_path)) != NULL)
				strcpy(basedir, current_path);
			else
				perror("getcwd() error");
		}
		
		if (basedir[strlen(basedir) - 1] != '/')
			strcat(basedir, "/");

		if (argc >= 3) {
			// Save log into file
			strcpy(file_log_path, argv[2]);
			save_log = 1;
			
			printf("Saving to log '%s'.\n", file_log_path);
		}
	}

	// Initialize array
	paths = (NOTIFY *) malloc((paths_count + 1) * sizeof(NOTIFY));
	
	// Capture signal
	signal(SIGINT, sigalCallback);
 
	// File LOG
	if (save_log) {
		fp_log = fopen(file_log_path, "a");
		if (fp_log == NULL) {
			fprintf(fp_log, "Error opening file LOG. All output will be redirected to the 'stdout'.\n");
			fp_log = stdout;
		}
	} else
		fp_log = stdout;
	
	// Start inotify
	int fd_notify;
	#ifdef IN_NONBLOCK
		fd_notify = inotify_init1(IN_NONBLOCK);
	#else
		fd_notify = inotify_init();
	#endif

	if (fd_notify < 0) {
		if (save_log)
			fprintf(fp_log, "ERROR: Couldn't initialize inotify.\n");
		else
			perror("ERROR: Couldn't initialize inotify.");
			
		exit(0);
	}

	// Add base dir to inotify watch
	unsigned long events_count = addNewWatch(fd_notify, basedir);
	printf("Total Events: %lu\n\n", events_count);
   
   	int length, i;
	char buffer[BUF_LEN];
	while (1) {
		i = 0;
		length = read(fd_notify, buffer, BUF_LEN);  

		if (length < 0)
			perror("ERROR: read().");

		/* Read the events*/
		while (i < length) {
			struct inotify_event *event = (struct inotify_event *) &buffer[i];
			if (event->len) {				
				time_t t = time(NULL);
				struct tm tm = *localtime(&t);
				char fullpath[PATH_MAX];

				int id = searchIDFromWD(event->wd);
				if (id >= 0)
					strcpy(fullpath, paths[id].full_path);
				else
					fullpath[0] = '\0';

				int e;
				for (e = 0; e < sizeof(notify_events) / sizeof(notify_events[0]); e++) {
					if (event->mask & notify_events[e]) {
						if (strcmp(last_name, event->name) != 0) {
							if (event->mask & IN_ISDIR)
								fprintf(fp_log, "[%02d:%02d:%02d %02d/%s/%d - %s] %s%s (DIR)\n", tm.tm_hour, tm.tm_min, tm.tm_sec, tm.tm_mday, months_names[tm.tm_mon], tm.tm_year + 1900, notify_events_name[e], paths[id].full_path, event->name);
							else
								fprintf(fp_log, "[%02d:%02d:%02d %02d/%s/%d - %s] %s%s (FILE)\n", tm.tm_hour, tm.tm_min, tm.tm_sec, tm.tm_mday, months_names[tm.tm_mon], tm.tm_year + 1900, notify_events_name[e], paths[id].full_path, event->name);    
						}
					
						strcpy(last_name, event->name);
					}
				}

				if (save_log)
					fflush(fp_log);
			}
			
			i += EVENT_SIZE + event->len;
		}
	}

	close(fd_notify);

	return 0;
}

unsigned long addNewWatch(int fd, char *path) {
	int wd;
	unsigned long events_count = 0;
	struct dirent *entry;
	DIR *dp;

	// Add backslash if necessary
	if (path[strlen(path) - 1] != '/')
		strcat(path, "/");

	dp = opendir(path);
	if (dp == NULL) {
		perror("ERROR: Opening the directory.");
		exit(0);
	}

	// Add watch
	wd = inotify_add_watch(fd, path, IN_CREATE | IN_DELETE | IN_MODIFY | IN_ACCESS | IN_MOVE | IN_ATTRIB | IN_OPEN); 
	if (wd == -1)
		fprintf(fp_log, "ERROR: Couldn't add watch to %s\n", path);
	else {
		paths = (NOTIFY *) realloc(paths, (paths_count + 1) * sizeof(NOTIFY));
		paths[paths_count].wd = wd;
		paths[paths_count].full_path = (char *) malloc((strlen(path) + 1) * sizeof(char));
		strcpy(paths[paths_count].full_path, path);	
		paths_count++;
		events_count++;
	}

	while ((entry = readdir(dp))) {
		char new_dir[1024];
		// If its a directory, add a watch recursively
		if (entry->d_type == DT_DIR) {
			if (strcmp(entry->d_name, (char *) "..") != 0 && strcmp(entry->d_name, ".") != 0) {
				strcpy((char *) new_dir, path);
				//strcat((char *) new_dir, (char *) "/");
				strcat((char *) new_dir, entry->d_name);

				events_count += addNewWatch(fd, (char *) new_dir);
			}
		}
	}

	closedir(dp);
	
	return events_count;
}

int searchIDFromWD(int wd) {
	if (wd) {
		int i;
		for (i = 0; i < paths_count; i++)
			if (paths[i].wd == wd) 
				return i;
	}
	
	return -1;
}
 
void sigalCallback(int sigal) {
	// Flush and close log file
	if (save_log) {
		fflush(fp_log);
		fclose(fp_log);
	}
	
	// Clean memory
	free(paths);

    exit(0);
}

Repositorio GitHub

https://github.com/SecurityNull/filetrack


Compilación

gcc filetrack.c -o filetrack

Ejecución

Una vez compilado, su sintaxis es la siguinte:

./filetrack <ruta de la carpeta a monitorizar> [ruta del archivo de registro]

El primer parámetro es obligatorio y debemos indicar la carpeta a monitorizar (incluyendo todas sus subcarpetas), y el segundo parámetro es opcional y permite indicar el archivo donde se guardará el registro con todos los cambios en archivos y carpetas. Si no se especifica el archivo de registro entonces se mostrará en la consola los cambios o eventos producidos.


Registro

El registro de cambios se mostrará de la siguiente forma:

[Hora y fecha – Evento] – Ruta archivo o carpeta (FILE o DIR)

Ejemplo:

[13:14:17 23/JANUARY/2014 – OPEN] /share/cloud/securitynull (DIR)
[13:14:20 23/JANUARY/2014 – CREATE] /share/cloud/securitynull/Nueva carpeta (DIR)
[13:14:20 23/JANUARY/2014 – OPEN] /share/cloud/securitynull (DIR)
[13:14:20 23/JANUARY/2014 – OPEN] /share/cloud/securitynull/Nueva carpeta (DIR)
[13:14:23 23/JANUARY/2014 – MOVE TO] /share/cloud/securitynull/test (DIR)
[13:14:23 23/JANUARY/2014 – OPEN] /share/cloud/securitynull (DIR)
[13:14:23 23/JANUARY/2014 – OPEN] /share/cloud/securitynull/test (DIR)
[13:14:25 23/JANUARY/2014 – CREATE] /share/cloud/securitynull/Nueva carpeta (DIR)
[13:14:25 23/JANUARY/2014 – OPEN] /share/cloud/securitynull (DIR)
[13:14:25 23/JANUARY/2014 – OPEN] /share/cloud/securitynull/Nueva carpeta (DIR)
[13:14:36 23/JANUARY/2014 – MOVE TO] /share/cloud/securitynull/SecurityNull.net (DIR)


Alternativas

Aunque FileTrack sólo es compatible con Linux, para Windows tenemos otras alternativas como MultiMon, que permite detectar cambios en archivos o en el registro de Windows además del portapapeles, y también tenemos FileMon que es similar al anterior.

Para Linux también existen otras alternativas:

  • incron: Es un servicio (incrond) similar al clásico cron pero en el que se definen las acciones en base a eventos del sistema de ficheros en lugar de eventos temporales. También dispone de un comando incrontab similar a crontab para que los usuarios puedan añadir sus propios eventos.
  • inotifywait: Forma parte del paquete inotify-tools y permite ejecutar comandos cuando se produce algún cambio en un archivo o directorio.
  • Por ejemplo con este comando cuando un archivo sea modificado se imprimiría por consola un texto:

    while inotifywait -r -e MODIFY .; do echo "se produjo un cambio"; done;
    
  • inotifywatch: Forma parte del paquete inotify-tools y monitorea todas las ocurrencias de eventos especificados durante un cierto tiempo y al final presenta unas estadísticas acerca de eventos detectados.
  • pyinotify: Es un script escrito en python que permite detectar y notificar los cambios el archivos o carpetas.
  • import sys
    import pyinotify as inotify
     
    class EventHandler(inotify.ProcessEvent):
        def process_IN_CREATE(self, event):
            print "Create:", event.pathname
     
        def process_IN_DELETE(self, event):
            print "Delete:", event.pathname
     
        def process_IN_MODIFY(self, event):
            print "Modify:", event.pathname
     
    wm = inotify.WatchManager()
    wm.add_watch(sys.argv[1],
                 inotify.IN_MODIFY | inotify.IN_CREATE | inotify.IN_DELETE, rec=True)
     
    notifier = inotify.Notifier(wm, EventHandler())
    notifier.loop()
    

Soporte

Para ayuda, reportar bugs, aportar ideas o mejoras contactar a: projects@securitynull.net


FUENTES

www.geeksstory.wordpress.com
www.quepagina.es
www.puppetlinux.blogspot.com.es
www.thegeekstuff.com
www.linuxjournal.com
www.linuxforu.com