/*
 * RemoteAccess.m - An SSH and SCP interface class
 *
 *
 * Written by: Craig A. Lindley
 * Version: 1.0
 * Last Update: 02/20/2014
 */

#include <sys/stat.h>
#import "RemoteAccess.h"

#define BUSY_CHECK_PROGRAM "~/nodeBusy"
#define BUSY_FILE_NAME "~/busy"
#define RUN_FILE_NAME "~/runfile"

@implementation RemoteAccess

// Class constructor
- (id) initWithIP: (char *) _ipAddress User: (char *) _user PW: (char *) _password {

	self = [super init];
	if (self) {
		memset(ipAddress, 0, sizeof(ipAddress));
		memset(user, 0, sizeof(user));
		memset(password, 0, sizeof(password));

		// Save incoming
		strcpy(ipAddress, _ipAddress);
		strcpy(user, _user);
		strcpy(password, _password);

		// Indicate not connected
		connected = false;
	}
	return self;
}

- (char *) ipAddress {
	return ipAddress;
}

- (char *) user {
	return user;
}

- (char *) password {
	return password;
}

int verify_knownhost(ssh_session session) {

        int state, hlen;
        unsigned char *hash = NULL;
        char *hexa;
        char buf[10];

        state = ssh_is_server_known(session);
        hlen = ssh_get_pubkey_hash(session, &hash);
        if (hlen < 0) {
                return -1;
        }
        switch (state) {

                case SSH_SERVER_KNOWN_OK:
                        break; /* ok */

                case SSH_SERVER_KNOWN_CHANGED:
                        fprintf(stderr, "Host key for server changed: it is now:\n");
                        ssh_print_hexa("Public key hash", hash, hlen);
                        fprintf(stderr, "For security reasons, connection will be stopped\n");
                        free(hash);
                        return -1;

                case SSH_SERVER_FOUND_OTHER:
                        fprintf(stderr, "The host key for this server was not found but another type of key exists.\n");
                        fprintf(stderr, "An attacker might change the default server key to"
                                        "confuse your client into thinking the key does not exist\n");
                        free(hash);
                        return -1;

                case SSH_SERVER_FILE_NOT_FOUND:
                        fprintf(stderr, "Could not find known host file.\n");
                        fprintf(stderr, "If you accept the host key here, the file will be"
                                        "automatically created.\n");

                /* fallback to SSH_SERVER_NOT_KNOWN behavior */
                case SSH_SERVER_NOT_KNOWN:
                        hexa = ssh_get_hexa(hash, hlen);
                        fprintf(stderr,"The server is unknown. Do you trust the host key?\n");
                        fprintf(stderr, "Public key hash: %s\n", hexa);
                        free(hexa);
                        if (fgets(buf, sizeof(buf), stdin) == NULL) {
                                free(hash);
                                return -1;
                        }
                        if (strncasecmp(buf, "yes", 3) != 0) {
                                free(hash);
                                return -1;
                        }
                        if (ssh_write_knownhost(session) < 0) {
                                fprintf(stderr, "Error %s\n", strerror(errno));
                                free(hash);
                                return -1;
                        }
                        break;

                case SSH_SERVER_ERROR:
                        fprintf(stderr, "Error %s", ssh_get_error(session));
                        free(hash);
                        return -1;
        }
        free(hash);
        return 0;
}

// Connect to specified remote SSH server
- (void) connect {

        int verbosity = SSH_LOG_NOLOG;
        int rc, port = 22;

        session = ssh_new();
        if (session == NULL) {
                exit(-1);
        }

        ssh_options_set(session, SSH_OPTIONS_HOST, ipAddress);
        ssh_options_set(session, SSH_OPTIONS_USER, user);
        ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity);
        ssh_options_set(session, SSH_OPTIONS_PORT, &port);

        // Connect to server
        rc = ssh_connect(session);
        if (rc != SSH_OK) {
                fprintf(stderr, "Error connecting to host: %s\n", ssh_get_error(session));
                ssh_free(session);
                exit(-1);
        }

        // Verify the server's identity
        if (verify_knownhost(session) < 0) {
                ssh_disconnect(session);
                ssh_free(session);
                exit(-1);
        }

        // Authenticate ourselves
        rc = ssh_userauth_password(session, NULL, password);
        if (rc != SSH_AUTH_SUCCESS) {
                fprintf(stderr, "Error authenticating with password: %s\n",
                ssh_get_error(session));
                ssh_disconnect(session);
                ssh_free(session);
                exit(-1);
        }
	connected = true;
}

// Close down the sesson
- (void) close {

	if (connected) {
		ssh_disconnect(session);
		connected = false;
	}
	ssh_free(session);
	session = 0;
}

// Exec command line remotely on slave
- (int) execCmd: (char *) cmdline {

        ssh_channel channel;
        int rc;

	// Are we connected ?
	if (! connected) {
		fprintf(stderr, "execCmd: Not connected\n");
		return SSH_ERROR;
	}
		
        channel = ssh_channel_new(session);
        if (channel == NULL) {
                return SSH_ERROR;
        }

        rc = ssh_channel_open_session(channel);
        if (rc != SSH_OK) {
                ssh_channel_free(channel);
                return rc;
        }

	// Execute the command line on slave
        rc = ssh_channel_request_exec(channel, cmdline);
        if (rc != SSH_OK) {
                ssh_channel_close(channel);
                ssh_channel_free(channel);
                return rc;
        }

	// Read whatever is output to stdio on slave
        unsigned int nbytes = ssh_channel_read(channel, tmpBuffer, sizeof(tmpBuffer), 0);

fprintf(stderr, "cmdline: %s - nbytes: %d\n", cmdline, nbytes);

        while (nbytes > 0) {

                if (fwrite(tmpBuffer, 1, nbytes, stdout) != nbytes) {
                        ssh_channel_close(channel);
                        ssh_channel_free(channel);
                        return SSH_ERROR;
                }
                nbytes = ssh_channel_read(channel, tmpBuffer, sizeof(tmpBuffer), 0);
        }

        if (nbytes < 0) {
                ssh_channel_close(channel);
                ssh_channel_free(channel);
                return SSH_ERROR;
        }
        ssh_channel_send_eof(channel);
        ssh_channel_close(channel);
        ssh_channel_free(channel);
        return SSH_OK;
}

// Create runfile remotely
- (int) createRunFile {

	char buffer[40];
	
	memset(buffer, 0, sizeof(buffer));
	strcpy(buffer, "touch ");
	strcat(buffer, RUN_FILE_NAME);

	return [self execCmd:buffer];
}

// Delete busyfile remotely
- (int) deleteBusyFile {

	char buffer[40];
	
	memset(buffer, 0, sizeof(buffer));
	strcpy(buffer, "rm ");
	strcat(buffer, BUSY_FILE_NAME);

	return [self execCmd:buffer];
}

// Delete runfile remotely
- (int) deleteRunFile {

	char buffer[40];
	
	memset(buffer, 0, sizeof(buffer));
	strcpy(buffer, "rm ");
	strcat(buffer, RUN_FILE_NAME);

	return [self execCmd:buffer];
}

// Check if the slave is busy
- (bool) isBusy {

	[self execCmd:BUSY_CHECK_PROGRAM];
	return tmpBuffer[0] == '1';
}

// Copy a file from master to slave
- (int) copyFile: (char *) filename ToDir: (char *) toDir WithFilename: (char *) toFilename {

	ssh_scp scp;
	int rc;

	// Are we connected ?
	if (! connected) {
		fprintf(stderr, "copyFile: Not connected\n");
		return SSH_ERROR;
	}
		
	// Create new scp session
	scp = ssh_scp_new(session, SSH_SCP_WRITE, toDir);
	if (scp == NULL) {
		fprintf(stderr, "Error allocating scp session: %s\n",
		ssh_get_error(session));
		return SSH_ERROR;
	}

	rc = ssh_scp_init(scp);
	if (rc != SSH_OK) {
		fprintf(stderr, "Error initializing scp session: %s\n",
		ssh_get_error(session));
		ssh_scp_free(scp);
		return rc;
	}

	// We are now prepared to copy the file
	struct stat statStruct;

	// Test for file existance
	if (stat(filename, &statStruct) != 0) {
                fprintf(stderr, "File: %s not found\n", filename);
		ssh_scp_close(scp);
                ssh_scp_free(scp);
		return SSH_ERROR;
	}
	// Get file size
	long size = statStruct.st_size;

	// Specify dest filename and permissions
	rc = ssh_scp_push_file(scp, toFilename, size, S_IRUSR | S_IWUSR);
	if (rc != SSH_OK) {
		fprintf(stderr, "Can't open remote file: %s\n", ssh_get_error(session));
		ssh_scp_close(scp);
                ssh_scp_free(scp);
		return rc;
	}

	// Open source file
	FILE *fp = fopen(filename, "r");
	if (fp == NULL) {
		fprintf(stderr, "Can't open local file: %s\n", filename);
		ssh_scp_close(scp);
                ssh_scp_free(scp);
		return SSH_ERROR;
	}

	// Read the local file piece by piece
	int result = fread (tmpBuffer, 1, sizeof(tmpBuffer), fp);
	while (result > 0) {
		// Write the file's data
		rc = ssh_scp_write(scp, tmpBuffer, sizeof(tmpBuffer));
		if (rc != SSH_OK) {
			fprintf(stderr, "Can't write to remote file: %s\n", ssh_get_error(session));
			fclose(fp);
			ssh_scp_close(scp);
			ssh_scp_free(scp);
			return rc;
		}
		result = fread (tmpBuffer, 1, sizeof(tmpBuffer), fp);
	}
	// Close the local file
	fclose(fp);

	// Close down scp session
	ssh_scp_close(scp);
	ssh_scp_free(scp);
	return SSH_OK;
}

/*
// Program entry point
int main(void) {

	RemoteAccess *ra = [[RemoteAccess alloc] initWithIP:"192.168.0.17" User:"pi" PW:"raspberry"];
	[ra connect];

	[ra copyFile: "src/RemoteAccess.m" ToDir:"tempDir" WithFilename:"RemoteAccess.m"];
	
	[ra close];
}
*/

@end


