/*
 * Distributed under the terms of the GNU General Public License v2
 * $Header: $
 */

 /********************************************************************
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston,
 * MA 02111-1307, USA. 
 ********************************************************************/

/*
 * To enable syslog logging support
 * #define _USE_SYSLOG
 * #define _USE_SYSLOG_ENV
 */

#include <stdio.h>
#include <dlfcn.h>
#include <stdlib.h>		/* for EXIT_FAULURE */
#include <unistd.h>		/* for _exit() */
#include <sys/types.h>
#include <sys/stat.h>

#if (defined(_USE_SYSLOG_ENV) && !defined(_USE_SYSLOG))
#define _USE_SYSLOG
#endif

#ifdef _USE_SYSLOG
#include <sys/syslog.h>
#endif

#ifndef RTLD_NEXT
#define RTLD_NEXT      ((void *) -1l)
#endif

typedef int (*chmod_t) (char *path, mode_t mode);
typedef int (*fchmod_t) (int fildes, mode_t mode);

static void *get_libc_func(const char *funcname)
{
   void *func;
   char *error;

   func = dlsym(RTLD_NEXT, funcname);
   if ((error = dlerror()) != NULL) {
      fprintf(stderr,
	      "unable to locate function pointer with dlsym %s error: %s",
	      funcname, error);
      _exit(EXIT_FAILURE);
   }
   return func;
}

static mode_t sfperms(mode_t mode)
{
   mode_t newmode = mode;

   /* if setuid then chmod go-rw */
   if (newmode & (S_ISUID))
      newmode &= ~((S_IRGRP | S_IWGRP) | (S_IROTH | S_IWOTH));

   /* if setgid then chmod o-rw */
   if (newmode & (S_ISGID))
      newmode &= ~(S_IROTH | S_IWOTH);

#ifdef EBUG
   if (mode != newmode)
      printf("%s(%o) = %o;\n", __FUNCTION__, mode, newmode);
#endif
   return newmode;
}

static void sfperms_alert(const char *func, mode_t mode, const char *path,
			  int fildes)
{

#ifdef _USE_SYSLOG
   if (mode != sfperms(mode))
#ifdef _USE_SYSLOG_ENV
      if ((getenv("SFPERMS_SYSLOG")) != NULL)
#endif
	 syslog(LOG_INFO,
		"sfperms.%s(%o) = %o; pid/ppid %d:%d uid/gid=%d:%d euid/egid=%d:%d tty=%s path=%s fildes=%d\n",
		func, mode, sfperms(mode), getpid(), getppid(), getuid(),
		getgid(), geteuid(), getegid(), ttyname(0),
		(path != NULL) ? path : "?", fildes);
#endif

}

int chmod(const char *path, mode_t mode)
{
   chmod_t real_chmod;

   real_chmod = (chmod_t) get_libc_func("chmod");
   if (mode != sfperms(mode))
      sfperms_alert(__FUNCTION__, mode, path, -1);

   return real_chmod((char *) path, sfperms(mode));
}

int fchmod(int fildes, mode_t mode)
{
   fchmod_t real_fchmod = (fchmod_t) get_libc_func("fchmod");

   if (mode != sfperms(mode))
      sfperms_alert(__FUNCTION__, mode, NULL, fildes);

   return real_fchmod(fildes, sfperms(mode));
}
