/*
** server.c -- a stream socket server demo
*/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <signal.h>

extern char *optarg;
extern int optind;
extern int optopt;
extern int opterr;
extern int optreset;

#define PORT "3490"  // the port users will be connecting to

#define BACKLOG 10     // how many pending connections queue will hold

#define MAXDATASIZE 1024 // max size of a read off the socket into a local buffer

void sigchld_handler(int s)
{
  while(waitpid(-1, NULL, WNOHANG) > 0);
}

// get sockaddr, IPv4 or IPv6:
void *get_in_addr(struct sockaddr *sa)
{
    if (sa->sa_family == AF_INET) {
        return &(((struct sockaddr_in*)sa)->sin_addr);
    }

    return &(((struct sockaddr_in6*)sa)->sin6_addr);
}


char *get_ip_str(const struct sockaddr *sa, char *s, size_t maxlen)
{
    switch(sa->sa_family) {
        case AF_INET:
            inet_ntop(AF_INET, &(((struct sockaddr_in *)sa)->sin_addr),
                    s, maxlen);
            break;

        case AF_INET6:
            inet_ntop(AF_INET6, &(((struct sockaddr_in6 *)sa)->sin6_addr),
                    s, maxlen);
            break;

        default:
            strncpy(s, "Unknown AF", maxlen);
            return NULL;
    }

    return s;
}

void
usage()
{
  fprintf(stderr,"usage: server\n") ;
  fprintf(stderr,"  Options:\n") ;
  fprintf(stderr,"    -d secs - TCP_DEFER_ACCEPT set to secs\n");
}


int main(int argc, char *argv[])
{
  int sockfd, new_fd;  // listen on sock_fd, new connection on new_fd
  struct addrinfo hints, *servinfo, *p;
  struct sockaddr_storage their_addr; // connector's address information
  socklen_t sin_size;
  struct sigaction sa;
  int yes=1;
  char s[INET6_ADDRSTRLEN];
  int rv;
  char rbuf[MAXDATASIZE+512];
  char *buf ;
  int numbytes ;
  int apt=0 ;
  int bflag = 0;
  char ch ;
  
  // get command line args  
  while ((ch = getopt(argc, argv, "d:")) != -1) {
    switch (ch) {
    case 'd':
      apt = atoi(optarg);
      break;
    case '?':
    default:
      usage();
      }
    }
  argc -= optind;
  argv += optind;


  memset(&hints, 0, sizeof hints);
  hints.ai_family = AF_UNSPEC;
  hints.ai_socktype = SOCK_STREAM;
  hints.ai_flags = AI_PASSIVE; // use my IP

  strcpy(rbuf,"RECV: ") ;
  buf = &rbuf[6] ;
    
  if ((rv = getaddrinfo(NULL, PORT, &hints, &servinfo)) != 0) {
    fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
    return 1;
    }

  //printf("getaddrinfo returned:\n") ;
  //for(p = servinfo; p != NULL; p = p->ai_next) 
  //  printf("  %s (%d)\n",get_ip_str(p->ai_addr,s,INET6_ADDRSTRLEN),p->ai_family) ;

  // loop through all the results and bind to the first we can
  for(p = servinfo; p != NULL; p = p->ai_next) {
    printf("Attempting to bind to %s\n",get_ip_str(p->ai_addr,s,INET6_ADDRSTRLEN)) ;
        
    if ((sockfd = socket(p->ai_family, p->ai_socktype,
                p->ai_protocol)) == -1) {
      perror("server: socket");
      continue;
      }

    if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes,
                sizeof(int)) == -1) {
      perror("setsockopt");
      exit(1);
      }


#ifdef TCP_DEFER_ACCEPT
    if (apt > 0) { 
      if (setsockopt(sockfd, IPPROTO_TCP, TCP_DEFER_ACCEPT, &apt,
                sizeof(int)) == -1) {
        perror("setsockopt");
        exit(1);
        }
      }
#endif

    if (bind(sockfd, p->ai_addr, p->ai_addrlen) == -1) {
      close(sockfd);
      perror("server: bind");
      continue;
      }

    break;
    }

  if (p == NULL)  {
    fprintf(stderr, "server: failed to bind\n");
    return 2;
    }

  freeaddrinfo(servinfo); // all done with this structure

  if (listen(sockfd, BACKLOG) == -1) {
    perror("listen");
    exit(1);
    }

  sa.sa_handler = sigchld_handler; // reap all dead processes
  sigemptyset(&sa.sa_mask);
  sa.sa_flags = SA_RESTART;
  if (sigaction(SIGCHLD, &sa, NULL) == -1) {
      perror("sigaction");
      exit(1);
    }

  printf("server: waiting for connections...\n");

  while (1) {  // main accept() loop
    sin_size = sizeof their_addr;
    new_fd = accept(sockfd, (struct sockaddr *)&their_addr, &sin_size);
    if (new_fd == -1) {
      perror("accept");
      continue;
      }

    inet_ntop(their_addr.ss_family,
            get_in_addr((struct sockaddr *)&their_addr),
            s, sizeof s);
    printf("server: got connection from %s\n", s);

    if (!fork()) { // this is the child process
      close(sockfd); // child doesn't need the listener
      if ((numbytes = recv(new_fd, buf, MAXDATASIZE-6, 0)) == -1) { 
	perror("recv"); 
	exit(1); 
        } 
      buf[numbytes] = '\0'; 
      if (send(new_fd, rbuf, numbytes+6, 0) == -1)
                perror("send");
      close(new_fd);
      exit(0);
      }
    close(new_fd);  // parent doesn't need this
    }

  return 0;
}
