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

*/
 

/*********************************************************************/
/*                                                                   */
/*  TOOLKITS: "object" library                                       */
/*                                                                   */
/*********************************************************************/

#define INET

#include "cf.defs.h"
#include "cf.extern.h"
#include "../pub/global.h"
#include "../pub/md5.h"


#ifdef HAVE_DB_H
# include <db.h>
#endif


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

int DirPush(name,sb)          /* Enter dir and check for race exploits */

char *name;
struct stat *sb;

{
if (chdir(name) == -1)
   {
   snprintf(OUTPUT,bufsize,"Could not change to directory %s, mode %o in tidy",name,sb->st_mode & 07777);
   CfLog(cfinform,OUTPUT,"chdir");
   return false;
   }
else
   {
   Debug("Changed directory to %s\n",name);
   }

CheckLinkSecurity(sb,name);
return true; 
}

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

void DirPop(goback,name,sb)      /* Exit dir and check for race exploits */

char *name;
int goback;
struct stat *sb;

{
if (goback && TRAVLINKS)
   {
   if (chdir(name) == -1)
      {
      snprintf(OUTPUT,bufsize,"Error in backing out of recursive descent securely to %s",name);
      CfLog(cferror,OUTPUT,"chdir");
      HandleSignal(SIGTERM);
      }
   
   CheckLinkSecurity(sb,name); 
   }
else if (goback)
   {
   if (chdir("..") == -1)
      {
      snprintf(OUTPUT,bufsize,"Error in backing out of recursive descent securely to %s",name);
      CfLog(cferror,OUTPUT,"chdir");
      HandleSignal(SIGTERM);
      }
   }
}

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

void CheckLinkSecurity(sb,name)

struct stat *sb;
char *name;

{ struct stat security;

Debug("Checking the inode and device to make sure we are where we think we are...\n"); 

if (stat(".",&security) == -1)
   {
   snprintf(OUTPUT,bufsize,"Could not stat directory %s after entering!",name);
   CfLog(cferror,OUTPUT,"stat");
   return;
   }

if ((sb->st_dev != security.st_dev) || (sb->st_ino != security.st_ino))
   {
   snprintf(OUTPUT,bufsize,"SERIOUS SECURITY ALERT: path race exploited in recursion to/from %s. Not safe for agent to continue - aborting",name);
   CfLog(cferror,OUTPUT,"");
   HandleSignal(SIGTERM);
   /* Exits */
   }
}

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

void GetNameInfo()

{ int i, sz, found = false;
  char *sp,*sp2,**q;
  time_t tloc;
  struct hostent *hp;
  struct sockaddr_in cin;
#ifdef AIX
  char real_version[_SYS_NMLN];
#endif
  
VFQNAME[0] = VUQNAME[0] = '\0';
  
if (uname(&VSYSNAME) == -1)
   {
   perror("uname ");
   FatalError("Uname couldn't get kernel name info!!\n");
   }

#ifdef AIX
sprintf(real_version,"%.80s.%.80s", VSYSNAME.version, VSYSNAME.release);
strncpy(VSYSNAME.release, real_version, _SYS_NMLN);
#endif 

for (sp = VSYSNAME.sysname; *sp != '\0'; sp++)
   {
   *sp = ToLower(*sp);
   }

for (sp = VSYSNAME.machine; *sp != '\0'; sp++)
   {
   *sp = ToLower(*sp);
   }


for (i = 0; CLASSATTRIBUTES[i][0] != '\0'; i++)
   {
   if (WildMatch(CLASSATTRIBUTES[i][0],VSYSNAME.sysname))
      {
      if (WildMatch(CLASSATTRIBUTES[i][1],VSYSNAME.machine))
         {
         if (WildMatch(CLASSATTRIBUTES[i][2],VSYSNAME.release))
            {
	    if (UNDERSCORE_CLASSES)
	       {
	       sprintf(VBUFF,"_%s",CLASSTEXT[i]);
	       AddClassToHeap(VBUFF);
	       }
	    else
	       {
               AddClassToHeap(CLASSTEXT[i]);
	       }
            found = true;
            break;
            }
         }
      else
         {
         Debug2("Cfengine: I recognize %s but not %s\n",VSYSNAME.sysname,VSYSNAME.machine);
         continue;
         }
      }
   }

if ((sp = malloc(strlen(VSYSNAME.nodename)+1)) == NULL)
   {
   FatalError("malloc failure in initialize()");
   }

strcpy(sp,VSYSNAME.nodename);
SetDomainName(sp);

strcpy(VPREFIX,VSYSNAME.nodename);

for (sp2=sp; *sp2 != '\0'; sp2++)  /* Truncate fully qualified name */
   {
   if (*sp2 == '.')
      {
      *sp2 = '\0';
      Debug("Truncating fully qualified hostname %s to %s\n",VSYSNAME.nodename,sp);
      break;
      }
   }


VDEFAULTBINSERVER.name = sp;

AddClassToHeap(CanonifyName(sp));

VSYSTEMHARDCLASS = (enum classes) i;
 
if ((tloc = time((time_t *)NULL)) == -1)
   {
   printf("Couldn't read system clock\n");
   }

if (VERBOSE)
   {
   if (UNDERSCORE_CLASSES)
      {
      sprintf(VBUFF,"_%s",CLASSTEXT[i]);
      }
   else
      {
      sprintf(VBUFF,"%s",CLASSTEXT[i]);
      }

   if (ISCFENGINE)
      {
      printf ("GNU Configuration Engine - \n%s\n%s\n\n",VERSION,COPYRIGHT);
      }
   else
      {
      printf ("GNU Cfengine server daemon - \n%s\n%s\n\n",VERSION,COPYRIGHT);      
      }

   printf ("------------------------------------------------------------------------\n\n");
   printf ("Host name is: %s\n",VSYSNAME.nodename);
   printf ("Operating System Type is %s\n",VSYSNAME.sysname);
   printf ("Operating System Release is %s\n",VSYSNAME.release);
   printf ("Architecture = %s\n\n\n",VSYSNAME.machine);
   printf ("Using internal soft-class %s for host %s\n\n",VBUFF,VSYSNAME.nodename);
   printf ("The time is now %s\n\n",ctime(&tloc));
   printf ("------------------------------------------------------------------------\n\n");

   }

sprintf(VBUFF,"%d_bit",sizeof(long)*8);
AddClassToHeap(VBUFF);
Verbose("Additional hard class defined as: %s\n",CanonifyName(VBUFF));

sprintf(VBUFF,"%s_%s",VSYSNAME.sysname,VSYSNAME.release);
AddClassToHeap(CanonifyName(VBUFF));

AddClassToHeap(CanonifyName(VSYSNAME.machine));
 
Verbose("Additional hard class defined as: %s\n",CanonifyName(VBUFF));

sprintf(VBUFF,"%s_%s",VSYSNAME.sysname,VSYSNAME.machine);
AddClassToHeap(CanonifyName(VBUFF));

Verbose("Additional hard class defined as: %s\n",CanonifyName(VBUFF));

sprintf(VBUFF,"%s_%s_%s",VSYSNAME.sysname,VSYSNAME.machine,VSYSNAME.release);
AddClassToHeap(CanonifyName(VBUFF));

Verbose("Additional hard class defined as: %s\n",CanonifyName(VBUFF));

#ifdef HAVE_SYSINFO
#ifdef SI_ARCHITECTURE
sz = sysinfo(SI_ARCHITECTURE,VBUFF,bufsize);
if (sz == -1)
  {
  Verbose("cfengine internal: sysinfo returned -1\n");
  }
else
  {
  AddClassToHeap(CanonifyName(VBUFF));
  Verbose("Additional hard class defined as: %s\n",VBUFF);
  }
#endif
#endif

sprintf(VBUFF,"%s_%s_%s_%s",VSYSNAME.sysname,VSYSNAME.machine,VSYSNAME.release,VSYSNAME.version);

if (strlen(VBUFF) < maxvarsize-2)
   {
   VARCH= strdup(CanonifyName(VBUFF));
   }
else
   {
   Verbose("cfengine internal: $(arch) overflows maxvarsize! Truncating\n");
   VARCH = strdup(CanonifyName(VSYSNAME.sysname));
   }

AddClassToHeap(VARCH);

Verbose("Additional hard class defined as: %s\n",VARCH);

if (! found)
   {
   CfLog(cferror,"Cfengine: I don't understand what architecture this is!","");
   }

strcpy(VBUFF,AUTOCONF_SYSNAME);

AddClassToHeap(CanonifyName(VBUFF));

Verbose("\nGNU autoconf class from compile time: %s\n\n",VBUFF);
Verbose("  Careful with this - it might not be correct at run time if you have\n");
Verbose("  several OS versions with binary compatibilty!\n\n");

/* Get IP address from nameserver */

if ((hp = gethostbyname(VSYSNAME.nodename)) == NULL)
   {
   return;
   }
else
   {
   bzero(&cin,sizeof(cin));
   cin.sin_addr.s_addr = ((struct in_addr *)(hp->h_addr))->s_addr;
   Verbose("Address given by nameserver: %s\n",inet_ntoa(cin.sin_addr));
   strcpy(VIPADDRESS,inet_ntoa(cin.sin_addr));
   for (sp = VIPADDRESS+strlen(VIPADDRESS)-1; *sp != '.'; sp--)
      {
      }
   *sp = '\0';
   AddClassToHeap(CanonifyName(VIPADDRESS));

   strcpy(VIPADDRESS,inet_ntoa(cin.sin_addr));
   AddClassToHeap(CanonifyName(VIPADDRESS));

   for (i=0; hp->h_aliases[i]!= NULL; i++)
      {
      Debug("Adding alias %s..\n",hp->h_aliases[i]);
      AddClassToHeap(CanonifyName(hp->h_aliases[i])); 
      }
   }
}

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

void GetInterfaceInfo(void)

{ int fd,len,i,j;
  struct ifreq ifbuf[64],ifr;
  struct ifconf list;
  struct sockaddr_in *sin;
  struct hostent *hp;

if ((fd=socket(AF_INET, SOCK_DGRAM, 0)) == -1)
   {
   CfLog(cferror,"Couldn't open socket","socket");
   exit(1);
   }

list.ifc_len = sizeof(ifbuf);
list.ifc_req = ifbuf;

if (ioctl(fd, SIOCGIFCONF, &list) == -1 || (list.ifc_len < (sizeof(struct ifreq))))
   {
   CfLog(cferror,"Couldn't get interfaces","ioctl");
   exit(1);
   }

len=list.ifc_len/sizeof(struct ifreq);
Debug("Found: %d interfaces\n",len);

for (j=0;j<len;j++)
   {
   Debug("Interface %d: %s\n", j+1, ifbuf[j].ifr_name);

   strncpy(ifr.ifr_name,ifbuf[j].ifr_name,sizeof(ifbuf[j].ifr_name));
   if (ioctl(fd,SIOCGIFFLAGS,&ifr) == -1)
      {
      CfLog(cferror,"No such network device","ioctl");
      return;
      }

   if ((ifr.ifr_flags & IFF_UP) && !(ifr.ifr_flags & IFF_LOOPBACK))
      {
      sin=(struct sockaddr_in *)&ifbuf[j].ifr_addr;

      if ((hp=gethostbyaddr((char *)&(sin->sin_addr.s_addr),sizeof(sin->sin_addr.s_addr),AF_INET)) == NULL)
         {
         Debug("Host information for %s not found\n", inet_ntoa(sin->sin_addr));
         }
      else
         {
         if (hp->h_name != NULL)
            {
            Debug("Adding hostip %s..\n",inet_ntoa(sin->sin_addr));
            AddClassToHeap(CanonifyName(inet_ntoa(sin->sin_addr)));
            Debug("Adding hostname %s..\n",hp->h_name);
            AddClassToHeap(CanonifyName(hp->h_name));
            for (i=0;hp->h_aliases[i]!=NULL;i++)
               {
               Debug("Adding alias %s..\n",hp->h_aliases[i]);
               AddClassToHeap(CanonifyName(hp->h_aliases[i]));
               }
            }
         }
      }
   }
 
close(fd);
}

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

void AddNetworkClass(netmask) /* Function contrib David Brownlee <abs@mono.org> */

char *netmask;

{ struct in_addr ip,nm;
  char *sp,nmbuf[maxvarsize],ipbuf[maxvarsize];

    /*
     * Has to differentiate between cases such as:
     *		192.168.101.1/24 -> 192.168.101  	and
     *		192.168.101.1/26 -> 192.168.101.0 
     * We still have the, um... 'interesting' Class C default Network Class
     * set by GetNameInfo()
     */

    /* This is also a convenient method to ensure valid dotted quad */
  if ((nm.s_addr = inet_addr(netmask)) != -1 && (ip.s_addr = inet_addr(VIPADDRESS)) != -1)
    {
    ip.s_addr &= nm.s_addr;	/* Will not work with IPv6 */
    strcpy(ipbuf,inet_ntoa(ip));
    
    strcpy(nmbuf,inet_ntoa(nm));
    
    while( (sp = strrchr(nmbuf,'.')) && strcmp(sp,".0") == 0 )
       {
       *sp = 0;
       *strrchr(ipbuf,'.') = 0;
       }
    AddClassToHeap(CanonifyName(ipbuf)); 
    }
}

/*********************************************************************/
/* TOOLKIT : files/directories                                       */
/**********************************************************************/

void TruncateFile(name)

char *name;

{ struct stat statbuf;
  int fd;

if (stat(name,&statbuf) == -1)
   {
   Debug2("cfengine: didn't find %s to truncate\n",name);
   return;
   }
else
   {
   if ((fd = creat(name,000)) == -1)      /* dummy mode ignored */
      {
      sprintf(OUTPUT,"creat(%s) failed\n",name);
      CfLog(cferror,OUTPUT,"creat");
      }
   else
      {
      close(fd);
      }
   }
}

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

int FileSecure (name)

char *name;

{ struct stat statbuf;

if (PARSEONLY || !CFPARANOID)
   {
   return true;
   }

if (stat(name,&statbuf) == -1)
   {
   return false;
   }

if (statbuf.st_uid != getuid())
   {
   sprintf(OUTPUT,"File %s is not owned by uid %d (security exception)",name,getuid());
   CfLog(cferror,OUTPUT,"");
   }
 
/* Is the file writable by ANYONE except the owner ? */
 
if (statbuf.st_mode & (S_IWGRP | S_IWOTH))
   {
   sprintf(OUTPUT,"File %s (owner %d) is writable by others (security exception)",name,getuid());
   CfLog(cferror,OUTPUT,"");
   return false;
   }

return true; 
}
/***************************************************************/

int ChecksumChanged(filename,digest,warnlevel,refresh)

    /* Returns false if filename never seen before, and adds a checksum
       to the database. Returns true if checksums do not match and also
       updates database to the new value */
    
char *filename;
char digest[17];
int warnlevel,refresh;

{ struct stat stat1, stat2;
  int i, matched, needupdate = false;
  char dbvalue[17],dbdigest[17];
  time_t tloc;
#if defined HAVE_LIBDB
  DBT key,value;
  DB *dbp;
  DB_ENV *dbenv = NULL;
  DBC *dbcp;
  db_recno_t recno;
  
Debug("ChecksumChanged: key %s in %s with data %s\n",filename,CHECKSUMDB,cfMDPrint(digest));

if (CHECKSUMDB[0] == '\0')
   {
   if (ISCFENGINE)
      {
      CfLog(cferror,"(cfd) No database defined","");
      return false;
      }
   else
      {
      Debug("Direct comparison (no db)\n");
      cfMDFile(filename,dbdigest);
      for (i = 0; i < 16; i++)
	 {
	 if (digest[i] != dbdigest[i])
	    {
	    return true;
	    }
	 }
      return false;
      }
   }

if (refresh)
   {
   /* Check whether database is current wrt local file */

   if (stat(filename,&stat1) == -1)
      {
      sprintf(OUTPUT,"Couldn't stat %s\n",filename);
      CfLog(cferror,OUTPUT,"stat");
      return false;
      }
   
   if (stat(CHECKSUMDB,&stat2) != -1)
      {
      if (stat1.st_mtime > stat2.st_mtime)
	 {
	 Debug("Checksum database is older than %s...refresh needed\n",filename);
	 needupdate = true;
	 }
      else
	 {
	 Debug("Checksum up to date..\n");
	 }
      }
   else
      {
      needupdate = true;
      }      
   }
 
 /* Open the database. -- do we need an environment now? */
 
if ((errno = db_create(&dbp,dbenv,0)) != 0)
   {
   sprintf(OUTPUT,"cfd: couldn't open checksum database %s\n",CHECKSUMDB);
   CfLog(cferror,OUTPUT,"db_open");
   Debug("Ended 2\n");
   return false;
   }
 
if ((errno = dbp->open(dbp,CHECKSUMDB,NULL,DB_BTREE,DB_CREATE,0664)) != 0)
   {
   sprintf(OUTPUT,"cfd: couldn't open checksum database %s\n",CHECKSUMDB);
   CfLog(cferror,OUTPUT,"db_open");
   Debug("Ended 2.1\n");
   dbp->close(dbp,0);
   return false;
   }

Debug("Opening database file %s\n",CHECKSUMDB); 
bzero(&value,sizeof(value)); 
bzero(&key,sizeof(key));       
      
key.data = filename;
key.size = strlen(filename)+1;
value.data = dbvalue;
value.size = 17; 

if (needupdate)
   {
   cfMDFile(filename,dbdigest);
   dbdigest[16] = '\0';
   
   key.data = filename;
   key.size = strlen(filename)+1;
   value.data = (void *) dbdigest;
   value.size = 17;
   
   Debug("cfd: updating checksum for %s to %s\n",filename,cfMDPrint(value.data));
   
   if ((errno = dbp->del(dbp,NULL,&key,0)) != 0)
      {
      CfLog(cferror,"","db_store");
      }
  
   key.data = filename;
   key.size = strlen(filename)+1;
   
   if ((errno = dbp->put(dbp,NULL,&key,&value,0)) != 0)
      {
      CfLog(cferror,"put failed","db->put");
      }      
   }

if ((errno = dbp->get(dbp,NULL,&key,&value,0)) == 0)
   {
   /* The filename key was found in the db */
   Debug("Comparing %s (sent) with %s (db)\n",cfMDPrint(digest),cfMDPrint(value.data));
   bcopy(value.data,dbdigest,17);
   
   for (i = 0; i < 16; i++)
      {
      if (digest[i] != dbdigest[i])
	 {
	 Debug("cfd: Found checksum for %s in database but it didn't match\n",filename);

	 CfLog(warnlevel,"!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","");
	 sprintf(OUTPUT,"SECURITY ALERT: Checksum for %s changed!",filename);
	 CfLog(warnlevel,OUTPUT,"");
	 CfLog(warnlevel,"!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","");

	 if (CHECKSUMUPDATES)
	    {
	    cfMDFile(filename,dbdigest);
	    dbdigest[16] = '\0';
	    
	    key.data = filename;
	    key.size = strlen(filename)+1;
	    value.data = (void *) dbdigest;
	    value.size = 17;
	    
	    Verbose("cfd: updating checksum for %s to %s\n",filename,cfMDPrint(value.data));
	    
	    if ((errno = dbp->del(dbp,NULL,&key,0)) != 0)
	       {
	       CfLog(cferror,"","db_store");
	       }
	    
	    key.data = filename;
	    key.size = strlen(filename)+1;
	    
	    if ((errno = dbp->put(dbp,NULL,&key,&value,0)) != 0)
	       {
	       CfLog(cferror,"put failed","db->put");
	       }
	    }

	 dbp->close(dbp,0);
	 return true;                        /* Checksum updated but was changed */
	 }
      }
   
   Debug("cfd: Found checksum for %s in database and it matched\n",filename);
   dbp->close(dbp,0);
   return false;
   }
else
   {
   /* Key was not found, so install it */

   if (ISCFENGINE)
      {
      sprintf(OUTPUT,"File %s was not in md5 database - new file found",filename);
      CfLog(cfsilent,OUTPUT,"");
      }
   
   cfMDFile(filename,dbdigest);
   digest[16] = '\0';

   key.data = filename;
   key.size = strlen(filename)+1;
   value.data = (void *) dbdigest;
   value.size = 17;

   Debug("cfd: storing checksum for %s in database %s\n",filename,cfMDPrint(dbdigest));

   if ((errno = dbp->put(dbp,NULL,&key,&value,0)) != 0)
      {
      CfLog(cferror,"put failed","db->put");
      }
   
   dbp->close(dbp,0);

   if (ISCFENGINE)
      {
      return false;      /* No need to warn when first installed */
      }
   else
      {
      return true;
      }
   }

#else
Verbose("cfd: No Berkeley DB3 database support available.\n");

if (!ISCFENGINE)
   {
   Debug("Direct comparison (no db)\n");
   cfMDFile(filename,dbdigest);
   for (i = 0; i < 16; i++)
      {
      if (digest[i] != dbdigest[i])
	 {
	 return true;
	 }
      }
   return false;
   }
 
return false;
#endif
}

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

int IgnoredOrExcluded(action,file,inclusions,exclusions)

enum actions action;
char *file;
struct Item *inclusions, *exclusions;

{ char linkbuf[bufsize], *lastnode;

Debug("IgnoredOrExcluded(%s)\n",file);

if (strstr(file,"/"))
   {
   for (lastnode = file+strlen(file); *lastnode != '/'; lastnode--)
      {
      }

   lastnode++;
   }
else
   {
   lastnode = file;
   }

if (inclusions != NULL && !IsWildItemIn(inclusions,lastnode))
   {
   Debug("cfengine: skipping non-included pattern %s\n",file);
   return true;
   }

switch(action)
   {
   case image:
              if (IsWildItemIn(VEXCLUDECOPY,lastnode) || IsWildItemIn(exclusions,lastnode))
                 {
                 Debug("Skipping excluded pattern %s\n",file);
                 return true;
                 }
   case links:
              if (IsWildItemIn(VEXCLUDELINK,lastnode) || IsWildItemIn(exclusions,lastnode))
                 {
                 Debug("Skipping excluded pattern %s\n",file);
                 return true;
                 }
   default:
              if (IsWildItemIn(exclusions,lastnode))
                 {
                 Debug("Skipping excluded pattern %s\n",file);
                 return true;
                 }       
   }

return false;
}


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

void Banner(string)

char *string;

{
Verbose("---------------------------------------------------------------------\n");
Verbose("%s\n",string);
Verbose("---------------------------------------------------------------------\n\n");
}


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

void SetDomainName(sp)           /* Bas van der Vlies */

char *sp;

{ char fqn[maxvarsize];
  char *ptr;
  char buffer[bufsize];

if (gethostname(fqn, sizeof(fqn)) != -1)
   {
   strcpy(VFQNAME,fqn);
   strcpy(buffer,VFQNAME);
   AddClassToHeap(CanonifyName(buffer));
   AddClassToHeap(CanonifyName(ToLowerStr(buffer)));

   if (strstr(fqn,"."))
      {
      ptr = strchr(fqn, '.');
      strcpy(VDOMAIN, ++ptr);
      }
   }

if (strstr(VFQNAME,".") == 0)
   {
   strcat(VFQNAME,".");
   strcat(VFQNAME,VDOMAIN);
   }

AddClassToHeap(CanonifyName(VDOMAIN)); 
}

