/* 

        Copyright (C) 1995
        Free Software Foundation, Inc.

   This file is part of GNU cfengine - written and maintained 
   by Mark Burgess, Dept of Computing and Engineering, Oslo College,
   Dept. of Theoretical physics, University of Oslo
 
   This program is free software; you can redistribute it and/or modify it
   under the terms of the GNU General Public License as published by the
   Free Software Foundation; either version 2, or (at your option) any
   later version.
 
   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.
 
  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA

*/

/*******************************************************************/
/*                                                                 */
/* File Image copying                                              */
/*                                                                 */
/* client part for remote copying                                  */
/*                                                                 */
/*******************************************************************/

#define INET 1

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

/* Protocol specs:

   Stat file:  AUTH client-name server-name
               SYNCH long-time-string STAT filename

	       reply with OK: <stat-reply-string>

   Get file:   AUTH client-name server-name
               GET filename

	       reply with <multiple buffers>, break on zero length buffer
	       or BAD: <message>

   Opendir:    AUTH client-name server-name
               OPENDIR dir-name

	       reply with <multiple buffers>, break on zero length buffer

   Exec:       AUTH client-name server-name
               EXEC option-string

   	       reply with <multiple buffers>, break on zero length buffer


Replies:

  OK: message (succeeded)

  BAD: message (failed)
   
 */

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

cf_rstat(file,buf,ip,stattype)

/* If a link, this reads readlink and sends it back in the same
   package. It then caches the value for each copy command */

char *file;
struct stat *buf;
struct Image *ip;
char *stattype;

{ struct sockaddr_in cin;
  char sendbuffer[bufsize];
  char recvbuffer[bufsize];
  struct cfstat cfst;
  int sd,i,err,TimeOut(),ret;
  time_t tloc;
  struct hostent *hp;

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

ret = GetCachedStatData(file,buf,ip,stattype);

if (ret != 0)
   {
   return ret;
   }

bzero(&cin,sizeof(cin));

cin.sin_port = PORTNUMBER;
cin.sin_addr.s_addr = ((struct in_addr *)((ip->dns)->h_addr))->s_addr;
cin.sin_family = AF_INET; 

Debug("Trying to connect to %s = %s, port h=%d\n",ip->server,inet_ntoa(cin.sin_addr),PORTNUMBER);

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

signal(SIGALRM,(void *)TimeOut);
alarm(CF_TIMEOUT);

if (err=connect(sd,(void *) &cin,sizeof(cin)) == -1)
   {
   printf("cfengine:%s: Couldn't connect to host %s\n",VPREFIX,ip->server);
   perror("connect");
   return -1;
   }

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

Debug("Sending on socket...\n");

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

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

sprintf(sendbuffer,"SYNCH %d STAT %s",tloc,file);

if (send(sd,sendbuffer,bufsize,0) == -1)
   {
   printf("Socket only sent %d bytes\n");
   perror("send");
   close(sd);
   return -1;
   }

if (RecvSocketStream(sd,recvbuffer,bufsize,0) == -1)
   {
   return -1;
   }

if (strncmp(recvbuffer,"BAD",3) == 0)
   {
   Verbose("Server returned error: %s\n",recvbuffer+4);
   close(sd);
   errno = EPERM;
   return -1;
   }


if (strncmp(recvbuffer,"OK:",3) == 0)
   {
   sscanf(recvbuffer,"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);

   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 ino=%d nlnk=%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,cfst.cf_ino,cfst.cf_nlink);

   bzero(recvbuffer,bufsize);
   
   if (RecvSocketStream(sd,recvbuffer,bufsize,0) == -1)
      {
      return -1;
      }
   
   Debug("Linkbuffer: %s\n",recvbuffer);

   if (strlen(recvbuffer) > 3)
      {
      cfst.cf_readlink = malloc(strlen(recvbuffer));
      strcpy(cfst.cf_readlink,recvbuffer+3);
      }
   else
      {
      cfst.cf_readlink = NULL;
      }

   switch (cfst.cf_type)
      {
      case cf_reg:   cfst.cf_mode |= (mode_t) S_IFREG;
	             break;
      case cf_dir:   cfst.cf_mode |= (mode_t) S_IFDIR;
   	             break;
      case cf_char:  cfst.cf_mode |= (mode_t) S_IFCHR;
	             break;
      case cf_fifo:  cfst.cf_mode |= (mode_t) S_IFIFO;
	             break;
      case cf_sock:  cfst.cf_mode |= (mode_t) S_IFSOCK;
	             break;
      case cf_block: cfst.cf_mode |= (mode_t) S_IFBLK;
	             break;
      }


   cfst.cf_filename = malloc(strlen(file)+1);
   strcpy(cfst.cf_filename,file);

   cfst.cf_server = malloc(strlen(ip->server)+1);
   strcpy(cfst.cf_server,ip->server);

   cfst.cf_failed = false;

   if (cfst.cf_lmode != 0)
      {
      cfst.cf_lmode |= (mode_t) S_IFLNK;
      }

   CacheData(&cfst,ip);

   if ((cfst.cf_lmode != 0) && (strcmp(stattype,"link") == 0))
      {
      buf->st_mode = cfst.cf_lmode;
      Debug("Reconstructed link mode is %o\n",buf->st_mode);      
      }
   else
      {
      buf->st_mode = cfst.cf_mode;
      Debug("Reconstructed file mode is %o\n",buf->st_mode);
      }

   buf->st_uid = cfst.cf_uid;
   buf->st_gid = cfst.cf_gid;
   buf->st_size = cfst.cf_size;
   buf->st_mtime = cfst.cf_mtime;
   buf->st_ctime = cfst.cf_ctime;
   buf->st_atime = cfst.cf_atime;
   buf->st_ino   = cfst.cf_ino;
   buf->st_nlink = cfst.cf_nlink;
   
   close(sd);
   return 0;
   }


Verbose("cfengine: server didn't respond correctly (Got: %s)\n",recvbuffer);
errno = EPERM;

close(sd);
return -1;
}

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

CFDIR *cf_ropendir(dirname,ip)

char *dirname;
struct Image *ip;

{ struct sockaddr_in cin;
  char sendbuffer[bufsize];
  char recvbuffer[bufsize];
  int sd,i,err,TimeOut(),offset,n;
  CFDIR *cfdirh;
  char *sp;

Debug("CfOpenDir(%s:%s)\n",ip->server,dirname);

bzero(&cin,sizeof(cin));

cin.sin_port = PORTNUMBER;
cin.sin_addr.s_addr = ((struct in_addr *)((ip->dns)->h_addr))->s_addr;
cin.sin_family = AF_INET; 

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

signal(SIGALRM,(void *)TimeOut);
alarm(CF_TIMEOUT);

if (err=connect(sd,(void *) &cin,sizeof(cin)) == -1)
   {
   printf("cfengine:%s: Couldn't connect to host\n",VPREFIX,ip->server);
   perror("connect");
   return NULL;
   }

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

Debug("Sending on socket...\n");

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

if ((cfdirh = (CFDIR *)malloc(sizeof(CFDIR))) == NULL)
   {
   printf("cfengine:%s: couldn't allocate memory in cf_ropendir\n",VPREFIX);
   exit(1);
   }

cfdirh->cf_list = NULL;
cfdirh->cf_listpos = NULL;
cfdirh->cf_dirh = NULL;

sprintf(sendbuffer,"OPENDIR %s",dirname);

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

while (true)
   {
   if ((n = recv(sd, recvbuffer, bufsize,0)) == -1)
      {
      if (errno == EINTR) 
         {
         continue;
         }
      close(sd);
      return false;
      }

   if (n == 0)
      {
      break;
      }

   if (strncmp(recvbuffer,"BAD:",4) == 0) /* opendir failed */
      {
      Verbose("cfengine: %s",recvbuffer+4);
      continue;
      }

   for (sp = recvbuffer; *sp != '\0'; sp = sp + strlen(sp) + 1)
      {
      AppendItem(&(cfdirh->cf_list),sp,NULL);
      }
   }

close(sd);

cfdirh->cf_listpos = cfdirh->cf_list;
return cfdirh;
}

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

FlushClientCache(ip)

struct Image *ip;

{
free(ip->cache);

ip->cache = NULL;
}

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

CompareMD5Net(file1,file2,ip)

char *file1, *file2;
struct Image *ip;

{ static unsigned char d[16];
  struct sockaddr_in cin;
  char sendbuffer[bufsize];
  char recvbuffer[bufsize];
  int sd,i,err,TimeOut(),offset;
  
bzero(&cin,sizeof(cin));

cin.sin_port = PORTNUMBER;
cin.sin_addr.s_addr = ((struct in_addr *)((ip->dns)->h_addr))->s_addr;
cin.sin_family = AF_INET; 

if ((sd = socket(AF_INET,SOCK_STREAM,0)) == -1)
   {
   perror("socket");
   Verbose("No answer from host, assuming checksum ok to avoid remote copy for now...\n");
   return false;
   }

signal(SIGALRM,(void *)TimeOut);
alarm(CF_TIMEOUT);

if (err=connect(sd,(void *) &cin,sizeof(cin)) == -1)
   {
   Verbose("No answer from host, assuming checksum ok to avoid remote copy for now...\n");
   return false;
   }

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

Debug("Sending on socket...\n");

if (! IdentifyForVerification(sd,VIPADDRESS,VFQNAME))
   {
   Verbose("cfengine:%s: server registration procedure failed\n",VPREFIX);
   Verbose("        :%s: assuming checksum ok to avoid remote copy for now...\n",VPREFIX);
   errno = EPERM;
   return true;
   }

cfMDFile(file2,d);   /* send this to the server for comparison */

bzero(recvbuffer,bufsize);
bzero(sendbuffer,bufsize);

if (20 + 3*16 + strlen(file1) > bufsize-1)
   {
   Verbose("cfengine:%s: MD5 transfer overflow...updating for safety\n",VPREFIX);
   return true;
   }

sprintf(sendbuffer,"MD5 %s %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d \0",file1,
	d[0],d[1],d[2],d[3],d[4],d[5],d[6],d[7],d[8],d[9],d[10],d[11],d[12],d[13],d[14],d[15]);

if (send(sd,sendbuffer,bufsize,0) == -1)
   {
   perror("send");
   close(sd);
   Verbose("No answer from host, assuming checksum ok to avoid remote copy for now...\n");
   return false;
   }

if (RecvSocketStream(sd, recvbuffer, bufsize,0) == -1)
   {
   close(sd);
   Verbose("No answer from host, assuming checksum ok to avoid remote copy for now...\n");
   return false;  /* Do this to avoid starting a copy if remote host is down */
   }

if (strcmp(CFD_TRUE,recvbuffer) == 0)
   {
   Debug("MD5 mismatch: %s\n",recvbuffer);
   return true; /* mismatch */
   }
else
   {
   Debug("MD5 match: %s\n",recvbuffer);
   return false;
   }

close(sd);
}

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

CopyRegNet(source,new,ip)

char *source, *new;
struct Image *ip;

{ int sd, dd, buf_size, TimeOut();
  char *buf, *cp, sendbuffer[bufsize];
  int n_read, *intp, err;
  long n_read_total = 0;
  int last_write_made_hole = 0;
  struct sockaddr_in cin;

Debug("CopyRegNet(%s,%s)\n",source,new);
  
bzero(&cin,sizeof(cin));

cin.sin_port = PORTNUMBER;
cin.sin_addr.s_addr = ((struct in_addr *)((ip->dns)->h_addr))->s_addr;
cin.sin_family = AF_INET; 

if ((dd = open(new,O_WRONLY | O_CREAT | O_TRUNC, 0600)) == -1)
   {
   printf("cfengine:%s: can't copy to destination %s!\n",VPREFIX,new);
   perror("open");
   unlink(new);
   return false;
   }

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

signal(SIGALRM,(void *)TimeOut);
alarm(CF_TIMEOUT);

if (err = connect(sd,(void *) &cin,sizeof(cin)) == -1)
   {
   printf("cfengine:%s: Couldn't connect to host\n",VPREFIX,ip->server);
   perror("connect");
   return false;
   }

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

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

sprintf(sendbuffer,"GET %s",source);

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

buf_size = ST_BLKSIZE(dstat);
buf = (char *) malloc(buf_size + sizeof(int));

while (true)
   {
   if ((n_read = recv(sd, buf, buf_size,0)) == -1)
      {
      if (errno == EINTR) 
         {
         continue;
         }

      close(sd);
      close(dd);
      free(buf);
      return false;
      }

   if (strncmp(buf,"BAD: Host authentication failed",strlen("BAD: Host authentication failed")) == 0)
      {
      printf("cfengine:%s: network access to %s:%s denied\n",VPREFIX,ip->server,source);
      close(sd);
      close(dd);
      free(buf);
      return false;      
      }

   if (n_read == 0)
      {
      break;
      }

   n_read_total += n_read;

   intp = 0;

   if (ip->makeholes)
      {
      buf[n_read] = 1;	                   /* Sentinel to stop loop.  */

      /* Find first non-zero *word*, or the word with the sentinel.  */

      intp = (int *) buf;

      while (*intp++ == 0)
         {
         }

      /* Find the first non-zero *byte*, or the sentinel.  */

      cp = (char *) (intp - 1);

      while (*cp++ == 0)
         {
         }

      /* If we found the sentinel, the whole input block was zero,
         and we can make a hole.  */

      if (cp > buf + n_read)
         {
         /* Make a hole.  */
         if (lseek (dd, (off_t) n_read, SEEK_CUR) < 0L)
            {
            printf ("cfengine: lseek in CopyReg, dest=%s\n", new);
            perror("lseek");
            free(buf);
	    unlink(new);
            return false;
            }
         last_write_made_hole = 1;
         }
      else
         {
         /* Clear to indicate that a normal write is needed. */
         intp = 0;
         }
      }

   if (intp == 0)
      {
      if (cf_full_write (dd, buf, n_read) < 0)
         {
         printf ("cfengine: full_write failed in CopyReg()\n");
         close(sd);
         close(dd);
         free(buf);
	 unlink(new);
         return false;
         }
      last_write_made_hole = 0;
      }
   }

  /* If the file ends with a `hole', something needs to be written at
     the end.  Otherwise the kernel would truncate the file at the end
     of the last write operation.  */

  if (last_write_made_hole)
    {
    /* Write a null character and truncate it again.  */

    if (cf_full_write (dd, "", 1) < 0 || ftruncate (dd, n_read_total) < 0)
       {
       printf("cfengine: full_write or ftruncate error in CopyReg\n");
       free(buf);
       unlink(new);
       return false;
       }
    }

close(sd);
close(dd);

free(buf);
return true;
}

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

GetCachedStatData(file,statbuf,ip,stattype)

char *file;
struct stat *statbuf;
struct Image *ip;
char *stattype;

{ struct cfstat *sp;

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

for (sp = ip->cache; sp != NULL; sp=sp->next)
   {
   if ((strcmp(ip->server,sp->cf_server) == 0) && (strcmp(file,sp->cf_filename) == 0))
      {
      if (sp->cf_failed)  /* cached failure from cfopendir */
	 {
	 errno = EPERM;
	 return -1;
	 }

      if ((strcmp(stattype,"link") == 0) && (sp->cf_lmode != 0))
	 {
	 statbuf->st_mode  = sp->cf_lmode;
	 }
      else
	 {
         statbuf->st_mode  = sp->cf_mode;
	 }

      statbuf->st_uid   = sp->cf_uid;
      statbuf->st_gid   = sp->cf_gid;
      statbuf->st_size  = sp->cf_size;
      statbuf->st_atime = sp->cf_atime;
      statbuf->st_mtime = sp->cf_mtime;
      statbuf->st_ctime = sp->cf_ctime;
      statbuf->st_ino   = sp->cf_ino;
      statbuf->st_nlink = sp->cf_nlink;      

      return true;
      }
   }

return false;
}

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

CacheData(data,ip)

struct cfstat *data;
struct Image *ip;

{ struct cfstat *sp;

if ((sp = (struct cfstat *) malloc(sizeof(struct cfstat))) == NULL)
   {
   printf("cfengine: Memory allocation faliure in CacheData()\n");
   return;
   }

bcopy(data,sp,sizeof(struct cfstat));

sp->next = ip->cache;
ip->cache = sp;
}

