/* 

        Copyright (C) 1995,96,97
        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

*/



/*******************************************************************/
/*                                                                 */
/*  Cfengine : remote server daemon example                        */
/*                                                                 */
/*  This uses heavy weight processes, which is not very efficient  */
/*                                                                 */
/*  Mark Burgess 1997                                              */
/*                                                                 */
/*******************************************************************/

#define INET 1

#include "cf.defs.h"
#include "cf.extern.h"
#include "../pub/getopt.h"
#include "cfd.h"

#include <sys/ipc.h>
#include <sys/shm.h>

#if HAVE_TCPD_H
# include <tcpd.h>
#endif

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

#if HAVE_DB_DB_H
#include <db/db.h>
#endif

/*******************************************************************/
/* Level 0 : Main                                                  */
/*******************************************************************/

main (argc,argv)

int argc;
char **argv;

{
CheckOptsAndInit(argc,argv);
GetNameInfo();
ParseInputFiles();

if (PARSEONLY)
   {
   exit(0);
   }

CheckVariables();
SummarizeParsing();
StartServer(argc,argv);
}

/********************************************************************/
/* Level 1                                                          */
/********************************************************************/

CheckOptsAndInit(argc,argv)

int argc;
char **argv;

{ extern char *optarg;
  int optindex = 0;
  int c, i;

umask(0);

strcpy(VINPUTFILE,CFD_INPUT);

ISCFENGINE = false;   /* Switch for the parser */
PARSEONLY  = false;

for (i = 0; i < hashtablesize; i++)
   {
   HASH[i] = 0;
   }

AddClassToHeap("any");      /* This is a reserved word / wildcard */

while ((c=getopt_long(argc,argv,"d:f:vhpFV",CFDOPTIONS,&optindex)) != EOF)
  {
  switch ((char) c)
      {
      case 'f': strcpy(VINPUTFILE,optarg);
                break;

      case 'd': 

                switch (*optarg)
                   {
                   case '1': D1 = true;
                             break;
                   case '2': D2 = true;
                             break;
                   default:  DEBUG = true;
                             break;
                   }
                break;

      case 'v': VERBOSE = true;
	        break;

      case 'V': printf("GNU %s daemon\n%s\n",CFVERSION,COPYRIGHT);
	        printf("This program is covered by the GNU Public License and may be\n");
		printf("copied free of charge. No warrenty is implied.\n\n");
                exit(0);
	        break;

      case 'p': PARSEONLY = true;
	        break;

      case 'F': NO_FORK = true;
	        break;

      default:  Syntax();
                exit(1);

      }
   }


sprintf(VBUFF,"%s/test",LOCKFILEDIR);

MakeDirectoriesFor(VBUFF);
strcpy(VLOCKDIR,LOCKFILEDIR);

sprintf(VBUFF,"%s/test",LOGFILEDIR);

MakeDirectoriesFor(VBUFF);
strcpy(VLOGDIR,LOGFILEDIR);

strcpy(VDOMAIN,"undefined.domain");

strcpy(VCANONICALFILE,CanonifyName(VINPUTFILE));
}

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

CheckVariables()

{ struct stat statbuf;
  int i, value = -1;

bzero(CHECKSUMDB,bufsize);

if (GetMacroValue("ChecksumDatabase"))
   {
   ExpandVarstring("$(ChecksumDatabase)",CHECKSUMDB,NULL);

   if (*CHECKSUMDB != '/')
      {
      FatalError("$(ChecksumDatabase) does not expand to an absolute filename\n");
      }
   }

if (GetMacroValue("IfElapsed"))
   {
   ExpandVarstring("$(IfElapsed)",VBUFF,NULL);
   sscanf(VBUFF,"%d",&value);

   if (value < 0)
      {
      printf("cfd: silly IfElapsed value in control\n");
      exit(1);
      }
   else
      {
      VIFELAPSED = value;
      Verbose("cfd: IfElapsed time: %d minutes\n",VIFELAPSED);
      }
   }
  
bzero(VBUFF,bufsize);

if (GetMacroValue("cfrunCommand"))
   {
   ExpandVarstring("$(cfrunCommand)",VBUFF,NULL);

   if (*VBUFF != '/')
      {
      FatalError("$(cfrunCommand) does not expand to an absolute filename\n");
      }

   sscanf(VBUFF,"%s",CFRUNCOMMAND);
   Debug("cfrunCommand is %s\n",CFRUNCOMMAND);

   if (stat(CFRUNCOMMAND,&statbuf) == -1)
      {
      FatalError("$(cfrunCommand) points to a non-existent file\n");
      }
   }

strcpy(VFQNAME,VSYSNAME.nodename);

if (GetMacroValue("MaxConnections"))
   {
   bzero(VBUFF,bufsize);
   ExpandVarstring("$(MaxConnections)",VBUFF,NULL);

   CFD_MAXPROCESSES = atoi(VBUFF);

   if (CFD_MAXPROCESSES < 1)
      {
      FatalError("cfd: MaxConnections with silly value");
      }

   Debug("MaxConnections = %d\n",CFD_MAXPROCESSES);
   }
else
   {
   CFD_MAXPROCESSES = 10;
   }

i = 0;

if (strstr(VSYSNAME.nodename,VDOMAIN))
   {
   strcpy(VFQNAME,VSYSNAME.nodename);
   
   while(VSYSNAME.nodename[i++] != '.')
      {
      }
   
   strncpy(VUQNAME,VSYSNAME.nodename,i-1);
   }
else
   {
   sprintf(VFQNAME,"%s.%s",VSYSNAME.nodename,VDOMAIN);
   strcpy(VUQNAME,VSYSNAME.nodename);
   }
}

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

SummarizeParsing()

{ struct Auth *ptr;
  struct Item *ip;
  
if (DEBUG || D2 || D3)
   {
   printf("ACCESS GRANTED ----------------------:\n\n");

   for (ptr = VADMIT; ptr != NULL; ptr=ptr->next)
      {
      printf("Path: %s\n",ptr->path);

      for (ip = ptr->accesslist; ip != NULL; ip=ip->next)
	 {
	 printf("   Admit: %s\n",ip->name);
	 }
      }

   printf("ACCESS DENIAL ------------------------ :\n\n");

   for (ptr = VDENY; ptr != NULL; ptr=ptr->next)
      {
      printf("Path: %s\n",ptr->path);

      for (ip = ptr->accesslist; ip != NULL; ip=ip->next)
	 {
	 printf("   Deny: %s\n",ip->name);
	 }      
      }
   
   printf("Hosts denied access to :\n\n");
   }


if (ERRORCOUNT > 0)
   {
   FatalError("Execution terminated after parsing due to errors in program");
   }
}

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

StartServer(argc,argv)

int argc;
char **argv;

{ struct sockaddr_in cin, sin; 
  char recvbuffer[bufsize], sendbuffer[bufsize];
  struct servent *server;
  int sd, addrlen;
  struct cfd_connection conn;
  int portnumber, yes=1, shmid;
  void ExitCleanly();
  
if ((!NO_FORK) && (fork() != 0))
   {
   return;
   }

signal(SIGINT,ExitCleanly);
signal(SIGTERM,ExitCleanly);
signal(SIGHUP,SIG_IGN);
signal(SIGPIPE,SIG_IGN);

if ((shmid = shmget(IPC_PRIVATE,sizeof(int),IPC_CREAT)) == -1)
    {
    printf("cfd: Couldn't get shared memory object\n");
    perror("shmget");
    exit(0);
    }

if ((SHM_DAEMON_COUNT = (int *) shmat(shmid,0,0)) == (void *) -1)
   {
   printf("cfd: Couldn't attach shared memory object\n");
   perror("shmat");
   exit(0);
   }

*SHM_DAEMON_COUNT = 0;

if ((server = getservbyname(CFENGINE_SERVICE,"tcp")) == NULL)
   {
   perror("getservbyname");
   exit (1);
   }

bzero(&cin,sizeof(cin));

sin.sin_port = (unsigned short)(server->s_port); /*  Service returns network byte order */
sin.sin_addr.s_addr = INADDR_ANY;
sin.sin_family = AF_INET; 

if ((sd = socket(AF_INET,SOCK_STREAM,0)) == -1)
   {
   perror("socket");
   exit (1);
   }

if (setsockopt (sd, SOL_SOCKET, SO_REUSEADDR, (char *) &yes, sizeof (int)) == -1)
   {
   perror ("setsockopt");
   exit (1);
   }

if (bind(sd,(struct sockaddr *)&sin,sizeof(sin)) == -1)  /* Must have this on server */
   {
   perror("bind");
   exit(1);
   }

if (listen(sd,queuesize) == -1)
   {
   perror("listen");
   exit(1);
   }

Verbose("Listening for connections on port %d..\n",server->s_port);

while (true)
   {
   if ((conn.sd_reply = accept(sd, (struct sockaddr *)&cin, &addrlen)) == -1)
      {
      perror("accept");
      exit(1);
      }
   else
      {
      Debug("New connection...\n");
      conn.sd_recv = sd;

      while (!FinishedWithConnection(recvbuffer,sendbuffer,&conn))
         {
         }
      
      close (conn.sd_reply);
      Debug("End of session\n");

      CheckFileChanges(argc,argv,sd);
      }
   }
}


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

Syntax()

{ int i;

printf("GNU cfengine daemon: server module\n%s\n%s\n",CFVERSION,COPYRIGHT);
printf("\n");
printf("Options:\n\n");

for (i=0; CFDOPTIONS[i].name != NULL; i++)
   {
   printf("--%-20s    (-%c)\n",CFDOPTIONS[i].name,(char)CFDOPTIONS[i].val);
   }

printf("\nBug reports to bug-cfengine@prep.ai.mit.edu (News: gnu.cfengine.bug)\n");
printf("General help to help-cfengine@prep.ai.mit.edu (News: gnu.cfengine.help)\n");
printf("Info & fixes at http://www.iu.hioslo.no/~mark/cfengine.html\n");
}

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

FinishedWithConnection(recvbuffer,sendbuffer,conn)

char recvbuffer[], sendbuffer[];
struct cfd_connection *conn;

  /* This is the protocol section. Here we must   */
  /* check that the incoming data are sensible    */
  /* and extract the information from the message */

{ time_t tloc, trem = 0;
  char filename[bufsize],args[bufsize];
  long time_no_see = 0;
  struct cfd_thread_arg thr_args;

if (RecvSocketStream(conn->sd_reply,recvbuffer,bufsize,0) == -1)
   {
   return true;
   }

if (strlen(recvbuffer) == 0)
   {
   Debug("cfd: received an empty transmission!\n");
   }
  
Debug("Received: [%s] on socket %d\n",recvbuffer,conn->sd_reply);

switch (GetCommand(recvbuffer))
   {
   case cfd_exec:    bzero(args,bufsize);
                     sscanf(recvbuffer,"EXEC %[^\n]",args);

		     if (! conn->id_verified)
			{
			RefuseAccess(conn->sd_reply,sendbuffer);
			return true;
			}

		     if (!AccessControl(CFRUNCOMMAND,conn->hostname))
			{
			RefuseAccess(conn->sd_reply,sendbuffer);
			return true;			
			}

     		     if (!MatchClasses(conn,recvbuffer))
			{
			Terminate(conn->sd_reply);
			return true;
			}

		     DoExec(conn,sendbuffer,args);
		     Terminate(conn->sd_reply);
		     return true;
		     
   case cfd_auth:    conn->id_verified = VerifyConnection(conn,recvbuffer+strlen("AUTH "));
                     return false; /* not finished yet */
		     
   case cfd_get:     bzero(filename,bufsize);
                     sscanf(recvbuffer,"GET %s",filename);

		     if (! conn->id_verified)
			{
			RefuseAccess(conn->sd_reply,sendbuffer);
			return true;
			}

		     if (!AccessControl(filename,conn->hostname))
			{
			RefuseAccess(conn->sd_reply,sendbuffer);
			return true;			
			}

		     thr_args.sd_reply = conn->sd_reply;
		     thr_args.replybuff = sendbuffer;
		     thr_args.replyfile = filename;
		     
		     SpawnGetFile(&thr_args);
		     return true;
                     break;
		     
   case cfd_opendir: bzero(filename,bufsize);
                     sscanf(recvbuffer,"OPENDIR %s",filename);
		     
		     if (! conn->id_verified)
			{
			RefuseAccess(conn->sd_reply,sendbuffer);
			return true;
			}

		     if (!AccessControl(filename,conn->hostname))
			{
			RefuseAccess(conn->sd_reply,sendbuffer);
			return true;			
			}		     

		     CfOpenDirectory(conn,sendbuffer,filename);
                     return true;
		     
   case cfd_synch:
		     if (! conn->id_verified)
			{
			RefuseAccess(conn->sd_reply,sendbuffer);
			return true;
			}
		     
                     bzero(filename,bufsize);
                     sscanf(recvbuffer,"SYNCH %ld STAT %s",&time_no_see,filename);

		     trem = (time_t) time_no_see;

                     if (time_no_see == 0 || filename[0] == '\0')
			{
			break;
			}
		     
		     if ((tloc = time((time_t *)NULL)) == -1)
                        {
                        printf("Couldn't read system clock\n");
                        }

                     if (tloc - trem > CLOCK_DRIFT)
			{
			Verbose("Clocks are too far unsynchronized %ld/%ld\n",(long)tloc,(long)trem);
			}
		     else
			{
			Verbose("Clocks were off by %ld\n",(long)tloc-(long)trem);
			Verbose("Statting file %s\n",filename);

			StatFile(conn->sd_reply,sendbuffer,filename);
			}
		     
		     return true;

   case cfd_md5:
		     if (! conn->id_verified)
			{
			RefuseAccess(conn->sd_reply,sendbuffer);
			return true;
			}
		     
                     bzero(filename,bufsize);
		     bzero(args,bufsize);
		     
                     CompareLocalChecksum(conn,sendbuffer,recvbuffer);
		     return true;		     
   }

sprintf (sendbuffer,"BAD: Malformed protocol request\n%c",EOF);

if (send(conn->sd_reply,sendbuffer,bufsize,0) == -1)
   {
   perror("send");
   exit(1);
   }

Verbose("Bad command\n");
return true;
}

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

CheckFileChanges(argc,argv,sd)

int argc;
char **argv;
int sd;

{ struct stat newstat;
  char filename[bufsize], line[bufsize], *sp;
  FILE *pp;

bzero(&newstat,sizeof(struct stat));

filename[0] = '\0';

if ((sp=getenv(CFINPUTSVAR)) != NULL)
   {
   if (! IsAbsoluteFileName(VINPUTFILE))     /* Don't prepend to absolute names */
      { 
      strcpy(filename,sp);
      if (filename[strlen(filename)-1] != '/')
	 {
	 strcat(filename,"/");
	 }
      }
   }

strcat(filename,VINPUTFILE);

stat(filename,&newstat);

Debug("Checking file updates on %s \n",filename);

if (CFSTARTTIME < newstat.st_ctime)
   {
   Verbose("cfd: restarting and rereading config files %s..\n",filename);

   sprintf(filename,"%s -p 2>&1\n",argv[0]);

   Verbose("cfd: checking ok to restart %s\n",filename);

   if ((pp=popen(filename,"r")) == NULL)
      {
      printf("cfd: unable to restart process\n");
      return;
      }

   while (!feof(pp))
      {
      bzero(line,bufsize);
	 
      if (ferror(pp))  /* abortable */
	 {
         pclose(pp);
	 printf("cfd: unable to restart process\n");
	 return;
	 }
	 
      fgets(line,bufsize,pp);

      if (strlen(line) > 0)
	 {
	 pclose(pp);
	 printf("cfd: unable to restart process\n");     
         return;
	 }

      if (ferror(pp))  /* abortable */
	 {
	 pclose(pp);
 	 printf("cfd: unable to restart process\n");     
         return;
	 }	 
      }

   pclose(pp);
   
   close(sd);

   if (execv(argv[0],argv) == -1)
      {
      printf("cfd: unable to restart process\n");
      }
   }
}


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

void ExitCleanly()

{
Verbose("cfd: Exit %d\n",getpid());
exit(0);
}

/**************************************************************/
/* Level 3                                                    */
/**************************************************************/

MatchClasses(conn,recvbuffer)

struct cfd_connection *conn;
char *recvbuffer;

{ char *sp;
  struct Item *classlist, *ip;
  int count = 0, n_read;

Debug("Match classes\n");

while (true)
   {
   count++;
   bzero (recvbuffer,bufsize);

   if (RecvSocketStream(conn->sd_reply, recvbuffer, bufsize,0) == -1)
      {
      if (errno == EINTR) 
         {
         continue;
         }
      }

   Debug("Got class buffer %s\n",recvbuffer);

   if (strncmp(recvbuffer,CFD_TERMINATOR,3) == 0)
      {
      if (count == 1)
	 {
	 Debug("No classes were sent, assuming no restrictions...\n");
	 return true;
	 }
      
      break;
      }
   
   classlist = SplitStringAsItemList(recvbuffer,' ');

   for (ip = classlist; ip != NULL; ip=ip->next)
      {
      if (IsDefinedClass(ip->name))
	 {
	 Debug("Class %s matched, accepting...\n",ip->name);
	 return true;
	 }
      
      if (strcmp(ip->name,CFD_TERMINATOR) == 0)
	 {
	 Debug("No classes matched, rejecting....\n");
	 ReplyNothing(conn);
	 DeleteItemList(classlist);
	 return false;
	 }
      }
   }

ReplyNothing(conn);
Debug("No classes matched, rejecting....\n");
DeleteItemList(classlist);
return false;
}

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

DoExec(conn,sendbuffer,args)

struct cfd_connection *conn;
char *sendbuffer;
char *args;

{ char buffer[bufsize], line[bufsize], *sp;
  FILE *pp;
  int print,i;

bzero(buffer,bufsize);

if (GetMacroValue("cfrunCommand") == NULL)
   {
   Verbose("cfd: exec request: no cfrunCommand defined\n");
   sprintf(sendbuffer,"Exec request: no cfrunCommand defined\n");
   
   if (send(conn->sd_reply,sendbuffer,bufsize,0) == -1)
      {
      perror("send");
      }
   return;
   }

for (sp = args; *sp != '\0'; sp++) /* Blank out -K */
   {
   if (strncmp(sp,"-K",2) == 0)
      {
      *sp = ' ';
      *(sp+1) = ' ';
      }
   else if (strncmp(sp,"--no-lock",9) == 0)
      {
      for (i = 0; i < 9; i++)
	 {
	 *(sp+i) = ' ';
	 }
      }
   }

if (!GetLock("cfd","exec",VIFELAPSED,VEXPIREAFTER,VUQNAME,CFSTARTTIME))
   {
   sprintf(sendbuffer,"cfd: Couldn't get a lock -- too soon.\n");

   if (send(conn->sd_reply,sendbuffer,bufsize,0) == -1)
      {
      perror("send");
      }
   
   return;
   }

ExpandVarstring("$(cfrunCommand) --no-splay ",buffer,"");

if (strlen(buffer)+strlen(args)+6 > bufsize)
   {
   sprintf(sendbuffer,"Command line too long with args: %s\n",buffer);

   if (send(conn->sd_reply,sendbuffer,bufsize,0) == -1)
      {
      perror("send");
      }

   ReleaseCurrentLock(CFSTARTTIME);
   return;
   }
else
   {
   if ((args != NULL) & (strlen(args) > 0))
      {
      strcat(buffer," ");
      strcat(buffer,args);

      sprintf(sendbuffer,"cfd: Executing %s\n",buffer);

      if (send(conn->sd_reply,sendbuffer,bufsize,0) == -1)
         {
         perror("send");
         }
      }
   }

strcat(buffer," 2>&1");

Verbose("cfd: Executing command %s\n",buffer);

bzero(sendbuffer,bufsize);

if ((pp = popen(buffer,"r")) == NULL)
   {
   printf("cfd: Couldn't open pipe to command %s\n",buffer);
   perror("popen");
   sprintf(sendbuffer,"Unable to run %s\n",buffer);
   if (send(conn->sd_reply,sendbuffer,strlen(sendbuffer),0) == -1)
      {
      perror("send");
      }
   ReleaseCurrentLock(CFSTARTTIME);
   return;
   }

while (!feof(pp))
   {
   bzero(line,bufsize);
	 
   if (ferror(pp))  /* abortable */
      {
      Verbose("cfd: shell command %s\n",buffer);
      fflush(pp);
      break;
      }
	 
   fgets(line,bufsize,pp);

   if (ferror(pp))  /* abortable */
      {
      fflush(pp);
      break;
      }	 

   Chop(line);
   print = false;
	 
   for (sp = line; *sp != '\0'; sp++)
      {
      if (! isspace(*sp))
	 {
	 print = true;
	 break;
	 }
      }
	 
   if (print)
      {
      sprintf(sendbuffer,"%s: %s\n",VSYSNAME.nodename,line);

      if (send(conn->sd_reply,sendbuffer,strlen(sendbuffer),0) == -1)
         {
         perror("send");
	 break;
         }
      }
  }
      
pclose(pp);

ReleaseCurrentLock(CFSTARTTIME);
}


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

GetCommand (str)

char *str;

{ int i;
  char op[bufsize];

sscanf(str,"%s",op);

for (i = 0; COMMANDS[i] != NULL; i++)
   {
   if (strcmp(op,COMMANDS[i])==0)
      {
      return i;
      }
   }
}

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

VerifyConnection(conn,buf)

struct cfd_connection *conn;
char buf[bufsize];

{ struct sockaddr_in saddr,raddr;
  char ipstring[maxvarsize], fqname[maxvarsize];
  struct hostent *hp;
  int len;
   
Debug("Connecting host identifies itself as %s\n",buf);

bzero(ipstring,maxvarsize);
bzero(fqname,maxvarsize);

sscanf(buf,"%s %s",ipstring,fqname);
len = sizeof(struct sockaddr_in);

if (getpeername(conn->sd_reply,(struct sockaddr *)&saddr,&len) == -1)
   {
   printf("cfd: couldn't get socket address\n");
   perror("getsockname");
   return false;
   }

Verbose("Socket originates from %s=%s\n",inet_ntoa(saddr.sin_addr),fqname);

#ifdef HAVE_LIBWRAP

if (!hosts_ctl("cfd",fqname,inet_ntoa(saddr.sin_addr),STRING_UNKNOWN))
   {
   Verbose("TCPwrappers rejects this packet.");
   return false;
   }

#endif

/* Do our own checking ... */

if (strcmp(ipstring,inet_ntoa(saddr.sin_addr)) != 0)
   {
   Debug("Host identification failure - attempt at DNS spoofing? - got %s != %s\n",inet_ntoa(saddr.sin_addr),ipstring);
   return false;
   }
  
if ((hp = gethostbyname(fqname)) == NULL)
   {
   Debug("Couldn't look up name %s\n",fqname);
   return false;
   }

raddr.sin_addr.s_addr = ((struct in_addr *)(hp->h_addr))->s_addr;

if (strcmp(inet_ntoa(saddr.sin_addr),inet_ntoa(raddr.sin_addr)) != 0)
   {
   Debug("Reverse hostname lookup failed, host claiming to be %s was %s\n",buf,inet_ntoa(raddr.sin_addr));
   return false;
   }

conn->hostname = (char *) malloc(strlen(fqname)+1);

if (conn->hostname != NULL)
   {
   Debug("Host ID verified as %s\n",fqname);
   strcpy(conn->hostname,fqname);
   return true;   
   }

return false;
}

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

RefuseAccess(sd,sendbuffer)

int sd;
char *sendbuffer;

{
sprintf(sendbuffer,"BAD: Host authentication failed - did you set the correct domainname?\n");
Verbose("BAD: Host authentication failed - did you set the correct domainname?\n");

if (send(sd,sendbuffer,bufsize,0) == -1)
   {
   perror("send");
   }
}

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

AccessControl(filename,hostname)

char *filename, *hostname;

{ struct Auth *ap;
  int access = false;
  char realname[bufsize];

bzero(realname,bufsize);

#ifdef HAVE_REALPATH
if (realpath(filename,realname) == NULL)
   {
   Verbose("cfd: couldn't resolve filename %s from host %s\n",filename,hostname);
   return false;
   }
#else
CompressPath(realname,filename); /* in links.c */
#endif
  
Debug("AccessControl(%s,%s)\n",realname,hostname);
  
if (VADMIT == NULL)
   {
   Verbose("cfd: access list is empty, no files are visible\n");
   return false;
   }

for (ap = VADMIT; ap != NULL; ap=ap->next)
   {
   if (strncmp(ap->path,realname,strlen(ap->path)) == 0)
      {
      access = IsWildItemIn(ap->accesslist,hostname);
      break;
      }
   }

for (ap = VDENY; ap != NULL; ap=ap->next)
   {
   if (strncmp(ap->path,realname,strlen(ap->path)) == 0)
      {
      if (IsWildItemIn(ap->accesslist,hostname))
         {
         access = false;
         break;
         }
      }
   }

if (access)
   {
   Verbose("Host %s granted access to %s\n",hostname,realname);
   }
else
   {
   Verbose("Host %s denied access to %s\n",hostname,realname);
   }

return access;
}

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

SpawnGetFile(args)

struct cfd_thread_arg *args;

{ int pid = -1, i;

/* semaphores ! Probably unnecessary in practice ... but still */

/* 
*SHM_DAEMON_COUNT++;

if (*SHM_DAEMON_COUNT < CFD_MAXPROCESSES)
   {
   if ((pid = fork()) != 0)
      {
      Verbose("cfd: Spawning child pid %d to handle %s, current total = %d\n",pid,args->replyfile,*SHM_DAEMON_COUNT);
      return;
      }
   }
else
   {
   Verbose("cfd: Reached maximum limit on number of concurrent daemons\n");
   }

   */

GetFile(args);

if (pid == 0) /* Child thread */
   {
   *SHM_DAEMON_COUNT--;
   
   Verbose("cfd: done with child %d\n",getpid());
   exit(0);
   }
}

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

StatFile(sd,sendbuffer,filename)

int sd;
char *sendbuffer, *filename;

/* Because we do not know the size or structure of remote datatypes,*/
/* the simplest way to transfer the data is to convert them into */
/* plain text and interpret them on the other side. */

{ struct cfstat cfst;
  struct stat statbuf;
  char linkbuf[bufsize];

if (lstat(filename,&statbuf) == -1)
   {
   perror("lstat");
   sprintf(sendbuffer,"BAD: unable to stat file\n");
   return -1;
   }

cfst.cf_readlink = NULL;
cfst.cf_lmode = 0;
bzero(linkbuf,bufsize);

if (S_ISLNK(statbuf.st_mode))
   {
   cfst.cf_type = cf_link;                   /* pointless - overwritten */
   cfst.cf_lmode = statbuf.st_mode & 07777;
       
   if (readlink(filename,linkbuf,bufsize) == -1)
      {
      perror("readlink");
      sprintf(sendbuffer,"BAD: unable to read link\n");

      if (send(sd,sendbuffer,bufsize,0) == -1)
	 {
	 perror("send");
	 }
      
      return -1;
      }

   Debug("readlink: %s\n",linkbuf);

   cfst.cf_readlink = linkbuf;
   }

if (stat(filename,&statbuf) == -1)
   {
   perror("stat");
   sprintf(sendbuffer,"BAD: unable to stat file\n");

   if (send(sd,sendbuffer,bufsize,0) == -1)
      {
      perror("send");
      }
         
   return -1;
   }

if (S_ISDIR(statbuf.st_mode))
   {
   cfst.cf_type = cf_dir;
   }

if (S_ISREG(statbuf.st_mode))
   {
   cfst.cf_type = cf_reg;
   }

if (S_ISSOCK(statbuf.st_mode))
   {
   cfst.cf_type = cf_sock;
   }

if (S_ISCHR(statbuf.st_mode))
   {
   cfst.cf_type = cf_char;
   }

if (S_ISBLK(statbuf.st_mode))
   {
   cfst.cf_type = cf_block;
   }

if (S_ISFIFO(statbuf.st_mode))
   {
   cfst.cf_type = cf_fifo;
   }

cfst.cf_mode     = statbuf.st_mode  & 07777;      /* Make sure everything is 32 bit */
cfst.cf_uid      = statbuf.st_uid   & 0xFFFFFFFF;
cfst.cf_gid      = statbuf.st_gid   & 0xFFFFFFFF;
cfst.cf_size     = statbuf.st_size  & 0xFFFFFFFF;
cfst.cf_atime    = statbuf.st_atime & 0xFFFFFFFF;
cfst.cf_mtime    = statbuf.st_mtime & 0xFFFFFFFF;
cfst.cf_ctime    = statbuf.st_ctime & 0xFFFFFFFF;
cfst.cf_ino      = statbuf.st_ino;
cfst.cf_nlink    = statbuf.st_nlink;
cfst.cf_readlink = linkbuf;

#ifndef IRIX
if (statbuf.st_size > statbuf.st_blocks * DEV_BSIZE)
#else
# ifdef HAVE_ST_BLOCKS
if (statbuf.st_size > statbuf.st_blocks * DEV_BSIZE)
# else
if (statbuf.st_size > ST_NBLOCKS(statbuf) * DEV_BSIZE)
# endif
#endif
   {
   cfst.cf_makeholes = 1;   /* must have a hole to get checksum right */
   }
else
   {
   cfst.cf_makeholes = 0;
   }


bzero(sendbuffer,bufsize);

 /* send as plain text */

Debug("OK: type=%d\n mode=%o\n lmode=%o\n uid=%d\n gid=%d\n size=%d\n atime=%d\n mtime=%d\n",
	cfst.cf_type,cfst.cf_mode,cfst.cf_lmode,cfst.cf_uid,cfst.cf_gid,cfst.cf_size,
	cfst.cf_atime,cfst.cf_mtime);


sprintf(sendbuffer,"OK: %d %d %d %d %d %d %d %d %d %d %d %d",
	cfst.cf_type,cfst.cf_mode,cfst.cf_lmode,cfst.cf_uid,cfst.cf_gid,cfst.cf_size,
	cfst.cf_atime,cfst.cf_mtime,cfst.cf_ctime,cfst.cf_makeholes,cfst.cf_ino,cfst.cf_nlink);

if (send(sd,sendbuffer,bufsize,0) == -1)
   {
   perror("send");
   exit(1);
   }


bzero(sendbuffer,bufsize);

if (cfst.cf_readlink != NULL)
   {
   strcpy(sendbuffer,"OK:");
   strcat(sendbuffer,cfst.cf_readlink);
   }
else
   {
   sprintf(sendbuffer,"OK:");
   }

if (send(sd,sendbuffer,bufsize,0) == -1)
   {
   perror("send");
   exit(1);
   }

}


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

CompareLocalChecksum(conn,sendbuffer,recvbuffer)

struct cfd_connection *conn;
char *sendbuffer, *recvbuffer;

{ unsigned char digest1[16],digest2[17], num[5],filename[bufsize];
  char *sp;
  int i;


sscanf(recvbuffer,"MD5 %s",filename);

for (sp = recvbuffer+5; *sp != ' '; sp++)
   {
   }

for (i = 0; i < 16; i++)
   {
   bzero(num,5);
   sscanf(sp,"%s",num);

   digest1[i] = (char) atoi(num);
   sp += strlen(num) + 1;
   }

digest2[16] = '\0'; /* for libdb */

Debug("CompareLocalChecksums(%16s)\n",digest1);

bzero(sendbuffer,bufsize);

if (! InChecksumDB(filename,digest2))
   {
   cfMDFile(filename,digest2);
   }

for (i = 0; i < 16; i++)
   {
   if (digest1[i] != digest2[i])
      {
      sprintf(sendbuffer,"%s",CFD_TRUE);
      Debug("Checksums didn't match\n");

      if (send(conn->sd_reply,sendbuffer,bufsize,0) == -1)
         {
         perror("send");
         }
      
      return;
      }
   }

sprintf(sendbuffer,"%s",CFD_FALSE);

Debug("Checksums matched ok\n");

if (send(conn->sd_reply,sendbuffer,bufsize,0) == -1)
   {
   perror("send");
   return;
   }
}

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

CfOpenDirectory(conn,sendbuffer,dirname)

struct cfd_connection *conn;
char *sendbuffer, *dirname;

{ DIR *dirh;
  struct dirent *dirp;
  int offset;

Debug("CfOpenDirectory(%s)\n",dirname);
  
if (*dirname != '/')
   {
   sprintf(sendbuffer,"BAD: request to access a non-absolute filename\n");
   return -1;
   }

if ((dirh = opendir(dirname)) == NULL)
   {
   Debug("cfengine, couldn't open dir %s\n",dirname);
   sprintf(sendbuffer,"BAD: cfengine, couldn't open dir %s\n",dirname);
   if (send(conn->sd_reply,sendbuffer,bufsize,0) == -1)
      {
      perror("send");
      exit(1);
      }
   return -1;
   }

/* Pack names for transmission */

bzero(sendbuffer,bufsize);
offset = 0;

for (dirp = readdir(dirh); dirp != NULL; dirp = readdir(dirh))
   {
   if (strlen(dirp->d_name)+1+offset > bufsize)
      {
      if (send(conn->sd_reply,sendbuffer,bufsize,0) == -1)
         {
         perror("send");
         exit(1);
        }
      
      offset = 0;
      bzero( sendbuffer,bufsize);
      }
   else
      {
      strcpy(sendbuffer+offset,dirp->d_name);
      offset += strlen(dirp->d_name) + 1;     /* + zero byte separator */
      }
   
   /* could send the stat value here */
   }

if (send(conn->sd_reply,sendbuffer,bufsize,0) == -1)
   {
   perror("send");
   exit(1);
   }

closedir(dirh);
}

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

Terminate(sd)

int sd;

{ char buffer[bufsize];

bzero(buffer,bufsize);

strcpy(buffer,CFD_TERMINATOR);

if (send(sd,buffer,bufsize,0) == -1)
   {
   perror("send");
   Verbose("Unable to reply with terminator...\n");
   }
}

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

DeleteAuthList(ap)

struct Auth *ap;

{
if (ap != NULL)
   {
   DeleteAuthList(ap->next);
   ap->next = NULL;

   DeleteItemList(ap->accesslist);

   free((char *)ap);
   }
}

/***************************************************************/
/* Level 4                                                     */
/***************************************************************/

GetFile(args)

struct cfd_thread_arg *args;

{ int sd,fd, n_read;
  char *sendbuffer, *filename;

sd         = args->sd_reply;
sendbuffer = args->replybuff;
filename   = args->replyfile;

Debug("GetFile(%s on sd=%d)\n",filename,sd);

if ((fd = open(filename,O_RDONLY)) == -1)
   {
   printf("cfengine: can't copy %s!\n",filename);
   sprintf(sendbuffer,"BAD: can't access %s on server!\n",filename);
   perror("open");
   return false;
   }

while(true)
   {
   bzero(sendbuffer,bufsize);

   if ((n_read = read(fd,sendbuffer,bufsize)) == -1)
      {
      close(fd);
      perror("read");
      sprintf(sendbuffer,"BAD: GetFile, read failed\n");
      return false;
      }

   if (n_read == 0)
      {
      break;
      }

   if (send(sd,sendbuffer,n_read,0) == -1)
      {
      close(fd);
      perror("send");
      return false;
      }
   }

close(fd);
return true;
}

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

ReplyNothing(conn)

struct cfd_connection *conn;

{ char buffer[bufsize];

sprintf(buffer,"Hello %s, nothing to be done on %s\n",conn->hostname,VFQNAME);

if (send(conn->sd_reply,buffer,bufsize,0) == -1)
   {
   perror("send");
   }
}

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

InChecksumDB(filename,digest)

char *filename;
char digest[16];

{     
#if defined HAVE_LIBDB && (defined HAVE_DB_H || defined HAVE_DB_DB_H)
  struct stat stat1, stat2;
  int i, uptodate = true;
  int result = 0;
  DBT key,value;
  DB *db;

Debug("InChecksumDB(%s from %s)\n",filename,CHECKSUMDB);
  
if (stat(filename,&stat1) == -1)
   {
   return false;
   }

if (stat(CHECKSUMDB,&stat2) != -1)
   {
   if (stat1.st_mtime > stat2.st_mtime)
      {
      uptodate = false;
      }
   }
  
if (CHECKSUMDB[0] == '\0')
   {
   Verbose("cfd: No cache database defined\n");
   return false;
   }

if ((db = dbopen(CHECKSUMDB, O_RDWR | O_CREAT, 0644, DB_BTREE,NULL)) == NULL)
   {
   printf("cfd: couldn't open checksum database %s\n",CHECKSUMDB);
   perror("db_open");
   return false;
   }

key.data = filename;
key.size = strlen(filename)+1;
      
if ((result = db->get(db,&key,&value,0)) == 0)
   {
   if (uptodate)
      {
      Debug("cfd: Found checksum for %s in database\n",filename);
      bcopy(value.data,digest,17);
      db->close(db);
      return true;
      }
   else
      {
      cfMDFile(filename,digest);
      digest[16] = '\0';

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

      Debug("cfd: updating checksum for %s\n",filename);
      
      if (db->del(db,&key,0) != 0)
	 {
	 perror("db_store");
	 }

      key.data = filename;
      key.size = strlen(filename)+1;

      if ((result = db->put(db,&key,&value,0)) != 0)
	 {
	 Debug("retrun val = %d\n",result);
	 perror("db_store");
	 }

      db->close(db);
      return true;
      }
   }
else
   {
   Debug("result= %d\n",result);
   perror("get");
   cfMDFile(filename,digest);
   digest[16] = '\0';

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

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

   if ((result = db->put(db,&key,&value,0)) != 0)
      {
      printf("return value = %d\n",result);
      perror("db_store");
      }
   
   db->close(db);
   return true;   
   }

#else
Verbose("cfd: No database support available.\n");
return false;
#endif
}


/* EOF */

/* Fudge stubs */

CheckForHoles(){}
HandleEdit(){}
CopyReg(){}
MatchAFileSystem(){}
IgnoreFile(){}
CheckImage(){}
RotateFiles(){}
