/*
 * Copyright (c) 2000-2002 Apple Computer, Inc. All Rights Reserved.
 * The contents of this file constitute Original Code as defined in and are
 * subject to the Apple Public Source License Version 1.2 (the 'License').
 * You may not use this file except in compliance with the License. Please
 * obtain a copy of the License at http://www.apple.com/publicsource and
 * read it before using this file.
 *
 * This Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. Please
 * see the License for the specific language governing rights and
 * limitations under the License.
 */

/******************************************************************

	Title  : pcscdaemon.c
	Package: PC/SC Lite
	Author : David Corcoran
	Date   : 10/24/99
	License: Copyright (C) 1999 David Corcoran
			<corcoran@linuxnet.com>
	Purpose: This is the main pcscd daemon.

$Id: pcscdaemon.c,v 1.4 2004/12/07 01:15:43 mb Exp $

********************************************************************/

#include "config.h"

#include <time.h>
#include <syslog.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/errno.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>

#ifdef  HAVE_GETOPT_H
#include <getopt.h>
#endif

#include "wintypes.h"
#include "pcsclite.h"
#include "winscard_msg.h"
#include "winscard_svc.h"
#include "sys_generic.h"
#include "thread_generic.h"
#include "hotplug.h"
#include "debuglog.h"
#include "readerfactory.h"
#include "configfile.h"

#ifdef PCSC_TARGET_OSX
#include "powermgt_generic.h"
#endif

static char AraKiri = 0;
static char Init = 1;
extern int errno;

/*
 * Some internal functions 
 */

void SVCServiceRunLoop();
void SVCClientCleanup(request_object *msgStruct);
void at_exit(void);
void clean_temp_files(void);
void signal_trap(int);
void print_version (void);
void print_usage (char const * const);

PCSCLITE_MUTEX usbNotifierMutex;

/*
 * Cleans up messages still on the queue when a client dies 
 */
void SVCClientCleanup(request_object *msgStruct)
{
	/*
	 * May be implemented in future releases 
	 */
}

/*
 * The Message Queue Listener function 
 */
void SVCServiceRunLoop()
{

	char errMessage[200];
	request_object request = {};
	int currHandle, rsp;

	currHandle = 0, rsp = 0;

	/*
	 * Initialize the comm structure 
	 */
	rsp = MSGServerSetupCommonChannel();

	if (rsp == -1)
	{
		DebugLogA("SVCServiceRunLoop: Error initializing pcscd.");
		exit(-1);
	}

	/*
	 * Solaris sends a SIGALRM and it is annoying 
	 */

	signal(SIGALRM, SIG_IGN);
	signal(SIGPIPE, SIG_IGN);
	signal(SIGHUP, SIG_IGN);	/* needed for Solaris. The signal is sent
								 * when the shell is existed */

	/*
	 * This function always returns zero 
	 */
	rsp = SYS_MutexInit(&usbNotifierMutex);

	/*
	 * Set up the search for USB/PCMCIA devices 
	 */
	//HPSearchHotPluggables();
	HPRegisterForHotplugEvents();

#ifdef PCSC_TARGET_OSX
	/*
	 * Set up the power management callback routine for OS X
	 */
	PMRegisterForPowerEvents();
#endif

	while (1)
	{
		switch (rsp = MSGServerProcessEvents(&request, 0))
		{

		case 0:
			if (request.mtype == CMD_CLIENT_DIED)
			{
				/*
				 * Clean up the dead client 
				 */
				SYS_MutexLock(&usbNotifierMutex);
				MSGCleanupClient(&request);
				SYS_MutexUnLock(&usbNotifierMutex);
				snprintf(errMessage, sizeof(errMessage), "%s%d%s",
					"SVCServiceRun: Client ", request.socket,
					" has disappeared.");
				DebugLogB("%s", errMessage);
			} else
			{
				continue;
			}

			break;

		case 1:
			if (request.mtype == CMD_FUNCTION)
			{
				reply_object reply = {0,};
				reply_header *replyh = &reply.message.header;

				reply.additional_data = NULL;

				/*
				 * Command must be found 
				 */
				SYS_MutexLock(&usbNotifierMutex);
				rsp = MSGFunctionDemarshall(&request, &reply);
				/* Command is bogus, ignore it, we should probably
				   also get rid of this client. */
				if (rsp)
					continue;
				/* note that fields in reply are in byte network order for sending */
				if (!reply.additional_data)
					replyh->additional_data_size = 0;
				rsp = MSGSendData(request.socket, PCSCLITE_SERVER_ATTEMPTS,
					replyh, ntohl(replyh->size));
				if (!rsp && replyh->additional_data_size)
				{
					rsp = MSGSendData(request.socket, PCSCLITE_SERVER_ATTEMPTS,
						reply.additional_data, ntohl(replyh->additional_data_size));
				}

				if (reply.additional_data)
				{
					if (replyh->additional_data_size)
						memset(reply.additional_data, 0, ntohl(replyh->additional_data_size));
					free(reply.additional_data);
				}

				SYS_MutexUnLock(&usbNotifierMutex);
			} else
			{
				continue;
			}

			break;

		case 2:
			// timeout in MSGServerProcessEvents(): do nothing
			// this is used to catch the Ctrl-C signal at some time when
			// nothing else happens
			break;

		case -1:
			DebugLogA("SVCServiceRun: Error in MSGServerProcessEvents");
			break;

		default:
			DebugLogB("SVCServiceRun: MSGServerProcessEvents returned: %d",
				rsp);
			break;
		}

		if (request.additional_data)
		{
			if (request.message.header.additional_data_size)
				memset(request.additional_data, 0,
					request.message.header.additional_data_size);
			free(request.additional_data);
			request.additional_data = NULL;
		}

		if (AraKiri)
			RFCleanupReaders(1);
	}
}

int main(int argc, char **argv)
{

	int rv;
	char setToForeground;
	char *newReaderConfig;
	struct stat fStatBuf;
	int opt;
#ifdef HAVE_GETOPT_LONG
	int option_index;
	static struct option long_options[] = {
		{"config", 1, 0, 'c'},
		{"foreground", 0, 0, 'f'},
		{"debug", 1, 0, 'd'},
		{"help", 0, 0, 'h'},
		{"version", 0, 0, 'v'},
		{"apdu", 0, 0, 'a'},
		{0, 0, 0, 0}
	};
#endif
	
	rv = 0;
	newReaderConfig = 0;
	setToForeground = 0;

	/*
	 * test the version 
	 */
	if (strcmp(PCSCLITE_VERSION_NUMBER, VERSION) != 0)
	{
		printf("BUILD ERROR: The release version number PCSCLITE_VERSION_NUMBER\n");
		printf("  in pcsclite.h (%s) does not match the release version number\n",
			PCSCLITE_VERSION_NUMBER);
		printf("  generated in config.h (%s) (see configure.in).\n", VERSION);

		return 1;
	}

	/*
	 * log to stderr by default
	 */
	DebugLogSetLogType(DEBUGLOG_STDERR_DEBUG);

	/*
	 * Handle any command line arguments 
	 */
#ifdef  HAVE_GETOPT_LONG
	while ((opt = getopt_long (argc, argv, "c:fd:hva", long_options,
		&option_index)) != -1) {
#else
	while ((opt = getopt (argc, argv, "c:fd:hva")) != -1) {
#endif
		switch (opt) {
			case 'c':
				DebugLogB("main: using new config file: %s", optarg);
				newReaderConfig = optarg;
				break;

			case 'f':
				DebugLogA("main: pcscd set to foreground");
				setToForeground = 1;
				break;

			case 'd':
				if (strncmp(optarg, "syslog", PCSCLITE_MAX_COMSIZE) == 0) 
					DebugLogSetLogType(DEBUGLOG_SYSLOG_DEBUG);
				else if (strncmp(optarg, "stderr", PCSCLITE_MAX_COMSIZE) == 0) {
					DebugLogSetLogType(DEBUGLOG_STDERR_DEBUG);
					DebugLogA("main: debug messages to stderr");
					setToForeground = 1;
				} 
				else if (strncmp(optarg, "stdout", PCSCLITE_MAX_COMSIZE) == 0) {
					DebugLogSetLogType(DEBUGLOG_STDOUT_DEBUG);
					DebugLogA("main: debug messages to stdout");
					setToForeground = 1;
				}
				else
				{
					DebugLogB("unknown debug argument: %s", optarg);
					print_usage (argv[0]);
					return 1;
				}
				break;

			case 'h':
				print_usage (argv[0]);
				return 0;

			case 'v':
				print_version ();
				return 0;

			case 'a':
				DebugLogSetCategory(DEBUG_CATEGORY_APDU);
				break;

			default:
				print_usage (argv[0]);
				return 1;
		}

	}
	
	/*
	 * test the presence of /tmp/pcsc 
	 */

	rv = SYS_Stat(PCSCLITE_IPC_DIR, &fStatBuf);

	if (rv == 0)
	{
#ifdef USE_RUN_PID

		// read the pid file to get the old pid and test if the old pcscd is
		// still running 

		FILE *f;
		// pids are only 15 bits but 4294967296 (32 bits in case of a new
		// system use it) is on 10 bytes
#define PID_ASCII_SIZE 11
		char pid_ascii[PID_ASCII_SIZE];
		int pid;

		if ((f = fopen(USE_RUN_PID, "rb")) != NULL)
		{
			fgets(pid_ascii, PID_ASCII_SIZE, f);
			fclose(f);

			pid = atoi(pid_ascii);

			if (kill(pid, 0) == 0)
			{
				DebugLogA("main: Directory " PCSCLITE_IPC_DIR " already exists.");
				DebugLogA("Another pcscd seems to be running");
				return 1;
			}
			else
				// the old pcscd is dead. make some cleanup
				clean_temp_files();
		}
		else
		{
			DebugLogA("main: Directory " PCSCLITE_IPC_DIR " already exists.");
			DebugLogA("Maybe another pcscd is running?");
			DebugLogA("Can't read process pid from " USE_RUN_PID);
			DebugLogA("Remove " PCSCLITE_IPC_DIR " if pcscd is not running");
			DebugLogA("to clear this message.");
			return 1;
		}
#else
		DebugLogA("main: Directory " PCSCLITE_IPC_DIR " already exists.");
		DebugLogA("Maybe another pcscd is running?");
		DebugLogA("Remove " PCSCLITE_IPC_DIR " if pcscd is not running");
		DebugLogA("to clear this message.");
		return 1;
#endif
	}

	/*
	 * If this is set to one the user has asked it not to fork 
	 */

	if (setToForeground == 0)
	{
#ifndef HAVE_DAEMON
		switch (SYS_Fork())
		{
		case -1:
			return (-1);
		case 0:
			break;
		default:
			return (0);
		}

		if (SYS_CloseFile(0))
			DebugLogB("main: SYS_CloseFile(0) failed: %s", strerror(errno));

		if (SYS_CloseFile(1))
			DebugLogB("main: SYS_CloseFile(1) failed: %s", strerror(errno));

		if (SYS_CloseFile(2))
			DebugLogB("main: SYS_CloseFile(2) failed: %s", strerror(errno));

		if (SYS_Chdir("/"))
			DebugLogB("main: SYS_Chdir() failed: %s", strerror(errno));
#else
		if (SYS_Daemon(0, 0))
			DebugLogB("main: SYS_Daemon() failed: %s", strerror(errno));
#endif
	}

	/*
	 * cleanly remove /tmp/pcsc when exiting 
	 */
	signal(SIGQUIT, signal_trap);
	signal(SIGTERM, signal_trap);
	signal(SIGINT, signal_trap);
	signal(SIGHUP, signal_trap);

#ifdef USE_RUN_PID
	/*
	 * Record our pid to make it easier
	 * to kill the correct pcscd
	 */
	{
		FILE *f;

		if ((f = fopen(USE_RUN_PID, "wb")) != NULL)
		{
			fprintf(f, "%u\n", (unsigned) getpid());
			fclose(f);
		}
	}
#endif

	/*
	 * Create the /tmp/pcsc directory and chmod it 
	 */
	rv = SYS_Mkdir(PCSCLITE_IPC_DIR,
        S_ISVTX | S_IROTH | S_IXOTH | S_IRGRP | S_IXGRP | S_IRWXU);
	if (rv != 0)
	{
		DebugLogB("main: cannot create " PCSCLITE_IPC_DIR ": %s",
			strerror(errno));
		return 1;
	}

	rv = SYS_Chmod(PCSCLITE_IPC_DIR,
        S_ISVTX | S_IROTH | S_IXOTH | S_IRGRP | S_IXGRP | S_IRWXU);
	if (rv != 0)
	{
		DebugLogB("main: cannot chmod " PCSCLITE_IPC_DIR ": %s",
			strerror(errno));
		return 1;
	}

	/* cleanly remove /tmp/pcsc when exiting */
	if (atexit(at_exit))
		DebugLogB("main: atexit() failed: %s", strerror(errno));

	/*
	 * Allocate memory for reader structures 
	 */
	RFAllocateReaderSpace(PCSCLITE_MAX_CONTEXTS);

	/*
	 * Grab the information from the reader.conf 
	 */
	if (newReaderConfig)
	{
		rv = DBUpdateReaders(newReaderConfig);
		if (rv != 0)
		{
			DebugLogB("main: invalid file %s\n", newReaderConfig);
			at_exit();
			return 1;
		}
	} else
	{
		rv = DBUpdateReaders(PCSCLITE_READER_CONFIG);

		if (rv == 1)
		{
			DebugLogA("main: warning: no reader.conf found\n");
			/*
			 * Token error in file 
			 */
		} else if (rv == -1)
		{
			at_exit();
			return 1;
		}
	}

	/*
	 * Set the default globals 
	 */
	g_rgSCardT0Pci.dwProtocol = SCARD_PROTOCOL_T0;
	g_rgSCardT1Pci.dwProtocol = SCARD_PROTOCOL_T1;
	g_rgSCardRawPci.dwProtocol = SCARD_PROTOCOL_RAW;

	DebugLogA("main: pcsc-lite daemon ready.");

	/*
	 * post initialistion 
	 */
	Init = 0;

	/*
	 * signal_trap() does just set a global variable used by the main loop 
	 */
	signal(SIGQUIT, signal_trap);
	signal(SIGTERM, signal_trap);
	signal(SIGINT, signal_trap);
	signal(SIGHUP, signal_trap);

	SVCServiceRunLoop();

	DebugLogA("pcscdaemon.c: main: SVCServiceRunLoop returned");
	return 1;
}

void at_exit(void)
{
	DebugLogA("at_exit: cleaning " PCSCLITE_IPC_DIR);

	clean_temp_files();

	SYS_Exit(1);
}

void clean_temp_files(void)
{
	int rv;

	rv = SYS_Unlink(PCSCLITE_PUBSHM_FILE);
	if (rv != 0)
		DebugLogB("main: Cannot unlink " PCSCLITE_PUBSHM_FILE ": %s",
			strerror(errno));

	rv = SYS_Unlink(PCSCLITE_CSOCK_NAME);
	if (rv != 0)
		DebugLogB("main: Cannot unlink " PCSCLITE_CSOCK_NAME ": %s",
			strerror(errno));

	rv = SYS_Rmdir(PCSCLITE_IPC_DIR);
	if (rv != 0)
		DebugLogB("main: Cannot rmdir " PCSCLITE_IPC_DIR ": %s",
			strerror(errno));

#ifdef USE_RUN_PID
	rv = SYS_Unlink(USE_RUN_PID);
	if (rv != 0)
		DebugLogB("main: Cannot unlink " USE_RUN_PID ": %s",
			strerror(errno));
#endif
}

void signal_trap(int sig)
{
	// the signal handler is called several times for the same Ctrl-C
	if (AraKiri == 0)
	{
		DebugLogA("Preparing for suicide");
		AraKiri = 1;

		// if still in the init/loading phase the AraKiri will not be
		// seen by the main event loop
		if (Init)
		{
			DebugLogA("Suicide during init");
			at_exit();
		}
	}
}

void print_version (void)
{
	printf("%s version %s.\n",  PACKAGE, VERSION);
	printf("Copyright (C) 1999-2002 by David Corcoran <corcoran@linuxnet.com>.\n");
	printf("Report bugs to <sclinux@linuxnet.com>.\n");
}

void print_usage (char const * const progname)
{
	printf("Usage: %s [-c file] [-f] [-d output] [-v] [-h]\n", progname);
	printf("Options:\n");
#ifdef HAVE_GETOPT_LONG
	printf("  -a, --apdu		log APDU commands and results\n");
	printf("  -c, --config		path to reader.conf\n");
	printf("  -f, --foreground	run in foreground (no daemon)\n");
	printf("  -d, --debug output	display debug messages. Output may be:\n"); 
	printf("			\"stdout\" (imply -f), \"stderr\" (imply -f),\n");
	printf("			or \"syslog\"\n");
	printf("  -h, --help		display usage information\n");
	printf("  -v, --version		display the program version number\n");
#else
	printf("  -a    log APDU commands and results\n");
	printf("  -c 	path to reader.conf\n");
	printf("  -f 	run in foreground (no daemon)\n");
	printf("  -d 	display debug messages. Output may be:\n"); 
	printf("	stdout (imply -f), stderr (imply -f),\n");
	printf("	or syslog\n");
	printf("  -h 	display usage information\n");
	printf("  -v 	display the program version number\n");
#endif
}
