summaryrefslogtreecommitdiffstats
path: root/ssh-common.c
diff options
context:
space:
mode:
Diffstat (limited to 'ssh-common.c')
-rw-r--r--ssh-common.c388
1 files changed, 388 insertions, 0 deletions
diff --git a/ssh-common.c b/ssh-common.c
new file mode 100644
index 0000000..bfa48b5
--- /dev/null
+++ b/ssh-common.c
@@ -0,0 +1,388 @@
+/*
+ Copyright (c) 2016 Red Hat, Inc. <http://www.redhat.com>
+ This file is part of gluster-block.
+
+ This file is licensed to you under your choice of the GNU Lesser
+ General Public License, version 3 or any later version (LGPLv3 or
+ later), or the GNU General Public License, version 2 (GPLv2), in all
+ cases as published by the Free Software Foundation.
+*/
+
+
+# define _GNU_SOURCE
+
+# include <stdio.h>
+# include <stdlib.h>
+# include <string.h>
+# include <stdbool.h>
+# include <errno.h>
+# include <uuid/uuid.h>
+
+# include "utils.h"
+# include "ssh-common.h"
+
+
+
+static int
+glusterBlockSSHAuthKbdint(ssh_session blksession, const char *password)
+{
+ int err;
+ const char *ret;
+ const char *answer;
+ char buffer[128];
+ size_t i;
+ int n;
+
+ err = ssh_userauth_kbdint(blksession, NULL, NULL);
+ while (err == SSH_AUTH_INFO) {
+ ret = ssh_userauth_kbdint_getname(blksession);
+ if (ret && strlen(ret) > 0)
+ MSG("%s", ret);
+
+ ret = ssh_userauth_kbdint_getinstruction(blksession);
+ if (ret && strlen(ret) > 0)
+ MSG("%s", ret);
+
+ n = ssh_userauth_kbdint_getnprompts(blksession);
+ for (i = 0; i < n; i++) {
+ char echo;
+
+ ret = ssh_userauth_kbdint_getprompt(blksession, i, &echo);
+ if (!ret)
+ break;
+
+ if (echo) {
+ char *p;
+
+ MSG("%s", ret);
+
+ if (!fgets(buffer, sizeof(buffer), stdin))
+ return SSH_AUTH_ERROR;
+
+ buffer[sizeof(buffer) - 1] = '\0';
+ if ((p = strchr(buffer, '\n')))
+ *p = '\0';
+
+ if (ssh_userauth_kbdint_setanswer(blksession, i, buffer) < 0)
+ return SSH_AUTH_ERROR;
+
+ memset(buffer, 0, strlen(buffer));
+ } else {
+ if (password && strstr(ret, "Password:")) {
+ answer = password;
+ } else {
+ buffer[0] = '\0';
+
+ if (ssh_getpass(ret, buffer, sizeof(buffer), 0, 0) < 0)
+ return SSH_AUTH_ERROR;
+
+ answer = buffer;
+ }
+ err = ssh_userauth_kbdint_setanswer(blksession, i, answer);
+ memset(buffer, 0, sizeof(buffer));
+ if (err < 0)
+ return SSH_AUTH_ERROR;
+ }
+ }
+ err = ssh_userauth_kbdint(blksession, NULL, NULL);
+ }
+
+ return err;
+}
+
+
+static int
+glusterBlockSSHAuthConsole(ssh_session blksession)
+{
+ int rc;
+ int method;
+ char *banner;
+ char password[128] = {0};
+
+ // Try to authenticate
+ rc = ssh_userauth_none(blksession, NULL);
+ if (rc == SSH_AUTH_ERROR) {
+ ERROR("%s", ssh_get_error(blksession));
+ return rc;
+ }
+
+ method = ssh_userauth_list(blksession, NULL);
+
+ while (rc != SSH_AUTH_SUCCESS) {
+
+ // Try to authenticate through the "gssapi-with-mic" method.
+ if (method & SSH_AUTH_METHOD_GSSAPI_MIC) {
+ rc = ssh_userauth_gssapi(blksession);
+ if (rc == SSH_AUTH_ERROR) {
+ ERROR("%s", ssh_get_error(blksession));
+ return rc;
+ } else if (rc == SSH_AUTH_SUCCESS) {
+ break;
+ }
+ }
+
+ // Try to authenticate with public key first
+ if (method & SSH_AUTH_METHOD_PUBLICKEY) {
+ rc = ssh_userauth_publickey_auto(blksession, NULL, NULL);
+ if (rc == SSH_AUTH_ERROR) {
+ ERROR("%s", ssh_get_error(blksession));
+ return rc;
+ } else if (rc == SSH_AUTH_SUCCESS) {
+ break;
+ }
+ }
+
+ // Try to authenticate with keyboard interactive";
+ if (method & SSH_AUTH_METHOD_INTERACTIVE) {
+ rc = glusterBlockSSHAuthKbdint(blksession, NULL);
+ if (rc == SSH_AUTH_ERROR) {
+ ERROR("%s", ssh_get_error(blksession));
+ return rc;
+ } else if (rc == SSH_AUTH_SUCCESS) {
+ break;
+ }
+ }
+
+ if (ssh_getpass("Password: ", password, sizeof(password),
+ 0, 0) < 0)
+ return SSH_AUTH_ERROR;
+
+ // Try to authenticate with password
+ if (method & SSH_AUTH_METHOD_PASSWORD) {
+ rc = ssh_userauth_password(blksession, NULL, password);
+ if (rc == SSH_AUTH_ERROR) {
+ ERROR("%s", ssh_get_error(blksession));
+ return rc;
+ } else if (rc == SSH_AUTH_SUCCESS) {
+ break;
+ }
+ }
+
+ memset(password, 0, sizeof(password));
+ }
+
+ banner = ssh_get_issue_banner(blksession);
+ if (banner) {
+ ERROR("%s", banner);
+ ssh_string_free_char(banner);
+ }
+
+ return rc;
+}
+
+
+static int
+glusterBlockSSHVerifyKnownHost(ssh_session blksession)
+{
+ ssh_key srv_pubkey;
+ unsigned char *hash = NULL;
+ char *hexa;
+ char buf[10];
+ size_t hlen;
+ int rc;
+ int ret;
+
+ rc = ssh_get_publickey(blksession, &srv_pubkey);
+ if (rc < 0)
+ return -1;
+
+ rc = ssh_get_publickey_hash(srv_pubkey,
+ SSH_PUBLICKEY_HASH_SHA1, &hash, &hlen);
+ ssh_key_free(srv_pubkey);
+ if (rc < 0)
+ return -1;
+
+ switch (ssh_is_server_known(blksession)) {
+
+ case SSH_SERVER_KNOWN_OK:
+ break; /* ok we have password less access */
+
+ case SSH_SERVER_KNOWN_CHANGED:
+ ERROR("%s", "Host key for server changed : server's one is new :");
+ ssh_print_hexa("Public key hash", hash, hlen);
+ ssh_clean_pubkey_hash(&hash);
+ ERROR("%s", "For security reason, connection will be stopped");
+ return -1;
+
+ case SSH_SERVER_FOUND_OTHER:
+ ERROR("%s", "The host key for this server was not found, but an other "
+ "type of key exists.");
+ ERROR("%s", "An attacker might change the default server key to "
+ "confuse your client into thinking the key does not exist"
+ "\nWe advise you to rerun the client with -d or -r for "
+ "more safety.");
+ return -1;
+
+ case SSH_SERVER_FILE_NOT_FOUND:
+ ERROR("%s", "Could not find known host file. If you accept the host "
+ "key here, the file will be automatically created.");
+ /* fallback to SSH_SERVER_NOT_KNOWN behavior */
+
+ case SSH_SERVER_NOT_KNOWN:
+ hexa = ssh_get_hexa(hash, hlen);
+ ERROR("The server is unknown. Do you trust the host key ?\n"
+ "Public key hash: %s", hexa);
+ ssh_string_free_char(hexa);
+ if (!fgets(buf, sizeof(buf), stdin)) {
+ ret = -1;
+ goto fail;
+ }
+ if (strncasecmp(buf, "yes", 3) != 0) {
+ ret = -1;
+ goto fail;
+ }
+ ERROR("%s", "This new key will be written on disk for further usage. "
+ "do you agree ?");
+ if (!fgets(buf, sizeof(buf), stdin)) {
+ ret = -1;
+ goto fail;
+ }
+ if (strncasecmp(buf, "yes", 3) == 0) {
+ if (ssh_write_knownhost(blksession) < 0) {
+ ERROR("%s", strerror(errno));
+ ret = -1;
+ goto fail;
+ }
+ }
+ break;
+
+ case SSH_SERVER_ERROR:
+ ERROR("%s", ssh_get_error(blksession));
+ ret = -1;
+ goto fail;
+ }
+
+ ret = 0;
+
+ fail:
+ ssh_clean_pubkey_hash(&hash);
+
+ return ret;
+}
+
+
+static ssh_session
+glusterBlockSSHConnect(const char *host, const char *user, int verbosity)
+{
+ int auth = 0;
+ ssh_session blksession;
+
+ blksession = ssh_new();
+ if (!blksession)
+ return NULL;
+
+ if (user) {
+ if (ssh_options_set(blksession, SSH_OPTIONS_USER, user) < 0)
+ goto sfree;
+ }
+
+ if (ssh_options_set(blksession, SSH_OPTIONS_HOST, host) < 0)
+ goto sfree;
+
+ ssh_options_set(blksession, SSH_OPTIONS_LOG_VERBOSITY, &verbosity);
+ if (ssh_connect(blksession)) {
+ ERROR("Connection failed : %s", ssh_get_error(blksession));
+ goto sdnt;
+ }
+
+ if (glusterBlockSSHVerifyKnownHost(blksession)<0)
+ goto sdnt;
+
+ auth = glusterBlockSSHAuthConsole(blksession);
+ if (auth == SSH_AUTH_SUCCESS) {
+ return blksession;
+ } else if (auth == SSH_AUTH_DENIED) {
+ ERROR("%s", "Authentication failed");
+ } else {
+ ERROR("while authenticating : %s", ssh_get_error(blksession));
+ }
+
+ sdnt:
+ ssh_disconnect(blksession);
+
+ sfree:
+ ssh_free(blksession);
+
+ return NULL;
+}
+
+
+char *
+glusterBlockSSHRun(char *host, char *cmd, bool console)
+{
+ FILE *fd = NULL;
+ int ret;
+ int nbytes;
+ int rc;
+ uuid_t out;
+ char uuid[256];
+ char *file;
+ char buffer[256];
+ ssh_session blksession;
+ ssh_channel blkchannel;
+
+ blksession = glusterBlockSSHConnect(host, NULL/*user*/, 0);
+ if (!blksession) {
+ ssh_finalize();
+ return NULL;
+ }
+
+ blkchannel = ssh_channel_new(blksession);
+ if (!blkchannel) {
+ ret = 1;
+ goto chfail;
+ }
+
+ rc = ssh_channel_open_session(blkchannel);
+ if (rc < 0) {
+ ret = 1;
+ goto fail;
+ }
+
+ rc = ssh_channel_request_exec(blkchannel, cmd);
+ if (rc < 0) {
+ ret = 1;
+ goto fail;
+ }
+
+ if (!console) {
+ uuid_generate(out);
+ uuid_unparse(out, uuid);
+ asprintf(&file, "/tmp/%s", uuid);
+ fd = fopen(file, "w");
+ }
+
+ nbytes = ssh_channel_read(blkchannel, buffer, sizeof(buffer), 0);
+ while (nbytes > 0) {
+ if (fwrite(buffer, 1, nbytes, fd ? fd : stdout) != (unsigned int) nbytes) {
+ ret = 1;
+ goto fail;
+ }
+ nbytes = ssh_channel_read(blkchannel, buffer, sizeof(buffer), 0);
+ }
+
+ if (nbytes < 0) {
+ ret = 1;
+ goto fail;
+ }
+
+ ssh_channel_send_eof(blkchannel);
+ ret = 0;
+
+ if (console && ret == 0)
+ file = "stdout"; /* just to differentiate b/w success and failure */
+
+ fail:
+ if (!console)
+ fclose(fd);
+
+ ssh_channel_close(blkchannel);
+ ssh_channel_free(blkchannel);
+
+ chfail:
+ ssh_disconnect(blksession);
+ ssh_free(blksession);
+ ssh_finalize();
+
+ return (!ret) ? file : NULL;
+}