/* Derived from an 
 * Example of server using TCP protocol
 * pp 284-5 Stevens UNIX Network Programming
 */

#include "mindexd.h"

char Zehostname[128];
    
struct one_slave {
   int  port;
   char *host[128];
   int  sockfd;
};
struct one_slave slaves[MAXSLAVES];

int LastSlave;


/*  read the config file and fill the slaves struct, returns
    the number of slave servers */
int
getconfig( the_filename )
  char *the_filename;
{
    char *cp;
    int  n;
    char *local;
    int i;
    FILE *fp;
    char    line[256];      /* line buffer for reading config */
    char    *linep;         /* pointer to 'line' */


    local = the_filename;

    if ((fp = fopen(local, "r")) == NULL) {
            exit(1);
    }

    LastSlave = -1;
    for (;;) {
        if (fgets(line, sizeof line, fp) == NULL)
               break;  /* done */

        if ((i = strlen(line)))
               line[i-1] = 0;  /* remove trailing newline */
        if (line[0] == '#' || line[0] == 0)
               continue;       /* skip comment lines */
        else {
               LastSlave++;
               linep = line;
               /* the port number is seperated from the host name by a space */

               slaves[LastSlave].port = atoi( index(linep, ' ') );
               cp = index(linep, ' ');
               cp[0] = 0; /* trim off stuff after the hostname */
               cp = slaves[LastSlave].host;
               strcpy(cp, linep);
        }
    }
    close(fp);

    return( LastSlave );
}




void
main(argc, argv)
int 	argc;
char 	*argv[];
{
     int sockfd;
     int newsockfd;
     int clilen;
     int childpid;
     int i;
     char *config_filename;

     struct sockaddr_in cli_addr;
     struct sockaddr_in serv_addr;

     pname = argv[0];
     
     if (DEBUG == FALSE)
	  daemon_start(0);

     /** Open a TCP socket (an internet stream socket **/
     
     if ( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
	  err_dump("server: can't open stream socket");
     
     /*
      * Bind our local address so that the client can send to us
      */
     
     bzero((char *) &serv_addr, sizeof(serv_addr));
     serv_addr.sin_family 		= AF_INET;
     serv_addr.sin_addr.s_addr 	= htonl(INADDR_ANY);
     serv_addr.sin_port		= htons(GOPHER_INDEX_PORT);
    

     if (argc < 1){
             syntax(argv[0]);
     }
     for (i=1;i<argc-1;i++){
             if (argv[i][0] == '-'){
                     if (strncmp(argv[i],"-p",2) == 0){
                             serv_addr.sin_port = htons( atoi(argv[++i]) );
                     } else {
                         if (strncmp(argv[i],"-c",2) == 0){
                             config_filename = argv[++i];
                         } else {
                             fprintf(stderr,"Unknown option %s.\n",argv[i]);
                             syntax(argv[0]);
                         }
                     }
             }
     }


     if ((LastSlave = getconfig(config_filename)) == -1) 
         err_dump("can't read config file" );
 
 
     if (bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) <0)
	  err_dump("server: can't bind local address");
     
     listen(sockfd, 5);
     
     for ( ; ; ) {
	  /*
	   * Wait for a connection from a client process.
	   * This is an example of a concurrent server.
	   */
	  
	  clilen = sizeof(cli_addr);
	  newsockfd = accept(sockfd, (struct sockaddr *) &cli_addr,
			     &clilen);
	  
	  if (newsockfd < 0)
	       err_dump("server: accept error");
	  
	  if ( (childpid = fork()) < 0)
	       err_dump("server: fork error");
	  
	  else if (childpid == 0) {	/* Child process */
	       close(sockfd);		/* close original socket */

	       if (gethostname(Zehostname, 128) !=0 )
		    strcpy(Zehostname, "<<no-name>>");


               /* process the request */
	       while(do_command(newsockfd)!=0);
	       exit(0);
	  }
	  /** clean up any zombie children **/
	  sig_child();

	  close(newsockfd); 		/* parent process */
     }
}

syntax(progname){
    fprintf(stderr,"syntax: %s -p port -c config_file\n",progname);
    exit(1);
}






/*
 * Connect_To_Host Performs a connecting to socket 'service' on host
 * 'host'.  Host can be a hostname or ip-address.  If 'host' is null, the
 * local host is assumed.   The parameter full_hostname will, on return,
 * contain the expanded hostname (if possible).  Note that full_hostname is a
 * pointer to a char *, and is allocated by connect_to_gopher()
 *
 * Errors:
 *
 * -1 get service failed
 *
 * -2 get host failed
 *
 * -3 socket call failed
 *
 * -4 connect call failed
 */

int connect_to_Host(port, host)
  int port;
  char *host;
{
     int s;
     char buf[MAXSTR];
     struct sockaddr_in server;

     struct hostent *hp;

/*
     if (host == '\0') {
	  gethostname(buf, sizeof(buf));
	  host = buf;
     }
*/

     if ((server.sin_addr.s_addr = inet_addr(host)) == -1) {
	  if ((hp = gethostbyname(host)) == NULL ) {
               return(-2);
	  } else
               bzero((char *) &server, sizeof(server));
               bcopy(hp->h_addr, (char *) &server.sin_addr, hp->h_length);
               server.sin_family = hp->h_addrtype;
     } else
	  server.sin_family = AF_INET;

     server.sin_port = (unsigned short) htons(port);

     if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0)
	  return (-3);

     setsockopt(s, SOL_SOCKET, ~SO_LINGER, 0, 0);
     setsockopt(s, SOL_SOCKET, SO_REUSEADDR, 0, 0);
     setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, 0, 0);
     if (connect( s, &server, sizeof(server)) < 0) {
	  close(s);
	  return (-4);
     }
     return s;
}



/* take the query the client passed us, and send it on to the slave index
   servers, gather up their responses and return them to our client 

  still need to add code to handle the case where a slave dies on us
*/
void
HandleQuery(sockfd, queryline )
  int  sockfd;
  char *queryline;
{
     char answerline[512];
     int  length_answer;
     int  i;
     int  childpid;


     /* send queries to all the slaves and gather up responces*/
     for (i = 0; i < ( LastSlave + 1); i++ ) {

        /* fork child processes to handle each of the slaves */
        if ( (childpid = fork()) < 0)
            err_dump("server: fork error");

        else if (childpid == 0) {     /* Child process */
            slaves[i].sockfd = connect_to_Host( slaves[i].port, slaves[i].host);
            writestring(slaves[i].sockfd, queryline );
            writestring(slaves[i].sockfd, "\r\n");
            if (sockfd > 0 )
              for(;;) {
                   length_answer = readline(slaves[i].sockfd, answerline, 512);
                   if (length_answer > 0) {
                        if (answerline[0] == '.') {
                           exit(0);
                        }
                        else {
                           writestring(sockfd, answerline);
                        }
                   }
              }
        }

     }

     /* make sure all the children are done */
     while (wait((int * ) 0) != -1) 
            ;

     /* all done now, tell the client we are finished */
     writestring(sockfd, ".\r\n");

}




int
do_command(sockfd)
int sockfd;
{
     char inputline[MAXLINE];
     int length;		/* Length of the command line */
     char out_line[MAXLINE];    /* for outgoing messages */
     FILE *foofile;

     length = readline(sockfd, inputline, MAXLINE); /** Get the line **/

     ZapCRLF(inputline);

     if (length <= 0)
	  err_dump("getcommand: readline error");
     
     /** Get the command from the input line **/
     
     if (inputline[0] == '\0'){
	  /* Wants information about what we got... 
             but we are an Index server, so complain */

          writestring(sockfd, "Leaping Lizards for the moons of Saturn!!!\r\n");
          writestring(sockfd, "I am a middleman gopher index server...\r\n");
          writestring(sockfd, "Don't send me blank lines!\r\n");
	  return(0);
     }

     /* Okay, now we have the argument, let's process it. */
     
     HandleQuery(sockfd, inputline );
     close(sockfd); 
     return(0);
}

