/* 

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

#define INET 1


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

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

#ifdef HAVE_PTHREAD_H
# include <pthread.h>
#endif

#ifdef HAVE_SCHED_H
# include <sched.h>
#endif

#ifdef HAVE_PTHREAD_H
pthread_attr_t PTHREADDEFAULTS;
pthread_mutex_t MUTEX_COUNT = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t MUTEX_HOSTNAME = PTHREAD_MUTEX_INITIALIZER;
#endif

static time_t next_auto;
static pid_t auto_pid = 0;

/*******************************************************************/
/* Functions internal to cfd.c                                     */
/*******************************************************************/

void CheckOptsAndInit ARGLIST((int argc,char **argv));
void CheckVariables ARGLIST((void));
void SummarizeParsing ARGLIST((void));
void StartServer ARGLIST((int argc, char **argv));
void Syntax ARGLIST((void));
void PurgeOldConnections ARGLIST((struct Item **list,char *now));
void SpawnConnection ARGLIST((int sd_reply));
void CheckFileChanges ARGLIST((int argc, char **argv, int sd));
void *HandleConnection ARGLIST((struct cfd_connection *conn));
int BusyWithConnection ARGLIST((struct cfd_connection *conn));
void ExitCleanly ARGLIST((void));
void AutoExec ARGLIST((void));
int MatchClasses ARGLIST((struct cfd_connection *conn));
void DoExec ARGLIST((struct cfd_connection *conn, char *sendbuffer, char *args));
int GetCommand ARGLIST((char *str));
int VerifyConnection ARGLIST((struct cfd_connection *conn, char *buf));
void RefuseAccess ARGLIST((struct cfd_connection *conn, char *sendbuffer));
int AccessControl ARGLIST((char *filename, struct cfd_connection *conn, int secure));
int StatFile ARGLIST((struct cfd_connection *conn, char *sendbuffer, char *filename));
void CfGetFile ARGLIST((struct cfd_get_arg *args));
void CompareLocalChecksum ARGLIST((struct cfd_connection *conn, char *sendbuffer, char *recvbuffer));
int CfOpenDirectory ARGLIST((struct cfd_connection *conn, char *sendbuffer, char *dirname));
void Terminate ARGLIST((int sd));
void DeleteAuthList ARGLIST((struct Auth *ap));
int DoRFC931 ARGLIST((struct in_addr *laddr, struct in_addr *raddr, short unsigned int local, short unsigned int remote, char *username));
void ReplyNothing ARGLIST((struct cfd_connection *conn));
struct cfd_connection *NewConn ARGLIST((int sd));
void DeleteConn ARGLIST((struct cfd_connection *conn));
void SendTransaction ARGLIST((struct cfd_connection *conn, char *buffer));
time_t SecondsTillAuto ARGLIST((void));
void SetAuto ARGLIST((int seconds));
int CfenginePort ARGLIST((void));
int cfscanf ARGLIST((char *in, int len1, int len2, char *out1, char *out2, char *out3));

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

int main (argc,argv)

int argc;
char **argv;

{
CheckOptsAndInit(argc,argv);
GetNameInfo();
GetInterfaceInfo(); 
ParseInputFiles();
CheckVariables();
SummarizeParsing();

 if (PARSEONLY)
   {
   exit(0);
   }

StartServer(argc,argv);

/* Never exit here */
return 0;
}

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

void CheckOptsAndInit(argc,argv)

int argc;
char **argv;

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

openlog(VPREFIX,LOG_PID|LOG_NOWAIT|LOG_ODELAY,LOG_DAEMON);
strcpy(VINPUTFILE,CFD_INPUT);
OUTPUT[0] = '\0';
VFQNAME[0] = '\0';

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

InitHashTable();

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

while ((c=getopt_long(argc,argv,"d:f:vmhpFV",CFDOPTIONS,&optindex)) != EOF)
  {
  switch ((char) c)
      {
      case 'f': strncpy(VINPUTFILE,optarg,bufsize-1);
                break;

      case 'd': 

                switch ((optarg==NULL)?3:*optarg)
                   {
                   case '1': D1 = true;
                             break;
                   case '2': D2 = true;
                             break;
                   default:  DEBUG = true;
                             break;
                   }
		
		NO_FORK = true;
		printf("cfd: Debug mode: running in foreground\n");
                break;

      case 'v': VERBOSE = true;
	        break;

      case 'V': printf("GNU %s-%s daemon\n%s\n",PACKAGE,VERSION,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;

      case 'm': /* No longer needed */
	        break;

      default:  Syntax();
                exit(1);

      }
   }


LOGGING = true;                    /* Do output to syslog */
 
sprintf(VPREFIX,"cfd:%s:",VUQNAME); 
sprintf(VBUFF,"%s/test",LOCKFILEDIR);
MakeDirectoriesFor(VBUFF);
strncpy(VLOCKDIR,LOCKFILEDIR,bufsize-1);

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

MakeDirectoriesFor(VBUFF);
strncpy(VLOGDIR,LOGFILEDIR,bufsize-1);

strcpy(VDOMAIN,"undefined.domain");

VCANONICALFILE = strdup(CanonifyName(VINPUTFILE));
}

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

void CheckVariables()

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

#ifdef HAVE_PTHREAD_H
 CfLog(cfinform,"cfd: Multithreaded version","");
#else
 CfLog(cfinform,"cfd: Single threaded version","");
#endif

strncpy(VFQNAME,VSYSNAME.nodename,bufsize-1);
LoadSecretKeys();
 
if ((CFDSTARTTIME = time((time_t *)NULL)) == -1)
   {
   printf("Couldn't read system clock\n");
   }

if (GetMacroValue("CheckIdent") && (strcmp(GetMacroValue("CheckIdent"),"on") == 0))
   {
   CHECK_RFC931 = true;
   }

if (GetMacroValue("DenyBadClocks") && (strcmp(GetMacroValue("DenyBadClocks"),"off") == 0))
   {
   DENYBADCLOCKS = false;
   }
 
if (GetMacroValue("LogAllConnections") && (strcmp(GetMacroValue("LogAllConnections"),"on") == 0))
   {
   LOGCONNS = true;
   }
 
if (GetMacroValue("ChecksumDatabase"))
   {
   ExpandVarstring("$(ChecksumDatabase)",VBUFF,NULL);

   CHECKSUMDB = strdup(VBUFF);

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

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,"%4095s",CFRUNCOMMAND);
   Debug("cfrunCommand is %s\n",CFRUNCOMMAND);

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

if (GetMacroValue("MaxConnections"))
   {
   bzero(VBUFF,bufsize);
   ExpandVarstring("$(MaxConnections)",VBUFF,NULL);
   Debug("$(MaxConnections) Expanded to %s\n",VBUFF);

   CFD_MAXPROCESSES = atoi(VBUFF);
   
   MAXTRIES = CFD_MAXPROCESSES / 3;  /* How many attempted connections over max
				        before we commit suicide? */
   if ((CFD_MAXPROCESSES < 1) || (CFD_MAXPROCESSES > 1000))
      {
      FatalError("cfd: MaxConnections with silly value");
      }
    }
else
   {
   CFD_MAXPROCESSES = 10;
   MAXTRIES = 5;
   }

if (GetMacroValue("AutoExecInterval"))
   {
   bzero(VBUFF,bufsize);
   ExpandVarstring("$(AutoExecInterval)",VBUFF,NULL);
   Debug("$(AutoExecInterval) Expanded to %s\n",VBUFF);

   CFD_INTERVAL = 60 * atoi(VBUFF);

   if ((CFD_INTERVAL < 0) || (CFD_INTERVAL > 60*24*7))
      {
      FatalError("cfd: AutoExecInterval with silly value in minutes (>0 and less than 1 week)");
      }
   }
else
   {
   CFD_INTERVAL = 0;
   }

Debug("MaxConnections = %d\n",CFD_MAXPROCESSES);
Debug("AutoExecInterval = %d\n",CFD_INTERVAL);

CHECKSUMUPDATES = true;
 
if (GetMacroValue("ChecksumUpdates") && (strcmp(GetMacroValue("ChecksumUpdates"),"off") == 0))
  {
  CHECKSUMUPDATES = false;
  } 
 
i = 0;

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

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

void SummarizeParsing()

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

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

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

   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("Host IPs allowed connection access :\n\n");

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

   printf("Host IPs denied connection access :\n\n");

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

   printf("Host IPs allowed multiple connection access :\n\n");

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

   printf("Host IPs from NAT which we don't verify :\n\n");

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


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

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

int CfenginePort()

{
struct servent *server;

if ((server = getservbyname(CFENGINE_SERVICE,"tcp")) == NULL)
   {
   CfLog(cflogonly,"Couldn't get cfengine service, using default",
	"getservbyname");
   return htons(5308);
   }

 return server->s_port;
}

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

void StartServer(argc,argv)

int argc;
char **argv;

{ struct sockaddr_in cin, sin, raddr;
  char recvbuffer[bufsize], sendbuffer[bufsize],ipaddr[17],intime[64];
  int sd, sd_reply, addrlen = sizeof(cin),ageing, len;
  struct cfd_connection *conn;
  int port, yes=1;
  struct linger cflinger;
  void ExitCleanly(), AutoExec();
  time_t now;

/* Initialize cflinger */

cflinger.l_onoff = 1;
cflinger.l_linger = 60;
  
if ((!NO_FORK) && (fork() != 0))
   {
   sprintf(OUTPUT,"cfd: starting %.24s\n",ctime(&CFDSTARTTIME));
   CfLog(cfinform,OUTPUT,"");
   exit(0);
   }

if (!NO_FORK)
  {
    int fd;
#ifdef HAVE_SETSID
  setsid();
#endif
  fd = open("/dev/null", O_RDWR, 0);
  fclose (stdin);
  fclose (stdout);
  fclose (stderr);
  closelog();
  if (fd != -1)
    {
      dup2(fd, STDIN_FILENO);
      dup2(fd, STDOUT_FILENO);
      dup2(fd, STDERR_FILENO);
      close(fd);
    }
  }

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


SetAuto(CFD_INTERVAL);
  
port = CfenginePort();
  
bzero(&cin,sizeof(cin));

sin.sin_port = (unsigned short)(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)
   {
   CfLog(cferror,"Couldn't open socket","socket");
   exit (1);
   }

if (setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, (char *) &yes, sizeof (int)) == -1)
   {
   CfLog(cferror,"Couldn't set socket options","sockopt");
   exit (1);
   }

 if (setsockopt(sd, SOL_SOCKET, SO_LINGER, (char *) &cflinger, sizeof (struct linger)) == -1)
   {
   CfLog(cferror,"Couldn't set socket options","sockopt");
   exit (1);
   }

if (bind(sd,(struct sockaddr *)&sin,sizeof(sin)) == -1)  /* Must have this on server */
   {
   CfLog(cferror,"Couldn't bind to socket","bind");
   exit(1);
   }

if (listen(sd,queuesize) == -1)
   {
   CfLog(cferror,"listen failed","listen");
   exit(1);
   }

Verbose("Listening for connections on port %d..\n",ntohs(port));

ageing = 0;

while (true)
   {
#ifdef HAVE_WAITPID
   /*
    * Really need to reap autoexec processes all the time, but not sure how
    * to do it without waitpid()
    */

   if (auto_pid)
      {
      int result;
      int status;
      
      result = waitpid(auto_pid, &status, WNOHANG);
      if (result == auto_pid)
	 {
	 auto_pid = 0;
	 }
      else if (result == -1)
	 {
	 CfLog(cferror,strerror(errno),"waitpid");
	 }
      else if (result != 0)
	 {
	 CfLog(cferror,"unexpected result\n","waitpid");
	 }
      }
#endif

   CheckFileChanges(argc,argv,sd);

   if (CFD_INTERVAL)
      {
      /*
       * Auto-exec interval is non-zero, see if we should run the autoexec
       * command.
       */

      int timeout_secs;
      int ready;
      fd_set rfds;
      struct timeval tv;

      timeout_secs = SecondsTillAuto();
      if (timeout_secs <= 0)
	 {
	 /*
	  * Time for autoexec
	  */
	 AutoExec();
	 timeout_secs = SecondsTillAuto();
	 }

      /*
       * Sleep till next autoexec event, or an incoming connection is made.
       */
      if (timeout_secs <= 0)
	 {
	 tv.tv_sec = 0;
	 tv.tv_usec = 1;
	 }
      else
	 {
	 tv.tv_sec = timeout_secs;
	 tv.tv_usec = 0;
	 }
      
      FD_ZERO(&rfds);
      FD_SET(sd,&rfds);
      
      Debug("Going to sleep (select) for %d seconds\n", timeout_secs);

      ready = select(sd+1, &rfds, 0, 0, &tv);

      Debug("Woke up\n");

      if (ready <= 0)
	 {
	 /*
	  * No incoming connection - go back to top of loop.
	  */
	 continue;
	 }
      }

   if ((sd_reply = accept(sd,(struct sockaddr *)&cin, &addrlen)) != -1)
      {
      if (ageing++ > CFD_MAXPROCESSES*4) /* Insurance against stale db */
	 {                               /* estimate number of clients */
	 unlink(CHECKSUMDB);             /* arbitrary policy ..        */
	 ageing = 0;
	 }

      len = sizeof(struct sockaddr_in);  /* Check no other connections from this host current */

      if (getpeername(sd_reply,(struct sockaddr *)&raddr,&len) == -1)
	 {
	 CfLog(cferror,"Couldn't get socket address\n","getpeername");
	 continue;
	 }
      
      sprintf(ipaddr,"%s",inet_ntoa(raddr.sin_addr));
      
      if ((NONATTACKERLIST != NULL) && !IsFuzzyItemIn(NONATTACKERLIST,ipaddr))   /* Allowed Subnets */
	 {
	 sprintf(OUTPUT,"Denying connection from non-authorized IP %s\n",ipaddr);
	 CfLog(cferror,OUTPUT,"");
	 close(sd_reply);
	 continue;
	 }
      
      if (IsFuzzyItemIn(ATTACKERLIST,ipaddr))   /* Denied Subnets */
	 {
	 sprintf(OUTPUT,"Denying connection from non-authorized IP %s\n",ipaddr);
	 CfLog(cferror,OUTPUT,"");
	 close(sd_reply);
	 continue;
	 }      
      
      if ((now = time((time_t *)NULL)) == -1)
	 {
	 printf("Couldn't read system clock\n");
	 sprintf(intime,"0");
	 }
      else
	 {
	 sprintf(intime,"%d",(int)now);
	 }
      
      PurgeOldConnections(&CONNECTIONLIST,intime);
      
      if (!IsFuzzyItemIn(MULTICONNLIST,ipaddr))
	 {
	 if (IsItemIn(CONNECTIONLIST,ipaddr))
	    {
	    sprintf(OUTPUT,"Denying repeated connection from %s\n",ipaddr);
	    CfLog(cferror,OUTPUT,"");
	    close(sd_reply);
	    continue;
	    }
	 }

      if (LOGCONNS)
	 {
	 sprintf(OUTPUT,"Accepting connection from %s\n",ipaddr);
	 CfLog(cflogonly,OUTPUT,"");
	 }

      PrependItem(&CONNECTIONLIST,ipaddr,intime);
      SpawnConnection(sd_reply);
      }
   }
}

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

void Syntax()

{ int i;

printf("GNU cfengine daemon: server module\n%s-%s\n%s\n",PACKAGE,VERSION,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@gnu.org (News: gnu.cfengine.bug)\n");
printf("General help to help-cfengine@gnu.org (News: gnu.cfengine.help)\n");
printf("Info & fixes at http://www.iu.hio.no/cfengine\n");
}

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

void PurgeOldConnections(list,innow)

   /* Some connections might not terminate properly. These should be cleaned
      every couple of hours. That should be enough to prevent spamming. */

struct Item **list;
char *innow;

{ struct Item *ip;
 int now=0,then=0;

if (list == NULL)
   {
   return;
   }

Debug("Purging Old Connections...\n");

for (ip = *list; ip != NULL; ip=ip->next)
   {
   sscanf(innow,"%d",&now);
   sscanf(ip->classes,"%d",&then);

   if (now > then + 7200)
      {
      DeleteItem(list,ip);
      sprintf(OUTPUT,"Purging IP address %s from connection list %s\n",ip->name);
      CfLog(cfverbose,OUTPUT,"");
      }
   }

Debug("Done purging\n");
}

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

void SpawnConnection(sd_reply)

int sd_reply;

{ struct cfd_connection *conn;

#ifdef HAVE_PTHREAD_H
 pthread_t tid;
 size_t size = 0;
#endif
  void *HandleConnection();

Debug("New connection...\n");
conn = NewConn(sd_reply);

#ifdef HAVE_LIBPTHREAD

Debug("Spawning new thread...\n");

pthread_attr_init(&PTHREADDEFAULTS);
pthread_attr_setdetachstate(&PTHREADDEFAULTS,PTHREAD_CREATE_DETACHED);

/*#ifdef HAVE_PTHREAD_ATTR_SETSTACKSIZE This seems to cause problems on some OSes*/
pthread_attr_setstacksize(&PTHREADDEFAULTS,(size_t)(sysconf(_SC_THREAD_STACK_MIN)+100*1024));
/*#endif*/
 
if (pthread_create(&tid,&PTHREADDEFAULTS,HandleConnection,(void *)conn) != 0)
   {
   CfLog(cferror,"pthread_create failed","create");
   HandleConnection(conn);
   }

pthread_attr_destroy(&PTHREADDEFAULTS);

#else

/* Can't fork here without getting a zombie on some systems ? */

Debug("Single threaded...\n");

HandleConnection(conn);
 
#endif
}

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

void CheckFileChanges(argc,argv,sd)

int argc;
char **argv;
int sd;

{ struct stat newstat;
  char filename[bufsize],line[bufsize], *sp;
  char arg[maxshellargs][bufsize];
  FILE *pp;
  int i;
  
bzero(&newstat,sizeof(struct stat));
bzero(filename,bufsize);

if ((sp=getenv(CFINPUTSVAR)) != NULL)
   {
   if (! IsAbsoluteFileName(VINPUTFILE))     /* Don't prepend to absolute names */
      { 
      strncpy(filename,sp,bufsize-1);
      AddSlash(filename);
      }
   }

strcat(filename,VINPUTFILE);

if (stat(filename,&newstat) == -1)
   {
   sprintf(OUTPUT,"Input file %s missing!\n",filename);
   CfLog(cferror,OUTPUT,filename);
   return;
   }

Debug("Checking file updates on %s (%x/%x)\n",filename, newstat.st_ctime, CFDSTARTTIME);

if (CFDSTARTTIME < newstat.st_ctime)
   {
   sprintf(OUTPUT,"Rereading config files %s..\n",filename);
   CfLog(cfinform,OUTPUT,"");

   /* Free & reload -- lock this to avoid access errors during reload */

#if defined HAVE_PTHREAD_H && defined HAVE_LIBPTHREAD
   if (pthread_mutex_lock(&MUTEX_COUNT) != 0)
      {
      CfLog(cferror,"pthread_mutex_lock failed","lock");
      }
#endif
   
   for (i = 0; i < hashtablesize; i++)
      {
      if (HASH[i] != NULL)
	 {
	 free(HASH[i]);
	 HASH[i] = NULL;
	 }
      }

   DeleteItemList(VHEAP);
   DeleteItemList(VNEGHEAP);
   DeleteAuthList(VADMIT);
   DeleteAuthList(VDENY);
   strcpy(VDOMAIN,"undefined.domain");
   
   VADMIT = VADMITTOP = NULL;
   VDENY  = VDENYTOP  = NULL;
   VHEAP  = VNEGHEAP  = NULL;

   AddClassToHeap("any");
   GetNameInfo();
   ParseInputFiles();
   CheckVariables();
   SummarizeParsing();
   SetAuto(CFD_INTERVAL);

#if defined HAVE_PTHREAD_H && defined HAVE_LIBPTHREAD
   if (pthread_mutex_unlock(&MUTEX_COUNT) != 0)
      {
      CfLog(cferror,"pthread_mutex_unlock failed","unlock");
      }  
#endif
   }
}

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

void *HandleConnection(conn)

struct cfd_connection *conn;

{
#if defined HAVE_PTHREAD_H && defined HAVE_LIBPTHREAD
#ifdef HAVE_PTHREAD_SIGMASK
 sigset_t sigmask;

sigemptyset(&sigmask);
pthread_sigmask(SIG_BLOCK,&sigmask,NULL); 
#endif
 
if (pthread_mutex_lock(&MUTEX_COUNT) != 0)
   {
   CfLog(cferror,"pthread_mutex_lock failed","pthread_mutex_lock");
   DeleteConn(conn);
   return NULL;
   }

ACTIVE_THREADS++;

if (pthread_mutex_unlock(&MUTEX_COUNT) != 0)
   {
   CfLog(cferror,"pthread_mutex_unlock failed","unlock");
   }  

if (ACTIVE_THREADS >= CFD_MAXPROCESSES)
   {
   if (pthread_mutex_lock(&MUTEX_COUNT) != 0)
      {
      CfLog(cferror,"pthread_mutex_lock failed","pthread_mutex_lock");
      DeleteConn(conn);
      return NULL;
      }
   
   ACTIVE_THREADS--;
   
   if (TRIES++ > MAXTRIES)  /* When to say we're hung / apoptosis threshold */
      {
      CfLog(cferror,"Seem to be paralyzed. Committing apoptosis...","");
      ExitCleanly();
      }

   if (pthread_mutex_unlock(&MUTEX_COUNT) != 0)
      {
      CfLog(cferror,"pthread_mutex_unlock failed","unlock");
      }
   
   CfLog(cfinform,"Too many threads","");
   DeleteConn(conn);

   return NULL;
   }

TRIES = 0;   /* As long as there is activity, we're not stuck */
 
#endif
 
while (BusyWithConnection(conn))
   {
   }

DeleteConn(conn);
 
#ifdef HAVE_LIBPTHREAD

 Debug("Terminating thread...\n");
 
if (pthread_mutex_lock(&MUTEX_COUNT) != 0)
   {
   CfLog(cferror,"pthread_mutex_lock failed","pthread_mutex_lock");
   return NULL;
   }

ACTIVE_THREADS--;

if (pthread_mutex_unlock(&MUTEX_COUNT) != 0)
   {
   CfLog(cferror,"pthread_mutex_unlock failed","unlock");
   }
 
#endif

return NULL; 
}

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

int BusyWithConnection(conn)

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 recvbuffer[bufsize], sendbuffer[bufsize],check[bufsize];  
  char filename[bufsize],buffer[bufsize],args[bufsize],out[bufsize];
  long time_no_see = 0;
  int len=0, drift;
  struct cfd_get_arg get_args;

bzero(recvbuffer,bufsize);
bzero(&get_args,sizeof(get_args));

if (conn == NULL)
   {
   CfLog(cferror,"Null conn in BusyWithConnection. Should not happen.","");
   }
 
if (RecvSocketStream(conn->sd_reply,recvbuffer,bufsize,0) == -1)
   {
   return false;
   }

recvbuffer[bufsize-1] = '\0';
 
if (strlen(recvbuffer) == 0)
   {
   Debug("cfd: terminating NULL transmission!\n");
   return false;
   }
  
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,sendbuffer);
			return false;
			}

		     if (!AccessControl(CFRUNCOMMAND,conn,false))
			{
			RefuseAccess(conn,sendbuffer);
			return false;			
			}

     		     if (!MatchClasses(conn))
			{
			Terminate(conn->sd_reply);
			return false;
			}

		     SetAuto(CFD_INTERVAL); /* reset alarm */

		     DoExec(conn,sendbuffer,args);
		     Terminate(conn->sd_reply);
		     return false;

   case cfd_auth:    sprintf (sendbuffer,"BAD: Old protocol, you need to upgrade to 1.5.x\n");
                     SendTransaction(conn,sendbuffer);
                     return false;
		     
   case cfd_cauth:   conn->id_verified = VerifyConnection(conn,(char *)(recvbuffer+strlen("CAUTH ")));
                     if (! conn->id_verified)
			{
			RefuseAccess(conn,sendbuffer);
			}
                     return conn->id_verified; /* are we finished yet ? */

   case cfd_get:     bzero(filename,bufsize);
                     sscanf(recvbuffer,"GET %d %[^\n]",&(get_args.buf_size),filename);

		     if (get_args.buf_size < 0 || get_args.buf_size > bufsize)
			{
			RefuseAccess(conn,sendbuffer);
			return false;
			}

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

		     if (!AccessControl(filename,conn,false))
			{
			RefuseAccess(conn,sendbuffer);
			return false;			
			}

		     bzero(sendbuffer,bufsize);
		     
		     get_args.connect = conn;
		     get_args.secure = false;
		     get_args.replybuff = sendbuffer;
		     get_args.replyfile = filename;
		     
		     CfGetFile(&get_args);
		     
		     return true;

   case cfd_sget:    bzero(buffer,bufsize);
                     sscanf(recvbuffer,"SGET %d %d",&len,&(get_args.buf_size));
		     if (len < 0 || len > bufsize-32)
			{
			Debug("Bogus len received: %d\n",len);
			RefuseAccess(conn,sendbuffer);
			return false;
			}
                     bcopy(recvbuffer+31,out,bufsize-32);
                     cfdecrypt(out,buffer,CFDES1,CFDES2,CFDES3,len);
		     
                     cfscanf(buffer,3,8,check,get_args.key,filename);

		     Debug("Buffer was %s\n",buffer);

		     if (strcmp(check,"GET") != 0)
			{
			RefuseAccess(conn,sendbuffer);
			return false;
			}

		     if (get_args.buf_size < 0 || get_args.buf_size > 8192)
			{
			RefuseAccess(conn,sendbuffer);
			return false;
			}
		     
		     Debug("Confirm decryption, and thus validity of caller\n");
		     Debug("SGET %s with blocksize %d\n",filename,get_args.buf_size);

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

		     if (!AccessControl(filename,conn,true))
			{
			RefuseAccess(conn,sendbuffer);
			return false;			
			}

		     bzero(sendbuffer,bufsize);
		     
		     get_args.connect = conn;
		     get_args.secure = true;
		     get_args.replybuff = sendbuffer;
		     get_args.replyfile = filename;
		     
		     CfGetFile(&get_args);
		     return true;
		     
   case cfd_opendir: bzero(filename,bufsize);
                     sscanf(recvbuffer,"OPENDIR %[^\n]",filename);
		     
		     if (! conn->id_verified)
			{
			RefuseAccess(conn,sendbuffer);
			return false;
			}

		     if (!AccessControl(filename,conn,true)) /* opendir don't care about security */
			{
			RefuseAccess(conn,sendbuffer);
			return false;			
			}		     

		     CfOpenDirectory(conn,sendbuffer,filename);
                     return true;


   case cfd_ssynch:  bzero(buffer,bufsize);
                     sscanf(recvbuffer,"SSYNCH %d",&len);
		     if (len < 0 || len > bufsize-24)
			{
			Debug("Bogus len received: %d\n",len);
			RefuseAccess(conn,sendbuffer);
			return false;
			}
                     bcopy(recvbuffer+23,out,bufsize-24);
                     cfdecrypt(out,recvbuffer,CFDES1,CFDES2,CFDES3,len);
		     Debug("Descrypted: [%s]\n",out);
		     if (strncmp(recvbuffer,"SYNCH",5) !=0)
			{
			RefuseAccess(conn,sendbuffer);
			return false;
			}
		     /* roll through, no break */
		     
   case cfd_synch:
		     if (! conn->id_verified)
			{
			RefuseAccess(conn,sendbuffer);
			return false;
			}
		     
                     bzero(filename,bufsize);
                     sscanf(recvbuffer,"SYNCH %ld STAT %[^\n]",&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)
                        {
                        sprintf(conn->output,"Couldn't read system clock\n");
			CfLog(cfinform,conn->output,"time");
			SendTransaction(conn,"BAD: clocks out of synch");
			return true;
                        }

		     drift = (int)(tloc-trem);

                     if (DENYBADCLOCKS && (drift*drift > CLOCK_DRIFT*CLOCK_DRIFT))
			{
			sprintf(conn->output,"BAD: Clocks are too far unsynchronized %ld/%ld\n",(long)tloc,(long)trem);
			CfLog(cfinform,conn->output,"");
			SendTransaction(conn,conn->output);
			return true;
			}
		     else
			{
			sprintf(conn->output,"Clocks were off by %ld\n",(long)tloc-(long)trem);
			CfLog(cfverbose,conn->output,"");
			StatFile(conn,sendbuffer,filename);
			}

		     return true;

   case cfd_smd5:    bzero(buffer,bufsize);
                     sscanf(recvbuffer,"SMD5 %d",&len);
		     
		     if (len < 0 || len > bufsize-24)
			{
			Debug("Bogus len received: %d\n",len);
			RefuseAccess(conn,sendbuffer);
			return false;
			}
		     
                     bcopy(recvbuffer+23,out,bufsize-24);
                     cfdecrypt(out,recvbuffer,CFDES1,CFDES2,CFDES3,len);

		     if (strncmp(recvbuffer,"MD5",3) !=0)
			{
			RefuseAccess(conn,sendbuffer);
			return false;
			}
		     /* roll through, no break */
       
   case cfd_md5:
		     if (! conn->id_verified)
			{
			RefuseAccess(conn,sendbuffer);
			return false;
			}
		     
                     bzero(filename,bufsize);
		     bzero(args,bufsize);
		     
                     CompareLocalChecksum(conn,sendbuffer,recvbuffer);
		     return true;		     
   }

sprintf (sendbuffer,"BAD: Malformed protocol request\n");
SendTransaction(conn,sendbuffer);
CfLog(cfinform,"Closing connection\n",""); 
return false;
}

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

void ExitCleanly()

{ int i;
 
sprintf(OUTPUT,"Exit -- (waiting for threads), pid = %d\n",getpid());
CfLog(cfinform,OUTPUT,"");
closelog();

#ifdef HAVE_PTHREAD_H
/* Do we care about this? 
for (i = 0; i < MAXTHREAD ; i++)
   {
   if (THREADS[i] != NULL)
      {
      pthread_join(THREADS[i],(void **)NULL);
      }
   }
   */
#endif 
 
exit(0);
}

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

void AutoExec()

{ char logbuffer[bufsize], line[bufsize], *sp;
  FILE *pp; 
  int print;
  
VBUFF[0] = '\0';

Debug("Autoexec time\n");
SetAuto(CFD_INTERVAL); 
ExpandVarstring("$(AutoExecCommand)",VBUFF,"");

if (strlen(VBUFF) == 0)
   {
   CfLog(cferror,"No $(AutoExecCommand) variable defined","");
   return;
   }

/* Use same lock as for cfrun */
 
 if (!GetLock("cfd","daemon",VIFELAPSED,VEXPIREAFTER,VUQNAME,CFSTARTTIME))
   {
   sprintf(OUTPUT,"cfd: Couldn't get a lock -- too soon: IfElapsed %d, ExpireAfter %d\n",VIFELAPSED,VEXPIREAFTER);
   CfLog(cferror,OUTPUT,"");
   
   return;
   }
 
auto_pid = fork();
if (auto_pid == 0)
   {
   signal(SIGPIPE,SIG_IGN);
   
   ExpandVarstring("$(AutoExecCommand) --no-splay --inform",VBUFF,"");

   sprintf(OUTPUT,"Interval expired, executing command %s\n",VBUFF);
   CfLog(cfinform,OUTPUT,""); 

   bzero(logbuffer,bufsize);

   if ((pp = cfpopen(VBUFF,"r")) == NULL)
      {
      sprintf(OUTPUT,"Couldn't open pipe to command %s\n",VBUFF);
      CfLog(cferror,OUTPUT,"cfpopen");

      exit(1);
      }
   
   while (!feof(pp))
      {
      if (ferror(pp))  /* abortable */
	 {
	 fflush(pp);
	 break;
	 }
      
      Debug("starting read line\n");
      ReadLine(line,bufsize,pp);
      Debug("finished read line\n");
      
      if (ferror(pp))  /* abortable */
	 {
	 fflush(pp);
	 break;
	 }	 
      
      print = false;
      
      for (sp = line; *sp != '\0'; sp++)
	 {
	 if (! isspace(*sp))
	    {
	    print = true;
	    break;
	    }
	 }
      
      if (print)
	 {
	 sprintf(logbuffer,"%s\n",line);
	 CfLog(cfinform,logbuffer,"");
	 }
      }
   
   cfpclose(pp);
   closelog();
   exit(0);
   }

}

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

int MatchClasses(conn)

struct cfd_connection *conn;

{ char *sp, recvbuffer[bufsize];
  struct Item *classlist, *ip;
  int count = 0, n_read;

Debug("Match classes\n");

while (true && (count < 10))  /* arbitrary check to avoid infinite loop, DoS attack*/
   {
   count++;

   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,strlen(CFD_TERMINATOR)) == 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);
	 DeleteItemList(classlist);
	 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;
}

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

void 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 ((CFSTARTTIME = time((time_t *)NULL)) == -1)
   {
   CfLog(cferror,"Couldn't read system clock\n","time");
   }

if (GetMacroValue("cfrunCommand") == NULL)
   {
   Verbose("cfd: exec request: no cfrunCommand defined\n");
   sprintf(sendbuffer,"Exec request: no cfrunCommand defined\n");
   SendTransaction(conn,sendbuffer);
   return;
   }

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

if (!GetLock("cfd","exec",VIFELAPSED,VEXPIREAFTER,VUQNAME,CFSTARTTIME))
   {
   sprintf(sendbuffer,"cfd: Couldn't get a lock -- too soon: IfElapsed %d, ExpireAfter %d\n",VIFELAPSED,VEXPIREAFTER);
   SendTransaction(conn,sendbuffer);
   return;
   }

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

if (strlen(buffer)+strlen(args)+6 > bufsize)
   {
   sprintf(sendbuffer,"Command line too long with args: %s\n",buffer);
   SendTransaction(conn,sendbuffer);
   ReleaseCurrentLock();
   return;
   }
else
   {
   if ((args != NULL) & (strlen(args) > 0))
      {
      strcat(buffer," ");
      strcat(buffer,args);

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

sprintf(conn->output,"Executing command %s\n",buffer);
CfLog(cfinform,conn->output,""); 

bzero(sendbuffer,bufsize);

if ((pp = cfpopen(buffer,"r")) == NULL)
   {
   sprintf(conn->output,"Couldn't open pipe to command %s\n",buffer);
   CfLog(cferror,conn->output,"pipe");

   sprintf(sendbuffer,"Unable to run %s\n",buffer);
   SendTransaction(conn,sendbuffer);
   ReleaseCurrentLock();
   return;
   }

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

   ReadLine(line,bufsize,pp);
   
   if (ferror(pp))  /* abortable */
      {
      fflush(pp);
      break;
      }	 

   print = false;
	 
   for (sp = line; *sp != '\0'; sp++)
      {
      if (! isspace(*sp))
	 {
	 print = true;
	 break;
	 }
      }
	 
   if (print)
      {
      sprintf(sendbuffer,"%s\n",line);
      SendTransaction(conn,sendbuffer);
      }
  }
      
cfpclose(pp);
ReleaseCurrentLock();
}


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

int GetCommand (str)

char *str;

{ int i;
  char op[bufsize];

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

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

return -1;
}

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

int VerifyConnection(conn,buf)

 /* Try reverse tcpwrap (if we have it), reverse DNS lookup
    and RFC931 username lookup to check the authenticity. */

struct cfd_connection *conn;
char buf[bufsize];

{ struct sockaddr_in raddr,laddr;
  char ipstring[256], fqname[256], username[256];
  char name1[256], name2[256], name3[256], rusername[256];
  struct hostent *hp;
  int len, i, j, found;
   
Debug("Connecting host identifies itself as %s\n",buf);

bzero(ipstring,256);
bzero(fqname,256);
bzero(username,256); 

sscanf(buf,"%255s %255s %255s %8s",ipstring,fqname,username,conn->signature);
len = sizeof(struct sockaddr_in);

if (getpeername(conn->sd_reply,(struct sockaddr *)&raddr,&len) == -1)
   {
   CfLog(cferror,"Couldn't get socket address\n","getpeername");
   return false;
   }

strcpy(name1,ToLowerStr(fqname));
strcpy(name2,ToLowerStr(inet_ntoa(raddr.sin_addr))); 
strcpy(name3,ToLowerStr(ipstring));
strncpy(conn->ipaddr,name3,sizeof(conn->ipaddr)-1);

if (IsFuzzyItemIn(SKIPVERIFY,name2))
   {
   sprintf(OUTPUT,"Allowing %s to connect without checking ID (NAT)\n",name2);
   CfLog(cfinform,OUTPUT,"");
   return true;
   }
 
if (strcmp(name2,name3) != 0)
   {
   Verbose("IP address mismatch between client's assertion and socket - untrustworthy connection\n");
   return false;
   }

Verbose("Socket caller address appears honest\n");
 
sprintf(conn->output,"Socket originates from %s=%s with signature %8s\n",name2,name1,conn->signature);
CfLog(cfverbose,conn->output,""); 

#if defined(HAVE_TCPD_H) && defined(HAVE_LIBWRAP)

if (!hosts_ctl("cfd",name1,name2,username))
   {
   Verbose("TCPwrap(%s,%s,%s) rejects this packet\n",name1,name2,username);
   return false;
   }

#endif

/* Do our own checking ... */

Debug("Attempting to look up hostname %s\n",name1);

#ifdef HAVE_PTHREAD_H 
if (pthread_mutex_lock(&MUTEX_HOSTNAME) != 0)
   {
   CfLog(cferror,"pthread_mutex_lock failed","pthread_mutex_lock");
   return false;
   }
#endif 

if ((hp = gethostbyname(name1)) == NULL)
   {
   Verbose("cfd: Couldn't look up name %s\n",fqname);
   Verbose("     Make sure that fully qualified names can be looked up at your site!\n");
   Verbose("     i.e. www.gnu.org, not just www. If you use NIS or /etc/hosts\n");
   Verbose("     make sure that the full form is registered too as an alias!\n");

#ifdef HAVE_PTHREAD_H    
   if (pthread_mutex_unlock(&MUTEX_HOSTNAME) != 0)
      {
      CfLog(cferror,"pthread_mutex_unlock failed","unlock");
      }
#endif 
   
      
   return false;
   }
 
#ifdef HAVE_PTHREAD_H  
if (pthread_mutex_unlock(&MUTEX_HOSTNAME) != 0)
   {
   CfLog(cferror,"pthread_mutex_unlock failed","unlock");
   exit(1);
   }
#endif 

 
    /* Now let's verify that the ip which gave us this hostname
     * is really an ip for this hostname; or is someone trying to
     * break in? (THIS IS THE CRUCIAL STEP)
     * Patch from Gunnar Gunnarsson, Ericsson */

 for (i = 0; hp->h_addr_list[i]; i++)
    {
    if (memcmp(hp->h_addr_list[i],(char *)&(raddr.sin_addr),sizeof(raddr.sin_addr)) == 0)
       {
       break;                     /* name is good, keep it */
       }
    }
 
 /* If we did not find it, your DNS is messed up or someone is trying
  * to pull a fast one on you. :(
  */
 
 /*   Check even the aliases list. Work around for Solaris if dns goes over NIS */
 
 if (!hp->h_addr_list[i])
    {
    for (j=0;hp->h_aliases[j]!=0;j++)
       {
       if (strcmp(hp->h_aliases[j],name2) == 0)
	  {
	  break;                          /* name is good, keep it */
	  }
       }
    }
 
 
 if (!hp->h_addr_list[i] && !hp->h_aliases[j])
    {
    sprintf(conn->output,"Reverse hostname lookup failed, host claiming to be %s was %s\n",buf,inet_ntoa(raddr.sin_addr));
    CfLog(cfinform,conn->output,"");
    return false;
    }

Debug("Host ID is %s\n",name1);
strncpy(conn->hostname,name1,256);

if (getsockname(conn->sd_reply,(struct sockaddr *)&laddr,&len) == -1)
   {
   CfLog(cferror,"Couldn't get socket address\n","getsockname");
   return false;
   }

if (CHECK_RFC931)
   {
   if (DoRFC931(&(laddr.sin_addr),&(raddr.sin_addr),ntohs(laddr.sin_port),ntohs(raddr.sin_port),rusername))
      {
      Debug("Remote host got %s as username, local=%s\n",rusername,username);
      if (strcmp(ToLowerStr(username),ToLowerStr(rusername)) == 0)
	 {
	 Debug("User ID confirmed as %s\n",username); 
	 strncpy(conn->username,username,256);
	 return true;   
	 }
      else
	 {
	 CfLog(cferror,"Username RFC931 authentication failed","");
	 sprintf(conn->output,"Source spoofed user %s but was actually %s\n",username,rusername);
	 CfLog(cferror,conn->output,"");
	 return false;
	 }
      }
   else	
      {
      CfLog(cferror,"RFC931 lookup failed on source host\n","");
      return false;
      }
   }
 
Debug("User ID seems to be %s\n",username); 
strncpy(conn->username,username,256);
return true;   
}

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

void RefuseAccess(conn,sendbuffer)

struct cfd_connection *conn;
char *sendbuffer;

{ char *hostname, *username, *ipaddr;
  static char *def = "?"; 

if (conn->hostname == NULL)
   {
   hostname = def;
   }
else
   {
   hostname = conn->hostname;
   }

if (conn->username == NULL)
   {
   username = def;
   }
else
   {
   username = conn->username;
   }

if (conn->ipaddr == NULL)
   {
   ipaddr = def;
   }
else
   {
   ipaddr = conn->ipaddr;
   }
 
sprintf(sendbuffer,"%s",CFFAILEDSTR);
CfLog(cfinform,"Host authentication failed or access denied\n","");
SendTransaction(conn,sendbuffer);
sprintf(sendbuffer,"From (host=%s,user=%s,ip=%s)",hostname,username,ipaddr);
CfLog(cfinform,"Host authentication failed or access denied\n","");
}

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

int AccessControl(filename,conn,secure)

char *filename;
struct cfd_connection *conn;
int secure;

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

Debug("AccessControl(%s)\n",filename);
bzero(realname,bufsize);

#ifdef HAVE_REALPATH
if (realpath(filename,realname) == NULL)
   {
   sprintf(conn->output,"Couldn't resolve filename %s from host %s\n",filename,conn->hostname);
   CfLog(cferror,conn->output,"");
   return false;
   }
#else
CompressPath(realname,filename); /* in links.c */
#endif
  
Debug("AccessControl(%s,%s) secure=%d\n",realname,conn->hostname,secure);

#if defined HAVE_PTHREAD_H && defined HAVE_LIBPTHREAD
   if (pthread_mutex_lock(&MUTEX_COUNT) != 0)
      {
      CfLog(cferror,"pthread_mutex_unlock failed","lock");
      }
#endif
 
if (VADMIT == NULL)
   {
   Verbose("cfd: access list is empty, no files are visible\n");
   return false;
   }

conn->maproot = false;
 
for (ap = VADMIT; ap != NULL; ap=ap->next)
   {
   if (strncmp(ap->path,realname,strlen(ap->path)) == 0)
      {
      if (stat(ap->path,&statbuf) == -1)
	 {
	 Verbose("Warning cannot stat file object %s in admit/grant\n",ap->path);
	 continue;
	 }
      
      if (!secure && (ap->secure == true))
	 {
	 Debug("File requires secure connection...will not serve\n");
	 access = false;
	 }
      else
	 {
	 if (access = IsWildItemIn(ap->accesslist,conn->hostname))
	    {
	    }
	 else
	    {
	    access = IsWildItemIn(ap->accesslist,conn->ipaddr) || IsFuzzyItemIn(SKIPVERIFY,conn->ipaddr);
	    }

	 if (IsWildItemIn(ap->maproot,conn->hostname))
	    {
	    conn->maproot = true;
	    }
	 else if (IsWildItemIn(ap->maproot,conn->ipaddr) || IsFuzzyItemIn(SKIPVERIFY,conn->ipaddr))
	    {
	    conn->maproot = true;
	    }
	 }
      break;
      }
   }

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

if (access)
   {
   sprintf(conn->output,"Host %s granted access to %s\n",conn->hostname,realname);
   CfLog(cfverbose,conn->output,"");
   }
else
   {
   sprintf(conn->output,"Host %s denied access to %s\n",conn->hostname,realname);
   CfLog(cfinform,conn->output,"");
   }

#if defined HAVE_PTHREAD_H && defined HAVE_LIBPTHREAD
if (pthread_mutex_unlock(&MUTEX_COUNT) != 0)
   {
   CfLog(cferror,"pthread_mutex_unlock failed","unlock");
   }  
#endif
 
return access;
}

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

int StatFile(conn,sendbuffer,filename)

struct cfd_connection *conn;
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];

  Debug("StatFile(%s)\n",filename);

bzero(&cfst,sizeof(struct cfstat));
  
if (strlen(filename) > maxlinksize)
   {
   sprintf(sendbuffer,"BAD: Filename suspiciously long\n");
   CfLog(cferror,sendbuffer,"");
   SendTransaction(conn,sendbuffer);
   return -1;
   }

if (lstat(filename,&statbuf) == -1)
   {
   sprintf(sendbuffer,"BAD: unable to stat file %s",filename);
   CfLog(cfverbose,sendbuffer,"lstat");
   SendTransaction(conn,sendbuffer);
   return -1;
   }

cfst.cf_readlink = NULL;
cfst.cf_lmode = 0;
cfst.cf_nlink = cfnosize;

bzero(linkbuf,bufsize);

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

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

   cfst.cf_readlink = linkbuf;
   }

if (stat(filename,&statbuf) == -1)
   {
   sprintf(sendbuffer,"BAD: unable to stat file %s\n",filename);
   CfLog(cfverbose,conn->output,"stat");
   SendTransaction(conn,sendbuffer);
   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;
cfst.cf_uid      = statbuf.st_uid   & 0xFFFFFFFF;
cfst.cf_gid      = statbuf.st_gid   & 0xFFFFFFFF;
cfst.cf_size     = statbuf.st_size;
cfst.cf_atime    = statbuf.st_atime;
cfst.cf_mtime    = statbuf.st_mtime;
cfst.cf_ctime    = statbuf.st_ctime;
cfst.cf_ino      = statbuf.st_ino;
cfst.cf_readlink = linkbuf;

if (cfst.cf_nlink == cfnosize)
   {
   cfst.cf_nlink = statbuf.st_nlink;
   }

#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=%ld\n atime=%d\n mtime=%d\n",
	cfst.cf_type,cfst.cf_mode,cfst.cf_lmode,cfst.cf_uid,cfst.cf_gid,(long)cfst.cf_size,
	cfst.cf_atime,cfst.cf_mtime);


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

if (strlen(sendbuffer) > bufsize)
   {
   CfLog(cferror,"Buffer overflow in stat protocol - crack?","");
   ExitCleanly();
   }
 
SendTransaction(conn,sendbuffer);
bzero(sendbuffer,bufsize);

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

SendTransaction(conn,sendbuffer);
return 0;
}


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

void CfGetFile(args)

struct cfd_get_arg *args;

{ int sd,fd, n_read,total=0,size_at_start,i=0;
  char sendbuffer[bufsize+1],out[bufsize],*filename;
  struct stat statbuf;
  struct passwd *pw;
  uid_t uid;

#ifdef HAVE_UTIME_H
  struct utimbuf timebuf;
#endif

sd         = (args->connect)->sd_reply;
filename   = args->replyfile;

if ((pw=getpwnam((args->connect)->username)) == NULL)
   {
   uid = -2;
   }
else
   {
   uid = pw->pw_uid;
   }

stat(filename,&statbuf);
Debug("CfGetFile(%s on sd=%d), size=%d\n",filename,sd,statbuf.st_size);

size_at_start = statbuf.st_size; 

/* Now check to see if we have remote permission */

if (uid != 0 && !args->connect->maproot) /* should remote root be local root */
   {
   if (statbuf.st_uid == uid)
      {
      Debug("Caller %s is the owner of the file\n",(args->connect)->username);
      }
   else
      {
      /* We are not the owner of the file and we don't care about groups */
      if (statbuf.st_mode & S_IROTH)
	 {
	 Debug("Caller %s not owner of the file but permission granted\n",(args->connect)->username);
	 }
      else
	 {
	 Debug("Caller %s is not the owner of the file\n",(args->connect)->username);
	 RefuseAccess(args->connect,sendbuffer);
	 return;
	 }
      }
   }

if (args->buf_size < 512)
   {
   sprintf(args->connect->output,"blocksize for %s was only %d\n",filename,args->buf_size);
   CfLog(cferror,args->connect->output,"");
   }

 if ((fd = open(filename,O_RDONLY)) == -1)
   {
   sprintf(sendbuffer,"%s",CFFAILEDSTR);
   CfLog(cferror,sendbuffer,"open");
   SendTransaction(args->connect,sendbuffer);
   sprintf(sendbuffer,"Open error of file [%s]\n",filename);
   CfLog(cferror,sendbuffer,"open");
   }
else
   {
   while(true)
      {
      bzero(sendbuffer,bufsize);

      if ((n_read = read(fd,sendbuffer,args->buf_size)) == -1)
	 {
	 close(fd);
	 CfLog(cferror,"read failed in GetFile","read");
	 break;
	 }

      if (n_read == 0)
	 {
	 close(fd);
	 break;
	 }
      else
	 { int save = statbuf.st_size;

	 /* This can happen with log files /databases etc */
	 
	 stat(filename,&statbuf);
	 
	 if (statbuf.st_size != save)
	    {
	    sprintf(sendbuffer,"BAD:File %s changed while copying",filename);
	    if (send(sd,sendbuffer,args->buf_size,0) == -1)
	       {
	       CfLog(cferror,"Send failed in GetFile","send");
	       }
	    
	    Debug("Aborting transfer after %d: file is changing rapidly at source.\n",total);
	    close(fd);     
	    break;
	    }
	 }
      
      total += n_read;

      if (args->secure)
	 {
	 bzero(out,bufsize);
	 cfencrypt(sendbuffer,out,CFDES1,args->key,CFDES3,args->buf_size); /* use session key */

	 /* Need to pad out to buf_size rather than stop at n_read
	    because encryption requires an 8-byte divisible block  */
	 
	 if (send(sd,out,args->buf_size,0) == -1)
	    {
	    close(fd);
	    CfLog(cferror,"Send failed in GetFile","send");
	    break;
	    }
	 }
      else
	 {
	 if (send(sd,sendbuffer,args->buf_size,0) == -1)
	    {
	    close(fd);
	    CfLog(cferror,"Send failed in GetFile","send");
	    break;
	    }
	 }
      }
   }

Debug("Done with GetFile()\n"); 
}

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

void CompareLocalChecksum(conn,sendbuffer,recvbuffer)

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

{ unsigned char digest[17], num[5],filename[bufsize];
  char *sp;
  int i,len;

sscanf(recvbuffer,"MD5 %[^\n]",filename);

sp = recvbuffer+6+strlen(filename); /* Finr md5 */

 Debug("Got filename %s, sp=%s\n",filename,sp);

for (i = 0; i < 16; i++)
   {
   bzero(num,5);
   sscanf(sp,"%4s",num);
   digest[i] = (char) atoi(num);
   sp += strlen((char *)num) + 1;
   }
 
Debug("CompareLocalChecksums(%s)\n",cfMDPrint(digest));
bzero(sendbuffer,bufsize);

if (ChecksumChanged(filename,digest,cfverbose,true))
   {
   sprintf(sendbuffer,"%s",CFD_TRUE);
   Debug("Checksums didn't match\n");
   SendTransaction(conn,sendbuffer);
   return;
   }
else
   {
   sprintf(sendbuffer,"%s",CFD_FALSE);
   Debug("Checksums matched ok\n");
   SendTransaction(conn,sendbuffer);
   }
}

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

int 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");
   SendTransaction(conn,sendbuffer);
   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);
   SendTransaction(conn,sendbuffer);
   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 - buffer_margin)
      {
      SendTransaction(conn,sendbuffer);
      offset = 0;
      bzero(sendbuffer,bufsize);
      }

   strncpy(sendbuffer+offset,dirp->d_name,maxlinksize);
   offset += strlen(dirp->d_name) + 1;     /* + zero byte separator */
   }

strcpy(sendbuffer+offset,CFD_TERMINATOR);

SendTransaction(conn,sendbuffer);
closedir(dirh);
return 0;
}

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

void Terminate(sd)

int sd;

{ char buffer[bufsize];

bzero(buffer,bufsize);

strcpy(buffer,CFD_TERMINATOR);

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

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

void DeleteAuthList(ap)

struct Auth *ap;

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

   DeleteItemList(ap->accesslist);

   free((char *)ap);
   }
}

/***************************************************************/
/* Level 5                                                     */
/***************************************************************/

int DoRFC931(laddr,raddr,local,remote,username)

/* Not many OSes have this authentication service, alas */

struct in_addr *laddr, *raddr;
unsigned short local,remote;
char username[256];

{ struct sockaddr_in saddr;
  char sendbuffer[maxvarsize],recvbuffer[bufsize], *sp;
  int n_read, sd, yes=1;

 if ((sd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
    {
    return false;
    }

 /* Identify ourselves with this address */
 
 bind(sd,(struct sockaddr *)laddr,sizeof(laddr));

 setsockopt(sd,SOL_SOCKET,SO_REUSEADDR,(char *)&yes,sizeof(int));
 
 saddr.sin_family = AF_INET;
 saddr.sin_port = htons(RFC931_PORT);
 saddr.sin_addr.s_addr = raddr->s_addr;
 
 if (connect(sd,(struct sockaddr *)&saddr,sizeof(saddr)) == -1)
    {
    CfLog(cferror,"Couldn't connect to verify RFC931\n","connect");
    close(sd);
    return false;
    }

  /* The RFC document is misleading...careful with order */

 sprintf(sendbuffer,"%u , %u\r\n",(unsigned int)remote,(unsigned int)local);

 if (send(sd,sendbuffer,strlen(sendbuffer),0) == -1)
    {
    CfLog(cferror,"Couldn't send to verify RFC931\n","send");
    close(sd);
    return false;
    }

 if ((n_read = recv(sd, recvbuffer, bufsize,0)) == -1)
    {
    CfLog(cferror,"Couldn't recv to verify RFC931\n","recv");
    close(sd);
    return false;
    }
 
close(sd);

Debug("AUTH server returned %s\n",recvbuffer);

if (strstr(recvbuffer,"ERROR"))
   {
   Verbose("RFC931 checking reveals no such user connection at source!\n");
   return false;
   }

/* e.g. AUTH server returned 8530 , 5308 : USERID : UNIX : mark  */

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

sp++;
 
while (*sp != ':')
   {
   sp++;
   } 

sp++;

sendbuffer[0] = '\0';
username[0] = '\0';
Debug("Checking %s\n",sp); 
sscanf(sp,"%255s %*[:] %255s",sendbuffer,username);

Debug("Operating system type of remote host was %s and user was %s\n",sendbuffer,username);
return true; 
}


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

void ReplyNothing(conn)

struct cfd_connection *conn;

{ char buffer[bufsize];

sprintf(buffer,"Hello %s (%s), nothing relevant to do here...\n",conn->hostname,conn->ipaddr);

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

/***************************************************************/
/* Toolkit/Class: conn                                         */
/***************************************************************/

struct cfd_connection *NewConn(sd)  /* construct */

int sd;

{ struct cfd_connection *conn;

conn = (struct cfd_connection *) malloc(sizeof(struct cfd_connection));

if (conn == NULL)
   {
   CfLog(cferror,"Unable to allocate conn","malloc");
   ExitCleanly();
   }
 
conn->sd_reply = sd;
conn->id_verified = false;
conn->hostname[0] = '\0'; 
Debug("***New socket %d\n",sd);
 
return conn;
}

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

void DeleteConn(conn) /* destruct */

struct cfd_connection *conn;

{
Debug("***Closing socket %d from %s\n",conn->sd_reply,conn->ipaddr);
 
DeleteItemStarting(&CONNECTIONLIST,conn->ipaddr); 

close(conn->sd_reply);
free ((char *)conn);
}

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

void SendTransaction(conn,buffer)

struct cfd_connection *conn;
char *buffer;

{
/* Send a fixed size transmission
   so that it will be read at the other end */
Debug("SendTransaction()\n");
if (send(conn->sd_reply,buffer,bufsize,0) == -1)
   {
   CfLog(cferror,"Couldn't send","send");
   return;
   }
}


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

time_t SecondsTillAuto()
{
return (next_auto - time((time_t *) NULL));
}

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

void SetAuto(seconds)

int seconds;

{
sprintf(OUTPUT,"Setting autorun time to %d seconds\n",seconds); 
CfLog(cfinform,OUTPUT,""); 
next_auto = (seconds) + time((time_t *) NULL);
}
 
/***************************************************************/
/* ERS                                                         */
/***************************************************************/

int cfscanf(in,len1,len2,out1,out2,out3)
   
char *in,*out1,*out2,*out3;
int len1, len2;
   
{  
int len3=0;
char *sp;
   
   
sp = in;
bcopy(sp,out1,len1);
out1[len1]='\0';
   
sp += len1 + 1;
bcopy(sp,out2,len2);
   
sp += len2 + 1;
len3=strlen(sp);
bcopy(sp,out3,len3);
out3[len3]='\0';
   
return (len1 + len2 + len3 + 2);
}  
   

/* EOF */



