/* 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"

/*****************************************************************************/
/*                                                                           */
/* File: expand-tidy.c                                                       */
/*                                                                           */
/*****************************************************************************/

int RecursiveHomeTidy(char *name,int level,struct stat *sb)

{ struct stat statbuf;
  DIR *dirh;
  struct dirent *dirp;
  char pcwd[CF_BUFSIZE];
  time_t ticks;
  int done = false,goback;

Debug("\nHomeTidy(%s,%d)\n\n",name,level);

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

if (level > CF_RECURSION_LIMIT)
   {
   snprintf(OUTPUT,CF_BUFSIZE*2,"WARNING: Very deep nesting of directories (> %d deep): %s (Aborting tidy)",level,name);
   CfLog(cferror,OUTPUT,"");
   return true;
   }

Verbose("HomeTidy: Opening %s as .\n",name);

if ((dirh = opendir(".")) == NULL)
   {
   snprintf(OUTPUT,CF_BUFSIZE*2,"Can't open directory %s\n",name);
   CfLog(cfverbose,OUTPUT,"opendir");
   return true;
   }

if (level == 2)
   {
   if (LOGTIDYHOMEFILES)
      {
      int tempfd;
      strcpy(VLOGFILE,name);
      strcat(VLOGFILE,"/.cfengine.rm");
      
      /* Unlink here to avoid an exploit which could be used to
         overwrite a system file with root privileges. */
      
      if (unlink(VLOGFILE) == -1)
         {
         Debug("Pre-existing object %s could not be removed\n",VLOGFILE);
         }
      
      if ((tempfd = open(VLOGFILE, O_CREAT|O_EXCL|O_WRONLY,0600)) < 0)
         {
         snprintf(OUTPUT,CF_BUFSIZE,"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))  /* No ref to tp->ignores here...fix?*/
      {
      continue;
      }

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

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

   strcat(pcwd,dirp->d_name);

   if (TRAVLINKS)
      {
      Verbose("Warning: you are using travlinks=true. It is a potential security hazard if there are untrusted users\n");

      if (lstat(dirp->d_name,&statbuf) == -1)
         {
         snprintf(OUTPUT,CF_BUFSIZE*2,"Can't stat %s\n",pcwd);
         CfLog(cferror,OUTPUT,"stat");
         continue;
         }
      
      if (S_ISLNK(statbuf.st_mode) && (statbuf.st_mode != getuid()))   
         {
         snprintf(OUTPUT,CF_BUFSIZE,"File %s is an untrusted link. cfagent will not follow it with a destructive operation (tidy)",pcwd);
         CfLog(cfinform,OUTPUT,"");
         continue;
         }
      
      if (stat(dirp->d_name,&statbuf) == -1)
         {
         snprintf(OUTPUT,CF_BUFSIZE*2,"Can't stat %s\n",pcwd);
         CfLog(cferror,OUTPUT,"stat");
         continue;
         }
      }
   else
      {
      if (lstat(pcwd,&statbuf) == -1)
         {
         if (DEBUG || D2 || VERBOSE)
            {
            snprintf(OUTPUT,CF_BUFSIZE*2,"Can't stat %s\n",pcwd);
            CfLog(cferror,OUTPUT,"lstat");
            memset(VBUFF,0,CF_BUFSIZE);
            if (readlink(pcwd,VBUFF,CF_BUFSIZE-1) != -1)
               {
               snprintf(OUTPUT,CF_BUFSIZE*2,"File is link to -> %s\n",VBUFF);
               CfLog(cferror,OUTPUT,"");
               }
            }
         continue;
         }
      }

   if (S_ISDIR(statbuf.st_mode))
      {
      if (IsMountedFileSystem(&statbuf,pcwd,1))
         {
         continue;
         }
      else
         {
         if (!done)
            {
            /* Note, here we pass on the full path name, not relative name to retain
               state, but we have statted the right file above with opendir("."), so 
               the race test is still secure for the next recursion level */
            
            goback = RecursiveHomeTidy(pcwd,level+1,&statbuf);
            DirPop(goback,name,sb);     
            }
         }
      }
   else
      {
      if (!TidyHomeFile(pcwd,dirp->d_name,&statbuf,level))
         {
         done = true;
         }
      }
   }
 
 if (level == 2)
    {
    if (LOGTIDYHOMEFILES)
       {
       fclose(VLOGFP);
       chmod(VLOGFILE,DEFAULTMODE);
       }
    }
 
 closedir(dirh);
 return true;
}


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

int TidyHomeFile(char *path,char *name,struct stat *statbuf,int level)

  /* 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 = TRAVLINKS, savekilloldlinks = KILLOLDLINKS;

/* Note that we have to go through the whole tidy list here, even non-home,
   so be careful to pick out the rules the affect us! The info
   about home dissappears here, since we have expanded the home/
   directive into an actual path, so we make sure that no non-home
   rules can be applied to home directories */
  
for (tp = VTIDY; tp != NULL; tp=tp->next)
   {
   if ((strncmp(tp->path,"home/",5) != 0) && (strcmp(tp->path,"home") != 0))
      {
      continue;
      }

   if (tp->tidylist == NULL || tp->done == 'y')
      {
      continue;
      }

   if ((tp->maxrecurse != CF_INF_RECURSE) && (level > tp->maxrecurse+1))
      {
      Debug("Recursion maxed out at level %d/%d\n",level,tp->maxrecurse+1);
      /*return false;*/
      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;
         }

      if (!FileObjectFilter(path,statbuf,tlp->filters,tidy))
         {
         continue;
         }

      if (IgnoredOrExcluded(tidy,path,NULL,tp->exclusions))
         {
         Debug("Skipping ignored/excluded file %s\n",path);
         continue;
         }

      if (WildMatch(tlp->pattern,name) && CheckHomeSubDir(path,tp->path,tp->maxrecurse))
         {
         if ((tlp->recurse != CF_INF_RECURSE) && (level > tlp->recurse+1))
            {
            Debug("Not tidying %s - level %d > limit %d\n",path,level,tlp->recurse+1);
            continue;
            }

         DoTidyFile(path,name,tlp,statbuf,CF_USELOGFILE,false,false);
         }
      
      ResetOutputRoute('d','d');
      }
   }

TRAVLINKS = savetravlinks;
KILLOLDLINKS = savekilloldlinks;
return true; 
}

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

int RecursiveTidySpecialArea(char *name,struct Tidy *tp,int maxrecurse,struct stat *sb)

{ struct stat statbuf,topstatbuf;
  DIR *dirh;
  struct dirent *dirp;
  char pcwd[CF_BUFSIZE], *parentdir;
  int is_dir,level,goback;
  static char *defaultname = "/";
       
Debug("RecursiveTidySpecialArea(%s)\n",name);
memset(&statbuf,0,sizeof(statbuf));

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

if (IgnoredOrExcluded(tidy,name,NULL,tp->exclusions))
  {
  Debug("Skipping ignored/excluded file %s\n",name);
  return true;
  }

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

if (strlen(name) == 0)     /* Check for root dir */
   {
   name = defaultname;
   }

if (maxrecurse == tp->maxrecurse)
   {
   if (lstat(name,&topstatbuf) == -1)
      {
      if (DEBUG || D2 || VERBOSE)
         {
         snprintf(OUTPUT,CF_BUFSIZE*2,"Can't stat %s\n",name);
         CfLog(cferror,OUTPUT,"");
         memset(VBUFF,0,CF_BUFSIZE);
         if (readlink(name,VBUFF,CF_BUFSIZE-1) != -1)
            {
            snprintf(OUTPUT,CF_BUFSIZE*2,"File is link to -> %s\n",VBUFF);
            CfLog(cferror,OUTPUT,"");
            }
         }

      return true;
      }
   }
 
 if ((dirh = opendir(".")) == NULL)
    {
    snprintf(OUTPUT,CF_BUFSIZE*2,"Can't open directory [%s]\n",name);
    CfLog(cfverbose,OUTPUT,"opendir");
    free(name);
    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,tp->ignores))
      {
      continue;
      }

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

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

   level = tp->maxrecurse - maxrecurse;

   strcat(pcwd,dirp->d_name);

   if (lstat(dirp->d_name,&statbuf) == -1)          /* Check for links first */
      {
      Verbose("Can't stat %s (%s)\n",dirp->d_name,pcwd);
      continue;
      }
   else
      {
      if (S_ISLNK(statbuf.st_mode) && (statbuf.st_uid != getuid()))   
         {
         snprintf(OUTPUT,CF_BUFSIZE,"File %s is an untrusted link. cfagent will not follow it with a destructive operation (tidy)",pcwd);
         if (getuid() == 0)
            {
            TidyParticularFile(pcwd,dirp->d_name,tp,&statbuf,false,level,false);
            }
         continue;
         }
      }
   
   if (TRAVLINKS && (stat(dirp->d_name,&statbuf) == -1))
      {
      Verbose("Can't stat %s (%s)\n",dirp->d_name,pcwd);
      continue;
      }
   
   if (tp->xdev =='y' && DeviceChanged(statbuf.st_dev))
      {
      Verbose("Skipping %s on different device\n",pcwd);
      continue;
      }
   
   if (S_ISDIR(statbuf.st_mode))
      {
      is_dir =  true;
      }
   else
      {
      is_dir = false;
      }
   
   if (S_ISDIR(statbuf.st_mode))              /* note lstat above! */
      {
      if (IsMountedFileSystem(&statbuf,pcwd,1))
         {
         continue;
         }
      else
         {
  /* Note, here we pass on the full path name, not relative name to retain
     state, but we have statted the right file above with opendir("."), so 
     the race test is still secure for the next recursion level */
         
         goback = RecursiveTidySpecialArea(pcwd,tp,maxrecurse-1,&statbuf);
         DirPop(goback,name,sb);
         }  
      
      TidyParticularFile(pcwd,dirp->d_name,tp,&statbuf,is_dir,level,false);
      }
   else
      {
      TidyParticularFile(pcwd,dirp->d_name,tp,&statbuf,is_dir,level,false);
      }
   }

closedir(dirh);

if (maxrecurse == tp->maxrecurse)
   {
   struct TidyPattern *tlp;
   int ok = true;
   
   Debug("Checking tidy topmost directory %s\n",name);

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

      if (tlp->rmdirs == 's')
         {
         ok = false;
         }
      }
   
   if (ok)
      {
      char* basename=ReadLastNode(name); /* the last node of a pathname string  */
      
      if (strlen(name) == strlen(basename))  /* if name is something like "tmp" */
         {
         chdir("/");
         }
      else /* if the names are like "/tmp", or "/var/tmp" */
         {
         if ((parentdir = strdup(name)) == NULL)
            {
            FatalError("memory failure");
            }
         
         *(parentdir+strlen(name)-strlen(basename)) = '\0'; 
         
         chdir(parentdir); /* now it can chdir to "/var". */
         free(parentdir);
         }
      
      TidyParticularFile(name,basename,tp,&topstatbuf,true,tp->maxrecurse,true);
      }
   }

return true; 
}


