/*
**  server.c
**
**  Written by Dale Larson, Software Engineer, Commodore-Amiga, Inc.
**  Copyright 1991, Commodore-Amiga, Inc.
**  Permission to use granted provided this notice remains intact.
**
**  This is a simple version of the ncopy server.  It processes only one
**  request at a time.  This is adequate for a server which sees light or
**  infrequent usage, but can lead to unacceptable delays for a server which
**  sees heavy or even moderate trafic.  It also will cause problems if the
**  user copies a file from a remote machine to the same remote machine
**  (i.e.  ncopy sprite!ram:x sprite!work:y).
**
**  You should study and understand this server before you look at
**  server2.c, which is significantly more complex.
**
**  This server has no provisions for exiting.
**  A system requester will pop up if the server recieves a request to
**  read/write to/from a device a non-existant device.
*/


#include "ncopy.h"

struct Library *SockBase;
#define MAXSOCKS 10

char vers[] = "\0$VER: server1 1.0 (25.02.91)";

void handle_request(int s);
void send_file(int s, BPTR file);
void get_file(int s, BPTR file);


/*
**  main()
**
**  Get a socket, bind a name to it, express a willingness to accept
**  connections to it (listen) then wait to actually accept connections.
**  Once a connection has been accepted, give the handler function the
**  socket which accept() connected to a remote machine so that the handler
**  function can chat with the remote machine.  When the handler returns,
**  go back to waiting to accept connections.
*/
main(int argc, char **argv)
{
struct sockaddr_in sin;	/*  socket address (internet) to establish  */
int s, ns;		/*  socket and new socket  */
size_t len = sizeof(sin);

	/*  Open Shared Socket library:
	*/
        if(SockBase = OpenLibrary( "inet:libs/socket.library", 0L ))
        {
                setup_sockets( MAXSOCKS, &errno );
	}else
	{
		puts("Can't open socket.library.");
		exit(RETURN_ERROR);
	}

	/*
	**  Initialize a socket.
	*/
	if( (s = socket(AF_INET, SOCK_STREAM, 0)) < 0)
	{
		perror("socket");
		exit(RETURN_ERROR);
	}

	/*
	**  Bind name to socket.
	**  Since we got sin off the stack, it could contain anything, and so
	**  we zero it before initializing it.
	*/
	bzero(&sin, (int)len);
	sin.sin_family = AF_INET;
	/*
	**  While prototyping a program, pick an unused port number above
	**  1024 like I did here.  For a real program, assign a name and a
	**  port number above 1024 and stick it in inet:db/servers on all
	**  machines which will use the application.  That way the user can
	**  reconfigure port numbers used by new applications if necessary.
	**  You can modify the server to get the port number from the
	**  database by using code like this (instead of "sin.sin_port=
	**  htons(6666)" below):
	**
	**  struct servent *servptr;
	**  char serv[] = "servicename";
	**
	**  if( (servptr = getservbyname(serv, "tcp")) == NULL)
	**  {
	**  	printf("Unknown service %s.  Check inet:db/services.\n",serv);
	**	return RETURN_ERROR;
	**  }
	**  sin.sin_port = servptr->s_port;
	*/
	sin.sin_port = htons(6666);
	sin.sin_addr.s_addr = INADDR_ANY;
	if(bind(s, (struct sockaddr *)&sin, len) < 0)
	{
		perror("bind");
		exit(RETURN_ERROR);
	}

	/*
	**  Put socket into "listen" state (ready to accept connections).
	*/
	listen(s,5);

	/*
	**  Accept connections from s and process them on ns.
	*/
	while(1)
	{
		if( (ns = accept(s,NULL,NULL)) >= 0)
			handle_request(ns);
	}
}


/*
**  handle_request()
**
**  Handles a request to send or receive a file over a given socket.
**  Handling a request consists of the following:
**  	1)  receive the mode (send or receive -- MODE_OLDFILE or MODE_NEWFILE)
**	2)  receive the filename
**	3)  attempt to open the file
**	4)  return "\0" for succesful open,
**	    specially formated informative error message otherwise
**	5)  call function to send or receive the file
**	6)  close the file we opened and the socket we were passed
*/
void handle_request(int s)
{
char buffer[LENGTH];
int mode;	/*  AmigaDOS file open mode (MODE_OLDFILE or MODE_NEWFILE)  */
BPTR file;	/*  BCPL pointer to an AmigaDOS file handle  */


	/*  Get the mode.
	**  Don't worry about making sure that we got what we wanted because we
	**  can't really screw anything up and we can't send an error message
	**  if our communcications are error-prone.
	*/
	recv(s, (char *)&mode, (int)sizeof(int), 0);

	/*  Get the filename which we'll read or write (depending on mode).
	**  (Assume that we get only null-terminated strings.)
	*/
	recv(s, buffer, LENGTH, 0);

	/*
	**  Attempt to open file.
	*/
	if( !(file = Open(buffer, mode)) )
	{
	int len;

		len = strlen(buffer);
		strncat(buffer, "': ", LENGTH-4);  /*  4 not 3 'cause of nul  */
		Fault(IoErr(), NULL, buffer+len+3, LENGTH-len-4);
		send(s, buffer, strlen(buffer)+1, 0);
		close(s);
		return;
	}

	/*
	**  Indicate succesful opening of file.
	*/
	send(s, "\0", 1, 0);

	/*
	**  Send or recieve the file.
	*/
	if(mode == MODE_OLDFILE)
		send_file(s, file);
	else
		get_file(s, file);

	Close(file);
	close(s);
}


void send_file(int s, BPTR file)
{
char	buffer[LENGTH];
long	actualLength;

	do
	{
		actualLength = Read(file, (void *)buffer, LENGTH);
	        if(actualLength < 0)
			return;  /*  Error, but we have no way to report it.  */
		if(send(s, buffer, actualLength, 0) < 0)
			return;  /*  Error, but we have no way to report it.  */
	}while(actualLength);
}


void get_file(int s, BPTR file)
{
char	buffer[LENGTH];
int 	n;

	do
	{
		n = recv(s, buffer, LENGTH, 0);
		if(n < 0)
			return;  /*  Error, but we have no way to report it.  */
		if(Write(file, (void *)buffer, n) < 0)
			return;  /*  Error, but we have no way to report it.  */
	}while(n);
}
