/* micro_inetd - simple network service spawner
**
** Copyright (C)1996,2000 by Jef Poskanzer <jef@mail.acme.com>.
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions
** are met:
** 1. Redistributions of source code must retain the above copyright
**    notice, this list of conditions and the following disclaimer.
** 2. Redistributions in binary form must reproduce the above copyright
**    notice, this list of conditions and the following disclaimer in the
**    documentation and/or other materials provided with the distribution.
**
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
** ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
** OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
** HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
** SUCH DAMAGE.
*/

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

#if defined(AF_INET6) && defined(IN6_IS_ADDR_V4MAPPED)
#define USE_IPV6
#endif

#ifdef DEBUG
#define LOG "/tmp/server.log" /* SECURITY: hardcoded path in shared dir */
#endif

static int initialize_listen_socket( int pf, int af, unsigned short port );
static void child_handler( int sig );



/* TODO: make it possible to set the IP address */
int
server( unsigned short port )
{
	int listen_fd, conn_fd;
	struct sockaddr_in sin;
	socklen_t sz;
	char buffer[1024];
	char *answer;
	/* SECURITY: memory leak, malloc() but not free() */
	answer = malloc(sizeof(char)*10);
	int success; 
	int pid; 
	int status; 
#ifdef DEBUG
	FILE * logfile;
#endif

	bzero(buffer, sizeof buffer);
	bzero(answer, sizeof answer);

	/* Initialize listen socket.  If we have v6 use that, since its sockets
	 ** will accept v4 connections too.  Otherwise just use v4.
	 */
#ifdef USE_IPV6
	listen_fd = initialize_listen_socket( PF_INET6, AF_INET6, port );
#else /* USE_IPV6 */
	listen_fd = initialize_listen_socket( PF_INET, AF_INET, port );
#endif /* USE_IPV6 */

	printf ("Server socket initialized\n");

	/* Initialise log file */

	/* Set up a signal handler for child reaping. */
	/* (void) signal( SIGCHLD, child_handler ); */

	for (;;)
	{
		/* Accept a new connection. */
		sz = sizeof(sin);
		conn_fd = accept( listen_fd, (struct sockaddr*) &sin, &sz );
		if ( conn_fd < 0 )
		{
			if ( errno == EINTR )	/* because of SIGCHLD (or ptrace) */
				continue;
			perror( "accept" );
			exit( 1 );
		}

		printf ("New connection\n"); /* TODO: print where from */
		/* Fork a sub-process to handle the connection. */
		pid = fork();
		if ( pid < 0 ) {
			perror("fork");
			exit(1);
		}
		if ( pid == 0 ) {
			/* Close standard descriptors and the listen socket. */
			(void) close(0);
			(void) close(1);
			(void) close(2);
			(void) close(listen_fd);
#ifdef DEBUG
			/* SECURITY: Vulnerable to symlink attack and race condition */
			logfile = fopen( LOG, "a+" );
#endif

			/* Read a limited string from the socket */ 
			success = read (conn_fd, buffer, sizeof buffer-1);
			buffer[sizeof(buffer)]=0;
			if (success < 0) {
#ifdef DEBUG
				fprintf (logfile, strerror (errno));
				fclose(logfile);
#endif
				exit(1);
			} 
			/* Say hello! */
#ifdef DEBUG
			fprintf (logfile, "Read %i bytes\n", success); 
			fprintf (logfile, "Answering to ");
			/* SECURITY: This is a format string oveflow */
			fprintf (logfile, buffer);
			fprintf (logfile, "\n");
#endif
			printf ("Answering back.\n"); /* TODO: print where to */
			/* SECURITY: This is a buffer oveflow ?*/
			sprintf (answer, "hello, %s\n", buffer);
			/* success = send(conn_fd, "hello, ", strlen("hello, "), 0);
			if (success < 0) {
#ifdef DEBUG
				fprintf (logfile, strerror (errno));
				fclose(logfile);
#endif
				exit(1);
			}
			strcpy(answer[strlen(answer)], buffer); */
			printf ("Answer length is %i\n", strlen(answer));
			success = send(conn_fd, answer, strlen(answer), 0);
			if (success < 0) {
#ifdef DEBUG
				fprintf (logfile, strerror (errno));
				fclose(logfile);
#endif
				exit(1);
			}
			printf ("Answer sent.\n"); /* TODO: print where to */
#ifdef DEBUG
			fclose(logfile);
#endif
			exit(0);
		}
		/* Parent process. */
		printf ("Forked children %i\n", pid);
		(void) close( conn_fd );
		printf ("Waiting for children %i to die...\n", pid);
		waitpid(-1, &status, 0);
		if ( ! WIFEXITED(status) )  {
			printf ("The children did not die normally, exit %i, signal %i\n", WEXITSTATUS(status), WTERMSIG(status));
		} else {
			printf ("Child died.\n");
		}
	}
}


	static void
child_handler( int sig )
{
	pid_t pid;
	int status;

	/* Set up the signal handler again.  Don't need to do this on BSD
	 ** systems, but it doesn't hurt.
	 */
	(void) signal( SIGCHLD, child_handler );

	/* Reap defunct children until there aren't any more. */
	for (;;)
	{
		pid = waitpid( (pid_t) -1, &status, WNOHANG );
		if ( (int) pid == 0 )           /* none left */
			break;
		if ( (int) pid < 0 )
		{
			if ( errno == EINTR )       /* because of ptrace */
				continue;
			/* ECHILD shouldn't happen with the WNOHANG option, but with
			 ** some kernels it does anyway.  Ignore it.
			 */
			if ( errno != ECHILD )
				perror( "waitpid" );
			break;
		}
	}
}


	static int
initialize_listen_socket( int pf, int af, unsigned short port )
{
	int listen_fd;
	int on;
#ifdef USE_IPV6
	struct sockaddr_in6 sa;
#else /* USE_IPV6 */
	struct sockaddr_in sa;
#endif /* USE_IPV6 */

	/* Create socket. */
	listen_fd = socket( pf, SOCK_STREAM, 0 );
	if ( listen_fd < 0 )
	{
		perror( "socket" );
		exit( 1 );
	}

	/* Allow reuse of local addresses. */
	on = 1;
	if ( setsockopt(
				listen_fd, SOL_SOCKET, SO_REUSEADDR, (char*) &on, sizeof(on) ) < 0 )
	{
		perror( "setsockopt SO_REUSEADDR" );
		exit( 1 );
	}

	/* Set up the sockaddr. */
	(void) memset( (char*) &sa, 0, sizeof(sa) );
#ifdef USE_IPV6
	sa.sin6_family = af;
	sa.sin6_addr = in6addr_any;
	sa.sin6_port = htons( port );
#else /* USE_IPV6 */
	sa.sin_family = af;
	sa.sin_addr.s_addr = htonl( INADDR_ANY );
	sa.sin_port = htons( port );
#endif /* USE_IPV6 */

	/* Bind it to the socket. */
	if ( bind( listen_fd, (struct sockaddr*) &sa, sizeof(sa) ) < 0 )
	{
		perror( "bind" );
		exit( 1 );
	}

	/* Start a listen going. */
	if ( listen( listen_fd, 1024 ) < 0 )
	{
		perror( "listen" );
		exit( 1 );
	}

	return listen_fd;
}
