/* -------------------------------------------------------------------- */
/* SMS Client, send messages to mobile phones and pagers		*/
/*									*/
/* server.c								*/
/*									*/
/*  Copyright (C) 1997,1998,1999 Angelo Masci				*/
/*									*/
/*  This library is free software; you can redistribute it and/or	*/
/*  modify it under the terms of the GNU Library General Public		*/
/*  License as published by the Free Software Foundation; either	*/
/*  version 2 of the License, or (at your option) any later version.	*/
/*									*/
/*  This library 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	*/
/*  Library General Public License for more details.			*/
/*									*/
/*  You should have received a copy of the GNU Library General Public	*/
/*  License along with this library; if not, write to the Free		*/
/*  Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.	*/
/*									*/
/*  You can contact the author at this e-mail address:			*/
/*									*/
/*  angelo@styx.demon.co.uk						*/
/*									*/
/* --------------------------------------------------------------------
   $Id$
   -------------------------------------------------------------------- */

#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdarg.h>
#include <pwd.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <signal.h>
#include <netdb.h>

#include "lock/lock.h"
#include "server.h"
#include "logfile/logfile.h"
#include "common/common.h"

/* -------------------------------------------------------------------- */

#if !defined(WNOHANG)
#define WNOHANG 0
#endif

/* -------------------------------------------------------------------- */
/* Read a complete '\n' terminated string from socket			*/
/* upto maxlen characters will be read and a '\0' is appended		*/
/* -------------------------------------------------------------------- */
char *hgets(char *buf, int maxlen, int fd)
{
	int	result, 
		i;


	/* NOTE - 							*/
	/* As this function now uses recv instead of read		*/
	/* it probably makes sense to use the MSG_PEEK functionality	*/
	/* to read a block of data find the newline and then		*/
	/* make another call to receive with the correct length		*/
	/* of the line.							*/


	i = 0;
	buf[0] = '\0';
	while (i < (maxlen -1))
	{
		result = read(fd, &buf[i], 1);

		if (result < 0) 
		{ 	if (errno != EINTR) 
			{	lprintf(LOG_ERROR, "read() failed\n");
				exit(-1);
			}
		}
		else
		if (result == 0)
		{
			buf[i+1] = '\0';
			if (i == 0)
			{	return NULL;
			}

			break;
		}
		else
		{	if (buf[i] == '\n')
			{	i++;
				break;
			}

			i++;
		}
	}
	
	buf[i] = '\0';
	return buf;
}


/* -------------------------------------------------------------------- */
/* -------------------------------------------------------------------- */
void hprintf(int fd, char *fmt, ...)
{
	va_list args;
	int	line_len,
		len;
static	char	line[MAX_STRING_LEN];
	char	*line_ptr;


	/* ---------------------------- */

	va_start(args, fmt);

#if !defined(LINUX)
	vsprintf(line, fmt, args);
#else
	vsnprintf(line, MAX_STRING_LEN, fmt, args);
#endif

	va_end(args);

	line_len = strlen(line);
	line_ptr = line;

	while ((len = write(fd, line_ptr, line_len)) != line_len)
	{
		if (len < 0)
		{	if (errno == EINTR)
			{	continue;
			}
			else
			{	lprintf(LOG_ERROR, "write() failed\n");
				exit(-1);
			}	
		}

		line_ptr += len;
		line_len -= len;
	}
}


/* -------------------------------------------------------------------- */
/* Reap child processes on receipt of SIGCHLD				*/
/* -------------------------------------------------------------------- */
#if defined(LINUX) || defined(SOLARIS)
void sigchld_handler(int signum)
{
	while (waitpid((pid_t)-1, NULL, WNOHANG) > 0)
	{
		/* Reap children */
	}

	signal(SIGCHLD, sigchld_handler);	/* Re-establish signal 	*/
						/* handler 		*/
}
#endif

/* -------------------------------------------------------------------- */
/* -------------------------------------------------------------------- */
int server_init(int server_port)
{
        int 	sockfd;  	/* listen on sock_fd		*/
	struct 	sockaddr_in 
		server_addr;    /* my address information 	*/


	if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
	{	lprintf(LOG_ERROR, "socket() failed\n");
		return(-1);
	}

	server_addr.sin_family      = AF_INET; 			/* host byte order 		*/
	server_addr.sin_port        = htons(server_port); 	/* short, network byte order 	*/
	server_addr.sin_addr.s_addr = INADDR_ANY; 		/* auto-fill with my IP 	*/

	memset(&(server_addr.sin_zero), 0, 8);			/* zero rest of struct 		*/

	if (bind(sockfd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)) < 0)
	{
		switch (errno)
		{
		case EADDRINUSE:
			lprintf(LOG_ERROR, "bind() failed - Socket %d allready in use\n", server_port);
			break;
		case EACCES:
			lprintf(LOG_ERROR, "bind() failed - Socket %d protected port and not superuser\n", server_port);
			break;
		default:
			lprintf(LOG_ERROR, "bind() failed\n");
		}

		return(-1);
	}

	if (listen(sockfd, MAX_BACKLOG) < 0)
	{	lprintf(LOG_ERROR, "listen() failed\n");
		return(-1);
	}

	lprintf(LOG_STANDARD, "Listening on port %d\n", server_port);
	return sockfd;
}

/* -------------------------------------------------------------------- */
/* -------------------------------------------------------------------- */

extern void release_lock(void);

int server_quit;

void signal_term(int sig)
{	server_quit = TRUE;
}

void init_server_quit_handler(void)
{
	struct sigaction act;

	act.sa_flags   = 0;
	act.sa_handler = signal_term;
	sigemptyset(&act.sa_mask);

#if defined(SA_INTERRUPT)
	act.sa_flags  |= SA_INTERRUPT;
#endif

	server_quit = FALSE;
	sigaction(SIGTERM, &act, NULL);
}



int server_main(int server_port, void (*handle_child)(int new_fd), char *lockfile)
{
	pid_t	pid;
	int 	sockfd,
	 	sin_size,
	 	new_fd;
	struct 	sockaddr_in 
		client_addr; 	/* connector's address information 	*/


	if (deamon_init() != 0)
	{	return(-11);
	}

	resource_check_lockdir(lockfile);	
	if (resource_lock(lockfile))
	{	lprintf(LOG_ERROR, "smsd aborted, lockfile held by another smsd daemon\n");
		return(-1);
	}


	sockfd = server_init(server_port);
	if (sockfd == -1)
	{
		release_lock();
		return(-1);
	}


#if defined(LINUX) || defined(SOLARIS)
	signal(SIGCHLD, sigchld_handler);
#else
	signal(SIGCHLD, SIG_IGN);	/* Automagically reap zombies 	*/
					/* under SYSV			*/
#endif	
	set_consolelog(FALSE);		/* No more login output		*/
					/* to be sent to the console	*/


	init_server_quit_handler();
	while(! server_quit)
	{
		/* main accept() loop */

		sin_size = sizeof(struct sockaddr_in);
		if ((new_fd = accept(sockfd, (struct sockaddr *)&client_addr, &sin_size)) < 0)
		{
			if (errno == EINTR)
			{	continue;
			}
			else
			{	lprintf(LOG_ERROR, "accept() failed\n");

				release_lock();
				exit(-1);
			}
		}

		pid = fork();
		if (pid == 0)
		{	
			(*handle_child)(new_fd);

			close(new_fd);
			exit(0);
		}
		else if (pid < 0)
		{	lprintf(LOG_ERROR, "Trying to fork child process\n");

			release_lock();
			exit(-1);
		}

		close(new_fd);
	}

	release_lock();
	return 0;
}

/* -------------------------------------------------------------------- */
/* -------------------------------------------------------------------- */
void default_echo(int new_fd)
{
	char	buf[MAX_STRING_LEN];


	while (hgets(buf, MAX_STRING_LEN, new_fd) != NULL)
	{	printf("Received Data +%s+\n", buf);
		hprintf(new_fd, "Echo: +%s+\n", buf);
	}			
}


/* -------------------------------------------------------------------- */
/* -------------------------------------------------------------------- */
int deamon_init(void)
{
	pid_t	pid;
	

	pid = fork();		
	if (pid == -1)
	{
		lprintf(LOG_ERROR, "fork() Failed with errno %d\n", errno);
		return(-1);
	}
	else 
	if (pid != 0)
	{	exit(0);	/* Parent Exits			*/
	}

	/* ------------------------------------ */
	/* Child continues 			*/	
	/* ------------------------------------ */
	
	if (setsid() < 0)	/* Become session leader 	*/
	{
		lprintf(LOG_ERROR, "setsid() Failed with errno %d\n", errno);
		return(-1);
	}


	if (chdir("/") < 0)	/* Change working directory	*/
	{
		lprintf(LOG_ERROR, "chdir() Failed with errno %d\n", errno);
		return(-1);
	}

	return 0;
}

/* -------------------------------------------------------------------- */
/* -------------------------------------------------------------------- */
int get_client_information(int fd, char *host_name, char *ip_address)
{
	int 	sin_size;
	struct 	sockaddr_in 
		client_addr; 	/* connector's address information 	*/
	struct 	hostent
		*host;

	u_long	addr;
	

	sin_size = sizeof(struct sockaddr_in);
	if ((getpeername(fd, (struct sockaddr *)&client_addr, &sin_size)) < 0)
	{	return -1;
	}

	strcpy(ip_address, inet_ntoa(client_addr.sin_addr));

	addr = inet_addr(ip_address);
	host = gethostbyaddr((char *)&addr, sizeof(addr), AF_INET);
	if (host != NULL)
	{	strcpy(host_name, host->h_name);
	}
	else
	{	strcpy(host_name, "");
	}

	return 0;
}

