/* swgpg.c  --  routines involving gpg
 */

/*
 Copyright (C) 2007 Jim Lowe
 All Rights Reserved.
  
 COPYING TERMS AND CONDITIONS:
 This program is free software; you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 3, or (at your option)
 any later version.
 This program 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 General Public License for more details.
 You should have received a copy of the GNU General Public License
 along with this program; if not, write to the Free Software
 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
*/

#define FILENEEDDEBUG 1
#undef FILENEEDDEBUG

#include "swuser_config.h"
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>

#include "vplob.h"
#include "swlib.h"
#include "usgetopt.h"
#include "ugetopt_help.h"
#include "swinstall.h"
#include "swparse.h"
#include "swfork.h"
#include "swgp.h"
#include "swssh.h"
#include "strob.h"
#include "progressmeter.h"
#include "swevents.h"
#include "etar.h"
#include "swicol.h"
#include "swutillib.h"
#include "swproglib.h"
#include "swicat.h"
#include "swgpg.h"
	
static char * g_swgpg_dirname;
static char * g_swgpg_fifopath;

static
char *
make_dir(void)
{
	int ret;
	char *filename;
	mode_t um;
	int try;
	const int max_tries = 10;

	E_DEBUG("");
	try = 0;
	um = swlib_get_umask();
	umask(0002);
	E_DEBUG("");
	do {
		E_DEBUG("");
		try++;
		filename = tempnam (SWGPG_FIFO_DIR, SWGPG_FIFO_PFX);
		umask(um);
		ret = mkdir(filename, (mode_t)(0700));
		if (ret < 0) {
			fprintf(stderr, "%s: %s\n", swlib_utilname_get(), strerror(errno));
			free (filename);
			filename = NULL;
		}
	} while (ret < 0 && try < max_tries);
	E_DEBUG("");
	umask(um);
	if (try >= max_tries && ret < 0)
		return NULL;

	E_DEBUG("");
	return filename;
}

static
SHCMD * 
gpg_fifo_command(char * fifofilename, char * gpg_prog, int uverbose, int logger_fd)
{
	char * absname;
	SHCMD * cmd;

	E_DEBUG("");
	cmd = shcmd_open();
	absname = shcmd_find_in_path(getenv("PATH"), gpg_prog);
	if (!absname) {
		fprintf(stderr,
			"swbis: %s: Not found in current path\n", gpg_prog);
		return NULL;
	}
	if (uverbose >= 3) {
		STROB * tmp = strob_open(32);
		swlib_writef(logger_fd, tmp,
			"%s: using GNU Privacy Guard : %s\n", swlib_utilname_get(), absname);
		strob_close(tmp);
	}
	shcmd_add_arg(cmd, absname);		/* GPG command */
	shcmd_add_arg(cmd, "--status-fd=1");

	E_DEBUG("");
	if (logger_fd != STDERR_FILENO) {
		STROB * x = strob_open(32);
		strob_sprintf(x, 0, "--logger-fd=%d", logger_fd);
		shcmd_add_arg(cmd, strob_str(x));
		strob_close(x);
	}

	shcmd_add_arg(cmd, "--verify");
	shcmd_add_arg(cmd, fifofilename); 	/* Other end of FIFO */
	shcmd_add_arg(cmd, "-");
	if (uverbose < SWC_VERBOSE_2) {
		shcmd_set_errfile(cmd, "/dev/null");
	} 
	E_DEBUG("");
	return cmd;
}

static
int
get_number_of_blobs(SWGPG_VALIDATE * w)
{
	int n = 0;
	while(strar_get(w->list_of_status_blobsM, n) != NULL) {
		n++;
	}
	return n;
}


SWGPG_VALIDATE *
swgpg_create(void)
{
	SWGPG_VALIDATE * w = (SWGPG_VALIDATE *)malloc(sizeof(SWGPG_VALIDATE));
	E_DEBUG("");
	if (!w) return NULL;
	w->gpg_prognameM = swlib_strdup("gpg");
	w->list_of_sigsM = strar_open();
	w->list_of_sig_namesM = strar_open();
	w->list_of_status_blobsM = strar_open();
	w->list_of_logger_blobsM = strar_open();
	w->status_arrayM = strob_open(12);
	return w;
}

void
swgpg_delete(SWGPG_VALIDATE * w)
{
	E_DEBUG("");
	free(w->gpg_prognameM);
	strar_close(w->list_of_sigsM);
	strar_close(w->list_of_sig_namesM);
	strar_close(w->list_of_status_blobsM);
	strar_close(w->list_of_logger_blobsM);
	strob_close(w->status_arrayM);
	E_DEBUG("");
	free(w);
}

int
swgpg_show_all_signatures(SWGPG_VALIDATE * w, int fd)
{
	int n;
	STROB * tmp;
	tmp = strob_open(100);
	n = 0;
	while(strar_get(w->list_of_sigsM, n) != NULL) {
		swlib_writef(fd, tmp, "%s%s",
			strar_get(w->list_of_logger_blobsM, n),
			strar_get(w->list_of_status_blobsM, n));
		n++;
	}
	strob_close(tmp);
	return 0;
}

int
swgpg_show(SWGPG_VALIDATE * w, int index, int sig_fd, int status_fd, int logger_fd)
{
	int n;
	STROB * tmp;
	int num;
	char * s;

	tmp = strob_open(100);
	num = get_number_of_blobs(w);
	E_DEBUG2("index is %d", index);
	E_DEBUG2("number of sigs is %d", num);
	if (index < 0 || index >= num) return -1; 
	n = index;

	s = strar_get(w->list_of_logger_blobsM, n);
	if (logger_fd > 0 && s)
		swlib_writef(logger_fd, tmp, "%s", s);

	s = strar_get(w->list_of_status_blobsM, n);
	if (status_fd > 0)
		swlib_writef(status_fd, tmp, "%s", s);

	s = strar_get(w->list_of_sigsM, n);
	if (sig_fd > 0)
		swlib_writef(sig_fd, tmp, "%s", s);

	strob_close(tmp);
	return 0;
}

void swgpg_reset(SWGPG_VALIDATE * swgpg)
{
	E_DEBUG("");
	strar_reset(swgpg->list_of_sigsM);
	strar_reset(swgpg->list_of_sig_namesM);
	strar_reset(swgpg->list_of_status_blobsM);
	strar_reset(swgpg->list_of_logger_blobsM);
	strob_strcpy(swgpg->status_arrayM, "");
}

int
swgpg_get_status(SWGPG_VALIDATE * w, int index)
{
	int ret;
	ret = strob_get_char(w->status_arrayM, index);
	return ret;
}

int
swgpg_get_number_of_sigs(SWGPG_VALIDATE * w)
{
	/* FIXME, this should be integrated with get_number_of_blob() above */
	int n = 0;
	E_DEBUG("");
	while(strar_get(w->list_of_sigsM, n) != NULL) {
		n++;
	}
	return n;
}

void
swgpg_set_status(SWGPG_VALIDATE * w, int index, int value)
{
	strob_set_length(w->status_arrayM, index+1);	
	*(strob_str(w->status_arrayM) + index) = (unsigned char)value;	
}

void
swgpg_set_status_array(SWGPG_VALIDATE * w)
{
	char * sig;
	int i;
	int ret;
	strob_strcpy(w->status_arrayM, "");
        i = 0;
        while((sig=strar_get(w->list_of_sigsM, i)) != NULL) {
                ret = swgpg_determine_signature_status(strar_get(w->list_of_status_blobsM, i));
                swgpg_set_status(w, i, ret);
                i++;
        }
}

int
swgpg_disentangle_status_lines(SWGPG_VALIDATE * w, char * gpg_output_lines)
{
	char * line;
	STROB * tmp;
	STROB * status_lines;
	STROB * stderr_lines;

	E_DEBUG("");
	tmp = strob_open(10);
	status_lines = strob_open(10);
	stderr_lines = strob_open(10);

	line = strob_strtok(tmp, gpg_output_lines, "\n\r");
        while(line) {
		E_DEBUG("");
		if (strstr(line, GPG_STATUS_PREFIX) == line) {
			strob_sprintf(status_lines, STROB_DO_APPEND, "%s\n", line);
		} else {
			strob_sprintf(stderr_lines, STROB_DO_APPEND, "%s\n", line);
		}
		line = strob_strtok(tmp, NULL, "\n\r");
	}

	E_DEBUG("");
	strar_add(w->list_of_status_blobsM, strob_str(status_lines));
	strar_add(w->list_of_logger_blobsM, strob_str(stderr_lines));

	strob_close(tmp);
	strob_close(status_lines);
	strob_close(stderr_lines);
	E_DEBUG("");
	return 0;
}

char *
swgpg_create_fifo(STROB * buf)
{
	int ret;
	char * filename;
	char * dirname;

	E_DEBUG("");
	if (!buf) return NULL;
	if (g_swgpg_dirname != NULL) return NULL;
	if (g_swgpg_fifopath != NULL) return NULL;
	strob_strcpy(buf, "");
	dirname = make_dir();
	if (!dirname) return NULL;
	strob_strcpy(buf, dirname);
	g_swgpg_dirname = dirname;
	swlib_unix_dircat(buf, SWGPG_FIFO_NAME);
	g_swgpg_fifopath = strdup(strob_str(buf));

	filename = g_swgpg_fifopath;
	if (filename) {
		ret = mkfifo(filename, (mode_t)(0600));
		if (ret < 0) {
			fprintf(stderr, "%s: %s\n", swlib_utilname_get(), strerror(errno));
		}
	}

	E_DEBUG("");
	return strob_str(buf);
}

int
swgpg_remove_fifo(void)
{
	int ret;
	E_DEBUG("");
	if (g_swgpg_dirname == NULL) return -1;
	if (g_swgpg_fifopath == NULL) return -1;
	ret = unlink(g_swgpg_fifopath);
	if (ret == 0) {
		g_swgpg_fifopath = NULL;
		ret = rmdir(g_swgpg_dirname);
		if (ret == 0) {
			g_swgpg_dirname = NULL;
		}
	}
	E_DEBUG("");
	return ret;
}

int
swgpg_determine_signature_status(char * gpg_status_lines)
{
	STROB * tmp;
	char * line;
	int good_count;
	int bad_count;
	int nodata;
	int nokey;

	E_DEBUG("");
	tmp = strob_open(100);

	good_count=0;
	nodata=0;
	nokey=0;
	bad_count=0;
	line = strob_strtok(tmp, gpg_status_lines, "\n\r");
	E_DEBUG("");
        while(line) {
		/* fprintf(stderr, "%s\n", line); */
		E_DEBUG("");
		if (strstr(line, GPG_STATUS_PREFIX) == line) {
			if (strstr(line, GPG_STATUS_PREFIX GPG_STATUS_GOODSIG)) {
				good_count++;
			} else if (strstr(line, GPG_STATUS_BADSIG)) {
				bad_count++;
			} else if (strstr(line, GPG_STATUS_NO_PUBKEY)) {
				nokey++;
			} else if (strstr(line, GPG_STATUS_EXPSIG)) {
				bad_count++;
			} else if (strstr(line, GPG_STATUS_NODATA)) {
				nodata++;
			} else {
				; /* allow all other lines ?? */
			} 
		} else {
			;
			/* now stderr is mixed this output so just
			   ignore other lines */
			/* return SWGPG_SIG_ERROR; */
		}
		line = strob_strtok(tmp, NULL, "\n\r");
        }
	E_DEBUG("");
	strob_close(tmp);
	if (good_count == 1 && bad_count == 0 && nokey == 0 && nodata == 0) {
		return SWGPG_SIG_VALID;
	} else if (good_count == 0 && bad_count == 0 && nokey > 0) {
		return SWGPG_SIG_NO_PUBKEY;
	} else if (good_count == 0 && bad_count == 0 && nodata > 0) {
		return SWGPG_SIG_NODATA;
	} else {
		return SWGPG_SIG_NOT_VALID;
	}
	E_DEBUG("");
	return SWGPG_SIG_ERROR;
}

int
swgpg_run_checksig2(char * sigfilename, char * thisprog,
		char * filearg, char * gpg_prog, int uverbose,
		char * which_sig_arg)
{
	pid_t pid;
	int ret;
	int retval;
	int sbfd[2];
	int logger_fd;
	int status_fd;
	int status;
	SWGPG_VALIDATE * w;
	SHCMD * cmd[3];
	int u_verbose = uverbose;
	STROB * tmp = strob_open(30);

	cmd[0] = shcmd_open();
	cmd[1] = shcmd_open();
	cmd[2] = NULL;
	E_DEBUG("");

	retval = 1;
	pipe(sbfd);
	pid = swfork(NULL);
	if (pid == 0) {
		close(sbfd[0]);
		E_DEBUG("");
		swgp_signal(SIGPIPE, SIG_DFL);
		swgp_signal(SIGINT, SIG_DFL);
		swgp_signal(SIGTERM, SIG_DFL);
		swgp_signal(SIGUSR1, SIG_DFL);
		swgp_signal(SIGUSR2, SIG_DFL);
		shcmd_add_arg(cmd[0], thisprog);  
		strob_sprintf(tmp, 0, "--util-name=%s", swlib_utilname_get());
		shcmd_add_arg(cmd[0], strob_str(tmp));
		shcmd_add_arg(cmd[0], "-G");
		shcmd_add_arg(cmd[0], sigfilename); /* FIFO or /dev/tty */
		shcmd_add_arg(cmd[0], "-n");
		shcmd_add_arg(cmd[0], which_sig_arg);
		while (u_verbose > 1) {
			shcmd_add_arg(cmd[0], "-v");
			u_verbose--;
		}
		shcmd_add_arg(cmd[0], "--sleep");
		shcmd_add_arg(cmd[0], "1");
		/* sbfd[1] is the write fd for the signed bytes */
		strob_sprintf(tmp, 0, "--signed-bytes-fd=%d", sbfd[1]);
		shcmd_add_arg(cmd[0], strob_str(tmp));
		strob_sprintf(tmp, 0, "--logger-fd=%d", STDOUT_FILENO);
		shcmd_add_arg(cmd[0], strob_str(tmp));
		shcmd_add_arg(cmd[0], filearg); /* may be a "-" for stdin */
		E_DEBUG("");
		shcmd_unix_exec(cmd[0]);
		E_DEBUG("");
		fprintf(stderr, "exec error in swgp_run_checksig\n");
		_exit(1);
	} else if (pid < 0) {
		E_DEBUG("");
		retval = 1;
		close(sbfd[1]);
		goto out;
	} 
	close(sbfd[1]);

	E_DEBUG("");
	cmd[1] = gpg_fifo_command(sigfilename, gpg_prog, uverbose, STDOUT_FILENO);
	if (cmd[1] == NULL) {
		E_DEBUG("");
		retval = 1;	
		kill(pid, SIGTERM);
		waitpid(pid, &status, 0);
		goto out;
	}

	E_DEBUG("");
	strob_strcpy(tmp, "");
	swlib_exec_filter(cmd+1, sbfd[0], tmp);
	E_DEBUG("");

	ret = swgpg_determine_signature_status(strob_str(tmp));
	retval = ret;
	E_DEBUG2("signature status is %d", ret);

	E_DEBUG2("status line: %s", strob_str(tmp));
	w = swgpg_create();
	E_DEBUG("");
	swgpg_disentangle_status_lines(w, strob_str(tmp));
	E_DEBUG("");
	logger_fd = -1;
	status_fd = -1;
	if (retval) {
		status_fd = STDERR_FILENO;
		logger_fd = STDERR_FILENO;
	} else {
		if (uverbose >= SWC_VERBOSE_2) {
			logger_fd = STDOUT_FILENO;
		} 
		if (uverbose >= SWC_VERBOSE_3) {
			logger_fd = STDOUT_FILENO;
			status_fd = STDOUT_FILENO;
		}
	}
	E_DEBUG3("show: status_fd=%d, logger_fd=%d", status_fd, logger_fd);
	swgpg_show(w, 0, -1, status_fd, logger_fd);
	E_DEBUG("");
	swgpg_delete(w);	
out:
	if (cmd[0]) shcmd_close(cmd[0]);
	if (cmd[1]) shcmd_close(cmd[1]);
	close(sbfd[0]);
	strob_close(tmp);
	return retval;
}

int
swgpg_run_gpg_verify(SWGPG_VALIDATE * swgpg, int signed_bytes_fd, char * signature, int uverbose, STROB * gpg_status)
{
	SHCMD * gpg_cmd;
	SHCMD * cmdvec[2];
	STROB * fifo;
	STROB * output;
	char * fifo_path;
	int ret;
	int ret1;
	int ret2;
	int ret3;
	pid_t pid;
	int status;

	E_DEBUG("");
	E_DEBUG2("signed_bytes_fd = %d", signed_bytes_fd);
	fifo = strob_open(32);
	if (gpg_status == NULL)
		output = strob_open(32);
	else
		output = gpg_status;

	cmdvec[0] = NULL;
	cmdvec[1] = NULL;
	E_DEBUG("");
	fifo_path = swgpg_create_fifo(fifo);
	if (fifo_path == NULL) return -1;

	E_DEBUG("");
	gpg_cmd = gpg_fifo_command(fifo_path, swgpg->gpg_prognameM, uverbose, STDOUT_FILENO);
	E_DEBUG("");
	if (gpg_cmd == NULL) {
		swgpg_remove_fifo();
		return -1;
	}
	cmdvec[0] = gpg_cmd;
	
	E_DEBUG("");
	pid = fork();
	if (pid == 0) {
		int ffd;
		close(0);
		close(1); 
		ffd = open(fifo_path, O_WRONLY);
		if (ffd < 0) _exit(1);
		ret = uxfio_unix_safe_write(ffd, signature, strlen(signature));
		if (ret != (int)strlen(signature)) _exit(2);
		close(ffd);
		_exit(0);
	} else if (pid < 0) {
		return -1;
	}
	E_DEBUG("");
	strob_strcpy(output, "");
	ret1 = swlib_exec_filter(cmdvec, signed_bytes_fd, output);

	E_DEBUG("");
	if (ret1 == SHCMD_UNSET_EXITVAL) {
		/* FIXME abnormal exit for gpg, don't no why */
		E_DEBUG2("swlib_exec_filter ret=%d", ret1);
		ret1 = 0;
	}

	E_DEBUG2("swlib_exec_filter ret1=%d", ret1);

	ret2 = swgpg_determine_signature_status(strob_str(output));
	E_DEBUG2("swgpg_determine_signature_status ret2=%d", ret2);

	ret3 = waitpid(pid, &status, 0);
	if (ret3 < 0) {
		;
	} else if (ret3 == 0) {
		ret3 = 2;
	} else {
        	if (WIFEXITED(status)) {
			ret3 = WEXITSTATUS(status);
			E_DEBUG2("exit value = %d", ret3);
		} else {
			ret3 = 1;
		}
	}

	swgpg_remove_fifo();
	strob_close(fifo);
	if (gpg_status == NULL)
		strob_close(output);

	E_DEBUG2("RESULT ret1 = %d", ret1);
	E_DEBUG2("RESULT ret2 = %d", ret2);
	E_DEBUG2("RESULT ret3 = %d", ret3);
	if (ret1 == 0 && ret2 == 0 && ret3 == 0) {
		E_DEBUG("RETURNING returning 0");
		return 0;
	} else {
		E_DEBUG("RETURNING returning 1");
		return 1;
	}
}
