/* GAdmin-Rsync - An easy to use GTK+ frontend for the rsync backup client and server.
 * Copyright (C) 2007-2011 Magnus Loef <magnus-swe@telia.com> 
 *
 * 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 of the License, 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.
 *
*/

#include "../config.h"

/* Must be declared before gtk+ */
#define _GNU_SOURCE
#include <gtk/gtk.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/ioctl.h>
#include <sys/select.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include "handle_ssh.h"



char ** create_gen_key_cmd(char *type, char *bits, char *keypath)
{
    /* Setup argv for generating public and private certificates.
       IE: ssh-keygen -q -t dsa -b 1024 -f /root/.gadmin-rsync/keys/root.127.0.0.1.key */
    char **new_argv;
    new_argv = malloc((9) * sizeof(char *));

    new_argv[0]=SSH_KEYGEN_BINARY;
    new_argv[1]="-q";
    new_argv[2]="-t";
    new_argv[3]=type;
    new_argv[4]="-b";
    new_argv[5]=bits;
    new_argv[6]="-f";
    new_argv[7]=keypath;
    new_argv[8]=NULL;

    return new_argv;
}

char ** create_scp_upload_cmd(char *user, char *port, char *file1, char *file2, char *user_host_dir)
{
    /* Setup argv for SCP:ing file(s) to a remote host.
     * IE: scp -P 22 /tmp/file root@192.168.0.100:/root/ 
     * (Now using tilde ~ for the directory instead of the username)
     * IE: scp -P 22 /tmp/file root@192.168.0.100:~ 
     */
    char **new_argv;
    new_argv = malloc((7) * sizeof(char *));

    new_argv[0] = SCP_BINARY;
    new_argv[1] = "-P";
    new_argv[2] = port;
    new_argv[3] = file1;
    new_argv[4] = file2;
    new_argv[5] = user_host_dir;
    new_argv[6] = NULL;

    return new_argv;
}

char ** create_ssh_cmd(char *user, char *host, char *port, char *ssh_cmd)
{
    /* Setup argv for SSH:ing to a remote host and running a command.
       IE: ssh -l root -p 22 localhost ls -l /tmp */
    char **new_argv;
    new_argv = malloc((8) * sizeof(char *));

    new_argv[0] = SSH_BINARY;
    new_argv[1] = "-l";
    new_argv[2] = user;
    new_argv[3] = "-p";
    new_argv[4] = port;
    new_argv[5] = host;
    new_argv[6] = ssh_cmd;
    new_argv[7] = NULL;

    return new_argv;
}


int run_ssh_cmd(char *new_argv[], char *password)
{
    /* Create a pseudo terminal and fork the process */
    int i, masterpty, slavepty, childpid, child_status = 0;
    int retval_select =  0; /* -1 = Error. */
    int child_exit    =  0; /*  1 = Child exited. */
    int cmd_ret       =  0; /* Command return value. */
    pid_t wait_pid;
    const char *slave_pty_name;
    const char *askpass = "SSH_ASKPASS";
    const char *display = "DISPLAY";

    masterpty = posix_openpt(O_RDWR|O_NOCTTY);
    if( masterpty == -1 )
    {
	perror("Can not open a pseudo-terminal master");
	return 0;
    }

    if( grantpt(masterpty) !=0 )
    {
	perror("Can not change permission of the pseudo terminal");
	return 0;
    }
    if( unlockpt(masterpty) !=0 )
    {
	perror("Can not unlock pseudo terminal");
	return 0;
    }

    childpid = fork();
    if( childpid == 0 )
    {
	/* This is the child process. */

	/* Create a new session. */
	setsid();

	/* Get the name of the slave pty */	
	slave_pty_name = (const char *) ptsname(masterpty);

	/* Open the slave pty */
	slavepty = open(slave_pty_name, O_RDWR);
	close(masterpty);
	
	/* Show command to run. The password is not shown. */
	for(i=0; new_argv[i]!=NULL; i++)
	   fprintf(stderr, "%s ", new_argv[i]);
	fprintf(stderr, "\n");

	/* Unset SSH_ASKPASS and DISPLAY to disable ssh-askpass popup. */
	if( unsetenv(askpass) == -1 )
          fprintf(stderr, "Unable to unset SSH_ASKPASS environment variable.\n");
	if( unsetenv(display) == -1 )
          fprintf(stderr, "Unable to unset DISPLAY environment variable.\n");

	/* Run the command */
	execvp(new_argv[0], new_argv);

	fprintf(stderr, "Could not run command.\n");

	exit(errno);
    }
    else
    if( childpid < 0 )
    {
	fprintf(stderr, "Can not create child process.\n");
	return 0;
    }
	
    /* We are the parent */
    do
    {
	if( ! child_exit )
	{
	    fd_set readfd;

	    FD_ZERO(&readfd);
	    FD_SET(masterpty, &readfd);

	    retval_select = select(masterpty+1, &readfd, NULL, NULL, NULL);
	    if( retval_select > 0 )
	    {
		if( FD_ISSET(masterpty, &readfd) )
		{
		    if( ! handle_output(masterpty, password) )
		    {
			/* Process error. Close the master control pty for the process. */
			close(masterpty);
			break;
		    }
		}
	    }
	    wait_pid = waitpid(childpid, &child_status, WNOHANG);
	}
	else
	  wait_pid = waitpid(childpid, &child_status, 0);

    } while( wait_pid == 0 || (! WIFEXITED(child_status) && ! WIFSIGNALED(child_status)) );

    /* Child has exited, return child status */

    cmd_ret = (child_status & 0xff00) >> 8;

    if( cmd_ret == 0 )
      cmd_ret = 1;
    else
      cmd_ret = 0;

    return cmd_ret;
}


int write_string(int fd, char *string)
{
    int retval = 0;
    ssize_t bytes_written = 0;

    if( ! strcmp(string, "\n") )
	bytes_written =  write(fd, "\n", 1);
    else
    {
        bytes_written = write(fd, string, strlen(string));
        write(fd, "\n", 1);
    }

    if( bytes_written == -1 )
    {
	retval = 0;
        fprintf(stderr, "Error: No bytes written.\n");
    }
    else
    {
	retval = 1;
        fprintf(stderr, "Bytes written: %d\n", bytes_written);
    }

    return retval;
}


int handle_output(int fd, char *password)
{
    /* Answer process output. */
    char auth_line[]="ant to continue connecting";
    char pass_line[]="assword:";
    char pass_phrase_line[]="passphrase";
    char key_overwrite_line[]="verwrite (y/n)";
    int  retval = 1; /* 0 = Exit child process due to denial or write error */
    char output[128]="";
    char line[128]="";
    int  i=0, x=0, num_pass_tries=0, bytes_read=0;
    
    bytes_read = read(fd, output, sizeof(output));

    /* Nothing to read. Return OK. */
    if( bytes_read == -1 )
      return 1;


    for(i=0; i<=128; i++)
    {
	line[x] = output[i];
	x++;
	
	if( output[i]!='\0' && output[i]!='\r' && output[i]!='\n' )
	    continue;

	if( strlen(line) < 7 )
	  continue;

	if( strstr(line, key_overwrite_line) )
	{
	    fprintf(stderr, "Overwriting old keys.\n");
	    if( ! write_string(fd, "yes") )
	    {
	        retval = 0;
		break;
	    }
	}

	if( strstr(line, pass_phrase_line) )
	{
	    fprintf(stderr, "Writing empty key phrase.\n");
	    if( ! write_string(fd, "\n") )
	    {
	        retval = 0;
	        break;
	    }
	}

	if( strstr(line, auth_line) )
	{
	    fprintf(stderr, "Allowing authentication to proceed.\n");
	    if( ! write_string(fd, "yes") )
	    {
	        retval = 0;
	        break;
	    }
	}

	if( strstr(line, pass_line) )
	{
	    fprintf(stderr, "Supplying password.\n");
	    if( ! write_string(fd, password) )
	    {
	        retval = 0;
	        break;
	    }

// Must be global...
	    num_pass_tries++;
	    
	    if( num_pass_tries > 2 )
	    {
		fprintf(stderr, "Error: Wrong password.\n");
		retval = 0;
		break;
	    }
	}

	x = 0;
	line[x]='\0';
    }

    return retval;
}
