#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <errno.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <dirent.h>
#include <string.h>
#include <libgen.h>
#include <pwd.h>


/***************ReaglaFicheros.c**************************/
/*Copia ficheros y/o directorios de una cuenta a  a otra*/
/*usando ambas  credenciales*/
/*el ejecutable debe ser setud del que deja copiar de su cuenta*/



#define MAXNAME        1024                    /*longitud maxima de un nombre*/
#define BASEDIR        "/"                     /*por si queremos limitar la parte del arbol de directorios a la que acceder*/




int EsDirectorio (char * dir)
{
  struct stat s;
  if (lstat(dir,&s)==-1)
     return 0;
  return (S_ISDIR(s.st_mode));
}

int EsFichero (char * dir)
{
  struct stat s;
  if (lstat(dir,&s)==-1)
     return 0;
  return (S_ISREG(s.st_mode));
}

ssize_t TamanoFichero(char * f)
{
   struct stat s;
   if (lstat(f,&s)==-1)
      return -1;
   return (ssize_t) s.st_size;
}

ssize_t CopyFile (char * source, char *dest, pid_t euid, pid_t ruid, int verb)
{
  void * buff;
  ssize_t siz,n1,n2;
  int err, df1, df2;

  if (verb)
     printf ("    ------------------Copiando fichero %s -> %s\n", source,dest);
  
  /*se supone que se llama con credencial efectiva la del origen, euid*/
  if ((siz=TamanoFichero(source))==-1 || (df1=open(source, O_RDONLY))==-1)
     return -1;
  
  setuid (ruid);     /*cambiamos a la real, destino*/
  if ((df2=open(dest,O_WRONLY |O_CREAT |O_EXCL,0777))==-1){
     err=errno; close (df1); errno=err;
     return -1;
  }
  if ((buff=malloc (siz))==NULL){    /*una vez abierto, la credencial no influye en la lectura o escritura*/
     err=errno; close(df1); close(df2); errno=err;
     return -1;
  }
  if ((n1=read(df1,buff,siz))==-1 || (n2=write (df2,buff,n1))==-1){
     err=errno; close (df1);close(df2); free(buff); errno=err;
     return -1;
  }
  close (df1);close(df2); free(buff);
  setuid (euid);               /*volvemos a la credencial efectiva origen*/
  return n2;
}

char * CheckValidAccess (char * dir)         /*comprueba que  no hay .. en el path*/
{
   static char aux[MAXNAME];
   char *tr[MAXNAME/2];
   int i=1;

   if (dir==NULL)
      return NULL;
   strncpy (aux,dir, MAXNAME);
   if ((tr[0]=strtok(aux,"/"))==NULL)
      return 0;
   while ((tr[i]=strtok(NULL,"/"))!=NULL)
      i++;
   for (i=0; tr[i]!=NULL; i++)
      if (!strcmp(tr[i],".."))    /*algun componente es ".." */
         return NULL;
   if (!strcmp(BASEDIR,"/"))
      strncpy (aux,dir,MAXNAME); 
   else
      snprintf (aux,MAXNAME,"%s/%s",BASEDIR,dir);
   return aux;
}


int CopyDirectory (char * source, char *dest, pid_t euid, pid_t ruid,int rec, int verb)
{
    DIR *p;
    struct dirent *d;
    int res,err;
    char aux[MAXNAME], aux2[MAXNAME];

    if (verb)
        printf ("****************Copiando directorio %s -> %s\n", source,dest);

    if ((p=opendir(source))==NULL)    /*se supone que se llama con credencial efectiva la del origen, euid*/
       return -1;
  
    if (setuid (ruid)==-1 || mkdir (dest,0777)){ /*cambiamos a la real, destino*/
       err=errno; closedir (p); errno=err;
       return -1;
    }
    setuid (euid);   /*volvemos a la credencial efectiva origen*/
    while ((d=readdir(p))!=NULL){
       if (!strcmp(d->d_name,".") || !strcmp(d->d_name,".."))
          continue;
        
       snprintf (aux,MAXNAME,"%s/%s",source,d->d_name);snprintf(aux2,MAXNAME,"%s/%s",dest,d->d_name);

       if (EsFichero (aux))       /*los enlaces simbolicos no se siguen*/
          res=CopyFile(aux,aux2,euid,ruid,verb);
       if (rec && EsDirectorio(aux))     /*solo se copia los ficheors y los directorios si es recursivo*/
          res=CopyDirectory(aux,aux2,euid,ruid,rec,verb);  /*no los dispositivos ni los fifos...*/
       if (res==-1)
          printf("Error copiando %s->%s:%s\n", aux,aux2,strerror(errno));
    }
    setuid (euid);                            /*volvemos a la credencial efectiva origen*/
    return closedir(p);
}


  


void Usage(char *ejec){
    printf ("uso: %s [-r] [-v] origen destino\n",ejec);
    printf ("     copia origen en destino, -r para recursivo, -v para verboso \n");
    printf ("     si no se especifica destino, copia en directorio actual\n");
    printf ("     destino especificado a partir de (y limitado por)  %s\n",BASEDIR);
    printf ("     directorio destino no debe existir previamente\n");
}


void CheckCredentials (pid_t euid, pid_t ruid)
{  
  if (euid==ruid){
     printf ("Para copiar con la misma credenciar real y efectiva usar el comando cp\n");
     exit (1);
  }
  if (euid==0 || ruid==0){
     printf ("Este programa no funciona con el root\n");
     exit (1);
  }
}

char * Nombre(uid_t u)
{
  struct passwd *p=getpwuid(u);
  
  if (p!=NULL)
     return p->pw_name;
  return "???????";
}




int main (int argc, char *argv[])
{
  int i,pos=1,res=-1,recurse=0,verbose=0;
  char *source=NULL, dest[MAXNAME];    
  uid_t ruid=getuid(), euid=geteuid();

  for (i=1; argv[i]!=NULL; i++)
     if (!strcmp(argv[i],"-r")) {recurse=1; pos++;}     /*copia recursiva */
     else if (!strcmp(argv[i],"-v")) {verbose=1;pos++;}   /*imprime info*/
     else break;      /*ni -r ni -v, i contienen el directorio origen */ 

  if ((source=CheckValidAccess(argv[pos]))==NULL){      /*no se especifica que copiar o no es válido*/
     Usage(argv[0]);
     exit (1);
  }
  CheckCredentials (euid, ruid);


  if (argv[pos+1]!=NULL)
     strcpy(dest,argv[pos+1]);
  else 
        snprintf(dest,MAXNAME,"%s/%s",".",basename(source));

  if (verbose){
     printf ("Copiando %s -> %s\n", source,dest);
     printf ("  con credencial efectiva %d (%s) ", euid,Nombre(euid));
     printf (" real %d (%s)\n",ruid, Nombre(ruid));
  }
  if (EsDirectorio(source))     
     res=CopyDirectory(source,dest,euid,ruid,recurse,verbose);  /*no los dispositivos ni los fifos...*/
  if (EsFichero (source))       /*los enlaces simbolicos no se siguen*/
     res=CopyFile(source,dest,euid,ruid,verbose);
  if (res==-1)
     printf("Error al intentar copiar %s -> %s: %s\n", source,dest,strerror(errno));
  return 0;
}
















