/*  pscope (periscope) allows a macintosh connected to the network to
    "see" into a unix directory and retrieve files from it         */

#include    <sys/types.h>
#include    <sys/socket.h>
#include    <sys/time.h>
#include    <netinet/in.h>
#include    <netdb.h>
#include    <stdio.h>
#include <errno.h>
#include <sys/stat.h>
#include <pwd.h>

#define TRUE 1
#define SEND 0
#define RECEIVE 1
#define MORE 0
#define DONE 1
#define kTCPBlockSize 256
#define HANDSHAKE 2
#define PROTOCOL_ERR 2
#define TERMINATING 3
#define BAD_NAME 4

int inDescriptor;
struct stat inStat;
int inSize;
FILE *in = stdin,*out = stdout;
unsigned char *outbuf = NULL;        /* file buffer -> mac */

void handshake(buf,bytes,sock)
char *buf;
int bytes;
int sock;
{
  char *username;
  struct passwd *userinfo;

  unsigned char controlByte = buf[0];

  outbuf = (unsigned char *)malloc(1);

  if(controlByte != HANDSHAKE){
    /* protocol error */
    outbuf[0] = PROTOCOL_ERR;
#ifdef DEBUG
    fprintf(stderr,"Bad Handshake\n");
#endif
    if(write(sock,outbuf,1) < 0)
	perror("ERROR writing to socket");
    outbuf[0] = TERMINATING;
    if(write(sock,outbuf,1) < 0)
	perror("ERROR writing to socket");
    close(sock);
    exit(0);
  }
  
  /* rest of message is userid */
  username = (char *)malloc(bytes);
  memcpy(username,&buf[1],bytes-1);
  username[bytes-1] = '\0';
  userinfo = getpwuid(getuid());
  if(strcmp(username,userinfo->pw_name)){
    /* account names don't match */
    outbuf[0] = BAD_NAME;
#ifdef DEBUG
    fprintf(stderr,"Bad Account\n");
#endif
    if(write(sock,outbuf,1) < 0)
      perror("ERROR writing to socket");
    outbuf[0] = TERMINATING;
    if(write(sock,outbuf,1) < 0)
      perror("ERROR writing to socket");
    close(sock);
    free(userinfo);
    exit(0);
  }
  
  /* Don't worry, be happy! */
  free(userinfo);
  outbuf[0] = 0;
  if(write(sock,outbuf,1) < 0)
    perror("ERROR writing to socket");
  return;
}

void handleMessage(buf,size,msgsock)
char *buf;
int size;
int msgsock;
{
  unsigned char controlByte;
  int nBytes;
  char *data;
  char *filename;
  int bytesSent = 0;
  int bytesToGo;
  int sendBytes;
  controlByte = buf[0];

  switch(controlByte){
  case SEND:
    /* client wants server to send data, resulting from last request */

    /* allocate enough buffer space to transmit at least an error */
    outbuf = (unsigned char *)malloc(1);

    memcpy(&nBytes,&(buf[1]),sizeof(int)); /* no of bytes in file name */
    filename = (char *)malloc(nBytes+1);
    memcpy(filename,&(buf[5]),nBytes);    /* got the filename assigned */
    filename[nBytes] = '\0';              /* termninate fname string */

    /* set error flag */
    outbuf[0] = 1;
    
    if((inDescriptor = open(filename,0)) == -1){
#ifdef DEBUG
      fprintf(stderr,"failed to open %s\n",filename);
#endif
      if(write(msgsock,outbuf,1) < 0)
	perror("ERROR writing to socket");
      break;
    }
    free(filename);
    /* get input file info */

    fstat(inDescriptor,&inStat);
    inSize = inStat.st_size;

    /* dynamically allocate enough buffer space here */
    outbuf = (unsigned char *)realloc(outbuf,inSize+4+1);
    /* write number of bytes in data to buffer */
    memcpy(&outbuf[1],&inSize,sizeof(inSize));

    nBytes = read(inDescriptor,&(outbuf[5]),inSize);

#ifdef DEBUG
    fprintf(stderr,"read %d bytes\n",nBytes);
#endif

    if(nBytes < 0){
#ifdef DEBUG
      fprintf(stderr,"read error: %d\n",errno);
#endif      
      if(write(msgsock,outbuf,1) < 0)
	perror("ERROR writing to socket");
      break;
    }
    else if (nBytes == 0){
#ifdef DEBUG
      fprintf(stderr,"No data read\n");
#endif
      if(write(msgsock,outbuf,1) < 0)
	perror("ERROR writing to socket");
      break;
    }

    outbuf[0] = 0;                /* data is good */
    /* send control byte + size int */
    if(write(msgsock,outbuf,5) < 0)
      perror("ERROR writing to socket");
    if(write(msgsock,&(outbuf[5]),inSize) < 0)
      perror("ERROR writing to socket");

    break;

  case RECEIVE:
    /* client wants to send data to the server */
    memcpy(&nBytes,&(buf[1]),sizeof(int));
    fprintf(stderr,"RECEIVE\n");
    fflush(stderr);
    ntohs(nBytes);
    fprintf(stderr,"nBytes = %d\n",nBytes);
    fprintf(stderr,"%d\n%d\n%d\n%d\n",buf[1],buf[2],buf[3],buf[4]);
    data = (char *)malloc((nBytes + 1) * sizeof(char));
    memcpy(data,&(buf[1+sizeof(int)]),nBytes);
    data[nBytes] = '\0';
    fprintf(out,"-->%s\n",data);
    free(data);
    break;

  default:
    break;
  }
}
    
main(argc,argv)
int argc;
char **argv;
{

  int port = 32200;
  int sock , range = 10, length;
  struct sockaddr_in server;
  int msgsock;
  char buf[1024];
  int rval;
  fd_set ready;
  struct timeval to;
  char *filename;
  extern char *optarg;
  int nonOptArgs;
  char c;
  int p_flag = 0;
  int r_flag = 0;
  int done = 0;
  int result;

  /* process command line args */
  while((c = getopt(argc,argv,"p:r:")) != -1)
    switch(c){
    case 'p':
      p_flag = 1;
      port = atoi(optarg);
      break;
    case 'r':
      r_flag = 1;
      range = atoi(optarg);
      break;
    default:
#ifdef DEBUG
      fprintf("improper option ignored\n");
#endif
      ;
    }

  nonOptArgs = argc - 1 - p_flag - r_flag;

  /* Create Socket */

  sock = socket(AF_INET, SOCK_STREAM, 0);
  if (sock < 0)
    {
      perror("ERROR opening stream socket");
      exit(1);
    }
  
  /* Name socket using wilddcards */
  
  server.sin_family = AF_INET;
  server.sin_addr.s_addr = INADDR_ANY;
  server.sin_port = port;
  /* search until unused port# is found or range exhausted */
  while(!done && server.sin_port < (port + range)){
    result = bind(sock, (struct sockaddr *)&server,
	   sizeof server);
    if(result < 0)
      server.sin_port++;
    else
      done = 1;
  }
  if(!done){
    fprintf(stderr,"Unable to bind socket to port, exitting...\n");
    exit(1);
  }
#ifdef DEBUG
  fprintf(stderr,"Socket prot #%d\n", ntohs(server.sin_port));
#endif
  
  /* Start accepting connections */
  
  listen(sock, 5);
  do
    {
      FD_ZERO(&ready);
      FD_SET(sock, &ready);
      to.tv_sec = 5;
      if (select(sock + 1, &ready, (fd_set *)0,
		 (fd_set *)0, &to) < 0)
	{
	  perror("ERROR select");
	  continue;
	}
      if (FD_ISSET(sock, &ready))
	{
	  msgsock = accept(sock, (struct sockaddr *)0,
			   (int *)0);
	  if (msgsock == -1) perror("ERROR accept");
	  else {
	    if(fork() == 0){ /* child */
	      close(sock);

	      /* do the handshake */
	      bzero(buf,sizeof buf);
	      if ((rval = read(msgsock, buf, 1024)) < 0) {
		perror("ERROR reading stream message");
		close(msgsock);
		exit(0);    /* child dies */
	      }	      
	      if(rval == 0){
		close(msgsock);
		exit(0);
	      }
	      handshake(buf,rval,msgsock);

	      do
		{
		  bzero(buf, sizeof buf);
		  /* block until data is available */
		  if ((rval = read(msgsock, buf, 1024)) < 0) {
		    perror("ERROR reading stream message");
		    close(msgsock);
		    exit(0);    /* child dies */
		  }
		  else if (rval == 0) 
		    printf("No more data\n");
		  else
		    handleMessage(buf,1024,msgsock);
		}
	      while(rval > 0);
	    }
	    close(msgsock); /* parent */
	  }
	}
    }
  while(TRUE);
  exit(0);
}