/* cfengine for GNU
 
        Copyright (C) 1995
        Free Software Foundation, Inc.
 
   This file is part of GNU cfengine - written and maintained 
   by Mark Burgess, Dept of Computing and Engineering, Oslo College,
   Dept. of Theoretical physics, University of Oslo
 
   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, 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

*/
 

#include "cf.defs.h"
#include "cf.extern.h"


/*********************************************************************/
/*                                                                   */
/* Tidy object                                                       */
/*                                                                   */
/*********************************************************************/

int RecursiveHomeTidy(name,level,tp,sb)

char *name;
int level;
struct Tidy *tp;
struct stat *sb;

{ struct stat statbuf;
  DIR *dirh;
  struct dirent *dirp;
  char pcwd[bufsize];
  time_t ticks;
  int goback;

if (!DirPush(name,sb))
   {
   return false;
   }
  
if (strlen(name) == 0)
   {
   name = "/";
   }

if (level > recursion_limit)
   {
   sprintf(OUTPUT,"WARNING: Very deep nesting of directories (> %d deep): %s (Aborting tidy)",level,name);
   CfLog(cferror,OUTPUT,"");
   return true;
   }
 
Debug2("HomeTidy: Opening %s\n",name);

if ((dirh = opendir(".")) == NULL)
   {
   sprintf(OUTPUT,"Can't open directory %s\n",name);
   CfLog(cferror,OUTPUT,"");
   return true;
   }

if (level == 2)
   {
   int tempfd;
   strcpy(VLOGFILE,name);
   strcat(VLOGFILE,"/.cfengine.rm");

  /*
   * Fix for potential DoS by local users by Ben Collins
   * <bcollins@debian.org>, Upstream really needs to add this
   */   
   if (unlink(VLOGFILE) == -1)
      {
      sprintf(OUTPUT,"Pre-existing object %s could not be removed\n",VLOGFILE);
      CfLog(cfverbose,OUTPUT,"unlink");
      }
   else if((tempfd = open(VLOGFILE, O_CREAT|O_EXCL|O_WRONLY)) < 0)
      {
      sprintf(OUTPUT,"Couldn't open a file %s\n",VLOGFILE);
      CfLog(cferror,OUTPUT,"open");
      VLOGFP = stderr;
      }
   else if ((VLOGFP = fdopen(tempfd,"w")) == NULL)   
      {
      sprintf(OUTPUT,"Couldn't open a file %s\n",VLOGFILE);
      CfLog(cferror,OUTPUT,"fdopen");
      VLOGFP = stderr;
      }
   else
      {
      ticks = time((time_t *)NULL);
      fprintf(VLOGFP,"This file is generated by cfengine %s\n",VERSION);
      fprintf(VLOGFP,"It contains a log of the files which have been tidied.\n");
      fprintf(VLOGFP,"The time of writing is %s\n",ctime(&ticks));
      fprintf(VLOGFP,"If you have any questions about this, send them to %s.\n",VSYSADM);
      fprintf(VLOGFP,"-(Start transcript)---------------\n");
      }
   }

for (dirp = readdir(dirh); dirp != NULL; dirp = readdir(dirh))
   {
   if (!SensibleFile(dirp->d_name,name,NULL))
      {
      continue;
      }

   if (IgnoreFile(name,dirp->d_name,NULL))
      {
      continue;
      }

   strcpy(pcwd,name);                                 /* Assemble pathname */
   AddSlash(pcwd);

   if (BufferOverflow(pcwd,dirp->d_name))
      {
      return true;
      }

   strcat(pcwd,dirp->d_name);

   if (TRAVLINKS)
      {
      if (stat(dirp->d_name,&statbuf) == -1)
         {
         sprintf(OUTPUT,"Can't stat %s\n",pcwd);
	 CfLog(cferror,OUTPUT,"stat");
         continue;
         }
      }
   else
      {
      if (lstat(dirp->d_name,&statbuf) == -1)
         {
         if (DEBUG || D2 || VERBOSE)
            {
            sprintf(OUTPUT,"Can't stat %s\n",pcwd);
	    CfLog(cferror,OUTPUT,"lstat");
	    bzero(VBUFF,bufsize);
            if (readlink(pcwd,VBUFF,bufsize-1) != -1)
               {
               sprintf(OUTPUT,"File is link to -> %s\n",VBUFF);
	       CfLog(cferror,OUTPUT,"");
               }
            }
         continue;
         }
      }


   if (S_ISDIR(statbuf.st_mode))
      {
      if (IsMountedFileSystem(&statbuf,pcwd,1))
         {
         continue;
         }
      else
         {
         goback = RecursiveHomeTidy(pcwd,level+1,tp,&statbuf);

	 DirPop(goback,name,sb);
         }
      }
   else
      {
      TidyHomeFile(pcwd,dirp->d_name,&statbuf);
      }
   }

if (level == 2)
   {
   fclose(VLOGFP);
   chmod(VLOGFILE,DEFAULTMODE);
   }

closedir(dirh);
 return true; 
}


/*********************************************************************/

void TidyHomeFile(path,name,statbuf)

char *path;
char *name;
struct stat *statbuf;

  /* Tidy a file if it's past its sell-by date in kB, and if
     it is greater than the specified size. Don't need an OR,
     since size age can just be set to zero.                 */

{ struct Tidy *tp;
  struct TidyPattern *tlp;
  short savetravlinks, savekilloldlinks;

for (tp = VTIDY; tp != NULL; tp=tp->next)
   {
   if (tp->tidylist == NULL)  /* used to eliminate non-home searches */
      {
      continue;
      }

   for (tlp = tp->tidylist; tlp != NULL; tlp=tlp->next)
      {
      if (IsExcluded(tlp->classes))
	 {
	 continue;
	 }

      savetravlinks = TRAVLINKS;
      savekilloldlinks = KILLOLDLINKS;

      ResetOutputRoute(tlp->log,tlp->inform);
      
      if (tlp->travlinks == 'T')
         {
         TRAVLINKS = true;
         }
      else if (tlp->travlinks == 'F')
         {
         TRAVLINKS = false;
	 }
      else if (tlp->travlinks == 'K')
         {
         KILLOLDLINKS = true;
         }

      TRAVLINKS = savetravlinks;
      
      if (WildMatch(tlp->pattern,name) && CheckHomeSubDir(path,tp->path,tp->recurse))
         {
         DoTidyFile(path,name,tlp,statbuf,CF_USELOGFILE);
	 }
      
      ResetOutputRoute('d','d');
      }
   }

TRAVLINKS = savetravlinks;
KILLOLDLINKS = savekilloldlinks;
}


/*********************************************************************/

int RecursiveTidySpecialArea(name,tp,maxrecurse,sb)

char *name;
struct Tidy *tp;
int maxrecurse;
struct stat *sb;

{ struct stat statbuf,topstatbuf;
  DIR *dirh;
  struct dirent *dirp;
  char pcwd[bufsize];
  int is_dir,level,nostat=false,goback;

bzero(&statbuf,sizeof(statbuf));

if (!DirPush(name,sb))
   {
   return false;
   }

 
if (maxrecurse == -1)
   {
   Debug2("MAXRECURSE ran out, quitting at %s\n",name);
   return true;
   }

if (IgnoreFile(name,"",NULL))
   {
   Debug2("cfengine: Ignoring directory %s\n",name);
   return true;
   }

if (strlen(name) == 0)     /* Check for root dir */
   {
   name = (char *) malloc(2);
   name[0] = '/';
   name[1] = '\0';
   }

if (maxrecurse == tp->recurse)
   {
   if (lstat(name,&topstatbuf) == -1)
      {
      if (DEBUG || D2 || VERBOSE)
	 {
	 sprintf(OUTPUT,"Can't stat %s\n",name);
	 CfLog(cferror,OUTPUT,"");
	 bzero(VBUFF,bufsize);
	 if (readlink(name,VBUFF,bufsize-1) != -1)
	    {
	    sprintf(OUTPUT,"File is link to -> %s\n",VBUFF);
	    CfLog(cferror,OUTPUT,"");
	    }
         }
      return true;
      }
   }

if ((dirh = opendir(".")) == NULL)
   {
   sprintf(OUTPUT,"Can't open directory [%s]\n",name);
   CfLog(cferror,OUTPUT,"opendir");
   return true;
   }

Debug("Tidy: opening dir %s\n",name);

for (dirp = readdir(dirh); dirp != NULL; dirp = readdir(dirh))
   {
   if (!SensibleFile(dirp->d_name,name,NULL))
      {
      continue;
      }

   if (IgnoreFile(name,dirp->d_name,NULL))
      {
      continue;
      }

   strcpy(pcwd,name);                                   /* Assemble pathname */
   AddSlash(pcwd);

   if (BufferOverflow(pcwd,dirp->d_name))
      {
      return true;
      }

   strcat(pcwd,dirp->d_name);

   if (stat(dirp->d_name,&statbuf) == -1)
      {
      nostat=true;
      }

   if (S_ISDIR(statbuf.st_mode))
      {
      is_dir =  true;
      }
   else
      {
      is_dir = false;
      }
   
   if (!TRAVLINKS || nostat)   /* Don't try to travlinks where we can't stat destination - just remove them */
      {
      if (lstat(dirp->d_name,&statbuf) == -1)
         {
         if (DEBUG || D2 || VERBOSE)
            {
            sprintf(OUTPUT,"Can't stat %s\n",pcwd);
	    CfLog(cferror,OUTPUT,"lstat");
	    bzero(VBUFF,bufsize);
            if (readlink(pcwd,VBUFF,bufsize-1) != -1)
               {
               sprintf(OUTPUT,"File is link to -> %s\n",VBUFF);
	       CfLog(cferror,OUTPUT,"");
               }
            }
         continue;
         }
      }

   level = tp->recurse - maxrecurse;

   if (S_ISDIR(statbuf.st_mode))              /* note lstat above! */
      {
      if (IsMountedFileSystem(&statbuf,pcwd,1))
         {
         continue;
         }
      else
         {
         goback = RecursiveTidySpecialArea(pcwd,tp,maxrecurse-1,&statbuf);

	 DirPop(goback,name,sb);
         }

      TidyParticularFile(pcwd,dirp->d_name,tp,&statbuf,is_dir,level);
      }
   else
      {
      TidyParticularFile(pcwd,dirp->d_name,tp,&statbuf,is_dir,level);
      }
   }

closedir(dirh);

if (maxrecurse == tp->recurse)
   {
   Debug("Checking tidy topmost directory %s\n",name);

   TidyParticularFile(name,ReadLastNode(name),tp,&topstatbuf,true,tp->recurse);
   }

return true; 
}

/*********************************************************************/

void TidyParticularFile(path,name,tp,statbuf,is_dir,level)

char *path, *name;
struct Tidy *tp;
struct stat *statbuf;
int level,is_dir;

{ struct TidyPattern *tlp;

Debug2("TidyParticularFile(%s,%s)\n",path,name);

if (tp->tidylist == NULL)
   {
   return;
   }

for (tlp = tp->tidylist; tlp != NULL; tlp=tlp->next)
   {
   ResetOutputRoute(tlp->log,tlp->inform);

   if (S_ISLNK(statbuf->st_mode) && is_dir && (tlp->dirlinks == 'k') && (tlp->rmdirs == 'f'))  /* Keep links to directories */
      {
      ResetOutputRoute('d','d');
      continue;
      }

   if (is_dir && tlp->rmdirs == 'f')               /* not allowed to rmdir */
      {
      ResetOutputRoute('d','d');
      continue;
      }

   if ((level == tp->recurse) && tlp->rmdirs == 's') /* rmdir subdirs only */
      {
      ResetOutputRoute('d','d');
      continue;
      }
   
   if (level > tlp->recurse && tlp->recurse != INFINITERECURSE)
      {
      Debug2("[PATTERN %s RECURSE ENDED at %d(%d) BEFORE MAXVAL %d]\n",tlp->pattern,
		level,tlp->recurse,tp->recurse);
      ResetOutputRoute('d','d');
      continue;
      }
   
   if (IsExcluded(tlp->classes))
      {
      ResetOutputRoute('d','d');
      continue;
      }
   
   if (! WildMatch(tlp->pattern,name))
      {
      ResetOutputRoute('d','d');
      continue;
      }

   if (!FileObjectFilter(path,statbuf,tlp->filters,tidy))
      {
      continue;
      }
      
   if (S_ISLNK(statbuf->st_mode) && is_dir && (tlp->dirlinks == 't'))
      {
      Debug("Link to directory, dirlinks= says delete these\n");
      }
   else if (is_dir && !EmptyDir(path))
      {
      sprintf(OUTPUT,"Non-empty directory %s, skipping..\n",path);
      CfLog(cfinform,OUTPUT,"");
      ResetOutputRoute('d','d');
      continue;
      }
   
   Debug2("Matched %s to %s in %s\n",name,tlp->pattern,path);
   DoTidyFile(path,name,tlp,statbuf,CF_NOLOGFILE);
   ResetOutputRoute('d','d');
   }
}

/*********************************************************************/
/* Level 2                                                           */
/*********************************************************************/

void DoTidyFile(path,name,tlp,statbuf,logging_this)

char *path, *name;
struct TidyPattern *tlp;
struct stat *statbuf;
short logging_this;

{ time_t nowticks, fileticks;
  int size_match = false, age_match = false;

Debug2("DoTidyFile(%s,%s)\n",path,name);

nowticks = time((time_t *)NULL);             /* cmp time in days */

switch (tlp->searchtype)
   {
   case 'a': fileticks = statbuf->st_atime;
             break;
   case 'm': fileticks = statbuf->st_mtime;
	     break;
   case 'c': fileticks = statbuf->st_ctime;
	     break;
   default:  printf("cfengine: Internal error in DoTidyFile()\n");
             break;
   }

if (nowticks-fileticks < 0)                  /* shouldn't happen */
   {
   sprintf(OUTPUT,"ALERT: atime for %s is in the future. Check system clock!\n",path);
   CfLog(cfinform,OUTPUT,"");
   return;
   }

if (tlp->size == CF_EMPTYFILE)
   {
   if (statbuf->st_size == 0)
      {
      size_match = true;
      }
   else
      {
      size_match = false;
      }
   }
else
   {
   size_match = (tlp->size <= statbuf->st_size);
   }

age_match = tlp->age*ticksperday <= (nowticks-fileticks);

if (age_match && size_match)
   {
   if (logging_this)
      {
      fprintf(VLOGFP,"cf: rm %s\n",path);
      }

   if (! DONTDO)
      {
      if (S_ISDIR(statbuf->st_mode))
	 {
	 if (rmdir(name) == -1)
	    {
	    CfLog(cferror,"","unlink");
	    }
	 else
	    {
	    AddMultipleClasses(tlp->defines);
	    }
	 }
      else
	 {
	 if (tlp->compress == 't')
	    {
	    CompressFile(name);
	    }
         else if (unlink(name) == -1)
	    {
	    sprintf(OUTPUT,"Couldn't unlink %s tidying\n",path);
            CfLog(cfverbose,OUTPUT,"unlink");
	    }

	 sprintf(OUTPUT,"Deleting %s\n",path);
	 CfLog(cfinform,OUTPUT,"");
	 sprintf(OUTPUT,"Size=%d bytes, %c-age=%d days\n",
		 statbuf->st_size,tlp->searchtype,(nowticks-fileticks)/ticksperday);
	 CfLog(cfverbose,OUTPUT,"");
	 
	 AddMultipleClasses(tlp->defines);
	 }
      }
   else
      {
      if (tlp->compress == 't')
	 {
	 printf("%s: want to compress %s\n",VPREFIX,path);
	 }
      else
	 {
	 printf("%s: want to delete %s\n",VPREFIX,path);
	 }
      }
   }
else
   {
   Debug2("(No age match)\n");
   }
}


/*********************************************************************/

void DeleteTidyList(list)

struct TidyPattern *list;

{
if (list != NULL)
   {
   DeleteTidyList(list->next);
   list->next = NULL;

   if (list->classes != NULL)
      {
      free (list->classes);
      }

   free((char *)list);
   }
}







