#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>
#include <signal.h>
#include <fcntl.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <linux/ax25.h>			/* axutils.h needs this...	*/
#include <linux/rose.h>
#include <grp.h>
#include <sys/wait.h>

#include "axutils.h"
#include "axconfig.h"
#include "node.h"
#include "io.h"

#define ECMD_PIPE	1		/* Run through pipe 		*/
#define ECMD_RECONN	2		/* */

static int norm_extcmd(struct cmd *cmdp, char **argv)
{
	int pid;

	alarm(0L);
	pid = fork();
	if (pid == -1) {
		/* fork error */
		node_perror("norm_extcmd: fork", errno);
		return 0;
	}
	if (pid == 0) {
		/* child */
		setgroups(0, NULL);
		setgid(cmdp->gid);
		setuid(cmdp->uid);
		execve(cmdp->path, argv, NULL);
		node_perror("norm_extcmd: execve", errno);
		exit(1);
	}
	/* parent */
	waitpid(pid, NULL, 0);
	return 0;
}

static int pipe_extcmd(struct cmd *cmdp, char **argv)
{
	int pipe_in[2], pipe_out[2];
	int pid, c;
	fd_set fdset;

	if (pipe(pipe_in) == -1) {
		node_perror("pipe_extcmd: pipe_in", errno);
		return 0;
	}
	if (pipe(pipe_out) == -1) {
		node_perror("pipe_extcmd: pipe_out", errno);
		return 0;
	}
	signal(SIGCHLD, SIG_IGN);
	pid = fork();
	if (pid == -1) {
		/* fork error */
		node_perror("pipe_extcmd: fork", errno);
		signal(SIGCHLD, SIG_DFL);
		return 0;
	}
	if (pid == 0) {
		/* child */
		/*
		 * Redirect childs output to the pipes closing
		 * stdin/out/err as we go.
		 */
		dup2(pipe_in[0], STDIN_FILENO);
		dup2(pipe_out[1], STDOUT_FILENO);
		dup2(pipe_out[1], STDERR_FILENO);
		/* Close the other ends */
		close(pipe_in[1]);
		close(pipe_out[0]);
		setgroups(0, NULL);
		setgid(cmdp->gid);
		setuid(cmdp->uid);
		execve(cmdp->path, argv, NULL);
		perror("pipe_extcmd: execve");
		exit(1);
	}
	/* parent */
	close(pipe_in[0]);
	close(pipe_out[1]);
	if (fcntl(User.fd, F_SETFL, O_NONBLOCK) == -1 ||
	    fcntl(pipe_out[0], F_SETFL, O_NONBLOCK) == -1) {
		node_perror("pipe_extcmd: fcntl - pipe_out", errno);
		goto end;
	}
	if (init_io(pipe_in[1], 1024, UNSPEC_EOL) == -1) {
		node_perror("pipe_extcmd: Error initializing I/O", -1);
		goto end;
	}
	if (init_io(pipe_out[0], 1024, UNSPEC_EOL) == -1) {
		node_perror("pipe_extcmd: Error initializing I/O", -1);
		end_io(pipe_in[1]);
		goto end;
	}
	while (1) {
		FD_ZERO(&fdset);
		FD_SET(STDIN_FILENO, &fdset);
		FD_SET(pipe_out[0], &fdset);
		if (select(256, &fdset, 0, 0, 0) == -1) {
			node_perror("pipe_extcmd: select", errno);
			break;
		}
		if (FD_ISSET(User.fd, &fdset)) {
			alarm(ConnTimeout);
			while((c = usgetc(User.fd)) != -1)
				usputc(c, pipe_in[1]);
			if (errno != EAGAIN)
				break;
		}
		if (FD_ISSET(pipe_out[0], &fdset)) {
			alarm(ConnTimeout);
			while((c = usgetc(pipe_out[0])) != -1)
				usputc(c, User.fd);
			if (errno != EAGAIN) {
                                if (errno)
                                        node_msg("%s", strerror(errno));
                                break;
			}
		}
		usflush(User.fd);
		usflush(pipe_in[1]);
	}
	end_io(pipe_in[1]);
	end_io(pipe_out[0]);
end:
	signal(SIGCHLD, SIG_DFL);
	kill(pid, SIGKILL);
	close(pipe_in[1]);
	close(pipe_out[0]);
	if (fcntl(STDIN_FILENO, F_SETFL, 0) == -1)
		node_perror("pipe_extcmd: fcntl - stdin", errno);
	return 0;
}

int extcmd(struct cmd *cmdp, char **argv)
{
	int ret;

	User.state = STATE_EXTCMD;
	User.dl_type = AF_UNSPEC;
	strcpy(User.dl_name, cmdp->name);
	strupr(User.dl_name);
	update_user();
	if (cmdp->flags & ECMD_PIPE)
		ret = pipe_extcmd(cmdp, argv);
	else
		ret = norm_extcmd(cmdp, argv);
	if (cmdp->flags & ECMD_RECONN)
		node_msg("Reconnected to %s", NodeId);
	return ret;
}
