/*
 * Copyright (C) 1998-9 Pancrazio `Ezio' de Mauro <p@demauro.net>
 *
 * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * $Id: installwatch.c,v 0.6.3.2 2001/12/22 23:01:20 izto Exp $
 * 
 * april-15-2001 - Modifications by Felipe Eduardo Sanchez Diaz Duran
 *                                  <izto@asic-linux.com.mx>
 * Added backup() and make_path() functions.
 */

#include <sys/param.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
#include <syslog.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <time.h>
#include <dlfcn.h>

#include "localdecls.h"

/* #define DEBUG 1  */

#define LOGLEVEL (LOG_USER | LOG_INFO | LOG_PID)
#define BUFSIZE 1024

#define error(X) (X < 0 ? strerror(errno) : "success")

int __installwatch_refcount = 0;
int __installwatch_timecount = 0;

#define REFCOUNT __installwatch_refcount++
#define TIMECOUNT __installwatch_timecount++

static time_t (*true_time) (time_t *);
static int(*true_chmod)(const char *, mode_t);
static int(*true_chown)(const char *, uid_t, gid_t);
static int(*true_chroot)(const char *);
static int(*true_creat)(const char *, mode_t);
static int(*true_fchmod)(int, mode_t);
static int(*true_fchown)(int, uid_t, gid_t);
static FILE *(*true_fopen)(const char *,const char*);
static int(*true_ftruncate)(int, TRUNCATE_T);
static int(*true_lchown)(const char *, uid_t, gid_t);
static int(*true_link)(const char *, const char *);
static int(*true_mkdir)(const char *, mode_t);
static int(*true_open)(const char *, int, ...);
static int(*true_rename)(const char *, const char *);
static int(*true_rmdir)(const char *);
static int(*true_symlink)(const char *, const char *);
static int(*true_truncate)(const char *, TRUNCATE_T);
static int(*true_unlink)(const char *);

#if(GLIBC_MINOR >= 1)

static int(*true_creat64)(const char *, __mode_t);
static FILE *(*true_fopen64)(const char *,const char *);
static int(*true_ftruncate64)(int, __off64_t);
static int(*true_open64)(const char *, int, ...);
static int(*true_truncate64)(const char *, __off64_t);

#endif

static void log(const char *format, ...)
#ifdef __GNUC__
	/* Tell gcc that this function behaves like printf()
	 * for parameters 1 and 2                            */
	__attribute__((format(printf, 1, 2)))
#endif /* defined __GNUC__ */
;

void _init(void) {

	void *libc_handle;

	#ifdef BROKEN_RTLD_NEXT
//        	printf ("RTLD_LAZY");
        	libc_handle = dlopen(LIBC_VERSION, RTLD_LAZY);
	#else
 //       	printf ("RTLD_NEXT");
        	libc_handle = RTLD_NEXT;
	#endif


	true_time = dlsym(libc_handle, "time");
	true_chmod = dlsym(libc_handle, "chmod");
	true_chown = dlsym(libc_handle, "chown");
	true_chroot = dlsym(libc_handle, "chroot");
	true_creat = dlsym(libc_handle, "creat");
	true_fchmod = dlsym(libc_handle, "fchmod");
	true_fchown = dlsym(libc_handle, "fchown");
	true_fopen = dlsym(libc_handle, "fopen");
	true_ftruncate = dlsym(libc_handle, "ftruncate");
	true_lchown = dlsym(libc_handle, "lchown");
	true_link = dlsym(libc_handle, "link");
	true_mkdir = dlsym(libc_handle, "mkdir");
	true_open = dlsym(libc_handle, "open");
	true_rename = dlsym(libc_handle, "rename");
	true_rmdir = dlsym(libc_handle, "rmdir");
	true_symlink = dlsym(libc_handle, "symlink");
	true_truncate = dlsym(libc_handle, "truncate");
	true_unlink = dlsym(libc_handle, "unlink");

#if(GLIBC_MINOR >= 1)
	true_creat64 = dlsym(libc_handle, "creat64");
	true_fopen64 = dlsym(libc_handle, "fopen64");
	true_ftruncate64 = dlsym(libc_handle, "ftruncate64");
	true_open64 = dlsym(libc_handle, "open64");
	true_truncate64 = dlsym(libc_handle, "truncate64");
#endif

}

static void log(const char *format, ...) {
	char buffer[BUFSIZE], *logname;
	va_list ap;
	int count, logfd;

	va_start(ap, format);
	count = vsnprintf(buffer, BUFSIZE, format, ap);
	va_end(ap);
	if(count == -1) {
		/* The buffer was not big enough */
		strcpy(&(buffer[BUFSIZE - 5]), "...\n");
		count = BUFSIZE - 1;
	}
	if((logname = getenv("INSTALLWATCHFILE"))) {
		logfd = true_open(logname, O_WRONLY | O_CREAT | O_APPEND, 0666);
		if(logfd >= 0) {
			if(write(logfd, buffer, count) != count)
				syslog(LOGLEVEL, "Count not write `%s' in `%s': %s\n", buffer, logname, strerror(errno));
			if(close(logfd) < 0)
				syslog(LOGLEVEL, "Could not close `%s': %s\n", logname, strerror(errno));
		} else
			syslog(LOGLEVEL, "Could not open `%s' to write `%s': %s\n", logname, buffer, strerror(errno));
		
	} else
		syslog(LOGLEVEL, buffer);
}

static void canonicalize(const char *path, char *resolved_path) {
	if(!realpath(path, resolved_path) && (path[0] != '/')) {
		/* The path could not be canonicalized, append it
		 * to the current working directory if it was not 
		 * an absolute path                               */
		getcwd(resolved_path, MAXPATHLEN - 2);
		strcat(resolved_path, "/");
		strncat(resolved_path, path, MAXPATHLEN - 1);
	}
} 

static void make_path (char *path) {
 char checkdir[BUFSIZ];
 struct stat inode;
 int i = 0;

#if DEBUG
printf ("===== make_path: %s\n", path);
#endif

 while ( path[i] != '\0' ) {
    checkdir[i] = path[i];
    if (checkdir[i] == '/') {  /* Each time a '/' is found, check if the    */
       checkdir[i+1] = '\0';   /* path exists. If it doesn't, we create it. */
       if (stat (checkdir, &inode) < 0)
          true_mkdir (checkdir, S_IRWXU);
    }
    i++;
 }

}



static void backup(char *path) {

   char buffer[BUFSIZ];
   char checkdir[BUFSIZ];
   char backup_path[BUFSIZ];
   int orig,
       dest,
       placeholder,
       bytes,
       i,
       blen;


   struct stat inode, newinode, backup_inode;

#if DEBUG
printf ("========= backup () =========\npath: %s\n", path); 
#endif

/* If INSTALLWATCH_BACKUP_PATH is not defined then we won't do the backup */
 if (!getenv("INSTALLWATCH_BACKUP_PATH")) {
    #ifdef DEBUG
    printf ("Backup not enabled, path: %s\n", path);
    #endif
    return;
 }


 /* Check if this is inside /dev */
   if (strstr (path, "/dev") == path) {
     #if DEBUG
      printf ("%s is inside /dev. Ignoring.\n", path);
     #endif
      return; 
   }

 /* Now check for /tmp */
   if (strstr (path, "/tmp") == path) {
     #if DEBUG
      printf ("%s is inside /tmp. Ignoring.\n", path);
     #endif
      return; 
   }

 /* Finally, the backup path itself */
   if (strstr (path, getenv("INSTALLWATCH_BACKUP_PATH")) == path) {
     #if DEBUG
      printf ("%s is inside the backup path. Ignoring.\n", path);
     #endif
      return; 
   }
   

 /* Does it exist already? */
#if DEBUG
printf ("Existe %s?\n", path);
#endif
   if (stat(path, &inode) < 0) {

      /* It doesn't exist, we'll tag it so we won't back it up  */
      /* if we run into it later                                */

      strcpy (backup_path, getenv ("INSTALLWATCH_BACKUP_PATH"));
      strncat (backup_path, "/no-backup/", 11);
      strcat (backup_path, path);
      make_path(backup_path);

     /* This one's just a placeholder */
      placeholder = true_creat(backup_path, S_IREAD);  
      if (!(placeholder < 0)) close (placeholder);

#if DEBUG
printf ("NO EXISTE\n");
#endif
      return;
   }


/* Is this one tagged for no backup (i.e. it didn't previously exist)? */


strcpy (backup_path, getenv ("INSTALLWATCH_BACKUP_PATH"));
strncat (backup_path, "/no-backup/", 11);
strcat (backup_path, path);

if (stat (backup_path, &backup_inode) >= 0) {
#if DEBUG
printf ("%s no debe ser respaldado", backup_path);
#endif
return;
}


#if DEBUG
printf ("Si existe, veamos de que tipo es.\n");
#endif

 /* Append the path to the backup_path */

      strcpy (backup_path, getenv ("INSTALLWATCH_BACKUP_PATH"));
      strcat (backup_path, path);


/* Create the directory tree for this file in the backup dir */
   make_path (backup_path);
   
/* What kind of file is this? */

/* Regular file */
 if (S_ISREG(inode.st_mode)) {
    #if DEBUG
    printf ("%s es un archivo regular\n", path);
    #endif
    if ( (orig = true_open (path, O_RDONLY)) < 0) return; 

    /* Write the backup */
   if ( (dest = true_open (backup_path, O_WRONLY|O_CREAT|O_TRUNC, inode.st_mode)) < 0) {
      close (orig);
      return ;
   }

    while ( (bytes = read (orig, buffer, BUFSIZ)) > 0) 
      write (dest, buffer, bytes);
    close( dest );
    close( orig );
 }


/* Directory */
 if (S_ISDIR(inode.st_mode)) {
    #if DEBUG
    printf ("%s es un directorio\n", path);
    #endif
    true_mkdir (backup_path, inode.st_mode);
 }


/* Block special */

/* Since we're ignoring everything under /dev,
 * this one will be hard to find. */

 if (S_ISBLK(inode.st_mode)) {
    #if DEBUG
    printf ("%s es un blkspecial\n", path);
    #endif
    mknod (backup_path, inode.st_mode | S_IFBLK, inode.st_rdev);
 }


/* Character special */
 if (S_ISCHR(inode.st_mode)) {
    #if DEBUG
    printf ("%s es un charspecial\n", path);
    #endif
    mknod (backup_path, inode.st_mode | S_IFCHR, inode.st_rdev);
 }


/* FIFO */
 if (S_ISFIFO(inode.st_mode)) {
    #if DEBUG
    printf ("%s es un fifo\n", path);
    #endif
    mknod (backup_path, inode.st_mode | S_IFIFO, 0);
 }

/* If we have a new file, set it to the right owner and group */
 if (!stat (path, &newinode)) true_chown (backup_path, inode.st_uid, inode.st_gid);


/* Check the owner and permission of the created directories */
 i=0;
 blen = strlen (getenv ("INSTALLWATCH_BACKUP_PATH"));
 while ( path[i] != '\0' ) {
    checkdir[i] = backup_path[blen+i] = path[i];
    if (checkdir[i] == '/') {  /* Each time a '/' is found, check if the    */
       checkdir[i+1] = '\0';   /* path exists. If it does, set it's perms.  */
       if (!stat (checkdir, &inode)) {
          backup_path[blen+i+1]='\0';
          true_chmod (backup_path, inode.st_mode);
          true_chown (backup_path, inode.st_uid, inode.st_gid);
       }
    }
    i++;
 }

}

time_t time (time_t *timer) {
	TIMECOUNT;

#if DEBUG
	puts("time\n");
#endif

return true_time(timer);
}

int chmod(const char *path, mode_t mode) {
	int result;
	char canonic[MAXPATHLEN];

	REFCOUNT;
	canonicalize(path, canonic);
	
     #if DEBUG
	puts ("in installwatch chmod\n");
     #endif
	backup (canonic);

	result = true_chmod(path, mode);
	log("%d\tchmod\t%s\t0%04o\t#%s\n", result, canonic, mode, error(result));
	return result;
}

int chown(const char *path, uid_t owner, gid_t group) {
	int result;
	char canonic[MAXPATHLEN];

	REFCOUNT;
	canonicalize(path, canonic);

#if DEBUG
	puts("chown\n");
#endif
	backup (canonic);

	result = true_chown(path, owner, group);
	log("%d\tchown\t%s\t%d\t%d\t#%s\n", result, canonic, owner, group, error(result));
	return result;
}

int chroot(const char *path) {
	int result;
	char canonic[MAXPATHLEN];

	REFCOUNT;
	canonicalize(path, canonic);
	result = true_chroot(path);
	/* From now on, another log file will be written if *
	 * INSTALLWATCHFILE is set                          */
	log("%d\tchroot\t%s\t#%s\n", result, canonic, error(result));
	return result;
}

int creat(const char *pathname, mode_t mode) {
/* Is it a system call? */
	int result;
	char canonic[MAXPATHLEN];

	REFCOUNT;
	canonicalize(pathname, canonic);

#if DEBUG
	printf("creat\n");
#endif
	backup(canonic);
	

	result = true_open(pathname, O_CREAT | O_WRONLY | O_TRUNC, mode);
	log("%d\tcreat\t%s\t#%s\n", result, canonic, error(result));
	return result;
}

int fchmod(int filedes, mode_t mode) {
	int result;

	REFCOUNT;

#if DEBUG
	puts("fchmod\n");
#endif

	result = true_fchmod(filedes, mode);
	log("%d\tfchmod\t%d\t0%04o\t#%s\n", result, filedes, mode, error(result));
	return result;
}

int fchown(int fd, uid_t owner, gid_t group) {
	int result;

	REFCOUNT;

#if DEBUG
	puts("fchown\n");
#endif

	result = true_fchown(fd, owner, group);
	log("%d\tfchown\t%d\t%d\t%d\t#%s\n", result, fd, owner, group, error(result));
	return result;
}

FILE *fopen(const char *pathname, const char *mode) {
	FILE *result;
	char canonic[MAXPATHLEN];

	REFCOUNT;
	canonicalize(pathname, canonic);

#if DEBUG
	puts("fopen\n");
#endif
	if(mode[0]=='w'||mode[0]=='a'||mode[1]=='+') 
	backup (canonic);

	result = true_fopen(pathname,mode);
	if(mode[0]=='w'||mode[0]=='a'||mode[1]=='+') 
		log("%d\tfopen\t%s\t#%s\n", (int)result, canonic, error(result));
	return result;
}

int ftruncate(int fd, TRUNCATE_T length) {
	int result;

	REFCOUNT;

#if DEBUG
	puts ("ftruncate\n");
#endif

	result = true_ftruncate(fd, length);
	log("%d\tftruncate\t%d\t%d\t#%s\n", result, fd, (int)length, error(result));
	return result;
}

int lchown(const char *path, uid_t owner, gid_t group) {
/* Linux specific? */
	int result;
	char canonic[MAXPATHLEN];

	REFCOUNT;

#if DEBUG
	puts ("lchown\n");
#endif
	canonicalize(path, canonic);

	backup (canonic);

	result = true_chown(path, owner, group);
	log("%d\tlchown\t%s\t%d\t%d\t#%s\n", result, canonic, owner, group, error(result));
	return result;
}

int link(const char *oldpath, const char *newpath) {
	int result;
	char old_canonic[MAXPATHLEN], new_canonic[MAXPATHLEN];

	REFCOUNT;

#if DEBUG
	puts ("link\n");
#endif

	canonicalize(oldpath, old_canonic);
	canonicalize(newpath, new_canonic);
	result = true_link(oldpath, newpath);
	log("%d\tlink\t%s\t%s\t#%s\n", result, old_canonic, new_canonic, error(result));
	return result;
}

int mkdir(const char *pathname, mode_t mode) {
	int result;
	char canonic[MAXPATHLEN];

	REFCOUNT;
	canonicalize(pathname, canonic);
	result = true_mkdir(pathname, mode);
	log("%d\tmkdir\t%s\t#%s\n", result, canonic, error(result));
	return result;
}

int open(const char *pathname, int flags, ...) {
/* Eventually, there is a third parameter: it's mode_t mode */
	va_list ap;
	mode_t mode;
	int result;
	char canonic[MAXPATHLEN];

	REFCOUNT;
	va_start(ap, flags);
	mode = va_arg(ap, mode_t);
	va_end(ap);
	canonicalize(pathname, canonic);

#if DEBUG
	printf ("open\n");
#endif
	if(flags & (O_WRONLY | O_RDWR)) 
	backup (canonic);

	result = true_open(pathname, flags, mode);
	if(flags & (O_WRONLY | O_RDWR)) 
		log("%d\topen\t%s\t#%s\n", result, canonic, error(result));
	return result;
}

int rename(const char *oldpath, const char *newpath) {
	int result;
	char old_canonic[MAXPATHLEN], new_canonic[MAXPATHLEN];

	REFCOUNT;
	canonicalize(oldpath, old_canonic);

#if DEBUG
	puts ("rename\n");	
#endif
	backup (old_canonic);

	canonicalize(newpath, new_canonic);
	result = true_rename(oldpath, newpath);
	log("%d\trename\t%s\t%s\t#%s\n", result, old_canonic, new_canonic, error(result));
	return result;
}

int rmdir(const char *pathname) {
	int result;
	char canonic[MAXPATHLEN];

	REFCOUNT;
	canonicalize(pathname, canonic);

#if DEBUG
	printf ("rmdir\n");
#endif
	backup(canonic);

	result = true_rmdir(pathname);
	log("%d\trmdir\t%s\t#%s\n", result, canonic, error(result));
	return result;
}

int symlink(const char *oldpath, const char *newpath) {
	int result;
	char old_canonic[MAXPATHLEN], new_canonic[MAXPATHLEN];

	REFCOUNT;

#if DEBUG
	puts ("symlink\n");
#endif

	canonicalize(oldpath, old_canonic);
	canonicalize(newpath, new_canonic);
	result = true_symlink(oldpath, newpath);
	log("%d\tsymlink\t%s\t%s\t#%s\n", result, old_canonic, new_canonic, error(result));
	return result;
}

int truncate(const char *path, TRUNCATE_T length) {
	int result;
	char canonic[MAXPATHLEN];

	REFCOUNT;

#if DEBUG
	puts ("truncate\n");
#endif

	canonicalize(path, canonic);

	backup (canonic);

	result = true_truncate(path, length);
	log("%d\ttruncate\t%s\t%d\t#%s\n", result, path, (int)length, error(result));
	return result;
}

int unlink(const char *pathname) {
	int result;
	char canonic[MAXPATHLEN];

	REFCOUNT;
	canonicalize(pathname, canonic);

#if DEBUG
	printf ("unlink\n");
#endif
	backup(canonic);

	result = true_unlink(pathname);
	log("%d\tunlink\t%s\t#%s\n", result, canonic, error(result));
	return result;
}

#if(GLIBC_MINOR >= 1)

int creat64(const char *pathname, __mode_t mode) {
/* Is it a system call? */
	int result;
	char canonic[MAXPATHLEN];

	REFCOUNT;
	canonicalize(pathname, canonic);

#if DEBUG
        puts ("creat64\n");
#endif
	backup (canonic);

	result = true_open64(pathname, O_CREAT | O_WRONLY | O_TRUNC, mode);
	log("%d\tcreat\t%s\t#%s\n", result, canonic, error(result));
	return result;
}

int ftruncate64(int fd, __off64_t length) {
	int result;

	REFCOUNT;

#if DEBUG
        puts ("ftruncate64\n");
#endif

	result = true_ftruncate64(fd, length);
	log("%d\tftruncate\t%d\t%d\t#%s\n", result, fd, (int)length, error(result));
	return result;
}

FILE *fopen64(const char *pathname, const char *mode) {
	FILE *result;
	char canonic[MAXPATHLEN];

	REFCOUNT;
	canonicalize(pathname, canonic);

#if DEBUG
	puts("fopen64\n");
#endif
	if(mode[0]=='w'||mode[0]=='a'||mode[1]=='+') 
	backup (canonic);

	result = true_fopen(pathname,mode);
	if(mode[0]=='w'||mode[0]=='a'||mode[1]=='+') 
		log("%d\tfopen64\t%s\t#%s\n", (int)result, canonic, error(result));
	return result;
}

int open64(const char *pathname, int flags, ...) {
/* Eventually, there is a third parameter: it's mode_t mode */
	va_list ap;
	mode_t mode;
	int result;
	char canonic[MAXPATHLEN];

	REFCOUNT;

#if DEBUG
        puts ("open64\n");
#endif

	va_start(ap, flags);
	mode = va_arg(ap, mode_t);
	va_end(ap);
	canonicalize(pathname, canonic);
	if(flags & (O_WRONLY | O_RDWR)) 
	   backup(canonic);
	result = true_open64(pathname, flags, mode);
	if(flags & (O_WRONLY | O_RDWR)) 
		log("%d\topen\t%s\t#%s\n", result, canonic, error(result));
	return result;
}

int truncate64(const char *path, __off64_t length) {
	int result;
	char canonic[MAXPATHLEN];

	REFCOUNT;

#if DEBUG
        puts ("truncate64\n");
#endif

	canonicalize(path, canonic);

	backup(canonic);

	result = true_truncate64(path, length);
	log("%d\ttruncate\t%s\t%d\t#%s\n", result, path, (int)length, error(result));
	return result;
}

#endif /* GLIBC_MINOR >= 1 */

