/*
 * Copyright (c) 2007-2017 Hypertriton, Inc. 
 * 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 COPYRIGHT HOLDERS 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 
#ifdef HAVE_CONTROL
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include "mailprocd.h"
#include "pathnames.h"
#include "peercred.h"
char *ctlSockPath;			/* Path to control socket */
Uint ctlClientCount, ctlMaxClients;	/* Concurrent sessions */
struct ctl_session_q ctlSessions;
const char *ctlCommandNames[] = {
	"CTL_VERSION",
	"CTL_VERSION_SA",
	NULL
};
void
CTL_Init(CFG_File *cf)
{
	TAILQ_INIT(&ctlSessions);
	ctlClientCount = 0;
	CFG_GetStr(cf, "ctl.socket-path", &ctlSockPath, _PATH_CTL_SOCKET);
	CFG_GetUint(cf, "ctl.max-clients", &ctlMaxClients, 30);
}
void
CTL_Destroy(void)
{
	CTL_Session *sess, *sessNext;
	for (sess = TAILQ_FIRST(&ctlSessions);
	     sess != TAILQ_END(&ctlSessions);
	     sess = sessNext) {
		sessNext = TAILQ_NEXT(sess, sessions);
		free(sess);
	}
	TAILQ_INIT(&ctlSessions);
	unlink(ctlSockPath);
}
void
CTL_ForkInstance(int sock)
{
	pid_t pid;
	if (ctlClientCount+1 > ctlMaxClients) {
		syslog(LOG_ERR, "CTL: Too many clients (%d)", ctlMaxClients);
		return;
	}
	if ((pid = fork()) != 0) {
		CTL_Session *sess;
		sess = Malloc(sizeof(CTL_Session));
		sess->pid = pid;
		TAILQ_INSERT_TAIL(&ctlSessions, sess, sessions);
		Debug("New control session: %d (session #%d/%d)", (int)pid,
		    ctlClientCount, ctlMaxClients);
		ctlClientCount++;
	} else {
		dup2(sock, STDIN_FILENO);
		dup2(sock, STDOUT_FILENO);
		MPD_EnterServerProc("control");
		setvbuf(stdout, NULL, _IONBF, 0);
		CTL_Main();
		MPD_ExitServerProc(0);
	}
}
static void
ServerResponse(int rv, const char *errMsg)
{
	char code = (rv == 0) ? '0' : '!';
	char errBuf[CTL_ERROR_MAX];
	write(STDOUT_FILENO, &code, 1);
	if (rv != 0) {
		memset(errBuf, '\0', sizeof(errBuf));
		Strlcpy(errBuf, errMsg, sizeof(errBuf));
		write(STDOUT_FILENO, errBuf, sizeof(errBuf));
	}
}
void
CTL_Main(void)
{
	char cmd;
	if (write(STDOUT_FILENO, "mailprocd\n", 10) != 10) {
		goto fail_write;
	}
	if (read(STDIN_FILENO, &cmd, 1) != 1) {
		goto fail_read;
	}
	switch (cmd) {
	case CTL_VERSION:
		write(STDOUT_FILENO, VERSION, sizeof(VERSION));
		write(STDOUT_FILENO, "\n", 1);
		write(STDOUT_FILENO, RELEASE, sizeof(RELEASE));
		write(STDOUT_FILENO, "\n", 1);
		break;
	case CTL_VERSION_SA:
#ifdef HAVE_SA
		write(STDOUT_FILENO, saVersion, strlen(saVersion));
		write(STDOUT_FILENO, "\n", 1);
#else
		ServerResponse(-1, "Not available");
#endif
		break;
	default:
		ServerResponse(-1, "Unimplemented command");
		Debug("Bad command: 0x%x", cmd);
		syslog(LOG_ERR, "Invalid command 0x%x on control socket",
		    cmd);
		break;
	}
	return;
fail_write:
	syslog(LOG_ERR, "Write error on control socket");
	return;
fail_read:
	syslog(LOG_ERR, "Read error on control socket");
	return;
}
/* Issue a request on the control socket. */
int
CTL_SendCommand(enum ctl_command cmd, ...)
{
	char signature[11];
	char errMsg[CTL_ERROR_MAX];
	struct sockaddr_un sun;
	my_socklen_t socklen;
	unsigned char cmdData, errCode;
	int s;
	Debug("Sending %s", ctlCommandNames[cmd]);
	if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
		MPD_SetError("socket: %s", strerror(errno));
		return (-1);
	}
	sun.sun_family = AF_UNIX;
	Strlcpy(sun.sun_path, _PATH_CTL_SOCKET, sizeof(sun.sun_path));
	socklen = SUN_LEN(&sun);
	if (connect(s, (struct sockaddr *)&sun, socklen) == -1) {
		MPD_SetError("%s: %s", _PATH_CTL_SOCKET, strerror(errno));
		return (-1);
	}
	if (read(s, signature, 10) != 10) {
		MPD_SetErrorS("Error reading signature");
		goto fail;
	}
	signature[10] = '\0';
	if (strcmp(signature, "mailprocd\n") != 0) {
		MPD_SetErrorS("Bad signature");
		goto fail;
	}
	cmdData = (unsigned char)cmd;
	if (write(s, &cmdData, 1) != 1) {
		MPD_SetError("Error writing %s", ctlCommandNames[cmd]);
		goto fail;
	}
	if (read(s, &errCode, 1) != 1) {
		MPD_SetErrorS("Error reading response code");
		goto fail;
	}
	if (errCode == '!') {
		if (read(s, errMsg, sizeof(errMsg)) != sizeof(errMsg)) {
			MPD_SetErrorS("Error reading error message");
			goto fail;
		}
		errMsg[CTL_ERROR_MAX-1] = '\0';	
		MPD_SetError("Server: %s", errMsg);
		goto fail;
	}
	close(s);
	Debug("Sent %s command", ctlCommandNames[cmd]);
	return (0);
fail:
	close(s);
	return (-1);
}
#endif /* HAVE_CONTROL */