diff options
-rw-r--r-- | .gitignore | 6 | ||||
-rw-r--r-- | Makefile | 53 | ||||
-rw-r--r-- | glfs-operations.c | 115 | ||||
-rw-r--r-- | glfs-operations.h | 38 | ||||
-rw-r--r-- | gluster-block.c | 784 | ||||
-rw-r--r-- | ssh-common.c | 388 | ||||
-rw-r--r-- | ssh-common.h | 22 | ||||
-rw-r--r-- | utils.c | 71 | ||||
-rw-r--r-- | utils.h | 56 |
9 files changed, 1533 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..58e4823 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +gluster-block +*.o +cscope.* +tags +TAGS +*~ diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..6449fb2 --- /dev/null +++ b/Makefile @@ -0,0 +1,53 @@ +######################################################################## +# # +# 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. # +# # +######################################################################## + +CC = gcc + +BIN = gluster-block +OBJS = glfs-operations.o ssh-common.o utils.o gluster-block.o + +CFLAGS = -g -Wall +LIBS := $(shell pkg-config --libs uuid glusterfs-api libssh) + +DEPS_LIST = gcc tcmu-runner targetcli + +PREFIX ?= /usr/local/sbin +MKDIR_P = mkdir -p +LOGDIR = /var/log/ + + +all: $(BIN) + +$(BIN): $(OBJS) + @$(MKDIR_P) $(LOGDIR)$@ + $(CC) $(CFLAGS) $(LIBS) $^ -o $@ + +glfs-operations.o: glfs-operations.c glfs-operations.h + $(foreach x, $(DEPS_LIST),\ + $(if $(shell which $x), \ + $(info -- found $x),\ + $(else, \ + $(error "No $x in PATH, install '$x' and continue ...")))) + $(CC) $(CFLAGS) -c $< -o $@ + +$(BIN).o: $(BIN).c + $(CC) $(CFLAGS) -c $< -o $@ + +install: $(BIN) + cp $< $(PREFIX)/ + +.PHONY: clean distclean +clean distclean: + rm -f ./*.o $(BIN) + +uninstall: + rm -f $(PREFIX)/$(BIN) diff --git a/glfs-operations.c b/glfs-operations.c new file mode 100644 index 0000000..3a6bd32 --- /dev/null +++ b/glfs-operations.c @@ -0,0 +1,115 @@ +/* + 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. +*/ + + +# include "utils.h" +# include "glfs-operations.h" + +# define LOG_FILE "/var/log/gluster-block/block.log" +# define LOG_LEVEL 7 + + + +int +glusterBlockCreateEntry(glusterBlockDefPtr blk) +{ + struct glfs *glfs; + struct glfs_fd *fd; + int ret = 0; + + glfs = glfs_new(blk->volume); + if (!glfs) { + ERROR("%s", "glfs_new: returned NULL"); + return -1; + } + + ret = glfs_set_volfile_server(glfs, "tcp", blk->host, 24007); + if (ret) { + ERROR("%s", "glfs_set_volfile_server: failed"); + goto out; + } + + ret = glfs_set_logging(glfs, LOG_FILE, LOG_LEVEL); + if (ret) { + ERROR("%s", "glfs_set_logging: failed"); + goto out; + } + + ret = glfs_init(glfs); + if (ret) { + ERROR("%s", "glfs_init: failed"); + goto out; + } + + fd = glfs_creat(glfs, blk->filename, + O_WRONLY | O_CREAT | O_TRUNC, + S_IRUSR | S_IWUSR); + if (!fd) { + ERROR("%s", "glfs_creat: failed"); + ret = -errno; + } else { + ret = glfs_ftruncate(fd, blk->size); + if (ret) { + ERROR("%s", "glfs_ftruncate: failed"); + goto out; + } + + if (glfs_close(fd) != 0) { + ERROR("%s", "glfs_close: failed"); + ret = -errno; + } + } + + out: + glfs_fini(glfs); + return ret; +} + + +int +glusterBlockDeleteEntry(glusterBlockDefPtr blk) +{ + struct glfs *glfs; + int ret = 0; + + glfs = glfs_new(blk->volume); + if (!glfs) { + ERROR("%s", "glfs_new: returned NULL"); + return -1; + } + + ret = glfs_set_volfile_server(glfs, "tcp", blk->host, 24007); + if (ret) { + ERROR("%s", "glfs_set_volfile_server: failed"); + goto out; + } + + ret = glfs_set_logging(glfs, LOG_FILE, LOG_LEVEL); + if (ret) { + ERROR("%s", "glfs_set_logging: failed"); + goto out; + } + + ret = glfs_init(glfs); + if (ret) { + ERROR("%s", "glfs_init: failed"); + goto out; + } + + ret = glfs_unlink(glfs, blk->filename); + if (ret) { + ERROR("%s", "glfs_unlink: failed"); + goto out; + } + + out: + glfs_fini(glfs); + return ret; +} diff --git a/glfs-operations.h b/glfs-operations.h new file mode 100644 index 0000000..7659c7d --- /dev/null +++ b/glfs-operations.h @@ -0,0 +1,38 @@ +/* + 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. +*/ + + +# ifndef _GLFS_OPERATIONS_H +# define _GLFS_OPERATIONS_H 1 + +# include <stdio.h> +# include <stdlib.h> +# include <stdbool.h> +# include <errno.h> + +# include <glusterfs/api/glfs.h> + + + +typedef struct glusterBlockDef { + char *volume; + char *host; /* TODO: use proper Transport Object */ + char *filename; + size_t size; + bool status; +} glusterBlockDef; +typedef glusterBlockDef *glusterBlockDefPtr; + + +int glusterBlockCreateEntry(glusterBlockDefPtr blk); + +int glusterBlockDeleteEntry(glusterBlockDefPtr blk); + +#endif /* _GLFS_OPERATIONS_H */ diff --git a/gluster-block.c b/gluster-block.c new file mode 100644 index 0000000..a6e84d4 --- /dev/null +++ b/gluster-block.c @@ -0,0 +1,784 @@ +/* + 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 /* See feature_test_macros(7) */ + +# include <string.h> +# include <getopt.h> +# include <uuid/uuid.h> + +# include "utils.h" +# include "glfs-operations.h" +# include "ssh-common.h" + +# define UUID_BUF_SIZE 50 +# define CFG_STRING_SIZE 256 + +# define LIST "list" +# define CREATE "create" +# define DELETE "delete" +# define INFO "info" +# define MODIFY "modify" +# define BLOCKHOST "block-host" +# define HELP "help" + +# define GLFS_PATH "/backstores/user:glfs" +# define TARGETCLI_GLFS "targetcli "GLFS_PATH +# define TARGETCLI_ISCSI "targetcli /iscsi" +# define ATTRIBUTES "generate_node_acls=1 demo_mode_write_protect=0" +# define LIST_CMD "ls | grep ' %s ' | cut -d'[' -f2 | cut -d']' -f1" +# define LUNS_LIST "ls | grep -v user:glfs | cut -d'-' -f2 | cut -d' ' -f2" + +# define IQN_PREFIX "iqn.2016-12.org.gluster-block:" + +# define MSERVER_DELIMITER "," + + + +typedef struct blockServerDef { + size_t nhosts; + char **hosts; +} blockServerDef; +typedef blockServerDef *blockServerDefPtr; + + +static void +glusterBlockHelp(void) +{ + MSG("%s", + "gluster-block (Version 0.1) \n" + " -c, --create <name> Create the gluster block\n" + " -v, --volume <vol> gluster volume name\n" + " -h, --host <gluster-node> node addr from gluster pool\n" + " -s, --size <size> block storage size in KiB|MiB|GiB|TiB..\n" + "\n" + " -l, --list List available gluster blocks\n" + "\n" + " -i, --info <name> Details about gluster block\n" + "\n" + " -m, --modify <RESIZE|AUTH> Modify the metadata\n" + "\n" + " -d, --delete <name> Delete the gluster block\n" + "\n" + " [-b, --block-host <IP1,IP2,IP3...>] block servers, clubbed with any option\n"); +} + + +void +blockServerDefFree(blockServerDefPtr blkServers) +{ + size_t i; + + if (!blkServers) + return; + + for (i = 0; i < blkServers->nhosts; i++) + GB_FREE(blkServers->hosts[i]); + GB_FREE(blkServers->hosts); + GB_FREE(blkServers); +} + + +static void +glusterBlockDefFree(glusterBlockDefPtr blk) +{ + if (!blk) + return; + + GB_FREE(blk->volume); + GB_FREE(blk->host); + GB_FREE(blk->filename); + GB_FREE(blk); +} + + +static blockServerDefPtr +blockServerParse(char *blkServers) +{ + blockServerDefPtr list; + char *tmp = blkServers; + size_t i = 0; + + if (!blkServers) + return NULL; + + if (GB_ALLOC(list) < 0) + return NULL; + + /* count number of servers */ + while (*tmp) { + if (*tmp == ',') + list->nhosts++; + tmp++; + } + list->nhosts++; + tmp = blkServers; /* reset addr */ + + + if (GB_ALLOC_N(list->hosts, list->nhosts) < 0) + goto fail; + + for (i = 0; tmp != NULL; i++) { + if (GB_STRDUP(list->hosts[i], strsep(&tmp, MSERVER_DELIMITER)) < 0) + goto fail; + } + + return list; + + fail: + blockServerDefFree(list); + return NULL; +} + + +static size_t +glusterBlockCreateParseSize(char *value) +{ + char *postfix; + char *tmp; + size_t sizef; + + if (!value) + return -1; + + sizef = strtod(value, &postfix); + if (sizef < 0) { + ERROR("%s", "size cannot be negative number\n"); + return -1; + } + + tmp = postfix; + if (*postfix == ' ') + tmp = tmp + 1; + + switch (*tmp) { + case 'Y': + sizef *= 1024; + /* fall through */ + case 'Z': + sizef *= 1024; + /* fall through */ + case 'E': + sizef *= 1024; + /* fall through */ + case 'P': + sizef *= 1024; + /* fall through */ + case 'T': + sizef *= 1024; + /* fall through */ + case 'G': + sizef *= 1024; + /* fall through */ + case 'M': + sizef *= 1024; + /* fall through */ + case 'K': + case 'k': + sizef *= 1024; + /* fall through */ + case 'b': + case '\0': + return sizef; + break; + default: + ERROR("%s", "You may use k/K, M, G or T suffixes for " + "kilobytes, megabytes, gigabytes and terabytes."); + return -1; + } +} + + +static char * +glusterBlockListGetHumanReadableSize(size_t bytes) +{ + char *size; + char *types[] = {"Byte(s)", "KiB", "MiB", "GiB", + "TiB", "PiB", "EiB", "ZiB", "YiB"}; + size_t i; + + if (bytes < 1024) { + asprintf(&size, "%zu %s", bytes, *(types)); + return size; + } + + for (i = 1; i < 8; i++) { + bytes /= 1024; + if (bytes < 1024) { + asprintf(&size, "%zu %s ", bytes, *(types + i)); + return size; + } + } + + return NULL; +} + + +static int +glusterBlockCreate(int count, char **options, char *name) +{ + int c; + int ret = 0; + char *cmd = NULL; + char *exec = NULL; + char *iqn = NULL; + char *blkServers = NULL; + blockServerDefPtr list; + uuid_t out; + glusterBlockDefPtr blk; + char *sshout; + size_t i; + + + if (GB_ALLOC(blk) < 0) + return -1; + + blk->filename = CALLOC(UUID_BUF_SIZE); + + uuid_generate(out); + uuid_unparse(out, blk->filename); + + if (!name) { + ERROR("%s", "Insufficient arguments supplied for" + "'gluster-block create'"); + ret = -1; + goto out; + } + + while (1) { + static const struct option long_options[] = { + {"volume", required_argument, 0, 'v'}, + {"host", required_argument, 0, 'h'}, + {"size", required_argument, 0, 's'}, + {"block-host", required_argument, 0, 'b'}, + {0, 0, 0, 0} + }; + + /* getopt_long stores the option index here. */ + int option_index = 0; + + c = getopt_long(count, options, "b:v:h:s:", + long_options, &option_index); + + if (c == -1) + break; + + switch (c) { + case 'b': + blkServers = optarg; + break; + + case 'v': + GB_STRDUP(blk->volume, optarg); + ret++; + break; + + case 'h': + GB_STRDUP(blk->host, optarg); + ret++; + break; + + case 's': + blk->size = glusterBlockCreateParseSize(optarg); + ret++; + break; + + case '?': + MSG("unrecognized option '%s'", options[optind-1]); + MSG("%s", "Hint: gluster-block --help"); + goto out; + + default: + break; + } + } + + if (blkServers) { + list = blockServerParse(blkServers); + } else { + list = blockServerParse("localhost"); + } + + /* Print any remaining command line arguments (not options). */ + if (optind < count) { + ERROR("%s", "non-option ARGV-elements: "); + while (optind < count) + printf("%s ", options[optind++]); + putchar('\n'); + + ret = -1; + goto out; + } + + if (ret != 3) { + ERROR("%s", "Insufficient arguments supplied for" + "'gluster-block create'\n"); + ret = -1; + goto out; + } + + ret = glusterBlockCreateEntry(blk); + if (ret) + goto out; + + if (asprintf(&cmd, "%s %s %s %zu %s@%s/%s", TARGETCLI_GLFS, + CREATE, name, blk->size, blk->volume, blk->host, + blk->filename) < 0) + goto out; + + /* Created user-backed storage object LUN size 2147483648. */ + for (i = 0; i < list->nhosts; i++) { + MSG("[OnHost: %s]", list->hosts[i]); + sshout = glusterBlockSSHRun(list->hosts[i], cmd, true); + if (!sshout) { + ret = -1; + goto out; + } + + asprintf(&iqn, "%s%s-%s", + IQN_PREFIX, list->hosts[i], blk->filename); + asprintf(&exec, "%s %s %s", TARGETCLI_ISCSI, CREATE, iqn); + sshout = glusterBlockSSHRun(list->hosts[i], exec, true); + if (!sshout) { + ret = -1; + goto out; + } + GB_FREE(exec); + + + asprintf(&exec, "%s/%s/tpg1/luns %s %s/%s", + TARGETCLI_ISCSI, iqn, CREATE, GLFS_PATH, name); + sshout = glusterBlockSSHRun(list->hosts[i], exec, true); + if (!sshout) { + ret = -1; + goto out; + } + GB_FREE(exec); + + + asprintf(&exec, "%s/%s/tpg1 set attribute %s", + TARGETCLI_ISCSI, iqn, ATTRIBUTES); + sshout = glusterBlockSSHRun(list->hosts[i], exec, true); + if (!sshout) { + ret = -1; + goto out; + } + GB_FREE(exec); + GB_FREE(iqn); + putchar('\n'); + } + + out: + GB_FREE(cmd); + GB_FREE(exec); + GB_FREE(iqn); + glusterBlockDefFree(blk); + blockServerDefFree(list); + + return ret; +} + + +static int +glusterBlockParseCfgStringToDef(char* cfgstring, + glusterBlockDefPtr blk) +{ + int ret = 0; + char *p, *sep; + + /* part before '@' is the volume name */ + p = cfgstring; + sep = strchr(p, '@'); + if (!sep) { + ret = -1; + goto fail; + } + + *sep = '\0'; + if (GB_STRDUP(blk->volume, p) < 0) { + ret = -1; + goto fail; + } + + /* part between '@' and '/' is the server name */ + p = sep + 1; + sep = strchr(p, '/'); + if (!sep) { + ret = -1; + goto fail; + } + + *sep = '\0'; + if (GB_STRDUP(blk->host, p) < 0) { + ret = -1; + goto fail; + } + + /* part between '/' and '(' is the filename */ + p = sep + 1; + sep = strchr(p, '('); + if (!sep) { + ret = -1; + goto fail; + } + + *(sep - 1) = '\0'; /* discard extra space at end of filename */ + if (GB_STRDUP(blk->filename, p) < 0) { + ret = -1; + goto fail; + } + + /* part between '(' and ')' is the size */ + p = sep + 1; + sep = strchr(p, ')'); + if (!sep) { + ret = -1; + goto fail; + } + + *sep = '\0'; + blk->size = glusterBlockCreateParseSize(p); + if (blk->size < 0) { + ret = -1; + goto fail; + } + + + /* part between ')' and '\n' is the status */ + p = sep + 1; + sep = strchr(p, '\n'); + if (!sep) { + ret = -1; + goto fail; + } + + *sep = '\0'; + if (!strcmp(p, " activated")) + blk->status = true; + + return 0; + + fail: + glusterBlockDefFree(blk); + + return ret; +} + + +static char * +getCfgstring(char* name, char *blkServer) +{ + FILE *fd; + char *cmd; + char *exec; + char *sshout; + char *buf = CALLOC(CFG_STRING_SIZE); + + asprintf(&cmd, "%s %s", TARGETCLI_GLFS, LIST_CMD); + asprintf(&exec, cmd, name); + + sshout = glusterBlockSSHRun(blkServer, exec, false); + if (!sshout) { + GB_FREE(buf); + buf = NULL; + goto fail; + } + + fd = fopen(sshout, "r"); + if (!fgets(buf, CFG_STRING_SIZE, fd)) { + GB_FREE(buf); + buf = NULL; + } + + fail: + fclose(fd); + remove(sshout); + GB_FREE(sshout); + GB_FREE(cmd); + GB_FREE(exec); + + return buf; +} + + +/* TODO: need to implement sessions [ list | detail ] [sid] */ +static int +glusterBlockList(blockServerDefPtr blkServers) +{ + size_t i; + int ret; + char *cmd; + char *pos; + char *sshout; + char *cfgstring = NULL; + char buf[CFG_STRING_SIZE]; + FILE *fd = NULL; + glusterBlockDefPtr blk = NULL; + + MSG("%s", "BlockName Volname Host Size Status"); + + asprintf(&cmd, "%s %s", TARGETCLI_GLFS, LUNS_LIST); + + for (i = 0; i < blkServers->nhosts; i++) { + MSG("[OnHost: %s]", blkServers->hosts[i]); + sshout = glusterBlockSSHRun(blkServers->hosts[i], cmd, false); + if (!sshout) { + ret = -1; + goto fail; + } + + fd = fopen(sshout, "r"); + while (fgets(buf, sizeof(buf), fd)) { + pos = strtok(buf, "\n"); + + cfgstring = getCfgstring(pos, blkServers->hosts[i]); + if (!cfgstring) { + ret = -1; + goto fail; + } + + if (GB_ALLOC(blk) < 0) + goto fail; + + ret = glusterBlockParseCfgStringToDef(cfgstring, blk); + if (ret) + goto fail; + + fprintf(stdout, " %s %s %s %s %s\n", + pos, blk->volume, blk->host, + glusterBlockListGetHumanReadableSize(blk->size), + blk->status ? "Online" : "Offline"); + + GB_FREE(cfgstring); + glusterBlockDefFree(blk); + } + + fclose(fd); + remove(sshout); + GB_FREE(sshout); + } + + GB_FREE(cmd); + return 0; + + fail: + fclose(fd); + remove(sshout); + glusterBlockDefFree(blk); + GB_FREE(cfgstring); + + GB_FREE(sshout); + GB_FREE(cmd); + + return -1; +} + + +static int +glusterBlockDelete(char* name, blockServerDefPtr blkServers) +{ + size_t i; + int ret; + char *cmd; + char *exec = NULL; + char *sshout; + char *cfgstring = NULL; + char *iqn = NULL; + glusterBlockDefPtr blk = NULL; + + asprintf(&cmd, "%s %s %s", TARGETCLI_GLFS, DELETE, name); + + for (i = 0; i < blkServers->nhosts; i++) { + MSG("[OnHost: %s]", blkServers->hosts[i]); + if (cfgstring) + GB_FREE(cfgstring); + cfgstring = getCfgstring(name, blkServers->hosts[i]); + if (!cfgstring) { + ret = -1; + goto fail; + } + + sshout = glusterBlockSSHRun(blkServers->hosts[i], cmd, true); + if (!sshout) { + ret = -1; + goto fail; + } + + /* cfgstring is constant across all tcmu nodes */ + if (!blk) { + if (GB_ALLOC(blk) < 0) + goto fail; + + ret = glusterBlockParseCfgStringToDef(cfgstring, blk); + if (ret) + goto fail; + } + + asprintf(&iqn, "%s%s-%s", + IQN_PREFIX, blkServers->hosts[i], blk->filename); + asprintf(&exec, "%s %s %s", TARGETCLI_ISCSI, DELETE, iqn); + sshout = glusterBlockSSHRun(blkServers->hosts[i], exec, true); + if (!sshout) { + ret = -1; + goto fail; + } + GB_FREE(exec); + GB_FREE(iqn); + } + + ret = glusterBlockDeleteEntry(blk); + + fail: + glusterBlockDefFree(blk); + GB_FREE(cfgstring); + GB_FREE(iqn); + GB_FREE(exec); + GB_FREE(cmd); + + return ret; +} + + +static int +glusterBlockInfo(char* name, blockServerDefPtr blkServers) +{ + size_t i; + int ret = 0; + char *cmd; + char *sshout; + + asprintf(&cmd, "%s/%s %s", TARGETCLI_GLFS, name, INFO); + + for (i = 0; i < blkServers->nhosts; i++) { + MSG("[OnHost: %s]", blkServers->hosts[i]); + sshout = glusterBlockSSHRun(blkServers->hosts[i], cmd, true); + if (!sshout) + ret = -1; + putchar('\n'); + } + + GB_FREE(cmd); + return ret; +} + + +static int +glusterBlockParseArgs(int count, char **options) +{ + int c; + int ret = 0; + int optFlag = 0; + char *block = NULL; + char *blkServers = NULL; + blockServerDefPtr list = NULL; + + while (1) { + static const struct option long_options[] = { + {HELP, no_argument, 0, 'h'}, + {CREATE, required_argument, 0, 'c'}, + {DELETE, required_argument, 0, 'd'}, + {LIST, no_argument, 0, 'l'}, + {INFO, required_argument, 0, 'i'}, + {MODIFY, required_argument, 0, 'm'}, + {BLOCKHOST, required_argument, 0, 'b'}, + {0, 0, 0, 0} + }; + + /* getopt_long stores the option index here. */ + int option_index = 0; + + c = getopt_long(count, options, "hc:b:d:lim:", + long_options, &option_index); + + /* Detect the end of the options. */ + if (c == -1) + break; + + switch (c) { + case 'b': + blkServers = optarg; + if (optFlag) + goto opt; + break; + + case 'c': + ret = glusterBlockCreate(count, options, optarg); + break; + + case 'l': + case 'd': + case 'i': + if (optFlag) /* more than one main opterations ?*/ + goto out; + optFlag = c; + block = optarg; + if (blkServers) + goto opt; + break; + + case 'm': + MSG("option --modify yet TODO '%s'", optarg); + break; + + case 'h': + glusterBlockHelp(); + break; + + case '?': + /* getopt_long already printed an error message. */ + break; + } + } + + opt: + if (blkServers) { + list = blockServerParse(blkServers); + } else { + list = blockServerParse("localhost"); + } + + switch (optFlag) { + case 'l': + ret = glusterBlockList(list); + break; + case 'i': + ret = glusterBlockInfo(block, list); + break; + case 'd': + ret = glusterBlockDelete(block, list); + break; + } + + out: + if (ret == 0 && optind < count) { + ERROR("%s", "Unable to parse elements: "); + while (optind < count) + printf("%s ", options[optind++]); + putchar('\n'); + MSG("Hint: %s --help", options[0]); + } + + blockServerDefFree(list); + return ret; +} + + +int +main(int argc, char *argv[]) +{ + int ret; + if (argc <= 1) + glusterBlockHelp(); + + ret = glusterBlockParseArgs(argc, argv); + + return ret; +} 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; +} diff --git a/ssh-common.h b/ssh-common.h new file mode 100644 index 0000000..57c7f9d --- /dev/null +++ b/ssh-common.h @@ -0,0 +1,22 @@ +/* + 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. +*/ + + +# ifndef _SSH_COMMON_H +# define _SSH_COMMON_H 1 + +# include <libssh/libssh.h> + + + +char * +glusterBlockSSHRun(char *host, char *cmd, bool console); + +#endif /* _SSH_COMMON_H */ @@ -0,0 +1,71 @@ +/* + 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. +*/ + + +# include "utils.h" + + + +int +gbAlloc(void *ptrptr, size_t size, + const char *filename, const char *funcname, size_t linenr) +{ + *(void **)ptrptr = calloc(1, size); + if (*(void **)ptrptr == NULL) { + ERROR("%s", "Error: memory allocation failed"); + errno = ENOMEM; + return -1; + } + return 0; +} + + +int +gbAllocN(void *ptrptr, size_t size, size_t count, + const char *filename, const char *funcname, size_t linenr) +{ + *(void**)ptrptr = calloc(count, size); + if (*(void**)ptrptr == NULL) { + ERROR("%s", "Error: memory allocation failed"); + errno = ENOMEM; + return -1; + } + return 0; +} + + +void +gbFree(void *ptrptr) +{ + int save_errno = errno; + + if(*(void**)ptrptr) + return; + + free(*(void**)ptrptr); + *(void**)ptrptr = NULL; + errno = save_errno; +} + + +int +gbStrdup(char **dest, const char *src, + const char *filename, const char *funcname, size_t linenr) +{ + *dest = NULL; + if (!src) + return 0; + if (!(*dest = strdup(src))) { + ERROR("%s", "Error: string duplication failed"); + return -1; + } + + return 0; +} @@ -0,0 +1,56 @@ +/* + 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. +*/ + + +# ifndef _UTILS_H +# define _UTILS_H 1 + +# include <stdio.h> +# include <stdlib.h> +# include <stdbool.h> +# include <string.h> +# include <errno.h> + + +#define ERROR(fmt, ...) \ + fprintf(stderr, "Error: " fmt " [at %s+%d :<%s>]\n", \ + __VA_ARGS__, __FILE__, __LINE__, __FUNCTION__) + +#define MSG(fmt, ...) \ + fprintf(stdout, fmt "\n", __VA_ARGS__) + + +# define CALLOC(x) calloc(1, x) + +# define GB_ALLOC_N(ptr, count) gbAllocN(&(ptr), sizeof(*(ptr)), (count), \ + __FILE__, __FUNCTION__, __LINE__) + +# define GB_ALLOC(ptr) gbAlloc(&(ptr), sizeof(*(ptr)), \ + __FILE__, __FUNCTION__, __LINE__) + +# define GB_STRDUP(dst, src) gbStrdup(&(dst), src, \ + __FILE__, __FUNCTION__, __LINE__) + +# define GB_FREE(ptr) gbFree(1 ? (void *) &(ptr) : (ptr)) + + + +int gbAlloc(void *ptrptr, size_t size, + const char *filename, const char *funcname, size_t linenr); + +int gbAllocN(void *ptrptr, size_t size, size_t count, + const char *filename, const char *funcname, size_t linenr); + +int gbStrdup(char **dest, const char *src, + const char *filename, const char *funcname, size_t linenr); + +void gbFree(void *ptrptr); + +#endif /* _UTILS_H */ |