/*******************************************************************************
 *
 * ipc_unix.c
 *
 * Description:  Implements the AF_UNIX IPC method.
 *
 * Copyright (c) 1997-2000 Messaging Direct Ltd.
 * All rights reserved.
 *
 * Portions Copyright (c) 2003 Jeremy Rumpf
 * jrumpf@heavyload.net
 *
 * 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 MESSAGING DIRECT LTD. ``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 MESSAGING DIRECT LTD. OR
 * ITS EMPLOYEES OR AGENTS 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.
 *
 *
 * HISTORY
 *
 * 
 * This source file created using 8 space tabs.
 *
 ********************************************************************************/

/****************************************
 * enable/disable ifdef
*****************************************/
#include "saslauthd-main.h"

#ifdef USE_UNIX_IPC
/****************************************/

/****************************************
 * includes
*****************************************/
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <string.h>
#include <errno.h>
#include <netinet/in.h>

#include "globals.h"
#include "utils.h"

/****************************************
 * declarations/protos
 *****************************************/
static void	do_request(int);
static void	send_no(int, char *);
static int	rel_accept_lock();
static int	get_accept_lock();

/****************************************
 * module globals
 *****************************************/
static int			sock_fd;     /* descriptor for the socket          */
static int			accept_fd;   /* descriptor for the accept lock     */
static struct sockaddr_un	server;      /* domain socket control, server side */
static struct sockaddr_un	client;      /* domain socket control, client side */
static SALEN_TYPE		len;         /* length for the client sockaddr_un  */
static char			*sock_file;  /* path to the AF_UNIX socket         */
static char			*accept_file;/* path to the accept() lock file     */

/****************************************
 * flags       	global from saslauthd-main.c
 * run_path    	global from saslauthd-main.c
 * num_procs   	global from saslauthd-main.c
 * detach_tty()	function from saslauthd-main.c
 * rx_rec()		function from utils.c
 * tx_rec()		function from utils.c
 * logger()		function from utils.c
 *****************************************/


/*************************************************************
 * IPC init. Initialize the environment specific to the 
 * AF_UNIX IPC method.
 *
 * __Required Function__
 **************************************************************/
void ipc_init() {
	int	rc;
	size_t  sock_file_len;
	
        /*********************************************************
	 * When we're not preforking, using an accept lock is a
	 * waste of resources. Otherwise, setup the accept lock
	 * file.
	 **********************************************************/
	if (num_procs == 0) 
		flags &= ~USE_ACCEPT_LOCK;
	
	if (flags & USE_ACCEPT_LOCK) {
		size_t accept_file_len;

		accept_file_len = strlen(run_path) + sizeof(ACCEPT_LOCK_FILE) + 1;
		if ((accept_file = malloc(accept_file_len)) == NULL) {
			logger(L_ERR, L_FUNC, "could not allocate memory");
			exit(1);
		}

		strlcpy(accept_file, run_path, accept_file_len);
		strlcat(accept_file, ACCEPT_LOCK_FILE, accept_file_len);

		if ((accept_fd = open(accept_file, O_RDWR|O_CREAT|O_TRUNC, S_IWUSR|S_IRUSR)) == -1) {
			rc = errno;
			logger(L_ERR, L_FUNC, "could not open accept lock file: %s", accept_file);
                	logger(L_ERR, L_FUNC, "open: %s", strerror(rc));
			exit(1);
		}

		if (flags & VERBOSE)
			logger(L_DEBUG, L_FUNC, "using accept lock file: %s", accept_file);
	}

	/**************************************************************
	 * We're at the point where we can't really do anything else
	 * until we attempt to detach or daemonize.
	 **************************************************************/
	detach_tty();

	/**************************************************************
	 * Setup the UNIX domain socket
	 **************************************************************/
	sock_file_len = strlen(run_path) + sizeof(SOCKET_FILE) + 1;
	if ((sock_file = malloc(sock_file_len)) == NULL) {
		logger(L_ERR, L_FUNC, "could not allocate memory");
		exit(1);
	}

	strlcpy(sock_file, run_path, sock_file_len);
	strlcat(sock_file, SOCKET_FILE, sock_file_len);

	unlink(sock_file);
	memset(&server, 0, sizeof(server));
	strlcpy(server.sun_path, sock_file, sizeof(server.sun_path));	
	server.sun_family = AF_UNIX;
	
	if ((sock_fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
		rc = errno;
		logger(L_ERR, L_FUNC, "could not create socket");
		logger(L_ERR, L_FUNC, "socket: %s", strerror(rc));
		exit(1);
	}

	umask(0);

	if (bind(sock_fd, (struct sockaddr *)&server, sizeof(server)) == -1) {
		rc = errno;
		logger(L_ERR, L_FUNC, "could not bind to socket: %s", sock_file);
		logger(L_ERR, L_FUNC, "bind: %s", strerror(rc));
		exit(1);
	}

	if (chmod(sock_file, S_IRWXU|S_IRWXG|S_IRWXO) == -1) {
		rc = errno;
		logger(L_ERR, L_FUNC, "could not chmod socket: %s", sock_file);
		logger(L_ERR, L_FUNC, "chmod: %s", strerror(rc));
		exit(1);
	}

	fchmod(sock_fd, S_IRWXU|S_IRWXG|S_IRWXO);

	umask(077);

	if (listen(sock_fd, SOCKET_BACKLOG) == -1) {
		rc = errno;
		logger(L_ERR, L_FUNC, "could not listen on socket: %s", sock_file);
		logger(L_ERR, L_FUNC, "listen: %s", strerror(rc));
		exit(1);
	}


	logger(L_INFO, L_FUNC, "listening on socket: %s", sock_file);

	/**************************************************************
	 * Ok boys... Let's procreate... If necessary of course...
	 * Num_procs == 0 means we're running one shot per process. In
	 * that case, we'll handle forking on a per connection basis.
	 **************************************************************/
	if (num_procs != 0)
		flags |= USE_PROCESS_MODEL;

	return;
}

/*************************************************************
 * Main IPC loop. Handle all the socket accept stuff, fork if 
 * needed, then pass things off to do_request().
 *
 * __Required Function__
 **************************************************************/
void ipc_loop() {

	int		rc;
	int		conn_fd;


	while(1) {

		len = sizeof(client);

		/**************************************************************
		 * First, if needed, get the accept lock. If it fails, take a
		 * nap and go to the top of the loop. (or should we just die?)
		 *************************************************************/
		if (get_accept_lock() != 0) {
			sleep(5);
			continue;
		}

        	conn_fd = accept(sock_fd, (struct sockaddr *)&client, &len);
		rc = errno;

		rel_accept_lock();

		if (conn_fd == -1) {
			if (rc != EINTR) {
				logger(L_ERR, L_FUNC, "socket accept failure");
				logger(L_ERR, L_FUNC, "accept: %s", strerror(rc));
				sleep(5);
			}
			continue;
		}

		/**************************************************************
		 * If we're running one shot, drop off a kid to handle the
		 * connection.
		 *************************************************************/
		if (num_procs == 0) {
		    if(flags & DETACH_TTY) {
			if (have_baby() > 0) {	/* parent */
			    close(conn_fd);
			    continue;
			}
			
			close(sock_fd);         /* child  */
		    }
		    
		    do_request(conn_fd);
		    close(conn_fd);

		    if(flags & DETACH_TTY) {
			exit(0);	
		    } else {
			continue;
		    }
		    
		}

		/**************************************************************
		 * Normal prefork mode.
		 *************************************************************/
		do_request(conn_fd);
		close(conn_fd);
	}

	return;
}


/*************************************************************
 * General cleanup. Unlock, close, and unlink our files.
 *
 * __Required Function__
 **************************************************************/
void ipc_cleanup() {

	struct flock    lock_st;

	if (flags & USE_ACCEPT_LOCK) {

		lock_st.l_type = F_UNLCK;
		lock_st.l_start = 0;
		lock_st.l_whence = SEEK_SET;
		lock_st.l_len = 1;

		fcntl(accept_fd, F_SETLK, &lock_st);

		close(accept_fd);
		unlink(accept_file);

		if (flags & VERBOSE)
			logger(L_DEBUG, L_FUNC, "accept lock file removed: %s", accept_file);
	}

	close(sock_fd);
	unlink(sock_file);

	if (flags & VERBOSE)
		logger(L_DEBUG, L_FUNC, "socket removed: %s", sock_file);
}


/*************************************************************
 * Handle the comms on the socket, pass the request off to
 * do_auth() back in saslauthd-main.c, then transmit the
 * result back out on the socket.
 **************************************************************/
void do_request(int conn_fd) {

	unsigned short		count;                     /* input/output data byte count           */
	unsigned short		ncount;                    /* input/output data byte count, network  */ 
	char			*response;                 /* response to send to the client         */
	char			login[MAX_REQ_LEN + 1];    /* account name to authenticate           */
	char			password[MAX_REQ_LEN + 1]; /* password for authentication            */
	char			service[MAX_REQ_LEN + 1];  /* service name for authentication        */
	char			realm[MAX_REQ_LEN + 1];    /* user realm for authentication          */


	/**************************************************************
	 * The input data stream consists of the login id, password,
	 * service name and user realm as counted length strings.
	 * We read in each string, then dispatch the data.
	 **************************************************************/

	/* login id */
	if (rx_rec(conn_fd, (void *)&count, (size_t)sizeof(count)) != (ssize_t)sizeof(count)) 
		return;

	count = ntohs(count);

	if (count > MAX_REQ_LEN) {
		logger(L_ERR, L_FUNC, "login exceeded MAX_REQ_LEN: %d", MAX_REQ_LEN);
		send_no(conn_fd, "");
		return;
	}	

	if (rx_rec(conn_fd, (void *)login, (size_t)count) != (ssize_t)count) 
		return;
	
	login[count] = '\0';

	/* password */
	if (rx_rec(conn_fd, (void *)&count, (size_t)sizeof(count)) != (ssize_t)sizeof(count)) 
		return;

	count = ntohs(count);

	if (count > MAX_REQ_LEN) {
		logger(L_ERR, L_FUNC, "password exceeded MAX_REQ_LEN: %d", MAX_REQ_LEN);
		send_no(conn_fd, "");
		return;
	}	

	if (rx_rec(conn_fd, (void *)password, (size_t)count) != (ssize_t)count) 
		return;
		
	password[count] = '\0';

	/* service */
	if (rx_rec(conn_fd, (void *)&count, (size_t)sizeof(count)) != (ssize_t)sizeof(count)) 
		return;

	count = ntohs(count);

	if (count > MAX_REQ_LEN) {
		logger(L_ERR, L_FUNC, "service exceeded MAX_REQ_LEN: %d", MAX_REQ_LEN);
		send_no(conn_fd, "");
		return;
	}	

	if (rx_rec(conn_fd, (void *)service, (size_t)count) != (ssize_t)count) 
		return;

	service[count] = '\0';

	/* realm */
	if (rx_rec(conn_fd, (void *)&count, (size_t)sizeof(count)) != (ssize_t)sizeof(count)) 
		return;

	count = ntohs(count);

	if (count > MAX_REQ_LEN) {
		logger(L_ERR, L_FUNC, "realm exceeded MAX_REQ_LEN: %d", MAX_REQ_LEN);
		send_no(conn_fd, "");
		return;
	}	

	if (rx_rec(conn_fd, (void *)realm, (size_t)count) != (ssize_t)count) 
		return;

	realm[count] = '\0';

	/**************************************************************
 	 * We don't allow NULL passwords or login names
	 **************************************************************/
	if (*login == '\0') {
		logger(L_ERR, L_FUNC, "NULL login received");
		send_no(conn_fd, "NULL login received");
		return;
	}	
	
	if (*password == '\0') {
		logger(L_ERR, L_FUNC, "NULL password received");
		send_no(conn_fd, "NULL password received");
		return;
	}	

	/**************************************************************
	 * Get the mechanism response from do_auth() and send it back.
	 **************************************************************/
	response = do_auth(login, password, service, realm);

	memset(password, 0, strlen(password));

	if (response == NULL) {
		send_no(conn_fd, "NULL response from mechanism");
		return;
	}	

	count = strlen(response);
	ncount = htons(count);

	if (tx_rec(conn_fd, (void *)&ncount, (size_t)sizeof(ncount)) != (ssize_t)sizeof(ncount)) {
		free(response);
		return;
	}

	if (tx_rec(conn_fd, (void *)response, (size_t)count) != (ssize_t)sizeof(count)) {
		free(response);
		return;
	}

	if (flags & VERBOSE)
		logger(L_DEBUG, L_FUNC, "response: %s", response);

	free(response);

	return;
}


/*************************************************************
 * In case something went out to lunch while reading in the
 * request data, we may want to attempt to send out a default 
 * "NO" response on the socket. The mesg is optional.
 **************************************************************/
void send_no(int conn_fd, char *mesg) {
	char		buff[1024];
	unsigned short	count; 
	unsigned short	ncount; 

	buff[0] = 'N';
	buff[1] = 'O';
	buff[2] = ' ';

	/* buff, except for the trailing NUL and 'NO ' */
	strncpy(buff + 3, mesg, sizeof(buff) - 1 - 3);
	buff[1023] = '\0';

	count = strlen(buff);
	ncount = htons(count);

	if (tx_rec(conn_fd, (void *)&ncount, (size_t)sizeof(ncount)) != (ssize_t)sizeof(ncount))
		return;

	if (tx_rec(conn_fd, (void *)buff, (size_t)count) != (ssize_t)sizeof(count))
		return;

	if (flags & VERBOSE)
		logger(L_DEBUG, L_FUNC, "response: %s", buff);
	
	return;	
}


/*************************************************************
 * Attempt to get a write lock on the accept lock file.
 * Return 0 if everything went ok, return -1 if something bad
 * happened. This function is expected to block.
 **************************************************************/
int get_accept_lock() {

	struct flock    lock_st;
	int             rc;


	if (!(flags & USE_ACCEPT_LOCK))
		return 0;

	lock_st.l_type = F_WRLCK;
	lock_st.l_start = 0;
	lock_st.l_whence = SEEK_SET;
	lock_st.l_len = 1;

	errno = 0;

	do {
		rc = fcntl(accept_fd, F_SETLKW, &lock_st);
	} while (rc != 0 && errno == EINTR);

	if (rc != 0) {
		rc = errno;
		logger(L_ERR, L_FUNC, "could not acquire accept lock");
		logger(L_ERR, L_FUNC, "fcntl: %s", strerror(rc));
		return -1;
	}

	if (flags & VERBOSE)
		logger(L_DEBUG, L_FUNC, "acquired accept lock");

	return 0;
}


/*************************************************************
 * Attempt to release the write lock on the accept lock file.
 * Return 0 if everything went ok, return -1 if something bad
 * happened.
 **************************************************************/
int rel_accept_lock() {

	struct flock    lock_st;
	int             rc;


	if (!(flags & USE_ACCEPT_LOCK))
		return 0;

	lock_st.l_type = F_UNLCK;
	lock_st.l_start = 0;
	lock_st.l_whence = SEEK_SET;
	lock_st.l_len = 1;

	errno = 0;

	do {
		rc = fcntl(accept_fd, F_SETLKW, &lock_st);
	} while (rc != 0 && errno == EINTR);

	if (rc != 0) {
		rc = errno;
		logger(L_ERR, L_FUNC, "could not release accept lock");
		logger(L_ERR, L_FUNC, "fcntl: %s", strerror(rc));
		return -1;
	}

	if (flags & VERBOSE)
		logger(L_DEBUG, L_FUNC, "released accept lock");

	return 0;
}



#endif /* USE_UNIX_IPC */
