#include <errno.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>

int open_listenfd(char *port);
void echo(int connfd);

#define LISTEN_MAX 10		/* Maximum clients that can queue up */
#define MAXLINE 512

int main(int argc, char **argv) {
    int listenfd, connfd, clientlen;
    char * port;
    union {
	struct sockaddr_in client4;
	struct sockaddr_in6 client6;
    } clientaddr;
    char hostname[NI_MAXHOST];
    char hostaddr[NI_MAXHOST];
    int error;

    port = argv[1];	  /* the server listens on a port passed
                             on the command line */
    listenfd = open_listenfd(port);
    if (listenfd < 0) {
	if (listenfd == -1)
	    (void) fprintf (stderr, "port %s: %s\n", port, strerror(errno));
	exit(1);
    }

    while (1) {
        clientlen = sizeof clientaddr;
        connfd = accept(listenfd, (struct sockaddr *)&clientaddr, &clientlen);
        if (connfd == -1) {
	    fprintf (stderr, "accept: %s\n", strerror(errno));
	    continue;
        }

	error = getnameinfo((struct sockaddr*)&clientaddr, clientlen,
	  hostname, sizeof hostname, NULL, 0, 0);
	if (error != 0) {
	  fprintf (stderr, "Couldn't get name info for client: %s\n",
	   gai_strerror(error));
	  close(connfd);
	  continue;
	}
	error = getnameinfo((struct sockaddr*)&clientaddr, clientlen,
	  hostaddr, sizeof hostaddr, NULL, 0, NI_NUMERICHOST);
	if (error != 0) {
	  fprintf (stderr, "Couldn't get numeric info for client %s: %s\n",
	   hostname, gai_strerror(error));
	  close(connfd);
	  continue;
	}
        printf("server connected to %s (%s)\n", hostname, hostaddr);
        echo(connfd);
        close(connfd);
    }
}

int open_listenfd(char *port)
{
    int listenfd, optval=1;
    struct addrinfo hints;
    struct addrinfo *hostaddresses = NULL;
    int error;

    /* Find out the server's IP address and port */
    memset(&hints, 0, sizeof hints);
    hints.ai_flags = AI_ADDRCONFIG | AI_V4MAPPED | AI_PASSIVE;
    hints.ai_family = AF_INET6;
    hints.ai_socktype = SOCK_STREAM;
    error = getaddrinfo(NULL, port, &hints, &hostaddresses);
    if (error != 0) {
	freeaddrinfo(hostaddresses);
	fprintf (stderr, "port %s: %s\n", port, gai_strerror(error));
	return -2;
    }

    /* Create a socket descriptor */
    /* We take advantage of the fact that AF_* and PF_* are identical */
    listenfd = socket(hostaddresses->ai_family,
      hostaddresses->ai_socktype, hostaddresses->ai_protocol);
    if (listenfd == -1) {
	freeaddrinfo(hostaddresses);
        return -1;
    }

    /* Eliminates "Address already in use" error from bind. */
    if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR,
                   (const void *)&optval , sizeof optval) == -1)
        return -1;
    /* Listenfd will be an endpoint for all requests to port
       on any IP address for this host */
    if (bind(listenfd, hostaddresses->ai_addr, hostaddresses->ai_addrlen)
      == -1) {
	freeaddrinfo(hostaddresses);
        return -1;
    }

    /* Make it a listening socket ready to accept
       connection requests */
    freeaddrinfo(hostaddresses);
    if (listen(listenfd, LISTEN_MAX) == -1)
        return -1;

    return listenfd;
}

void echo(int connfd)
{
    size_t n;
    char buf[MAXLINE];

    while((n = read(connfd, buf, sizeof buf)) > 0) {
        printf("server received %d bytes\n", n);
        write(connfd, buf, n);
    }
}
