/* 

        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 client example                               */
/*                                                                 */
/*  Mark Burgess 1997                                              */
/*  modified : Bas van der Vlies <basv@sara.nl> 1998               */
/*  modified : Andrew Mayhew <amayhew@icewire.com> 2000            */
/*                                                                 */
/*******************************************************************/

#define INET 1

/*
 * Bas 
*/
#define FileVerbose if (VERBOSE || DEBUG || D2) fprintf

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

/* 
 * Bas
*/
int  MAXCHILD = 1;
int  FileFlag = 0;
char OUTPUTDIR[bufsize];

struct Item *VCFRUNCLASSES = NULL;
struct Item *VCFRUNHOSTLIST = NULL;

char VCFRUNHOSTS[bufsize] = "cfrun.hosts";

char CFRUNOPTIONS[bufsize];
char CFLOCK[bufsize] = "dummy";

/*******************************************************************/
/* Functions internal to cfrun.c                                   */
/*******************************************************************/

void CheckOptsAndInit(int argc,char **argv);
int ConnectToServer(char *host, char *options, int StoreInFile);
void SendClassData(int sd, char *sendbuffer);
void CheckAccess(char *users);
void cfrunSyntax();

/*
 * Bas 
 */
int ParseHostname(char *hostname, char *new_hostname);

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

int main (argc,argv)

int argc;
char **argv;

{ struct Item *ip;
  int 	 i=0;
  int	 status;
  int	 pid;

CheckOptsAndInit(argc,argv);

/*
 * Bas
*/
 
ip = VCFRUNHOSTLIST;
while (ip != NULL )
   {
   if (i < MAXCHILD)
      {
      if (fork() == 0) /* child */
	 {
	 printf("cfrun: calling %s - %d\n",ip->name, i);
	 Debug("pid = %d i = %d\n", getpid(), i);
	 if (ConnectToServer(ip->name,ip->classes, FileFlag))
	    {
	    printf("Connection to %s done...\n", ip->name);
	    }
	 else
	    {
	    Verbose("Connection to %s refused...\n", ip->name);
	    }
	 exit(0);
	 }
      else
	 {
	 /* parent */
	 i++;
	 }
      }
   else 
      {
      pid = wait(&status);
      Debug("wait result pid = %d number %d\n", pid, i);
      i--;
      }
   if ( i < MAXCHILD )
      {
      ip = ip->next;
      }
   }  // end forking while
 
 while (i > 0 )
    {
    pid = wait(&status);
    Debug("Child pid = %d ended\n", pid);
    i--;
    }
 
 exit(0);
 return 0; /* Keep ANSI compilers happy */
}

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

void CheckOptsAndInit(argc,argv)

int argc;
char **argv;

{ char buffer[maxvarsize], options[bufsize], line[bufsize], filename[bufsize], *
sp;
  struct Item *ip;
  FILE *fp;
  int i;
  int optgroup = 0;

/* Separate command args into options and classes */
bzero(CFRUNOPTIONS,bufsize);

for (i = 1; i < argc; i++) 
	{
	   if (optgroup == 0)
		{	 
		   if (strncmp(argv[i],"-h",2) == 0)		 
			{
			   cfrunSyntax();
			}
		   else if (strncmp(argv[i],"-f",2) == 0)
			{
			   i++;
			   if ((i >= argc) || (strncmp(argv[i],"-",1) == 0)) {
				 printf("Error: No filename listed after -f option.\n");
				 cfrunSyntax();
				 exit(0);
			   }
			   bzero(VCFRUNHOSTS,bufsize);
			   strcat(VCFRUNHOSTS,argv[i]);
			   Debug("cfrun: cfrun file = %s\n",VCFRUNHOSTS);
			}
		   else if (strncmp(argv[i],"-d",2) == 0)
			{
			   DEBUG = true;
			   VERBOSE = true;
			}
		   else if (strncmp(argv[i],"-v",2) == 0)
			{
			   VERBOSE=true;
			}
		   else if (strncmp(argv[i],"-S",2) == 0)
			{
			   SILENT = true;
			}
		   else if (strncmp(argv[i],"--",2) == 0) 
			{
			   optgroup++;
			}
		   else
			{
			   printf("Error: Unknown option.\n");
			   cfrunSyntax();
			   exit(0);
			}
		}
	   else if (optgroup == 1) 
		{
		   if (strncmp(argv[i],"--",2) == 0)
			{
			   optgroup++;
			}
		   else
			{
			   strcat(CFRUNOPTIONS,argv[i]);
			   strcat(CFRUNOPTIONS," ");
			}
		}
	   else
		{
		   AppendItem(&VCFRUNCLASSES,argv[i],"");
		}
	}

Debug("CFRUNOPTIONS string: %s\n",CFRUNOPTIONS);
   
for (ip = VCFRUNCLASSES; ip != NULL; ip=ip->next)
   {
   Debug("Class item: %s\n",ip->name);
   }

if (uname(&VSYSNAME) == -1)
   {
   perror("uname ");
   printf("cfrun: uname couldn't get kernel name info!!\n");
   exit(1);
   }

if ((strlen(VDOMAIN) > 0) && !strchr (VSYSNAME.nodename, '.'))
   {
   sprintf(VFQNAME,"%s.%s",VSYSNAME.nodename,VDOMAIN);
   }
else
   {
   sprintf(VFQNAME,"%s",VSYSNAME.nodename);
   }   
   
/* Read hosts file */

if ( ((sp=getenv(CFINPUTSVAR)) != NULL) && (!strchr(VCFRUNHOSTS, '/')) )
   {
   strcpy(filename,sp);
   if (filename[strlen(filename)-1] != '/')
      {
      strcat(filename,"/");
      }
   }

strcat(filename,VCFRUNHOSTS);

if ((fp = fopen(filename,"r")) == NULL)      /* Open root file */
   {
   printf("Unable to open %s\n",filename);
   return;
   }

while (!feof(fp))
   {
   bzero(buffer,maxvarsize);
   bzero(options,bufsize);
   bzero(line,bufsize);

   ReadLine(line,bufsize,fp);

   if (strncmp(line,"domain",6) == 0)
      {
      sscanf(line,"domain = %295[^# \n]",VDOMAIN);
      Verbose("Domain name = %s\n",VDOMAIN);
      continue;
      }

   if (strncmp(line,"maxchild", strlen("maxchild")) == 0)
      {
      sscanf(line,"maxchild = %295[^# \n]", buffer);

      if ( (MAXCHILD = atoi(buffer)) == 0 )
	MAXCHILD = 1;

      Verbose("cfrun: maxchild = %d\n", MAXCHILD);
      continue;
      }

   if (strncmp(line,"outputdir", strlen("outputdir")) == 0)
      {
      sscanf(line,"outputdir = %295[^# \n]", OUTPUTDIR);
	    Verbose("cfrun: outputdir = %s\n", OUTPUTDIR);
      if ( opendir(OUTPUTDIR) == NULL)
	 {
         printf("Directory %s does not exists\n", OUTPUTDIR);
	 exit(1);
         }

      FileFlag=1;
      continue;
      }

   if (strncmp(line,"access",6) == 0)
      {
      for (sp = line; (*sp != '=') && (*sp != '\0'); sp++)
	 {
	 }

      if (*sp == '\0' || *(++sp) == '\0')
	 {
	 continue;
	 }

      CheckAccess(sp);
      continue;
      }

   sscanf(line,"%295s %[^#\n]",buffer,options);

   if (buffer[0] == '#')
      {
      continue;
      }

   if (strlen(buffer) == 0)
      {
      continue;
      }

   if ((!strstr(buffer,".")) && (strlen(VDOMAIN) > 0))
      {
      strcat(buffer,".");
      strcat(buffer,VDOMAIN);
      }
      
   if (!IsItemIn(VCFRUNHOSTLIST,buffer))
       {
       AppendItem(&VCFRUNHOSTLIST,buffer,options);
       }
   }

for (ip = VCFRUNHOSTLIST; ip != NULL; ip=ip->next)
   {
   Debug("host item: %s (%s)\n",ip->name,ip->classes);
   }

fclose(fp);

}

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

int ParseHostname(hostname, new_hostname)

char *hostname;
char *new_hostname;

{ int port=0;

  sscanf(hostname,"%[^:]:%d", new_hostname, &port);
  return(port);
}

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

int ConnectToServer(host,options, StoreInFile)

char *host, *options;
int  StoreInFile;

{ struct hostent *hp;
  struct sockaddr_in raddr;
  char sendbuffer[bufsize];
  char recvbuffer[bufsize];
  char filebuffer[bufsize];
  char parsed_host[bufsize];

  struct servent *server;
  int sd,err,n_read, port;
  char *sp;
  FILE  *fp;


if (StoreInFile)
   {
   sprintf(filebuffer, "%s/%s", OUTPUTDIR, host);
   fp = fopen(filebuffer, "w");
   }
else
   {
   fp = stdout;
   }

port = ParseHostname(host, parsed_host);
FileVerbose(fp, "Connecting to server %s to port %d with options %s %s\n",
			parsed_host,port,options,CFRUNOPTIONS);

if ((hp = gethostbyname(parsed_host)) == NULL)
   {
   printf("Unknown host: %s\n", parsed_host);
   printf("Make sure that fully qualified names can be looked up at your site!\n");
   printf("i.e. prep.ai.mit.edu, not just prep. If you use NIS or /etc/hosts\n");
   printf("make sure that the full form is registered too as an alias!\n");
   exit(1);
   }

bzero(&raddr,sizeof(raddr));

/*
 * Bas: If port is set then use it else default one 
*/
if (port)
   {
   /* Thanks to Walter de Jong: walter@sara.nl */
   raddr.sin_port = htons(port);
   }
else
  {
  if ((server = getservbyname(CFENGINE_SERVICE,"tcp")) == NULL)
     {
     perror("getservbyname");
     exit (1);
     }
  else
     {                                                       
     raddr.sin_port = (unsigned int) server->s_port;
     } 
  }

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

Debug("Trying to connect to %s = %s, port h=%d n=%d\n",
	parsed_host, inet_ntoa(raddr.sin_addr),
        ntohs(raddr.sin_port),raddr.sin_port);

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

signal(SIGALRM,(void *)TimeOut);
alarm(15);                         /* 15 second timeout */

if (err=connect(sd,(void *) &raddr,sizeof(raddr)) == -1)
   {
   alarm(0);
   signal(SIGALRM,SIG_DFL);
   printf("Host %s isn't talking to anyone\n\n",parsed_host);
   return false;
   }

alarm(0);
signal(SIGALRM,SIG_DFL);

if (! IdentifyForVerification(sd,VFQNAME))
   {
   printf("%s: server registration procedure failed",VPREFIX);
   errno = EPERM;
   return false;
   }

sprintf(sendbuffer,"EXEC %s %s",options,CFRUNOPTIONS);

if (send(sd,sendbuffer,bufsize,0) == -1)
   {
   printf("Transmission rejected");
   close(sd);
   return false;
   }

SendClassData(sd,sendbuffer);

FileVerbose(fp, "\n%s replies..\n\n",parsed_host);

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

  /*
   * Bas: without MSG_WAITALL we did not got all the output, was 0
  */
  if ((n_read = recv(sd, recvbuffer, bufsize, MSG_WAITALL)) == -1)
      {
      if (errno == EINTR) 
         {
         continue;
         }

      close(sd);
      return true;;
      }

   if ((sp = strstr(recvbuffer,CFD_TERMINATOR)) != NULL)
      {
      *sp = '\0';
      fprintf(fp,"%s",recvbuffer);
      break;
      }

   if ((sp = strstr(recvbuffer,"BAD:")) != NULL)
      {
      *sp = '\0';
      fprintf(fp,"%s",recvbuffer);
      break;
      }   

   if (n_read == 0)
      {
      break;
      }

   if (strlen(recvbuffer) == 0)
      {
      continue;
      }

   if (strstr(recvbuffer,"too soon"))
      {
      printf("%s",recvbuffer);
      }

   if (strstr(recvbuffer,"cfd:")) /* Don't print messages fom cfd unless -v */
      {
      FileVerbose(fp, "%s",recvbuffer);
      sleep(1);
      continue;
      }

   fprintf(fp,"%s",recvbuffer);
   }

close(sd);
FileVerbose(fp,"\n========================================================================\n");

 return true;
}


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

void SendClassData(sd, sendbuffer)

int sd;
char *sendbuffer;

{ struct Item *ip;
  int used;
  char *sp;

sp = sendbuffer;
used = 0;
bzero(sendbuffer,bufsize);
  
for (ip = VCFRUNCLASSES; ip != NULL; ip = ip->next)
   {
   if (used + strlen(ip->name) +2 > bufsize)
      {
      if (send(sd,sendbuffer,bufsize,0) == -1)
         {
         perror("send");
         return;
         }

      used = 0;
      sp = sendbuffer;
      bzero(sendbuffer,bufsize);
      }
   
   strcat(sendbuffer,ip->name);
   strcat(sendbuffer," ");

   sp += strlen(ip->name)+1;
   used += strlen(ip->name)+1;
   }

if (used + strlen(CFD_TERMINATOR) +2 > bufsize)
   {
   if (send(sd,sendbuffer,bufsize,0) == -1)
      {
      perror("send");
      return;
      }

   used = 0;
   sp = sendbuffer;
   bzero(sendbuffer,bufsize);
   }
   
sprintf(sp,CFD_TERMINATOR);

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

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

void CheckAccess(users)

char *users;

{ char id[maxvarsize], *sp;
  struct passwd *pw;
  int uid,myuid;

myuid = getuid();

if (myuid == 0)
   {
   return;
   }

for (sp = users; *sp != '\0'; sp++)
   {
   id[0] = '\0';

   sscanf(sp,"%295[^,:]",id);

   sp += strlen(id);
   
   if (isalpha(id[0]))
      {
      if ((pw = getpwnam(id)) == NULL)
	 {
	 printf("cfrun: No such user (%s) in password database\n",id);
	 exit(0);
	 }
      
      if (pw->pw_uid == myuid)
	 {
	 return;
	 }
      }
   else
      {
      uid = atoi(id);
      if (uid == myuid)
	 {
	 return;
	 }
      }
   }

 printf("cfrun: you have not been granted permission to run cfrun\n");
 exit(0);
}

void cfrunSyntax() {
   printf("Usage: cfrun [-f cfrun.hosts|-h|-d|-S|-v] [-- OPTIONS [-- CLASSES]]\n");
   printf("-f cfrun.hosts\tcfrun file to read in list of hosts (see below for syntax.)\n");
   printf("-h\t\tGet this help message.\n");
   printf("-d\t\tDebug mode, turns on verbose as well.\n");
   printf("-S\t\tSilent mode.\n");
   printf("-v\t\tVerbose mode.\n");
   printf("-- OPTIONS\tArguments to be passed to host application.\n");
   printf("-- CLASSES\tClasses to be defined for the hosts.\n\n");
   printf("cfrun.hosts file syntax:\n");
   printf("# starts a comment\n");
   printf("domain = [domain]\t# Domain to use for connection(s).\n");
   printf("maxchild = [num]\t# Maximum number of children to spawn during run.\n");
   printf("outputdir = [dir]\t# Directory where to put host output files.\n");
   printf("access = [user]\t\t# User allowed to do cfrun?\n");
   printf("[host]\t\t\t# One host per line list to cycle through.\n");
   printf("\t\t\t# Only the hosts are required for cfrun to operate.\n");
   
   exit(0);
}

/********************************************************************/
/* Toolkit                                                          */
/********************************************************************/

void FatalError(s)

char *s;

{
exit(1);
}

void AddMultipleClasses(classlist)

char *classlist;

{
 /* hack */
}

/* EOF */

