/* 

        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"

#ifdef HAVE_LIBCRYPTO
# include <openssl/des.h>
#endif

/* Protocol specs:

   Pre authorization: CAUTH (clear text auth) client-name server-name user-name

   Stat file:  SYNCH long-time-string STAT filename
	       reply with OK: <stat-reply-string>
	       keep open channel

   md5 file    Checksum compare

   Get file:   GET filename

	       reply with <multiple buffers>, break on zero length buffer
	       or BAD: <message>
	       keep open channel for more stat/get

   Opendir:    OPENDIR dir-name

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

   Exec:       EXEC option-string
   	       reply with <multiple buffers>, break on zero length buffer
               Close after this.

Replies:

  OK: message (succeeded)

  BAD: message (failed)
   
 */

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

int OpenServerConnection(serverlist,ip)

struct Item *serverlist;
struct Image *ip;

{ int err;
  struct sockaddr_in cin;

if (strcmp(ip->server,"localhost") == 0)
   {
   AUTHENTICATED = true;
   return true;
   }

AUTHENTICATED = false; 
 
if (SD == cf_not_connected)
   {
   Debug("Opening server connnection to %s\n",ip->server);
   bzero(&cin,sizeof(cin));

   cin.sin_port = PORTNUMBER;
   cin.sin_addr.s_addr = (ip->dns)->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)
      {
      CfLog(cferror,"Couldn't open a socket","socket");
      AUTHENTICATED = false;
      return false;
      }
   
   signal(SIGALRM,(void *)TimeOut);
   alarm(CF_TIMEOUT);
   
   if (err=connect(SD,(void *) &cin,sizeof(cin)) == -1)
      {
      sprintf(OUTPUT,"Couldn't connect to host %s\n",ip->server);
      CfLog(cfinform,OUTPUT,"connect");
      CloseServerConnection();
      AUTHENTICATED = false;
      return false;
      }
   
   alarm(0);
   signal(SIGALRM,SIG_DFL);
   
   Debug("Sending on socket...%d\n",SD);

   if (! IdentifyForVerification(SD,VFQNAME))
      {
      CfLog(cferror,"Server registration procedure failed","");
      errno = EPERM;
      CloseServerConnection();
      AUTHENTICATED = false;
      return false;
      }

   AUTHENTICATED = true;
   return true;
   }
else
   {
   Debug("Server connection to %s already open on %d\n",ip->server,SD);
   }

AUTHENTICATED = true; 
return true; 
}

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

void CloseServerConnection()

{
Debug("Closing current connection\n");
close(SD);
SD = cf_not_connected;
}

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

int 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;

{ char sendbuffer[bufsize];
  char recvbuffer[bufsize];
  char in[bufsize],out[bufsize];
  struct cfstat cfst;
  int i,ret;
  time_t tloc;
  struct hostent *hp;

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

if (strlen(file) > bufsize-30)
   {
   CfLog(cferror,"Filename too long","");
   return -1;
   }
 
ret = GetCachedStatData(file,buf,ip,stattype);

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

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

sendbuffer[0] = '\0';
 
if (ip->secure)
   {
#ifdef HAVE_LIBCRYPTO
   bzero(in,bufsize);
   bzero(out,bufsize);
   bzero(sendbuffer,bufsize);
   sprintf(in,"SYNCH %d STAT %s",tloc,file);
   cfencrypt(in,out,CFDES1,CFDES2,CFDES3,strlen(in));
   sprintf(sendbuffer,"SSYNCH %d",strlen(in));
   bcopy(out,sendbuffer+23,bufsize-24);
#endif   
   }
else
   {
   sprintf(sendbuffer,"SYNCH %d STAT %s",tloc,file);
   }

if (send(SD,sendbuffer,bufsize,0) == -1)
   {
   sprintf(OUTPUT,"Transmission failed/refused talking to %.255s:%.255s in stat",ip->server,file);
   CfLog(cfinform,OUTPUT,"send");
   return -1;
   }

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

if (strstr(recvbuffer,"unsynchronized"))
   {
   CfLog(cferror,"Clocks differ too much to do copy by date (security)","");
   CfLog(cferror,recvbuffer+4,"");
   return -1;
   }

if (strncmp(recvbuffer,"BAD:",4) == 0)
   {
   sprintf(OUTPUT,"Server returned error: %s\n",recvbuffer+4);
   CfLog(cfverbose,OUTPUT,"");
   errno = EPERM;
   return -1;
   }

if (strncmp(recvbuffer,"OK:",3) == 0)
   {
   long d1,d2,d3,d4,d5,d6,d7,d8,d9,d10,d11,d12=0;
   
   sscanf(recvbuffer,"OK: %1ld %5ld %14ld %14ld %14ld %14ld %14ld %14ld %14ld %14ld %14ld %14ld",
	  &d1,&d2,&d3,&d4,&d5,&d6,&d7,&d8,&d9,&d10,&d11,&d12);

   cfst.cf_type = (enum cf_filetype) d1;
   cfst.cf_mode = (mode_t) d2;
   cfst.cf_lmode = (mode_t) d3;
   cfst.cf_uid = (uid_t) d4;
   cfst.cf_gid = (gid_t) d5;
   cfst.cf_size = (off_t) d6;
   cfst.cf_atime = (time_t) d7;
   cfst.cf_mtime = (time_t) d8;
   cfst.cf_ctime = (time_t) d9;
   cfst.cf_makeholes = (char) d10;
   ip->makeholes = (char) d10;
   cfst.cf_ino = d11;
   cfst.cf_nlink = d12;

   /* Use %?d here to avoid memory overflow attacks */

   Debug("Mode = %d,%d\n",d2,d3);
   
   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 ino=%d nlnk=%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,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 = strdup(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 = strdup(file);
   cfst.cf_server =  strdup(ip->server);

   if ((cfst.cf_filename == NULL) ||(cfst.cf_server) == NULL)
      {
      FatalError("Memory allocation in cf_rstat");
      }
   
   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;
      }
   else
      {
      buf->st_mode = cfst.cf_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;
   
   return 0;
   }


sprintf(OUTPUT,"Transmission refused or failed statting %s\nGot: %s\n",file,recvbuffer); 
CfLog(cferror,OUTPUT,"");
errno = EPERM;

return -1;
}

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

CFDIR *cf_ropendir(dirname,ip)

char *dirname;
struct Image *ip;

{ char sendbuffer[bufsize];
  char recvbuffer[bufsize];
  int i,offset,n, done=false;
  CFDIR *cfdirh;
  char *sp;

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

if (strlen(dirname) > bufsize - 20)
   {
   CfLog(cferror,"Directory name too long","");
   return NULL;
   }

if ((cfdirh = (CFDIR *)malloc(sizeof(CFDIR))) == NULL)
   {
   CfLog(cferror,"Couldn't allocate memory in cf_ropendir\n","");
   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)
   {
   return NULL;
   }

while (!done)
   {
   if ((n = RecvSocketStream(SD,recvbuffer,bufsize,0)) == -1)
      {
      if (errno == EINTR) 
         {
         continue;
         }
      return false;
      }

   if (n == 0)
      {
      break;
      }

   if (strncmp(recvbuffer,CFFAILEDSTR,strlen(CFFAILEDSTR)) == 0)
      {
      sprintf(OUTPUT,"Network access to %s:%s denied\n",ip->server,dirname);
      CfLog(cfinform,OUTPUT,"");
      return false;      
      }

   if (strncmp(recvbuffer,"BAD:",4) == 0)
      {
      sprintf(OUTPUT,"%s\n",recvbuffer+4);
      CfLog(cfinform,OUTPUT,"");
      return false;      
      }

   for (sp = recvbuffer; *sp != '\0'; sp++)
      {
      if (strncmp(sp,CFD_TERMINATOR,strlen(CFD_TERMINATOR)) == 0)        /* End transmission */
	 {
	 cfdirh->cf_listpos = cfdirh->cf_list;
	 return cfdirh;
	 }

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

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

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

void FlushClientCache(ip)

struct Image *ip;

{
if (ip->cache)
   {
   free(ip->cache);
   ip->cache = NULL;
   }
}

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

int CompareMD5Net(file1,file2,ip)

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

{ static unsigned char d[16];
  char sendbuffer[bufsize];
  char recvbuffer[bufsize],in[bufsize],out[bufsize];
  int i,offset;

Debug("Send digest of %s to server\n",file2);
cfMDFile(file2,d);   /* send this to the server for comparison */

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

if (20 + 3*16 + strlen(file1) > bufsize-1)
   {
   CfLog(cferror,"MD5 transfer overflow...updating for safety\n","");
   return true;
   }


if (ip->secure)
   {
#ifdef HAVE_LIBCRYPTO
   bzero(in,bufsize);
   bzero(out,bufsize);
   bzero(sendbuffer,bufsize);
   sprintf(in,"MD5 %s\n %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]);
   cfencrypt(in,out,CFDES1,CFDES2,CFDES3,strlen(in));
   sprintf(sendbuffer,"SMD5 %d",strlen(in));
   bcopy(out,sendbuffer+23,bufsize-24);
#endif   
   }
else
   {
   sprintf(sendbuffer,"MD5 %s\n %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)
   {
   CfLog(cferror,"","send");
   return false;
   }

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

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

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

int CopyRegNet(source,new,ip,size)

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

{ int dd, buf_size, i=0;
  char *buf,*cp,*sp,in[bufsize],out[bufsize],sendbuffer[bufsize];
  unsigned char key[8];
  int n_read, *intp,err,toget,towrite,len;
  long n_read_total = 0;
  int last_write_made_hole = 0, done = false;

Debug("CopyRegNet(%s,%s,%d) to socket %d\n",source,new,size,SD);

if ((strlen(new) > bufsize-20))
   {
   CfLog(cferror,"Filename too long","");
   return false;
   }
 
unlink(new);  /* To avoid link attacks */ 
  
if ((dd = open(new,O_WRONLY|O_CREAT|O_TRUNC|O_EXCL|O_BINARY, 0600)) == -1)
   {
   sprintf(OUTPUT,"Copy %s:%s security - failed attempt to exploit a race? (Not copied)\n",ip->server,new);
   CfLog(cferror,OUTPUT,"open");
   unlink(new);
   return false;
   }

sendbuffer[0] = '\0';
 
if (ip->secure)
   {
#ifdef HAVE_LIBCRYPTO
   des_random_key(&key);
   bzero(in,bufsize);
   bzero(out,bufsize);
   bzero(sendbuffer,bufsize);
   len = cfprintf(in,8,"GET",key,source);
   cfencrypt(in,out,CFDES1,CFDES2,CFDES3,len);
   sprintf(sendbuffer,"SGET %4d %4d",len,ST_BLKSIZE(dstat));
   bcopy(out,sendbuffer+31,bufsize-32);
#endif
   }
else
   {
   sprintf(sendbuffer,"GET %d %s",ST_BLKSIZE(dstat),source);
   }

if (send(SD,sendbuffer,bufsize,0) == -1)
   {
   CfLog(cferror,"Couldn't send","send");
   close(dd);
   return false;
   }

buf_size = ST_BLKSIZE(dstat);
buf = (char *) malloc(bufsize + sizeof(int)); /* Note bufsize not buf_size */

n_read_total = 0;

while (!done)
   {
   if ((size - n_read_total)/buf_size > 0)
      {
      toget = towrite = buf_size;
      }
   else if (size != 0)
      {
      towrite = (size - n_read_total);
      toget = buf_size; /* padding required for encryption chaining */
      }
   else
      {
      toget = towrite = 0;
      }

   if ((n_read = RecvSocketStream(SD,buf,toget,0)) == -1)
      {
      if (errno == EINTR) 
         {
         continue;
         }

      CfLog(cferror,"Error in socket stream","recv");
      close(dd);
      free(buf);
      return false;
      }

   if (strncmp(buf,CFFAILEDSTR,strlen(CFFAILEDSTR)) == 0)
      {
      sprintf(OUTPUT,"Network access to %s:%s denied\n",ip->server,source);
      RecvSocketStream(SD,buf,bufsize-toget,0); /* flush rest of transaction */
      CfLog(cfinform,OUTPUT,"");
      close(dd);
      free(buf);
      return false;      
      }

   if (strncmp(buf,"BAD:File",8) == 0) /* error */
      {
      if (strncmp(buf,"BAD:File %s",11) != 0) /* Code copies itself! */
	 {
	 CfLog(cfinform,buf+4,"");
	 close(dd);
	 free(buf);
	 CfLog(cferror,buf,"");
	 return false;
	 }
      }

   if (ip->secure)
      {
      bzero(sendbuffer,bufsize);
      cfdecrypt(buf,sendbuffer,CFDES1,key,CFDES3,buf_size);
      bcopy(sendbuffer,buf,buf_size);
      }

   if (n_read == 0)
      {
      break;
      }

   n_read_total += towrite; /*n_read;*/

   if (n_read_total >= (long)size)  /* Handle EOF without closing socket */
      {                                 /* signature is 8 bytes */
      done = true;
      }

   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)
            {
            sprintf (OUTPUT,"lseek in CopyReg, dest=%s\n", new);
            CfLog(cferror,OUTPUT,"lseek");
            free(buf);
	    unlink(new);
	    close(dd);
	    FlushToEnd(SD,size - n_read_total);
            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,towrite) < 0)
         {
	 sprintf(OUTPUT,"CopyNetReg(%.256s:%.256s,%.256s) failed\n",ip->server,source,new);
         CfLog(cferror,OUTPUT,"write");
	 FlushToEnd(SD,size - n_read_total);
         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)
       {
       CfLog(cferror,"cfengine: full_write or ftruncate error in CopyReg\n","");
       free(buf);
       unlink(new);
       close(dd);
       FlushToEnd(SD,size - n_read_total);
       return false;
       }
    }

Debug("End of file\n");
close(dd);
free(buf);
return true;
}

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

int 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;
	 Debug("Cached failure to stat\n");
	 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;      

      Debug("Found in cache\n");
      return true;
      }
   }

Debug("Did not find in cache\n"); 
return false;
}

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

void CacheData(data,ip)

struct cfstat *data;
struct Image *ip;

{ struct cfstat *sp;

if ((sp = (struct cfstat *) malloc(sizeof(struct cfstat))) == NULL)
   {
   CfLog(cferror,"Memory allocation faliure in CacheData()\n","");
   return;
   }

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

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

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

void FlushToEnd(sd,toget)

int sd,toget;

{ int i;
  char buffer[2]; 

sprintf(OUTPUT,"Flushing rest of file...%d bytes\n",toget);
CfLog(cfinform,OUTPUT,""); 
 
for (i = 0; i < toget; i++)
   {
   recv(sd,buffer,1,0);  /* flush to end of current file */
   }
}


/*********************************************************************/
/* ERS                                                               */
/*********************************************************************/

int cfprintf(out,len2,in1,in2,in3)

char *out,*in1,*in2,*in3;
int len2;

{
int len1, len3;
char *sp;

len1=strlen(in1);
len3=strlen(in3);
   
sp = out;
bcopy(in1,sp,len1);
sp += len1;
   
*sp++ = ' ';
bcopy(in2,sp,len2);
sp += len2;
   
*sp++ = ' ';
bcopy(in3,sp,len3);
sp += len3;
*sp = '\0';
   
return (len1 + len2 + len3 + 2);
}  

