/*
 *  Nobanner is a dummy web server used to remove banners.
 *  Copyright (C) 2002, David Bélanger
 *
 *  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 of the License, 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
 *
 */

/*
 * nobanner.c
 *
 * Concurrent server.
 *
 *
 * Adapted from net_server.c, CS-435, Assignment 4
 *
 * $Id: nobanner.c,v 1.1.1.1 2003/03/22 17:34:32 dbelan2 Exp $
 *
 * $Release: 0.2.0 $
 *
 * $BuildInfo: 0.2.0 (build from: 0.4) $
 *
 */

#include <stdio.h>         /* for printf() */
#include <string.h>        /* for memset() */
#include <stdlib.h>        /* for exit()   */
#include <unistd.h>        /* for close()  */
#include <time.h>          /* for time()   */

/* for network */
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include <errno.h>         /* for errno */
#include <signal.h>        /* for signal handling */
#include <sys/wait.h>      /* for wait() */


/* For some reason, socklen_t does not get defined with Mac OS X */
#ifdef MACOSX
typedef unsigned int socklen_t;
#endif


#define DEFAULT_PORT 61111 /* default server port */

#define MAX_FILE_NAME 256

#define MAX_LINE 10000       /* send/recv buffer size   */
#define MAX_QUEUE 5        /* max. connection queued. */

/* functions prototypes */
void printusage(void);
void startserver();
void process_request(int acsd);
void signal_handler(int signal);


/* global, server port and dispatcher info */
/* Need to be global because required in signal handler */
int port;


int main(int argc, char *argv[]) {
  char *end;             /* used when parsing port      */

  /* default values */
  port = DEFAULT_PORT;

  /* scan arguments and (possibly) modify default settings. */
  if (argc > 2) {
    fprintf(stderr, "Incorrect number of arguments!\n");
    printusage();
    exit(1);
  } else if (argc == 2) {
      
    if (strcmp(argv[1], "-v") == 0) {
      /* $Format: "      printf(\"nobanner/$Release$\\n\");"$ */
      printf("nobanner/0.2.0\n");
      exit(0);
    } else {
      port = strtol(argv[1], &end, 0);
      if (*end != '\0') {
	fprintf(stderr, "Could not convert \"%s\" to an integer!\n", argv[1]);
	printusage();
	exit(1);
      }
    }
  }

#ifdef DEBUG
  fprintf(stderr, "Starting server...\n");
  fprintf(stderr, "Configuration: \n");
  fprintf(stderr, "  Will listen on port %d\n", port);
#endif

  startserver();
}


/* Prints command line args. */
void printusage(void) {
  fprintf(stderr, "Usage: nobanner [-v | port]\n");
  fprintf(stderr, "         port     server will listen on port specified.\n");
  fprintf(stderr, "                  (default: %d)\n", DEFAULT_PORT);
  fprintf(stderr, "         -v       prints the version info\n");
}


/*
 * Start server.  Do any processing that need to be done
 * before accepting connections (contacting dispatcher).
 */
void startserver() {

  /* for networking */
  int sockfd;          /* socket (file) descriptor */
  int acsd;            /* accepted connection socket descriptor */
  struct sockaddr_in my_addr;     /* this server's address */
  struct sockaddr_in client_addr; /* a client's address */
  socklen_t len=sizeof(client_addr); /* length of the client_addr structure */
  int yes=1;           /* for socket options */
  int in_conn;         /* counter for incoming connections */

  /* catch keyboard interrupt signal to halt the server */
  signal(SIGINT, signal_handler);

  /* catch child normal termination (avoid zombies) */
  signal(SIGCHLD, signal_handler);

  /* try to create a socket */
  if ( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
  {
    perror("socket");
    exit(1);
  }

  /* set up the my_addr sockaddr_in structure */
  
  /* Internet family */
  my_addr.sin_family = AF_INET;     /* in HBO */

  /* will listen on this host's IP address */
  my_addr.sin_addr.s_addr = htonl(INADDR_ANY); /* long, in NBO */

  /* will listen on port 'port' */
  my_addr.sin_port = htons(port); /* short, in NBO */

  /* zero the rest of the struct */
  memset(&(my_addr.sin_zero), '\0', 8); 

  /* make the socket re-usable for bind()            */
  /* otherwise, the kernel may still hang on to the port           */
  /* and bind() will fail. Without SO_REUSEADDR, just have to wait */
  if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int))==-1) {
    perror("setsockopt");
    exit(1);
  }

  /* associate the socket with a port ('port') on this machine */
  if (bind(sockfd, (struct sockaddr *) &my_addr,
	   sizeof(struct sockaddr)) < 0) {
    perror("bind");
    exit(1);
  }


  /* specify how many incoming connection client connections
   * will be queued
   *
   * Done before registring because connection can start to be
   * queued as soon as it is registered.  (Even before regist_server()
   * returns).
   */
  if (listen(sockfd, MAX_QUEUE) < 0) {
    perror("listen");
    exit(1);
  }

  /* Loops forever accepting and processing connections iteratively. */

  while(1) {

    /* accept an incoming connection, 
     *  get a socket descriptor: acsd 
     *  get information about the client in client_addr
     */
    if ((acsd = accept(sockfd, (struct sockaddr *)&client_addr, &len)) < 0) {

     /* The accept() system call is "slow" and may be interrupted
      * by a signal from a terminating child !
      * Not all kernels automatically restart interrupted system
      * calls. We do it here explicitly.
      */ 
      /*
       * Yes this is true.  On the Sun Servers, accept() is interupted
       * and the server stops.  But it works fine without this check
       * on Linux.
       */
      if (errno == EINTR)
	continue;
      else {
	perror("accept()");
	exit(1);
      }
    }

#ifdef DEBUG
    fprintf(stderr, "net_server.c: Connection from client %s, port %d\n",
	    inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
    fprintf(stderr, "net_server.c: Now fork()ing...\n");
#endif

    switch (fork()) {   /* Create new process to handle client request */
    case -1:   /* error */
      /* perform some error actions */
      perror("fork");
      break;
    case 0:                         /* child */
      signal(SIGINT, SIG_IGN);      /* ignore interrupt from kbd in child */
      close(sockfd);                /* close listening socket */
      process_request(acsd);        /* process the request */
      close(acsd);                  /* close connected socket */
      exit(0);
    default:                        /* parent, return to accept */
      close (acsd);                 /* close connected socket */
      break;
    }
  }

}

/*
 * Processes a client request.
 *
 * 'acsd' - The client socket returned by accept()
 *
 */
void process_request(int acsd) {
  
  char rbuf[MAX_LINE]; /* receiving buffer */
  int rbufsize;        /* number of bytes received */

  char sbuf[MAX_LINE]; /* sending buffer */
  int sbufsize;        /* number of bytes to send */
 


  /* reads client service request */
  if ((rbufsize = recv(acsd, rbuf, MAX_LINE, 0)) < 0) {
    perror("recv");
    exit(1);
  }
  
#ifdef DEBUG
  rbuf[rbufsize] = '\0';
  fprintf(stderr, "net_server.c_child: Received '%s' (%d bytes) from client.\n", rbuf, rbufsize);
#endif


#ifdef TEXTREPLY
  
  /* prepare the reply - Always the same*/
  /* Code based on an Apache reply */
  strcpy(sbuf, "HTTP/1.1 400 Bad Request\n");
  strcat(sbuf, "Server: nobanner\n");
  strcat(sbuf, "Connection: close\n");
  strcat(sbuf, "Content-Type: text/html; charset=iso-8859-1\n\n");
  
  strcat(sbuf, "<HTML><HEAD>\n");
  strcat(sbuf, "<TITLE>400 Bad Request</TITLE>\n");
  strcat(sbuf, "</HEAD><BODY>\n");
  strcat(sbuf, "<H1>Bad Request</H1>\n");
  strcat(sbuf, "<p>No banner.  Thank you.\n");
  strcat(sbuf, "<HR>\n");
  strcat(sbuf, "<ADDRESS>");
  /* $Format: "  strcat(sbuf, \"nobanner/$Release$\");"$ */
  strcat(sbuf, "nobanner/0.2.0");
  strcat(sbuf, " at localhost, port ");
  sprintf(sbuf + strlen(sbuf), "%d</ADDRESS>\n", port);
  strcat(sbuf, "</BODY></HTML>\n");
  
  sbufsize = strlen(sbuf) + 1;
 
#else

  {  
    int pos;
    int c;
    
    /* bytes of invisible.gif */
    char invisible[] = { /* first line in emacs hexl-mode */
      0x47, 0x49, 0x46, 0x38,
      0x37, 0x61, 0x02, 0x00,
      0x02, 0x00, 0x80, 0x00,
      0x00, 0xff, 0xff, 0xff,
      /* 2nd line */
      0x00, 0x00, 0x00, 0x2c,
      0x00, 0x00, 0x00, 0x00,
      0x02, 0x00, 0x02, 0x00,
      0x00, 0x02, 0x02, 0x84,
      /* 3rd line */
      0x51, 0x00, 0x3b,
      /* end of array marker */
      -1 };
    int p = 0;
    

    strcpy(sbuf, "");
      
    strcat(sbuf, "HTTP/1.1 200 OK\n");
    strcat(sbuf, "Server: nobanner\n");
    strcat(sbuf, "Connection: close\n");
    strcat(sbuf, "Content-Type: image/gif\n\n");
    
    pos = strlen(sbuf);
      
    while ((c = invisible[p++]) != -1) {
      sbuf[pos++] = c;
    }
    
    sbufsize = pos;
  }
#endif  

#ifdef DEBUG
  /* send the reply */
  fprintf(stderr, "net_server.c_child: Sending reply to client...\n");
#endif

  if (send(acsd, sbuf, sbufsize, 0) < 0) {
    perror("send");
    exit(1);
  }
  
#ifdef DEBUG
  /* close the socket through which the client is accessed */
  fprintf(stderr, "net_server.c_child: Closing the connection...\n");
#endif

  close(acsd);
}


/*
 * signal handler
 *
 *  SIGCHLD: child process (servicing a client) stopped or terminated, 
 *           must be handled to avoid zombies
 *           default action: ignored
 *
 *  SIGINT: interrupt from keyboard
 *          default action: terminate process
 *          We silently assume INT signals are sent 
 *          to the parent process only. 
 *
 *
 * Code studied in class.
 *
 */
void signal_handler(int signum) {
  int status;

  if (signum == SIGINT) {
#ifdef DEBUG
    fprintf(stderr, "Interrupt from keyboard.\n");
    fprintf(stderr, "Server terminating\n");
#endif DEBUG
    exit(0);
  }

  if (signum == SIGCHLD) {
    pid_t child_pid; /* the ending child's PID */

    /* while processing, ignore further SIGCHLD interrupts */
    signal(SIGCHLD, SIG_IGN);
    
    child_pid = wait(&status);
    /*
      The wait function suspends execution of the current process 
      until a child has exited, or until a signal is delivered  whose  
      action is to terminate the current process or to call a  signal  
      handling  function. If a child has already exited by the 
      time of the call (a so-called "zombie" process), the function 
      returns immediately.  Any resources used by the child are freed.
    */
    

#ifdef DEBUG
    if (WIFEXITED(status)) {
      fprintf(stdout, "\nfork()ed server child %d exited normally\n", child_pid);
      fflush(stdout);
    } else {
      fprintf(stderr, "Hmm ... child did not exit normally\n");
      fflush(stderr);
    }
    
    
    printf("Client serviced, process terminated\n\n");
    fflush(stdout);
#endif

    signal(SIGCHLD, signal_handler); /* capture this signal again */
  }
  
  return;
}
